@tscircuit/schematic-trace-solver 0.0.178 → 0.0.180
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 +8 -2
- package/dist/index.js +109 -4
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +141 -6
- package/lib/solvers/AvailableNetOrientationSolver/traces.ts +36 -0
- package/lib/solvers/AvailableNetOrientationSolver/types.ts +1 -0
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260902T092101Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260902T092425Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260902T092101Z/__snapshots__/bug-report-20260902T092101Z.snap.svg +117 -0
- package/tests/bug-reports/bug-report-20260902T092101Z/bug-report-20260902T092101Z.json +162 -0
- package/tests/bug-reports/bug-report-20260902T092101Z/bug-report-20260902T092101Z.test.ts +28 -0
- package/tests/bug-reports/bug-report-20260902T092425Z/__snapshots__/bug-report-20260902T092425Z.snap.svg +136 -0
- package/tests/bug-reports/bug-report-20260902T092425Z/bug-report-20260902T092425Z.json +238 -0
- package/tests/bug-reports/bug-report-20260902T092425Z/bug-report-20260902T092425Z.test.ts +12 -0
- package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +545 -0
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +10 -10
- package/tests/repros/__snapshots__/repro-smartwatch-power-sheet.snap.svg +4 -4
- package/tests/repros/assets/repro-hub-usb-sheet.input.json +3967 -0
- package/tests/repros/repro-battery-management-bq24073.test.ts +17 -0
- package/tests/repros/repro-hub-usb-sheet.test.ts +56 -0
- package/tests/repros/repro-smartwatch-power-sheet.test.ts +31 -5
package/dist/index.d.ts
CHANGED
|
@@ -831,7 +831,7 @@ type CandidateLabel = {
|
|
|
831
831
|
height: number;
|
|
832
832
|
};
|
|
833
833
|
type CandidateStatus$2 = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "trace-clearance-violation" | "netlabel-collision";
|
|
834
|
-
type CandidatePhase = "rotate" | "trace-anchor" | "connected-rail-shift" | "outward-trace-anchor" | "shift" | "lateral-shift";
|
|
834
|
+
type CandidatePhase = "rotate" | "trace-anchor" | "connected-rail-shift" | "downward-ground-rail-extension" | "outward-trace-anchor" | "shift" | "lateral-shift";
|
|
835
835
|
type EvaluatedCandidate = CandidateLabel & {
|
|
836
836
|
status: CandidateStatus$2;
|
|
837
837
|
selected: boolean;
|
|
@@ -900,6 +900,12 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
900
900
|
private evaluateCandidate;
|
|
901
901
|
private recordCandidateResult;
|
|
902
902
|
private getCandidateConnectorTrace;
|
|
903
|
+
/**
|
|
904
|
+
* A laterally shifted label can branch directly from the interior of its
|
|
905
|
+
* existing host trace. Prefer that tee over running a new connector beside
|
|
906
|
+
* the host trace from the old label anchor.
|
|
907
|
+
*/
|
|
908
|
+
private findProjectedConnectorSourceOnHostTrace;
|
|
903
909
|
private isStandaloneSinglePinNetLabel;
|
|
904
910
|
private labelIntersectsDifferentNetTrace;
|
|
905
911
|
private getSearchStartAnchor;
|
|
@@ -910,7 +916,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
910
916
|
private createCandidate;
|
|
911
917
|
private getNetLabelHeight;
|
|
912
918
|
private getCandidateStatus;
|
|
913
|
-
private
|
|
919
|
+
private isAcceptableConnectedChipBoundaryOverlap;
|
|
914
920
|
private staysOutsideConnectedPinRow;
|
|
915
921
|
private getBoundsStatus;
|
|
916
922
|
private isTraceTooCloseToLabel;
|
package/dist/index.js
CHANGED
|
@@ -7709,6 +7709,27 @@ var TraceCleanupSolver = class extends BaseSolver {
|
|
|
7709
7709
|
};
|
|
7710
7710
|
|
|
7711
7711
|
// lib/solvers/AvailableNetOrientationSolver/traces.ts
|
|
7712
|
+
var extendVerticalTracePathAtInteriorPoint = ({
|
|
7713
|
+
tracePath,
|
|
7714
|
+
sourcePoint,
|
|
7715
|
+
extensionEndPoint
|
|
7716
|
+
}) => {
|
|
7717
|
+
const sourceIndex = tracePath.findIndex(
|
|
7718
|
+
(point) => pointsEqual(point, sourcePoint)
|
|
7719
|
+
);
|
|
7720
|
+
if (sourceIndex <= 0 || sourceIndex >= tracePath.length - 1) return null;
|
|
7721
|
+
const extensionDirectionY = extensionEndPoint.y - sourcePoint.y;
|
|
7722
|
+
const adjacentIndex = [sourceIndex - 1, sourceIndex + 1].find((index) => {
|
|
7723
|
+
const point = tracePath[index];
|
|
7724
|
+
return isVertical2(point, sourcePoint) && (point.y - sourcePoint.y) * extensionDirectionY < 0;
|
|
7725
|
+
});
|
|
7726
|
+
if (adjacentIndex === void 0) return null;
|
|
7727
|
+
return tracePath.toSpliced(
|
|
7728
|
+
Math.max(sourceIndex, adjacentIndex),
|
|
7729
|
+
0,
|
|
7730
|
+
extensionEndPoint
|
|
7731
|
+
);
|
|
7732
|
+
};
|
|
7712
7733
|
var getPinMap = (inputProblem) => {
|
|
7713
7734
|
const pinMap = {};
|
|
7714
7735
|
for (const chip of inputProblem.chips) {
|
|
@@ -9028,6 +9049,7 @@ function getPaddedBounds(bounds, padding) {
|
|
|
9028
9049
|
|
|
9029
9050
|
// lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts
|
|
9030
9051
|
var LABEL_TRACE_CLEARANCE2 = 0.1;
|
|
9052
|
+
var MIN_DOWNWARD_GROUND_RAIL_EXTENSION = 0.2;
|
|
9031
9053
|
var CONNECTED_PIN_ROW_OVERLAP_TOLERANCE = 0.1;
|
|
9032
9054
|
var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
9033
9055
|
inputProblem;
|
|
@@ -9267,6 +9289,20 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9267
9289
|
}
|
|
9268
9290
|
addConnectorTrace(label, candidate, labelIndex) {
|
|
9269
9291
|
if (candidate.phase === "trace-anchor") return;
|
|
9292
|
+
if (candidate.phase === "downward-ground-rail-extension") {
|
|
9293
|
+
for (const mspPairId2 of label.mspConnectionPairIds) {
|
|
9294
|
+
const hostTrace = this.traceMap[mspPairId2];
|
|
9295
|
+
if (!hostTrace) continue;
|
|
9296
|
+
const extendedTracePath = extendVerticalTracePathAtInteriorPoint({
|
|
9297
|
+
tracePath: hostTrace.tracePath,
|
|
9298
|
+
sourcePoint: label.anchorPoint,
|
|
9299
|
+
extensionEndPoint: candidate.anchorPoint
|
|
9300
|
+
});
|
|
9301
|
+
if (!extendedTracePath) continue;
|
|
9302
|
+
hostTrace.tracePath = extendedTracePath;
|
|
9303
|
+
return;
|
|
9304
|
+
}
|
|
9305
|
+
}
|
|
9270
9306
|
const tracePath = this.getCandidateConnectorTrace(
|
|
9271
9307
|
label,
|
|
9272
9308
|
candidate,
|
|
@@ -9313,6 +9349,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9313
9349
|
(connection) => connection.netId === label.netId
|
|
9314
9350
|
);
|
|
9315
9351
|
const isTwoPinNet = netConnection?.pinIds.length === 2;
|
|
9352
|
+
const isDownwardGroundRail = netConnection?.isGround && isXOrientation(label.orientation) && orientations.length === 1 && requiredOrientation === "y-" && this.hasTraceContinuingInOrientation(label, "y+");
|
|
9316
9353
|
const isDistanceSplitVerticalRail = (netConnection?.pinIds.length ?? 0) > 2 && isYOrientation(requiredOrientation) && this.hasPortOnlyLabelOnSameNet(label);
|
|
9317
9354
|
const textBlockedMultiPinRailTraces = this.getTextBlockedMultiPinRailTraces(
|
|
9318
9355
|
label,
|
|
@@ -9370,6 +9407,26 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9370
9407
|
labelIndex,
|
|
9371
9408
|
orientations
|
|
9372
9409
|
);
|
|
9410
|
+
const rotatedCandidateNeedsLateralBranch = rotatedCandidate !== null && Math.abs(rotatedCandidate.anchorPoint.x - label.anchorPoint.x) > EPS13;
|
|
9411
|
+
if (isDownwardGroundRail && rotatedCandidateNeedsLateralBranch) {
|
|
9412
|
+
const downwardRailCandidate = this.findValidCandidateInShiftColumn({
|
|
9413
|
+
label,
|
|
9414
|
+
labelIndex,
|
|
9415
|
+
orientation: requiredOrientation,
|
|
9416
|
+
direction: dir(requiredOrientation),
|
|
9417
|
+
baseAnchor: label.anchorPoint,
|
|
9418
|
+
maxSearchDistance: this.getSearchDistanceLimit(
|
|
9419
|
+
label,
|
|
9420
|
+
requiredOrientation
|
|
9421
|
+
),
|
|
9422
|
+
outwardDistance: 0,
|
|
9423
|
+
phase: "downward-ground-rail-extension",
|
|
9424
|
+
stopOnTraceCollision: false,
|
|
9425
|
+
connectorSource: label.anchorPoint,
|
|
9426
|
+
startDistance: MIN_DOWNWARD_GROUND_RAIL_EXTENSION
|
|
9427
|
+
});
|
|
9428
|
+
if (downwardRailCandidate) return downwardRailCandidate;
|
|
9429
|
+
}
|
|
9373
9430
|
if (rotatedCandidate) return rotatedCandidate;
|
|
9374
9431
|
const traceAnchorCandidate = this.findValidTraceAnchorCandidate(
|
|
9375
9432
|
label,
|
|
@@ -9784,11 +9841,16 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9784
9841
|
x: baseAnchor.x + direction.x * distance7,
|
|
9785
9842
|
y: baseAnchor.y + direction.y * distance7
|
|
9786
9843
|
};
|
|
9844
|
+
const projectedConnectorSource = connectorSource ?? (phase === "lateral-shift" ? this.findProjectedConnectorSourceOnHostTrace(
|
|
9845
|
+
label,
|
|
9846
|
+
anchorPoint,
|
|
9847
|
+
orientation
|
|
9848
|
+
) : void 0);
|
|
9787
9849
|
const candidate = this.createCandidate(
|
|
9788
9850
|
label,
|
|
9789
9851
|
anchorPoint,
|
|
9790
9852
|
orientation,
|
|
9791
|
-
|
|
9853
|
+
projectedConnectorSource
|
|
9792
9854
|
);
|
|
9793
9855
|
const result = this.evaluateCandidate(
|
|
9794
9856
|
candidate,
|
|
@@ -9842,6 +9904,13 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9842
9904
|
candidate.anchorPoint
|
|
9843
9905
|
]);
|
|
9844
9906
|
}
|
|
9907
|
+
if (candidate.connectorSource) {
|
|
9908
|
+
return getConnectorTracePath(
|
|
9909
|
+
candidate.connectorSource,
|
|
9910
|
+
candidate.anchorPoint,
|
|
9911
|
+
candidate.orientation
|
|
9912
|
+
);
|
|
9913
|
+
}
|
|
9845
9914
|
if (candidate.phase === "lateral-shift") {
|
|
9846
9915
|
const chipOutwardDir = this.crowdedPortOnlyLabelIndices.has(labelIndex) ? this.getChipOutwardDirection(label.anchorPoint) : (() => {
|
|
9847
9916
|
const orientDir = dir(candidate.orientation);
|
|
@@ -9861,11 +9930,41 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
9861
9930
|
]);
|
|
9862
9931
|
}
|
|
9863
9932
|
return getConnectorTracePath(
|
|
9864
|
-
|
|
9933
|
+
label.anchorPoint,
|
|
9865
9934
|
candidate.anchorPoint,
|
|
9866
9935
|
candidate.orientation
|
|
9867
9936
|
);
|
|
9868
9937
|
}
|
|
9938
|
+
/**
|
|
9939
|
+
* A laterally shifted label can branch directly from the interior of its
|
|
9940
|
+
* existing host trace. Prefer that tee over running a new connector beside
|
|
9941
|
+
* the host trace from the old label anchor.
|
|
9942
|
+
*/
|
|
9943
|
+
findProjectedConnectorSourceOnHostTrace(label, targetAnchor, orientation) {
|
|
9944
|
+
const orientationDirection = dir(orientation);
|
|
9945
|
+
const projectedSources = [];
|
|
9946
|
+
for (const traceId of label.mspConnectionPairIds) {
|
|
9947
|
+
const hostTrace = this.traceMap[traceId];
|
|
9948
|
+
if (!hostTrace) continue;
|
|
9949
|
+
for (let pointIndex = 0; pointIndex < hostTrace.tracePath.length - 1; pointIndex++) {
|
|
9950
|
+
const start = hostTrace.tracePath[pointIndex];
|
|
9951
|
+
const end = hostTrace.tracePath[pointIndex + 1];
|
|
9952
|
+
const segmentIsPerpendicularToLabel = isYOrientation(orientation) ? Math.abs(start.y - end.y) <= EPS13 : Math.abs(start.x - end.x) <= EPS13;
|
|
9953
|
+
if (!segmentIsPerpendicularToLabel) continue;
|
|
9954
|
+
const source = isYOrientation(orientation) ? { x: targetAnchor.x, y: start.y } : { x: start.x, y: targetAnchor.y };
|
|
9955
|
+
if (!tracePathContainsPoint([start, end], label.anchorPoint) || !tracePathContainsPoint([start, end], source)) {
|
|
9956
|
+
continue;
|
|
9957
|
+
}
|
|
9958
|
+
const targetDistanceAlongOrientation = (targetAnchor.x - source.x) * orientationDirection.x + (targetAnchor.y - source.y) * orientationDirection.y;
|
|
9959
|
+
if (targetDistanceAlongOrientation >= -EPS13) {
|
|
9960
|
+
projectedSources.push(source);
|
|
9961
|
+
}
|
|
9962
|
+
}
|
|
9963
|
+
}
|
|
9964
|
+
return projectedSources.sort(
|
|
9965
|
+
(first, second) => Math.abs(first.x - targetAnchor.x) + Math.abs(first.y - targetAnchor.y) - (Math.abs(second.x - targetAnchor.x) + Math.abs(second.y - targetAnchor.y))
|
|
9966
|
+
)[0];
|
|
9967
|
+
}
|
|
9869
9968
|
isStandaloneSinglePinNetLabel(label) {
|
|
9870
9969
|
return this.inputProblem.netConnections.some(
|
|
9871
9970
|
(connection) => connection.netId === label.netId && connection.pinIds.length === 1 && connection.pinIds[0] === label.pinIds[0]
|
|
@@ -10054,7 +10153,12 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
10054
10153
|
label
|
|
10055
10154
|
});
|
|
10056
10155
|
if (boundsStatus !== "valid") {
|
|
10057
|
-
|
|
10156
|
+
const canOverlapConnectedChipBoundary = phase === "trace-anchor" || phase === "connected-rail-shift" || phase === "downward-ground-rail-extension";
|
|
10157
|
+
if (!canOverlapConnectedChipBoundary || boundsStatus !== "chip-collision" || !this.isAcceptableConnectedChipBoundaryOverlap(
|
|
10158
|
+
candidate,
|
|
10159
|
+
label,
|
|
10160
|
+
bounds
|
|
10161
|
+
) || phase === "connected-rail-shift" && !this.staysOutsideConnectedPinRow({ candidate, label, bounds })) {
|
|
10058
10162
|
return boundsStatus;
|
|
10059
10163
|
}
|
|
10060
10164
|
const nonChipBoundsStatus = this.getBoundsStatus({
|
|
@@ -10069,6 +10173,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
10069
10173
|
label,
|
|
10070
10174
|
{
|
|
10071
10175
|
anchorPoint: candidate.anchorPoint,
|
|
10176
|
+
connectorSource: phase === "lateral-shift" ? candidate.connectorSource : void 0,
|
|
10072
10177
|
orientation: candidate.orientation,
|
|
10073
10178
|
phase
|
|
10074
10179
|
},
|
|
@@ -10097,7 +10202,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
10097
10202
|
}
|
|
10098
10203
|
return "valid";
|
|
10099
10204
|
}
|
|
10100
|
-
|
|
10205
|
+
isAcceptableConnectedChipBoundaryOverlap(candidate, label, bounds) {
|
|
10101
10206
|
const collidingChips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds);
|
|
10102
10207
|
if (collidingChips.length === 0) return false;
|
|
10103
10208
|
const labelChipIds = new Set(
|
|
@@ -41,7 +41,12 @@ import {
|
|
|
41
41
|
tracePathIntersectsBounds,
|
|
42
42
|
} from "./geometry"
|
|
43
43
|
import { orderRoutedLabelsBeforeOverlappingPortLabels } from "./orderRoutedLabelsBeforeOverlappingPortLabels"
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
extendVerticalTracePathAtInteriorPoint,
|
|
46
|
+
getPinMap,
|
|
47
|
+
getTracePins,
|
|
48
|
+
toNetLabelPlacementPatch,
|
|
49
|
+
} from "./traces"
|
|
45
50
|
import type {
|
|
46
51
|
AvailableNetOrientationSolverParams,
|
|
47
52
|
Bounds,
|
|
@@ -55,6 +60,7 @@ import { visualizeAvailableNetOrientationSolver } from "./visualize"
|
|
|
55
60
|
import { AvailableNetOrientationObstacleIndex } from "./AvailableNetOrientationObstacleIndex"
|
|
56
61
|
|
|
57
62
|
const LABEL_TRACE_CLEARANCE = 0.1
|
|
63
|
+
const MIN_DOWNWARD_GROUND_RAIL_EXTENSION = 0.2
|
|
58
64
|
const CONNECTED_PIN_ROW_OVERLAP_TOLERANCE = 0.1
|
|
59
65
|
|
|
60
66
|
export class AvailableNetOrientationSolver extends BaseSolver {
|
|
@@ -357,6 +363,21 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
357
363
|
) {
|
|
358
364
|
if (candidate.phase === "trace-anchor") return
|
|
359
365
|
|
|
366
|
+
if (candidate.phase === "downward-ground-rail-extension") {
|
|
367
|
+
for (const mspPairId of label.mspConnectionPairIds) {
|
|
368
|
+
const hostTrace = this.traceMap[mspPairId]
|
|
369
|
+
if (!hostTrace) continue
|
|
370
|
+
const extendedTracePath = extendVerticalTracePathAtInteriorPoint({
|
|
371
|
+
tracePath: hostTrace.tracePath,
|
|
372
|
+
sourcePoint: label.anchorPoint,
|
|
373
|
+
extensionEndPoint: candidate.anchorPoint,
|
|
374
|
+
})
|
|
375
|
+
if (!extendedTracePath) continue
|
|
376
|
+
hostTrace.tracePath = extendedTracePath
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
360
381
|
const tracePath = this.getCandidateConnectorTrace(
|
|
361
382
|
label,
|
|
362
383
|
candidate,
|
|
@@ -416,6 +437,12 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
416
437
|
(connection) => connection.netId === label.netId,
|
|
417
438
|
)
|
|
418
439
|
const isTwoPinNet = netConnection?.pinIds.length === 2
|
|
440
|
+
const isDownwardGroundRail =
|
|
441
|
+
netConnection?.isGround &&
|
|
442
|
+
isXOrientation(label.orientation) &&
|
|
443
|
+
orientations.length === 1 &&
|
|
444
|
+
requiredOrientation === "y-" &&
|
|
445
|
+
this.hasTraceContinuingInOrientation(label, "y+")
|
|
419
446
|
const isDistanceSplitVerticalRail =
|
|
420
447
|
(netConnection?.pinIds.length ?? 0) > 2 &&
|
|
421
448
|
isYOrientation(requiredOrientation) &&
|
|
@@ -495,6 +522,30 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
495
522
|
labelIndex,
|
|
496
523
|
orientations,
|
|
497
524
|
)
|
|
525
|
+
const rotatedCandidateNeedsLateralBranch =
|
|
526
|
+
rotatedCandidate !== null &&
|
|
527
|
+
Math.abs(rotatedCandidate.anchorPoint.x - label.anchorPoint.x) > EPS
|
|
528
|
+
|
|
529
|
+
if (isDownwardGroundRail && rotatedCandidateNeedsLateralBranch) {
|
|
530
|
+
const downwardRailCandidate = this.findValidCandidateInShiftColumn({
|
|
531
|
+
label,
|
|
532
|
+
labelIndex,
|
|
533
|
+
orientation: requiredOrientation,
|
|
534
|
+
direction: dir(requiredOrientation),
|
|
535
|
+
baseAnchor: label.anchorPoint,
|
|
536
|
+
maxSearchDistance: this.getSearchDistanceLimit(
|
|
537
|
+
label,
|
|
538
|
+
requiredOrientation,
|
|
539
|
+
),
|
|
540
|
+
outwardDistance: 0,
|
|
541
|
+
phase: "downward-ground-rail-extension",
|
|
542
|
+
stopOnTraceCollision: false,
|
|
543
|
+
connectorSource: label.anchorPoint,
|
|
544
|
+
startDistance: MIN_DOWNWARD_GROUND_RAIL_EXTENSION,
|
|
545
|
+
})
|
|
546
|
+
if (downwardRailCandidate) return downwardRailCandidate
|
|
547
|
+
}
|
|
548
|
+
|
|
498
549
|
if (rotatedCandidate) return rotatedCandidate
|
|
499
550
|
|
|
500
551
|
const traceAnchorCandidate = this.findValidTraceAnchorCandidate(
|
|
@@ -1076,11 +1127,20 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1076
1127
|
x: baseAnchor.x + direction.x * distance,
|
|
1077
1128
|
y: baseAnchor.y + direction.y * distance,
|
|
1078
1129
|
}
|
|
1130
|
+
const projectedConnectorSource =
|
|
1131
|
+
connectorSource ??
|
|
1132
|
+
(phase === "lateral-shift"
|
|
1133
|
+
? this.findProjectedConnectorSourceOnHostTrace(
|
|
1134
|
+
label,
|
|
1135
|
+
anchorPoint,
|
|
1136
|
+
orientation,
|
|
1137
|
+
)
|
|
1138
|
+
: undefined)
|
|
1079
1139
|
const candidate = this.createCandidate(
|
|
1080
1140
|
label,
|
|
1081
1141
|
anchorPoint,
|
|
1082
1142
|
orientation,
|
|
1083
|
-
|
|
1143
|
+
projectedConnectorSource,
|
|
1084
1144
|
)
|
|
1085
1145
|
const result = this.evaluateCandidate(
|
|
1086
1146
|
candidate,
|
|
@@ -1163,6 +1223,14 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1163
1223
|
])
|
|
1164
1224
|
}
|
|
1165
1225
|
|
|
1226
|
+
if (candidate.connectorSource) {
|
|
1227
|
+
return getConnectorTracePath(
|
|
1228
|
+
candidate.connectorSource,
|
|
1229
|
+
candidate.anchorPoint,
|
|
1230
|
+
candidate.orientation,
|
|
1231
|
+
)
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1166
1234
|
if (candidate.phase === "lateral-shift") {
|
|
1167
1235
|
const chipOutwardDir = this.crowdedPortOnlyLabelIndices.has(labelIndex)
|
|
1168
1236
|
? this.getChipOutwardDirection(label.anchorPoint)
|
|
@@ -1185,12 +1253,69 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1185
1253
|
}
|
|
1186
1254
|
|
|
1187
1255
|
return getConnectorTracePath(
|
|
1188
|
-
|
|
1256
|
+
label.anchorPoint,
|
|
1189
1257
|
candidate.anchorPoint,
|
|
1190
1258
|
candidate.orientation,
|
|
1191
1259
|
)
|
|
1192
1260
|
}
|
|
1193
1261
|
|
|
1262
|
+
/**
|
|
1263
|
+
* A laterally shifted label can branch directly from the interior of its
|
|
1264
|
+
* existing host trace. Prefer that tee over running a new connector beside
|
|
1265
|
+
* the host trace from the old label anchor.
|
|
1266
|
+
*/
|
|
1267
|
+
private findProjectedConnectorSourceOnHostTrace(
|
|
1268
|
+
label: NetLabelPlacement,
|
|
1269
|
+
targetAnchor: Point,
|
|
1270
|
+
orientation: FacingDirection,
|
|
1271
|
+
): Point | undefined {
|
|
1272
|
+
const orientationDirection = dir(orientation)
|
|
1273
|
+
const projectedSources: Point[] = []
|
|
1274
|
+
|
|
1275
|
+
for (const traceId of label.mspConnectionPairIds) {
|
|
1276
|
+
const hostTrace = this.traceMap[traceId]
|
|
1277
|
+
if (!hostTrace) continue
|
|
1278
|
+
|
|
1279
|
+
for (
|
|
1280
|
+
let pointIndex = 0;
|
|
1281
|
+
pointIndex < hostTrace.tracePath.length - 1;
|
|
1282
|
+
pointIndex++
|
|
1283
|
+
) {
|
|
1284
|
+
const start = hostTrace.tracePath[pointIndex]!
|
|
1285
|
+
const end = hostTrace.tracePath[pointIndex + 1]!
|
|
1286
|
+
const segmentIsPerpendicularToLabel = isYOrientation(orientation)
|
|
1287
|
+
? Math.abs(start.y - end.y) <= EPS
|
|
1288
|
+
: Math.abs(start.x - end.x) <= EPS
|
|
1289
|
+
if (!segmentIsPerpendicularToLabel) continue
|
|
1290
|
+
|
|
1291
|
+
const source = isYOrientation(orientation)
|
|
1292
|
+
? { x: targetAnchor.x, y: start.y }
|
|
1293
|
+
: { x: start.x, y: targetAnchor.y }
|
|
1294
|
+
if (
|
|
1295
|
+
!tracePathContainsPoint([start, end], label.anchorPoint) ||
|
|
1296
|
+
!tracePathContainsPoint([start, end], source)
|
|
1297
|
+
) {
|
|
1298
|
+
continue
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
const targetDistanceAlongOrientation =
|
|
1302
|
+
(targetAnchor.x - source.x) * orientationDirection.x +
|
|
1303
|
+
(targetAnchor.y - source.y) * orientationDirection.y
|
|
1304
|
+
if (targetDistanceAlongOrientation >= -EPS) {
|
|
1305
|
+
projectedSources.push(source)
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
return projectedSources.sort(
|
|
1311
|
+
(first, second) =>
|
|
1312
|
+
Math.abs(first.x - targetAnchor.x) +
|
|
1313
|
+
Math.abs(first.y - targetAnchor.y) -
|
|
1314
|
+
(Math.abs(second.x - targetAnchor.x) +
|
|
1315
|
+
Math.abs(second.y - targetAnchor.y)),
|
|
1316
|
+
)[0]
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1194
1319
|
private isStandaloneSinglePinNetLabel(label: NetLabelPlacement) {
|
|
1195
1320
|
return this.inputProblem.netConnections.some(
|
|
1196
1321
|
(connection) =>
|
|
@@ -1451,10 +1576,18 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1451
1576
|
label,
|
|
1452
1577
|
})
|
|
1453
1578
|
if (boundsStatus !== "valid") {
|
|
1579
|
+
const canOverlapConnectedChipBoundary =
|
|
1580
|
+
phase === "trace-anchor" ||
|
|
1581
|
+
phase === "connected-rail-shift" ||
|
|
1582
|
+
phase === "downward-ground-rail-extension"
|
|
1454
1583
|
if (
|
|
1455
|
-
|
|
1584
|
+
!canOverlapConnectedChipBoundary ||
|
|
1456
1585
|
boundsStatus !== "chip-collision" ||
|
|
1457
|
-
!this.
|
|
1586
|
+
!this.isAcceptableConnectedChipBoundaryOverlap(
|
|
1587
|
+
candidate,
|
|
1588
|
+
label,
|
|
1589
|
+
bounds,
|
|
1590
|
+
) ||
|
|
1458
1591
|
(phase === "connected-rail-shift" &&
|
|
1459
1592
|
!this.staysOutsideConnectedPinRow({ candidate, label, bounds }))
|
|
1460
1593
|
) {
|
|
@@ -1474,6 +1607,8 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1474
1607
|
label,
|
|
1475
1608
|
{
|
|
1476
1609
|
anchorPoint: candidate.anchorPoint,
|
|
1610
|
+
connectorSource:
|
|
1611
|
+
phase === "lateral-shift" ? candidate.connectorSource : undefined,
|
|
1477
1612
|
orientation: candidate.orientation,
|
|
1478
1613
|
phase,
|
|
1479
1614
|
},
|
|
@@ -1511,7 +1646,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
1511
1646
|
return "valid"
|
|
1512
1647
|
}
|
|
1513
1648
|
|
|
1514
|
-
private
|
|
1649
|
+
private isAcceptableConnectedChipBoundaryOverlap(
|
|
1515
1650
|
candidate: CandidateLabel,
|
|
1516
1651
|
label: NetLabelPlacement,
|
|
1517
1652
|
bounds: Bounds,
|
|
@@ -1,8 +1,44 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
1
2
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
2
3
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
+
import {
|
|
5
|
+
isVertical,
|
|
6
|
+
pointsEqual,
|
|
7
|
+
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
|
|
3
8
|
import type { InputPin, InputProblem } from "lib/types/InputProblem"
|
|
4
9
|
import type { CandidateLabel } from "./types"
|
|
5
10
|
|
|
11
|
+
export const extendVerticalTracePathAtInteriorPoint = ({
|
|
12
|
+
tracePath,
|
|
13
|
+
sourcePoint,
|
|
14
|
+
extensionEndPoint,
|
|
15
|
+
}: {
|
|
16
|
+
tracePath: Point[]
|
|
17
|
+
sourcePoint: Point
|
|
18
|
+
extensionEndPoint: Point
|
|
19
|
+
}) => {
|
|
20
|
+
const sourceIndex = tracePath.findIndex((point) =>
|
|
21
|
+
pointsEqual(point, sourcePoint),
|
|
22
|
+
)
|
|
23
|
+
if (sourceIndex <= 0 || sourceIndex >= tracePath.length - 1) return null
|
|
24
|
+
|
|
25
|
+
const extensionDirectionY = extensionEndPoint.y - sourcePoint.y
|
|
26
|
+
const adjacentIndex = [sourceIndex - 1, sourceIndex + 1].find((index) => {
|
|
27
|
+
const point = tracePath[index]!
|
|
28
|
+
return (
|
|
29
|
+
isVertical(point, sourcePoint) &&
|
|
30
|
+
(point.y - sourcePoint.y) * extensionDirectionY < 0
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
if (adjacentIndex === undefined) return null
|
|
34
|
+
|
|
35
|
+
return tracePath.toSpliced(
|
|
36
|
+
Math.max(sourceIndex, adjacentIndex),
|
|
37
|
+
0,
|
|
38
|
+
extensionEndPoint,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
6
42
|
export const getPinMap = (inputProblem: InputProblem) => {
|
|
7
43
|
const pinMap: Record<string, InputPin & { chipId: string }> = {}
|
|
8
44
|
for (const chip of inputProblem.chips) {
|
package/package.json
CHANGED