@tscircuit/schematic-trace-solver 0.0.174 → 0.0.176
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 +5 -0
- package/dist/index.js +759 -91
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +5 -1
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +16 -1
- package/lib/solvers/InlineNetLabelSolver/restoreReroutesAroundSupersededLabels.ts +174 -0
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +2 -30
- package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +2 -0
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +748 -15
- package/lib/solvers/SameNetJunctionAlignmentSolver/findNearestSharedPinExitRail.ts +140 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +12 -5
- package/lib/utils/doesPathCoincideWithTraces.ts +17 -4
- package/lib/utils/pathIntersectsRenderedLabel.ts +33 -0
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260901T055358Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260901T064117Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260901T134241Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260707T134549Z/bug-report-20260707T134549Z.test.ts +9 -0
- package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260730T061837Z/bug-report-20260730T061837Z.test.ts +8 -4
- package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +6 -6
- package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +676 -0
- package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.json +2467 -0
- package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.test.ts +59 -0
- package/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg +134 -0
- package/tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.json +215 -0
- package/tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260901T134241Z/__snapshots__/bug-report-20260901T134241Z.snap.svg +115 -0
- package/tests/bug-reports/bug-report-20260901T134241Z/bug-report-20260901T134241Z.json +359 -0
- package/tests/bug-reports/bug-report-20260901T134241Z/bug-report-20260901T134241Z.test.ts +60 -0
- package/tests/examples/__snapshots__/example51.snap.svg +2 -2
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +10 -10
- package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +10 -10
- package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +18 -18
- package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +18 -18
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +10 -10
- package/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tida-01389-hbridge.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-trellis-core-gnd-net-label-overlap.snap.svg +134 -0
- package/tests/repros/__snapshots__/repro-wireless-mouse-charger-section.snap.svg +164 -0
- package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
- package/tests/repros/assets/repro-trellis-core-gnd-net-label-overlap.input.json +178 -0
- package/tests/repros/assets/repro-wireless-mouse-charger-section.input.json +591 -0
- package/tests/repros/repro-isolated-rs485-isow7841.test.ts +4 -1
- package/tests/repros/repro-powerbank-3v-system-power.test.ts +24 -0
- package/tests/repros/repro-tida-01389-hbridge.test.ts +24 -0
- package/tests/repros/repro-trellis-core-gnd-net-label-overlap.test.ts +73 -0
- package/tests/repros/repro-wireless-mouse-charger-section.test.ts +16 -0
- package/tests/solvers/InlineNetLabelSolver/restore-reroutes-around-superseded-labels.test.ts +186 -0
- package/tests/solvers/SameNetJunctionAlignmentSolver/parallel-load-rails.test.ts +1 -0
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
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
|
+
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
5
|
+
import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
4
6
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
7
|
import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
6
8
|
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
9
|
+
import { getMovedAnchorPointForReroute } from "lib/solvers/Example28Solver/getMovedAnchorPointForReroute"
|
|
7
10
|
import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
|
|
8
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
rectsOverlap,
|
|
13
|
+
tracePathContainsPoint,
|
|
14
|
+
} from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
|
|
9
15
|
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
10
16
|
import {
|
|
11
17
|
getVisibleTraceLength,
|
|
@@ -20,11 +26,17 @@ import {
|
|
|
20
26
|
pathEntersAnyNetLabel,
|
|
21
27
|
pathIntersectsAnyNetLabel,
|
|
22
28
|
} from "./pathIntersectsAnyNetLabel"
|
|
29
|
+
import {
|
|
30
|
+
findNearestSharedPinExitRail,
|
|
31
|
+
getPinFacingAxis,
|
|
32
|
+
isCoordinateOnPinFacingSide,
|
|
33
|
+
} from "./findNearestSharedPinExitRail"
|
|
23
34
|
|
|
24
35
|
interface AlignSameNetJunctionsInput {
|
|
25
36
|
inputProblem: InputProblem
|
|
26
37
|
traces: SolvedTracePath[]
|
|
27
38
|
netLabelPlacements: NetLabelPlacement[]
|
|
39
|
+
netLabelConnectorTraceIds: ReadonlySet<MspConnectionPairId>
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
interface HorizontalSegment {
|
|
@@ -39,8 +51,18 @@ const MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET = 0.2
|
|
|
39
51
|
// A shared vertical pin can anchor an existing rail when the rail is only a
|
|
40
52
|
// symbol-stem correction away from the pin itself.
|
|
41
53
|
const MAX_SHARED_PIN_RAIL_OFFSET = 0.05
|
|
54
|
+
// Nearby rails that leave the same junction can be combined even when the
|
|
55
|
+
// shared rail is slightly longer. The visible result has one continuous run
|
|
56
|
+
// instead of two parallel runs joined by a jog.
|
|
57
|
+
const MAX_SHARED_ENDPOINT_RAIL_OFFSET = 0.8
|
|
42
58
|
const MIN_RETURN_STEM_LENGTH = 0.05
|
|
43
59
|
|
|
60
|
+
const getSegmentAxis = (start: Point, end: Point) => {
|
|
61
|
+
if (isHorizontal(start, end)) return "x" as const
|
|
62
|
+
if (isVertical(start, end)) return "y" as const
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
44
66
|
export const getSharedPin = ({
|
|
45
67
|
donorTrace,
|
|
46
68
|
branchTrace,
|
|
@@ -264,11 +286,591 @@ const getAlignedBranchPath = ({
|
|
|
264
286
|
return [...sharedToOther].reverse()
|
|
265
287
|
}
|
|
266
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Reuse the row of an adjacent same-side pin for a branch leaving their
|
|
291
|
+
* shared outer stem. This removes the extra parallel jog produced when the
|
|
292
|
+
* branch initially routes beyond both pin rows before approaching a
|
|
293
|
+
* perpendicular target pin. The topology is detected from pin-facing axes
|
|
294
|
+
* and shared segments; the number and ordering of intermediate turns do not
|
|
295
|
+
* define eligibility.
|
|
296
|
+
*/
|
|
297
|
+
const getAlignedParallelPinExitPath = ({
|
|
298
|
+
donorTrace,
|
|
299
|
+
branchTrace,
|
|
300
|
+
}: {
|
|
301
|
+
donorTrace: SolvedTracePath
|
|
302
|
+
branchTrace: SolvedTracePath
|
|
303
|
+
}): Point[] | null => {
|
|
304
|
+
const sharedPin = getSharedPin({ donorTrace, branchTrace })
|
|
305
|
+
if (!sharedPin) return null
|
|
306
|
+
const adjacentPin = getOtherPin({ trace: donorTrace, sharedPin })
|
|
307
|
+
const targetPin = getOtherPin({ trace: branchTrace, sharedPin })
|
|
308
|
+
if (!adjacentPin || !targetPin) return null
|
|
309
|
+
|
|
310
|
+
const facing = sharedPin._facingDirection
|
|
311
|
+
const departureAxis = getPinFacingAxis(sharedPin)
|
|
312
|
+
if (!facing || !departureAxis) return null
|
|
313
|
+
const stemAxis = departureAxis === "x" ? "y" : "x"
|
|
314
|
+
if (
|
|
315
|
+
adjacentPin.chipId !== sharedPin.chipId ||
|
|
316
|
+
adjacentPin._facingDirection !== facing ||
|
|
317
|
+
!nearlyEqual(adjacentPin[departureAxis], sharedPin[departureAxis]) ||
|
|
318
|
+
getPinFacingAxis(targetPin) !== stemAxis
|
|
319
|
+
) {
|
|
320
|
+
return null
|
|
321
|
+
}
|
|
322
|
+
const adjacentPinOffset = Math.abs(
|
|
323
|
+
adjacentPin[stemAxis] - sharedPin[stemAxis],
|
|
324
|
+
)
|
|
325
|
+
if (
|
|
326
|
+
adjacentPinOffset > MAX_ALIGNED_LOAD_PIN_OFFSET &&
|
|
327
|
+
!nearlyEqual(adjacentPinOffset, MAX_ALIGNED_LOAD_PIN_OFFSET)
|
|
328
|
+
) {
|
|
329
|
+
return null
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const branchStartsAtSharedPin = branchTrace.pins[0].pinId === sharedPin.pinId
|
|
333
|
+
let sharedToTargetPath = simplifyPath(branchTrace.tracePath)
|
|
334
|
+
if (!branchStartsAtSharedPin) sharedToTargetPath.reverse()
|
|
335
|
+
if (sharedToTargetPath.length < 2) return null
|
|
336
|
+
const branchDepartureSegment: [Point, Point] = [
|
|
337
|
+
sharedToTargetPath[0]!,
|
|
338
|
+
sharedToTargetPath[1]!,
|
|
339
|
+
]
|
|
340
|
+
const sharedExitRail = findNearestSharedPinExitRail({
|
|
341
|
+
donorPath: donorTrace.tracePath,
|
|
342
|
+
branchDepartureSegment,
|
|
343
|
+
sharedPin,
|
|
344
|
+
adjacentPin,
|
|
345
|
+
})
|
|
346
|
+
if (!sharedExitRail) return null
|
|
347
|
+
|
|
348
|
+
const alignedRailCoordinate = adjacentPin[stemAxis]
|
|
349
|
+
if (
|
|
350
|
+
!isCoordinateOnPinFacingSide({
|
|
351
|
+
coordinate: alignedRailCoordinate,
|
|
352
|
+
axis: stemAxis,
|
|
353
|
+
pin: targetPin,
|
|
354
|
+
})
|
|
355
|
+
) {
|
|
356
|
+
return null
|
|
357
|
+
}
|
|
358
|
+
const alignedStemExit = {
|
|
359
|
+
...sharedExitRail.sharedPinExit,
|
|
360
|
+
[stemAxis]: alignedRailCoordinate,
|
|
361
|
+
}
|
|
362
|
+
const targetApproach = {
|
|
363
|
+
x: targetPin.x,
|
|
364
|
+
y: targetPin.y,
|
|
365
|
+
[stemAxis]: alignedRailCoordinate,
|
|
366
|
+
}
|
|
367
|
+
const candidate = simplifyPath([
|
|
368
|
+
{ x: sharedPin.x, y: sharedPin.y },
|
|
369
|
+
sharedExitRail.sharedPinExit,
|
|
370
|
+
alignedStemExit,
|
|
371
|
+
targetApproach,
|
|
372
|
+
{ x: targetPin.x, y: targetPin.y },
|
|
373
|
+
])
|
|
374
|
+
return branchStartsAtSharedPin ? candidate : candidate.reverse()
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Move the first movable rail of a branch onto the first rail of another
|
|
379
|
+
* trace leaving the same endpoint. The two perpendicular rail intervals must
|
|
380
|
+
* touch after the move; the caller then verifies that the combined visible
|
|
381
|
+
* trace loses a segment and that the reroute is clear.
|
|
382
|
+
*/
|
|
383
|
+
const getAlignedSharedEndpointRailPath = ({
|
|
384
|
+
donorTrace,
|
|
385
|
+
branchTrace,
|
|
386
|
+
netLabelConnectorTraceIds,
|
|
387
|
+
}: {
|
|
388
|
+
donorTrace: SolvedTracePath
|
|
389
|
+
branchTrace: SolvedTracePath
|
|
390
|
+
netLabelConnectorTraceIds: ReadonlySet<MspConnectionPairId>
|
|
391
|
+
}): Point[] | null => {
|
|
392
|
+
if (
|
|
393
|
+
netLabelConnectorTraceIds.has(donorTrace.mspPairId) ||
|
|
394
|
+
netLabelConnectorTraceIds.has(branchTrace.mspPairId)
|
|
395
|
+
) {
|
|
396
|
+
return null
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const sharedPin = getSharedPin({ donorTrace, branchTrace })
|
|
400
|
+
if (!sharedPin) return null
|
|
401
|
+
|
|
402
|
+
const pathFromShared = (trace: SolvedTracePath) => {
|
|
403
|
+
const path = simplifyPath(trace.tracePath)
|
|
404
|
+
return trace.pins[0]!.pinId === sharedPin.pinId ? path : [...path].reverse()
|
|
405
|
+
}
|
|
406
|
+
const donorPath = pathFromShared(donorTrace)
|
|
407
|
+
const branchPath = pathFromShared(branchTrace)
|
|
408
|
+
// The branch rail must be internal so moving it cannot move either pin.
|
|
409
|
+
if (donorPath.length < 3 || branchPath.length < 4) return null
|
|
410
|
+
|
|
411
|
+
const donorDepartureAxis = getSegmentAxis(donorPath[0]!, donorPath[1]!)
|
|
412
|
+
const branchDepartureAxis = getSegmentAxis(branchPath[0]!, branchPath[1]!)
|
|
413
|
+
if (!donorDepartureAxis || donorDepartureAxis !== branchDepartureAxis) {
|
|
414
|
+
return null
|
|
415
|
+
}
|
|
416
|
+
const donorDeparture =
|
|
417
|
+
donorPath[1]![donorDepartureAxis] - donorPath[0]![donorDepartureAxis]
|
|
418
|
+
const branchDeparture =
|
|
419
|
+
branchPath[1]![branchDepartureAxis] - branchPath[0]![branchDepartureAxis]
|
|
420
|
+
if (Math.sign(donorDeparture) !== Math.sign(branchDeparture)) return null
|
|
421
|
+
|
|
422
|
+
const donorRailAxis = getSegmentAxis(donorPath[1]!, donorPath[2]!)
|
|
423
|
+
const branchRailAxis = getSegmentAxis(branchPath[1]!, branchPath[2]!)
|
|
424
|
+
if (
|
|
425
|
+
!donorRailAxis ||
|
|
426
|
+
donorRailAxis !== branchRailAxis ||
|
|
427
|
+
donorRailAxis === donorDepartureAxis
|
|
428
|
+
) {
|
|
429
|
+
return null
|
|
430
|
+
}
|
|
431
|
+
// A return bus has another rail with this orientation near its far
|
|
432
|
+
// endpoint. Moving only the first rail would partially rewrite a shape that
|
|
433
|
+
// must be evaluated as a whole by the endpoint-bus transformation.
|
|
434
|
+
let branchParallelRailCount = 0
|
|
435
|
+
for (let index = 1; index < branchPath.length - 2; index++) {
|
|
436
|
+
if (
|
|
437
|
+
getSegmentAxis(branchPath[index]!, branchPath[index + 1]!) ===
|
|
438
|
+
branchRailAxis
|
|
439
|
+
) {
|
|
440
|
+
branchParallelRailCount++
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (branchParallelRailCount !== 1) return null
|
|
444
|
+
|
|
445
|
+
const railCoordinateAxis = donorRailAxis === "x" ? "y" : "x"
|
|
446
|
+
const donorCoordinate = donorPath[1]![railCoordinateAxis]
|
|
447
|
+
const branchCoordinate = branchPath[1]![railCoordinateAxis]
|
|
448
|
+
const railOffset = Math.abs(donorCoordinate - branchCoordinate)
|
|
449
|
+
if (
|
|
450
|
+
nearlyEqual(donorCoordinate, branchCoordinate) ||
|
|
451
|
+
railOffset > MAX_SHARED_ENDPOINT_RAIL_OFFSET
|
|
452
|
+
) {
|
|
453
|
+
return null
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const donorRange = [
|
|
457
|
+
donorPath[1]![donorRailAxis],
|
|
458
|
+
donorPath[2]![donorRailAxis],
|
|
459
|
+
].sort((a, b) => a - b)
|
|
460
|
+
const branchRange = [
|
|
461
|
+
branchPath[1]![branchRailAxis],
|
|
462
|
+
branchPath[2]![branchRailAxis],
|
|
463
|
+
].sort((a, b) => a - b)
|
|
464
|
+
const railsTouchAfterAlignment =
|
|
465
|
+
Math.min(donorRange[1]!, branchRange[1]!) >=
|
|
466
|
+
Math.max(donorRange[0]!, branchRange[0]!) - 1e-6
|
|
467
|
+
if (!railsTouchAfterAlignment) return null
|
|
468
|
+
|
|
469
|
+
const candidateFromShared = simplifyPath(
|
|
470
|
+
branchPath.map((point, index) =>
|
|
471
|
+
index === 1 || index === 2
|
|
472
|
+
? { ...point, [railCoordinateAxis]: donorCoordinate }
|
|
473
|
+
: point,
|
|
474
|
+
),
|
|
475
|
+
)
|
|
476
|
+
return branchTrace.pins[0]!.pinId === sharedPin.pinId
|
|
477
|
+
? candidateFromShared
|
|
478
|
+
: candidateFromShared.reverse()
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Remove the jog between a routed trace and an attached generated-label
|
|
483
|
+
* connector. The connector must initially run along one donor segment, then
|
|
484
|
+
* turn onto a nearby perpendicular rail whose interval touches the donor's
|
|
485
|
+
* adjacent rail after alignment. Only the generated connector is changed.
|
|
486
|
+
*/
|
|
487
|
+
const getAlignedAttachedLabelConnectorPath = ({
|
|
488
|
+
donorTrace,
|
|
489
|
+
connectorTrace,
|
|
490
|
+
netLabelConnectorTraceIds,
|
|
491
|
+
}: {
|
|
492
|
+
donorTrace: SolvedTracePath
|
|
493
|
+
connectorTrace: SolvedTracePath
|
|
494
|
+
netLabelConnectorTraceIds: ReadonlySet<MspConnectionPairId>
|
|
495
|
+
}): Point[] | null => {
|
|
496
|
+
if (
|
|
497
|
+
netLabelConnectorTraceIds.has(donorTrace.mspPairId) ||
|
|
498
|
+
!netLabelConnectorTraceIds.has(connectorTrace.mspPairId)
|
|
499
|
+
) {
|
|
500
|
+
return null
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const findCandidate = (connectorPath: Point[]) => {
|
|
504
|
+
if (connectorPath.length < 3) return null
|
|
505
|
+
const junction = connectorPath[0]!
|
|
506
|
+
const turn = connectorPath[1]!
|
|
507
|
+
const railEnd = connectorPath[2]!
|
|
508
|
+
const departureAxis = getSegmentAxis(junction, turn)
|
|
509
|
+
const railAxis = getSegmentAxis(turn, railEnd)
|
|
510
|
+
if (!departureAxis || !railAxis || departureAxis === railAxis) return null
|
|
511
|
+
|
|
512
|
+
const donorSegmentIndex = donorTrace.tracePath.findIndex(
|
|
513
|
+
(point, index, path) => {
|
|
514
|
+
const next = path[index + 1]
|
|
515
|
+
return (
|
|
516
|
+
next &&
|
|
517
|
+
getSegmentAxis(point, next) === departureAxis &&
|
|
518
|
+
tracePathContainsPoint([point, next], junction) &&
|
|
519
|
+
tracePathContainsPoint([point, next], turn)
|
|
520
|
+
)
|
|
521
|
+
},
|
|
522
|
+
)
|
|
523
|
+
if (donorSegmentIndex < 0) return null
|
|
524
|
+
|
|
525
|
+
const donorSegmentStart = donorTrace.tracePath[donorSegmentIndex]!
|
|
526
|
+
const donorSegmentEnd = donorTrace.tracePath[donorSegmentIndex + 1]!
|
|
527
|
+
// A connector already attached at a donor corner has no offset junction
|
|
528
|
+
// to remove; moving its rail would rewrite the label's intended escape.
|
|
529
|
+
if (
|
|
530
|
+
(nearlyEqual(junction.x, donorSegmentStart.x) &&
|
|
531
|
+
nearlyEqual(junction.y, donorSegmentStart.y)) ||
|
|
532
|
+
(nearlyEqual(junction.x, donorSegmentEnd.x) &&
|
|
533
|
+
nearlyEqual(junction.y, donorSegmentEnd.y))
|
|
534
|
+
) {
|
|
535
|
+
return null
|
|
536
|
+
}
|
|
537
|
+
const adjacentDonorRails = [
|
|
538
|
+
donorSegmentIndex > 0
|
|
539
|
+
? [donorTrace.tracePath[donorSegmentIndex - 1]!, donorSegmentStart]
|
|
540
|
+
: null,
|
|
541
|
+
donorSegmentIndex + 2 < donorTrace.tracePath.length
|
|
542
|
+
? [donorSegmentEnd, donorTrace.tracePath[donorSegmentIndex + 2]!]
|
|
543
|
+
: null,
|
|
544
|
+
].flatMap((segment) =>
|
|
545
|
+
segment && getSegmentAxis(segment[0], segment[1]) === railAxis
|
|
546
|
+
? [segment as [Point, Point]]
|
|
547
|
+
: [],
|
|
548
|
+
)
|
|
549
|
+
if (adjacentDonorRails.length === 0) return null
|
|
550
|
+
|
|
551
|
+
const railCoordinateAxis = railAxis === "x" ? "y" : "x"
|
|
552
|
+
const connectorRailCoordinate = turn[railCoordinateAxis]
|
|
553
|
+
const connectorRange = [turn[railAxis], railEnd[railAxis]].sort(
|
|
554
|
+
(a, b) => a - b,
|
|
555
|
+
)
|
|
556
|
+
const donorRail = adjacentDonorRails
|
|
557
|
+
.map((segment) => ({
|
|
558
|
+
segment,
|
|
559
|
+
coordinate: segment[0][railCoordinateAxis],
|
|
560
|
+
offset: Math.abs(
|
|
561
|
+
segment[0][railCoordinateAxis] - connectorRailCoordinate,
|
|
562
|
+
),
|
|
563
|
+
}))
|
|
564
|
+
.filter(({ segment, offset }) => {
|
|
565
|
+
if (offset > MAX_SHARED_ENDPOINT_RAIL_OFFSET) return false
|
|
566
|
+
const donorRange = [segment[0][railAxis], segment[1][railAxis]].sort(
|
|
567
|
+
(a, b) => a - b,
|
|
568
|
+
)
|
|
569
|
+
const overlapStart = Math.max(donorRange[0]!, connectorRange[0]!)
|
|
570
|
+
const overlapEnd = Math.min(donorRange[1]!, connectorRange[1]!)
|
|
571
|
+
// Join two end-to-end rails. Do not collapse a connector onto a donor
|
|
572
|
+
// rail when their intervals already overlap, which would reposition a
|
|
573
|
+
// deliberate label branch rather than remove a jog.
|
|
574
|
+
return (
|
|
575
|
+
overlapEnd >= overlapStart - 1e-6 &&
|
|
576
|
+
Math.abs(overlapEnd - overlapStart) <= 1e-6
|
|
577
|
+
)
|
|
578
|
+
})
|
|
579
|
+
.sort((a, b) => a.offset - b.offset)[0]
|
|
580
|
+
if (!donorRail || nearlyEqual(donorRail.offset, 0)) return null
|
|
581
|
+
|
|
582
|
+
return simplifyPath(
|
|
583
|
+
connectorPath.map((point, index) =>
|
|
584
|
+
index === 1 || index === 2
|
|
585
|
+
? { ...point, [railCoordinateAxis]: donorRail.coordinate }
|
|
586
|
+
: point,
|
|
587
|
+
),
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const forwardPath = simplifyPath(connectorTrace.tracePath)
|
|
592
|
+
const forwardCandidate = findCandidate(forwardPath)
|
|
593
|
+
if (forwardCandidate) return forwardCandidate
|
|
594
|
+
const reverseCandidate = findCandidate([...forwardPath].reverse())
|
|
595
|
+
return reverseCandidate ? reverseCandidate.reverse() : null
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const pathsIntersect = (firstPath: Point[], secondPath: Point[]) => {
|
|
599
|
+
if (
|
|
600
|
+
firstPath.some((point) => tracePathContainsPoint(secondPath, point)) ||
|
|
601
|
+
secondPath.some((point) => tracePathContainsPoint(firstPath, point))
|
|
602
|
+
) {
|
|
603
|
+
return true
|
|
604
|
+
}
|
|
605
|
+
for (let firstIndex = 1; firstIndex < firstPath.length; firstIndex++) {
|
|
606
|
+
for (let secondIndex = 1; secondIndex < secondPath.length; secondIndex++) {
|
|
607
|
+
if (
|
|
608
|
+
getSegmentIntersection(
|
|
609
|
+
firstPath[firstIndex - 1]!,
|
|
610
|
+
firstPath[firstIndex]!,
|
|
611
|
+
secondPath[secondIndex - 1]!,
|
|
612
|
+
secondPath[secondIndex]!,
|
|
613
|
+
)
|
|
614
|
+
) {
|
|
615
|
+
return true
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return false
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Keep generated label connectors joined when their host trace moves. Only a
|
|
624
|
+
* connector endpoint is projected, and only along its existing first/last
|
|
625
|
+
* segment, so the label and the rest of its route stay fixed.
|
|
626
|
+
*/
|
|
627
|
+
const preserveAttachedLabelConnectorJunctions = ({
|
|
628
|
+
originalTrace,
|
|
629
|
+
candidateTrace,
|
|
630
|
+
traces,
|
|
631
|
+
netLabelConnectorTraceIds,
|
|
632
|
+
}: {
|
|
633
|
+
originalTrace: SolvedTracePath
|
|
634
|
+
candidateTrace: SolvedTracePath
|
|
635
|
+
traces: SolvedTracePath[]
|
|
636
|
+
netLabelConnectorTraceIds: ReadonlySet<MspConnectionPairId>
|
|
637
|
+
}): SolvedTracePath[] | null => {
|
|
638
|
+
let outputTraces = traces.map((trace) =>
|
|
639
|
+
trace.mspPairId === originalTrace.mspPairId ? candidateTrace : trace,
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
for (const connector of traces) {
|
|
643
|
+
if (
|
|
644
|
+
connector.mspPairId === originalTrace.mspPairId ||
|
|
645
|
+
!netLabelConnectorTraceIds.has(connector.mspPairId) ||
|
|
646
|
+
connector.globalConnNetId !== originalTrace.globalConnNetId ||
|
|
647
|
+
!pathsIntersect(originalTrace.tracePath, connector.tracePath) ||
|
|
648
|
+
pathsIntersect(candidateTrace.tracePath, connector.tracePath)
|
|
649
|
+
) {
|
|
650
|
+
continue
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const endpointIndex = [0, connector.tracePath.length - 1].find((index) =>
|
|
654
|
+
tracePathContainsPoint(
|
|
655
|
+
originalTrace.tracePath,
|
|
656
|
+
connector.tracePath[index]!,
|
|
657
|
+
),
|
|
658
|
+
)
|
|
659
|
+
if (endpointIndex === undefined) return null
|
|
660
|
+
const originalJunction = connector.tracePath[endpointIndex]!
|
|
661
|
+
const movedJunction = getMovedAnchorPointForReroute(
|
|
662
|
+
originalJunction,
|
|
663
|
+
originalTrace.tracePath,
|
|
664
|
+
candidateTrace.tracePath,
|
|
665
|
+
)
|
|
666
|
+
if (!movedJunction) return null
|
|
667
|
+
|
|
668
|
+
const neighborIndex = endpointIndex === 0 ? 1 : endpointIndex - 1
|
|
669
|
+
const neighbor = connector.tracePath[neighborIndex]
|
|
670
|
+
if (
|
|
671
|
+
!neighbor ||
|
|
672
|
+
getSegmentAxis(originalJunction, neighbor) !==
|
|
673
|
+
getSegmentAxis(movedJunction, neighbor)
|
|
674
|
+
) {
|
|
675
|
+
return null
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const movedConnector = {
|
|
679
|
+
...connector,
|
|
680
|
+
tracePath: connector.tracePath.map((point, index) =>
|
|
681
|
+
index === endpointIndex ? movedJunction : point,
|
|
682
|
+
),
|
|
683
|
+
}
|
|
684
|
+
if (!pathsIntersect(candidateTrace.tracePath, movedConnector.tracePath)) {
|
|
685
|
+
return null
|
|
686
|
+
}
|
|
687
|
+
outputTraces = outputTraces.map((trace) =>
|
|
688
|
+
trace.mspPairId === connector.mspPairId ? movedConnector : trace,
|
|
689
|
+
)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return outputTraces
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
type PerpendicularPinRail = {
|
|
696
|
+
railCoordinate: number
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const getPerpendicularPinRail = ({
|
|
700
|
+
trace,
|
|
701
|
+
endpointPin,
|
|
702
|
+
}: {
|
|
703
|
+
trace: SolvedTracePath
|
|
704
|
+
endpointPin: InputPin
|
|
705
|
+
}): PerpendicularPinRail | null => {
|
|
706
|
+
const otherPin = getOtherPin({ trace, sharedPin: endpointPin })
|
|
707
|
+
if (!otherPin) return null
|
|
708
|
+
|
|
709
|
+
const startsAtEndpoint = trace.pins[0].pinId === endpointPin.pinId
|
|
710
|
+
let endpointToOtherPath = simplifyPath(trace.tracePath)
|
|
711
|
+
if (!startsAtEndpoint) endpointToOtherPath.reverse()
|
|
712
|
+
if (endpointToOtherPath.length !== 3) return null
|
|
713
|
+
|
|
714
|
+
const [endpoint, railPoint, other] = endpointToOtherPath
|
|
715
|
+
const endpointFacing = endpointPin._facingDirection
|
|
716
|
+
if (endpointFacing === "y+" || endpointFacing === "y-") {
|
|
717
|
+
if (
|
|
718
|
+
otherPin._facingDirection !== "x+" &&
|
|
719
|
+
otherPin._facingDirection !== "x-"
|
|
720
|
+
) {
|
|
721
|
+
return null
|
|
722
|
+
}
|
|
723
|
+
if (
|
|
724
|
+
!isVertical(endpoint!, railPoint!) ||
|
|
725
|
+
!isHorizontal(railPoint!, other!) ||
|
|
726
|
+
!railIsOnFacingSide({ railY: railPoint!.y, pin: endpointPin })
|
|
727
|
+
) {
|
|
728
|
+
return null
|
|
729
|
+
}
|
|
730
|
+
const approachesOtherFromFacingSide =
|
|
731
|
+
otherPin._facingDirection === "x-"
|
|
732
|
+
? railPoint!.x < otherPin.x
|
|
733
|
+
: railPoint!.x > otherPin.x
|
|
734
|
+
return approachesOtherFromFacingSide
|
|
735
|
+
? { railCoordinate: railPoint!.y }
|
|
736
|
+
: null
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (endpointFacing !== "x+" && endpointFacing !== "x-") return null
|
|
740
|
+
if (
|
|
741
|
+
otherPin._facingDirection !== "y+" &&
|
|
742
|
+
otherPin._facingDirection !== "y-"
|
|
743
|
+
) {
|
|
744
|
+
return null
|
|
745
|
+
}
|
|
746
|
+
if (!isHorizontal(endpoint!, railPoint!) || !isVertical(railPoint!, other!)) {
|
|
747
|
+
return null
|
|
748
|
+
}
|
|
749
|
+
const railIsOnEndpointFacingSide =
|
|
750
|
+
endpointFacing === "x+"
|
|
751
|
+
? railPoint!.x > endpointPin.x
|
|
752
|
+
: railPoint!.x < endpointPin.x
|
|
753
|
+
const approachesOtherFromFacingSide =
|
|
754
|
+
otherPin._facingDirection === "y+"
|
|
755
|
+
? railPoint!.y > otherPin.y
|
|
756
|
+
: railPoint!.y < otherPin.y
|
|
757
|
+
return railIsOnEndpointFacingSide && approachesOtherFromFacingSide
|
|
758
|
+
? { railCoordinate: railPoint!.x }
|
|
759
|
+
: null
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Level a bus between two same-axis pins with the perpendicular rails that
|
|
764
|
+
* connect those endpoints to their loads. The candidate reuses both existing
|
|
765
|
+
* endpoint stubs, so a long bus does not meet each stub on a nearby parallel
|
|
766
|
+
* rail and leave a visible jog.
|
|
767
|
+
*/
|
|
768
|
+
const getAlignedPerpendicularEndpointBusPath = ({
|
|
769
|
+
donorTrace,
|
|
770
|
+
branchTrace,
|
|
771
|
+
traces,
|
|
772
|
+
}: {
|
|
773
|
+
donorTrace: SolvedTracePath
|
|
774
|
+
branchTrace: SolvedTracePath
|
|
775
|
+
traces: SolvedTracePath[]
|
|
776
|
+
}): Point[] | null => {
|
|
777
|
+
const sharedPin = getSharedPin({ donorTrace, branchTrace })
|
|
778
|
+
if (!sharedPin) return null
|
|
779
|
+
const returnPin = getOtherPin({ trace: branchTrace, sharedPin })
|
|
780
|
+
if (!returnPin) return null
|
|
781
|
+
|
|
782
|
+
const getOtherSameNetTracesAtPin = (pin: InputPin) =>
|
|
783
|
+
traces.filter(
|
|
784
|
+
(trace) =>
|
|
785
|
+
trace.mspPairId !== branchTrace.mspPairId &&
|
|
786
|
+
trace.globalConnNetId === branchTrace.globalConnNetId &&
|
|
787
|
+
trace.pinIds.includes(pin.pinId),
|
|
788
|
+
)
|
|
789
|
+
const sharedDonorTraces = getOtherSameNetTracesAtPin(sharedPin)
|
|
790
|
+
const returnDonorTraces = getOtherSameNetTracesAtPin(returnPin)
|
|
791
|
+
if (
|
|
792
|
+
sharedDonorTraces.length !== 1 ||
|
|
793
|
+
sharedDonorTraces[0]!.mspPairId !== donorTrace.mspPairId ||
|
|
794
|
+
returnDonorTraces.length !== 1
|
|
795
|
+
) {
|
|
796
|
+
return null
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const sharedRail = getPerpendicularPinRail({
|
|
800
|
+
trace: donorTrace,
|
|
801
|
+
endpointPin: sharedPin,
|
|
802
|
+
})
|
|
803
|
+
if (!sharedRail) return null
|
|
804
|
+
|
|
805
|
+
const facing = sharedPin._facingDirection
|
|
806
|
+
const verticalEndpoints = facing === "y+" || facing === "y-"
|
|
807
|
+
const horizontalEndpoints = facing === "x+" || facing === "x-"
|
|
808
|
+
if (!verticalEndpoints && !horizontalEndpoints) return null
|
|
809
|
+
if (
|
|
810
|
+
verticalEndpoints !==
|
|
811
|
+
(returnPin._facingDirection === "y+" || returnPin._facingDirection === "y-")
|
|
812
|
+
) {
|
|
813
|
+
return null
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const branchStartsAtSharedPin = branchTrace.pins[0].pinId === sharedPin.pinId
|
|
817
|
+
let sharedToReturnPath = simplifyPath(branchTrace.tracePath)
|
|
818
|
+
if (!branchStartsAtSharedPin) sharedToReturnPath.reverse()
|
|
819
|
+
if (sharedToReturnPath.length !== 6) return null
|
|
820
|
+
|
|
821
|
+
const expectedSegmentOrientations = verticalEndpoints
|
|
822
|
+
? ["V", "H", "V", "H", "V"]
|
|
823
|
+
: ["H", "V", "H", "V", "H"]
|
|
824
|
+
const actualSegmentOrientations = sharedToReturnPath
|
|
825
|
+
.slice(1)
|
|
826
|
+
.map((point, index) =>
|
|
827
|
+
isHorizontal(sharedToReturnPath[index]!, point)
|
|
828
|
+
? "H"
|
|
829
|
+
: isVertical(sharedToReturnPath[index]!, point)
|
|
830
|
+
? "V"
|
|
831
|
+
: "D",
|
|
832
|
+
)
|
|
833
|
+
if (
|
|
834
|
+
actualSegmentOrientations.some(
|
|
835
|
+
(orientation, index) =>
|
|
836
|
+
orientation !== expectedSegmentOrientations[index],
|
|
837
|
+
)
|
|
838
|
+
) {
|
|
839
|
+
return null
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const returnRail = getPerpendicularPinRail({
|
|
843
|
+
trace: returnDonorTraces[0]!,
|
|
844
|
+
endpointPin: returnPin,
|
|
845
|
+
})
|
|
846
|
+
if (!returnRail) return null
|
|
847
|
+
|
|
848
|
+
const candidate = verticalEndpoints
|
|
849
|
+
? simplifyPath([
|
|
850
|
+
{ x: sharedPin.x, y: sharedPin.y },
|
|
851
|
+
{ x: sharedPin.x, y: sharedRail.railCoordinate },
|
|
852
|
+
{ x: sharedToReturnPath[2]!.x, y: sharedRail.railCoordinate },
|
|
853
|
+
{ x: sharedToReturnPath[3]!.x, y: returnRail.railCoordinate },
|
|
854
|
+
{ x: returnPin.x, y: returnRail.railCoordinate },
|
|
855
|
+
{ x: returnPin.x, y: returnPin.y },
|
|
856
|
+
])
|
|
857
|
+
: simplifyPath([
|
|
858
|
+
{ x: sharedPin.x, y: sharedPin.y },
|
|
859
|
+
{ x: sharedRail.railCoordinate, y: sharedPin.y },
|
|
860
|
+
{ x: sharedRail.railCoordinate, y: sharedToReturnPath[2]!.y },
|
|
861
|
+
{ x: returnRail.railCoordinate, y: sharedToReturnPath[3]!.y },
|
|
862
|
+
{ x: returnRail.railCoordinate, y: returnPin.y },
|
|
863
|
+
{ x: returnPin.x, y: returnPin.y },
|
|
864
|
+
])
|
|
865
|
+
return branchStartsAtSharedPin ? candidate : candidate.reverse()
|
|
866
|
+
}
|
|
867
|
+
|
|
267
868
|
const candidateIsClear = ({
|
|
268
869
|
candidateTrace,
|
|
269
870
|
originalTrace,
|
|
270
871
|
traces,
|
|
271
872
|
inputProblem,
|
|
873
|
+
originalNetLabelPlacements,
|
|
272
874
|
candidateNetLabelPlacements,
|
|
273
875
|
attachedLabelIndexes,
|
|
274
876
|
}: {
|
|
@@ -276,6 +878,7 @@ const candidateIsClear = ({
|
|
|
276
878
|
originalTrace: SolvedTracePath
|
|
277
879
|
traces: SolvedTracePath[]
|
|
278
880
|
inputProblem: InputProblem
|
|
881
|
+
originalNetLabelPlacements: NetLabelPlacement[]
|
|
279
882
|
candidateNetLabelPlacements: NetLabelPlacement[]
|
|
280
883
|
attachedLabelIndexes: number[]
|
|
281
884
|
}) => {
|
|
@@ -329,6 +932,71 @@ const candidateIsClear = ({
|
|
|
329
932
|
return false
|
|
330
933
|
}
|
|
331
934
|
|
|
935
|
+
// Moving an attached label is part of the reroute. It must not introduce a
|
|
936
|
+
// collision with an unchanged trace, component, or neighboring label.
|
|
937
|
+
const labelHitsTrace = (label: NetLabelPlacement, trace: SolvedTracePath) =>
|
|
938
|
+
pathIntersectsAnyNetLabel({
|
|
939
|
+
path: trace.tracePath,
|
|
940
|
+
netLabelPlacements: [label],
|
|
941
|
+
})
|
|
942
|
+
for (const labelIndex of attachedLabelIndexes) {
|
|
943
|
+
const originalLabel = originalNetLabelPlacements[labelIndex]!
|
|
944
|
+
const candidateLabel = candidateNetLabelPlacements[labelIndex]!
|
|
945
|
+
const originalBounds = getRectBounds(
|
|
946
|
+
originalLabel.center,
|
|
947
|
+
originalLabel.width,
|
|
948
|
+
originalLabel.height,
|
|
949
|
+
)
|
|
950
|
+
const candidateBounds = getRectBounds(
|
|
951
|
+
candidateLabel.center,
|
|
952
|
+
candidateLabel.width,
|
|
953
|
+
candidateLabel.height,
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
for (const trace of traces) {
|
|
957
|
+
if (trace.globalConnNetId === candidateLabel.globalConnNetId) continue
|
|
958
|
+
if (
|
|
959
|
+
labelHitsTrace(candidateLabel, trace) &&
|
|
960
|
+
!labelHitsTrace(originalLabel, trace)
|
|
961
|
+
) {
|
|
962
|
+
return false
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
for (const obstacle of obstacles) {
|
|
966
|
+
if (
|
|
967
|
+
rectsOverlap(candidateBounds, obstacle) &&
|
|
968
|
+
!rectsOverlap(originalBounds, obstacle)
|
|
969
|
+
) {
|
|
970
|
+
return false
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
for (
|
|
974
|
+
let otherIndex = 0;
|
|
975
|
+
otherIndex < candidateNetLabelPlacements.length;
|
|
976
|
+
otherIndex++
|
|
977
|
+
) {
|
|
978
|
+
if (otherIndex === labelIndex) continue
|
|
979
|
+
const originalOther = originalNetLabelPlacements[otherIndex]!
|
|
980
|
+
const candidateOther = candidateNetLabelPlacements[otherIndex]!
|
|
981
|
+
const originalOtherBounds = getRectBounds(
|
|
982
|
+
originalOther.center,
|
|
983
|
+
originalOther.width,
|
|
984
|
+
originalOther.height,
|
|
985
|
+
)
|
|
986
|
+
const candidateOtherBounds = getRectBounds(
|
|
987
|
+
candidateOther.center,
|
|
988
|
+
candidateOther.width,
|
|
989
|
+
candidateOther.height,
|
|
990
|
+
)
|
|
991
|
+
if (
|
|
992
|
+
rectsOverlap(candidateBounds, candidateOtherBounds) &&
|
|
993
|
+
!rectsOverlap(originalBounds, originalOtherBounds)
|
|
994
|
+
) {
|
|
995
|
+
return false
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
332
1000
|
const otherNetLabelPlacements = candidateNetLabelPlacements.filter(
|
|
333
1001
|
(label) => label.globalConnNetId !== candidateTrace.globalConnNetId,
|
|
334
1002
|
)
|
|
@@ -456,15 +1124,16 @@ export const alignSameNetJunctions = ({
|
|
|
456
1124
|
inputProblem,
|
|
457
1125
|
traces,
|
|
458
1126
|
netLabelPlacements,
|
|
1127
|
+
netLabelConnectorTraceIds,
|
|
459
1128
|
}: AlignSameNetJunctionsInput) => {
|
|
460
1129
|
let outputTraces = [...traces]
|
|
461
1130
|
let outputNetLabelPlacements = [...netLabelPlacements]
|
|
462
1131
|
let alignedJunctionCount = 0
|
|
1132
|
+
const alignedBranchTraceIds = new Set<string>()
|
|
463
1133
|
|
|
464
1134
|
// First level the load rails, then attach return branches to those final
|
|
465
1135
|
// rails. Doing this in one pass could attach a branch to a rail that moves later.
|
|
466
1136
|
for (const alignReturnBranches of [false, true]) {
|
|
467
|
-
const alignedBranchTraceIds = new Set<string>()
|
|
468
1137
|
// Reuse each aligned branch as the rail for the next load in the chain. An
|
|
469
1138
|
// aligned branch may already have had its donor turn, so queue it again when
|
|
470
1139
|
// its geometry changes.
|
|
@@ -483,15 +1152,48 @@ export const alignSameNetJunctions = ({
|
|
|
483
1152
|
|
|
484
1153
|
// Prefer the existing return stem. If extending the outer column
|
|
485
1154
|
// meets another net, try shorter escapes without moving any pins.
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
1155
|
+
const donorIsLabelConnector = netLabelConnectorTraceIds.has(
|
|
1156
|
+
donorTrace.mspPairId,
|
|
1157
|
+
)
|
|
1158
|
+
const branchIsLabelConnector = netLabelConnectorTraceIds.has(
|
|
1159
|
+
branchTrace.mspPairId,
|
|
1160
|
+
)
|
|
1161
|
+
const candidatePaths = branchIsLabelConnector
|
|
1162
|
+
? alignReturnBranches && !donorIsLabelConnector
|
|
1163
|
+
? [
|
|
1164
|
+
getAlignedAttachedLabelConnectorPath({
|
|
1165
|
+
donorTrace,
|
|
1166
|
+
connectorTrace: branchTrace,
|
|
1167
|
+
netLabelConnectorTraceIds,
|
|
1168
|
+
}),
|
|
1169
|
+
]
|
|
1170
|
+
: []
|
|
1171
|
+
: donorIsLabelConnector
|
|
1172
|
+
? []
|
|
1173
|
+
: alignReturnBranches
|
|
1174
|
+
? [
|
|
1175
|
+
...[1, 0.5, 0.25].map((returnStemScale) =>
|
|
1176
|
+
getAlignedReturnBranchPath({
|
|
1177
|
+
donorTrace,
|
|
1178
|
+
branchTrace,
|
|
1179
|
+
returnStemScale,
|
|
1180
|
+
}),
|
|
1181
|
+
),
|
|
1182
|
+
getAlignedPerpendicularEndpointBusPath({
|
|
1183
|
+
donorTrace,
|
|
1184
|
+
branchTrace,
|
|
1185
|
+
traces: outputTraces,
|
|
1186
|
+
}),
|
|
1187
|
+
getAlignedSharedEndpointRailPath({
|
|
1188
|
+
donorTrace,
|
|
1189
|
+
branchTrace,
|
|
1190
|
+
netLabelConnectorTraceIds,
|
|
1191
|
+
}),
|
|
1192
|
+
]
|
|
1193
|
+
: [
|
|
1194
|
+
getAlignedBranchPath({ donorTrace, branchTrace }),
|
|
1195
|
+
getAlignedParallelPinExitPath({ donorTrace, branchTrace }),
|
|
1196
|
+
]
|
|
495
1197
|
for (const candidatePath of candidatePaths) {
|
|
496
1198
|
if (!candidatePath) continue
|
|
497
1199
|
const candidateTrace = { ...branchTrace, tracePath: candidatePath }
|
|
@@ -520,12 +1222,20 @@ export const alignSameNetJunctions = ({
|
|
|
520
1222
|
netLabelPlacements: outputNetLabelPlacements,
|
|
521
1223
|
attachedLabelIndexes,
|
|
522
1224
|
})
|
|
1225
|
+
const candidateTraces = preserveAttachedLabelConnectorJunctions({
|
|
1226
|
+
originalTrace: branchTrace,
|
|
1227
|
+
candidateTrace,
|
|
1228
|
+
traces: outputTraces,
|
|
1229
|
+
netLabelConnectorTraceIds,
|
|
1230
|
+
})
|
|
1231
|
+
if (!candidateTraces) continue
|
|
523
1232
|
if (
|
|
524
1233
|
!candidateIsClear({
|
|
525
1234
|
candidateTrace,
|
|
526
1235
|
originalTrace: branchTrace,
|
|
527
|
-
traces:
|
|
1236
|
+
traces: candidateTraces,
|
|
528
1237
|
inputProblem,
|
|
1238
|
+
originalNetLabelPlacements: outputNetLabelPlacements,
|
|
529
1239
|
candidateNetLabelPlacements,
|
|
530
1240
|
attachedLabelIndexes,
|
|
531
1241
|
})
|
|
@@ -533,10 +1243,33 @@ export const alignSameNetJunctions = ({
|
|
|
533
1243
|
continue
|
|
534
1244
|
}
|
|
535
1245
|
|
|
536
|
-
|
|
537
|
-
if (
|
|
538
|
-
|
|
1246
|
+
const movedConnectorsAreClear = candidateTraces.every((trace) => {
|
|
1247
|
+
if (
|
|
1248
|
+
trace.mspPairId === branchTrace.mspPairId ||
|
|
1249
|
+
!netLabelConnectorTraceIds.has(trace.mspPairId)
|
|
1250
|
+
) {
|
|
1251
|
+
return true
|
|
1252
|
+
}
|
|
1253
|
+
const originalConnector = outputTraces.find(
|
|
1254
|
+
(originalTrace) => originalTrace.mspPairId === trace.mspPairId,
|
|
1255
|
+
)!
|
|
1256
|
+
if (originalConnector.tracePath === trace.tracePath) return true
|
|
1257
|
+
return candidateIsClear({
|
|
1258
|
+
candidateTrace: trace,
|
|
1259
|
+
originalTrace: originalConnector,
|
|
1260
|
+
traces: candidateTraces,
|
|
1261
|
+
inputProblem,
|
|
1262
|
+
originalNetLabelPlacements: outputNetLabelPlacements,
|
|
1263
|
+
candidateNetLabelPlacements,
|
|
1264
|
+
attachedLabelIndexes: getAttachedLabelIndexes(
|
|
1265
|
+
originalConnector,
|
|
1266
|
+
outputNetLabelPlacements,
|
|
1267
|
+
),
|
|
1268
|
+
})
|
|
539
1269
|
})
|
|
1270
|
+
if (!movedConnectorsAreClear) continue
|
|
1271
|
+
|
|
1272
|
+
outputTraces = candidateTraces
|
|
540
1273
|
outputNetLabelPlacements = candidateNetLabelPlacements
|
|
541
1274
|
alignedBranchTraceIds.add(branchTrace.mspPairId)
|
|
542
1275
|
alignedJunctionCount++
|