@tscircuit/schematic-trace-solver 0.0.55 → 0.0.56
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 +28 -1
- package/dist/index.js +220 -0
- package/lib/solvers/NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver.ts +294 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +15 -0
- package/package.json +1 -1
- package/site/examples/example33.page.tsx +4 -0
- package/tests/assets/example33.json +132 -0
- package/tests/examples/__snapshots__/example33.snap.svg +181 -0
- package/tests/examples/example33.test.ts +12 -0
package/dist/index.d.ts
CHANGED
|
@@ -820,6 +820,32 @@ declare class TraceAnchoredNetLabelOverlapSolver extends BaseSolver {
|
|
|
820
820
|
visualize(): GraphicsObject;
|
|
821
821
|
}
|
|
822
822
|
|
|
823
|
+
interface NetLabelTraceCollisionSolverParams {
|
|
824
|
+
inputProblem: InputProblem;
|
|
825
|
+
traces: SolvedTracePath[];
|
|
826
|
+
netLabelPlacements: NetLabelPlacement[];
|
|
827
|
+
}
|
|
828
|
+
declare class NetLabelTraceCollisionSolver extends BaseSolver {
|
|
829
|
+
inputProblem: InputProblem;
|
|
830
|
+
traces: SolvedTracePath[];
|
|
831
|
+
netLabelPlacements: NetLabelPlacement[];
|
|
832
|
+
outputTraces: SolvedTracePath[];
|
|
833
|
+
outputNetLabelPlacements: NetLabelPlacement[];
|
|
834
|
+
activeSubSolver: SingleOverlapSolver | null;
|
|
835
|
+
private recentlyFailed;
|
|
836
|
+
private detourCounts;
|
|
837
|
+
private currentOverlap;
|
|
838
|
+
constructor(params: NetLabelTraceCollisionSolverParams);
|
|
839
|
+
getConstructorParams(): ConstructorParameters<typeof NetLabelTraceCollisionSolver>[0];
|
|
840
|
+
_step(): void;
|
|
841
|
+
getOutput(): {
|
|
842
|
+
traces: SolvedTracePath[];
|
|
843
|
+
netLabelPlacements: NetLabelPlacement[];
|
|
844
|
+
};
|
|
845
|
+
private getOverlapKey;
|
|
846
|
+
visualize(): GraphicsObject;
|
|
847
|
+
}
|
|
848
|
+
|
|
823
849
|
/**
|
|
824
850
|
* Pipeline solver that runs a series of solvers to find the best schematic layout.
|
|
825
851
|
* Coordinates the entire layout process from chip partitioning through final packing.
|
|
@@ -849,12 +875,13 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
849
875
|
availableNetOrientationSolver?: AvailableNetOrientationSolver;
|
|
850
876
|
vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver;
|
|
851
877
|
traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver;
|
|
878
|
+
netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver;
|
|
852
879
|
startTimeOfPhase: Record<string, number>;
|
|
853
880
|
endTimeOfPhase: Record<string, number>;
|
|
854
881
|
timeSpentOnPhase: Record<string, number>;
|
|
855
882
|
firstIterationOfPhase: Record<string, number>;
|
|
856
883
|
inputProblem: InputProblem;
|
|
857
|
-
pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof VccNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver>)[];
|
|
884
|
+
pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof VccNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver>)[];
|
|
858
885
|
constructor(inputProblem: InputProblem);
|
|
859
886
|
getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
|
|
860
887
|
currentPipelineStepIndex: number;
|
package/dist/index.js
CHANGED
|
@@ -7150,6 +7150,214 @@ var TraceAnchoredNetLabelOverlapSolver = class extends BaseSolver {
|
|
|
7150
7150
|
}
|
|
7151
7151
|
};
|
|
7152
7152
|
|
|
7153
|
+
// lib/solvers/NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver.ts
|
|
7154
|
+
function buildMergedObstacleLabel(seedLabel, allLabels) {
|
|
7155
|
+
const TOUCH_MARGIN = 0.01;
|
|
7156
|
+
const getBounds3 = (l) => getRectBounds(l.center, l.width, l.height);
|
|
7157
|
+
const touches = (a, b) => {
|
|
7158
|
+
const ba = getBounds3(a);
|
|
7159
|
+
const bb = getBounds3(b);
|
|
7160
|
+
return ba.minX <= bb.maxX + TOUCH_MARGIN && ba.maxX >= bb.minX - TOUCH_MARGIN && ba.minY <= bb.maxY + TOUCH_MARGIN && ba.maxY >= bb.minY - TOUCH_MARGIN;
|
|
7161
|
+
};
|
|
7162
|
+
const group = /* @__PURE__ */ new Set([seedLabel]);
|
|
7163
|
+
let changed = true;
|
|
7164
|
+
while (changed) {
|
|
7165
|
+
changed = false;
|
|
7166
|
+
for (const l of allLabels) {
|
|
7167
|
+
if (group.has(l)) continue;
|
|
7168
|
+
if ([...group].some((g) => touches(g, l))) {
|
|
7169
|
+
group.add(l);
|
|
7170
|
+
changed = true;
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
}
|
|
7174
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
7175
|
+
for (const l of group) {
|
|
7176
|
+
const b = getBounds3(l);
|
|
7177
|
+
if (b.minX < minX) minX = b.minX;
|
|
7178
|
+
if (b.minY < minY) minY = b.minY;
|
|
7179
|
+
if (b.maxX > maxX) maxX = b.maxX;
|
|
7180
|
+
if (b.maxY > maxY) maxY = b.maxY;
|
|
7181
|
+
}
|
|
7182
|
+
const cx = (minX + maxX) / 2;
|
|
7183
|
+
const cy = (minY + maxY) / 2;
|
|
7184
|
+
const w = maxX - minX;
|
|
7185
|
+
const h = maxY - minY;
|
|
7186
|
+
return {
|
|
7187
|
+
...seedLabel,
|
|
7188
|
+
center: { x: cx, y: cy },
|
|
7189
|
+
width: w,
|
|
7190
|
+
height: h
|
|
7191
|
+
};
|
|
7192
|
+
}
|
|
7193
|
+
function detectInnerTraceLabelOverlaps(traces, labels) {
|
|
7194
|
+
const overlaps = [];
|
|
7195
|
+
for (const trace of traces) {
|
|
7196
|
+
for (const label of labels) {
|
|
7197
|
+
if (trace.globalConnNetId === label.globalConnNetId) continue;
|
|
7198
|
+
const bounds = getRectBounds(label.center, label.width, label.height);
|
|
7199
|
+
const path = trace.tracePath;
|
|
7200
|
+
const innerStart = 1;
|
|
7201
|
+
const innerEnd = path.length - 2;
|
|
7202
|
+
for (let i = innerStart; i < innerEnd; i++) {
|
|
7203
|
+
if (segmentIntersectsRect2(path[i], path[i + 1], bounds)) {
|
|
7204
|
+
overlaps.push({ trace, label });
|
|
7205
|
+
break;
|
|
7206
|
+
}
|
|
7207
|
+
}
|
|
7208
|
+
}
|
|
7209
|
+
}
|
|
7210
|
+
return overlaps;
|
|
7211
|
+
}
|
|
7212
|
+
var PADDING_BUFFER = 0.1;
|
|
7213
|
+
var MAX_DETOUR_ATTEMPTS = 3;
|
|
7214
|
+
var NetLabelTraceCollisionSolver = class extends BaseSolver {
|
|
7215
|
+
inputProblem;
|
|
7216
|
+
traces;
|
|
7217
|
+
netLabelPlacements;
|
|
7218
|
+
outputTraces;
|
|
7219
|
+
outputNetLabelPlacements;
|
|
7220
|
+
activeSubSolver = null;
|
|
7221
|
+
recentlyFailed = /* @__PURE__ */ new Set();
|
|
7222
|
+
detourCounts = /* @__PURE__ */ new Map();
|
|
7223
|
+
currentOverlap = null;
|
|
7224
|
+
constructor(params) {
|
|
7225
|
+
super();
|
|
7226
|
+
this.inputProblem = params.inputProblem;
|
|
7227
|
+
this.traces = params.traces;
|
|
7228
|
+
this.netLabelPlacements = params.netLabelPlacements;
|
|
7229
|
+
this.outputTraces = [...params.traces];
|
|
7230
|
+
this.outputNetLabelPlacements = [...params.netLabelPlacements];
|
|
7231
|
+
}
|
|
7232
|
+
getConstructorParams() {
|
|
7233
|
+
return {
|
|
7234
|
+
inputProblem: this.inputProblem,
|
|
7235
|
+
traces: this.traces,
|
|
7236
|
+
netLabelPlacements: this.netLabelPlacements
|
|
7237
|
+
};
|
|
7238
|
+
}
|
|
7239
|
+
_step() {
|
|
7240
|
+
if (this.activeSubSolver) {
|
|
7241
|
+
this.activeSubSolver.step();
|
|
7242
|
+
if (this.activeSubSolver.solved) {
|
|
7243
|
+
const solvedPath = this.activeSubSolver.solvedTracePath;
|
|
7244
|
+
if (solvedPath) {
|
|
7245
|
+
const idx = this.outputTraces.findIndex(
|
|
7246
|
+
(t) => t.mspPairId === this.activeSubSolver.initialTrace.mspPairId
|
|
7247
|
+
);
|
|
7248
|
+
if (idx !== -1) {
|
|
7249
|
+
this.outputTraces[idx] = {
|
|
7250
|
+
...this.outputTraces[idx],
|
|
7251
|
+
tracePath: solvedPath
|
|
7252
|
+
};
|
|
7253
|
+
}
|
|
7254
|
+
}
|
|
7255
|
+
this.activeSubSolver = null;
|
|
7256
|
+
this.currentOverlap = null;
|
|
7257
|
+
} else if (this.activeSubSolver.failed) {
|
|
7258
|
+
const key = this.getOverlapKey(
|
|
7259
|
+
this.activeSubSolver.initialTrace,
|
|
7260
|
+
this.activeSubSolver.label
|
|
7261
|
+
);
|
|
7262
|
+
this.recentlyFailed.add(key);
|
|
7263
|
+
this.activeSubSolver = null;
|
|
7264
|
+
this.currentOverlap = null;
|
|
7265
|
+
}
|
|
7266
|
+
return;
|
|
7267
|
+
}
|
|
7268
|
+
const overlaps = detectInnerTraceLabelOverlaps(
|
|
7269
|
+
this.outputTraces,
|
|
7270
|
+
this.outputNetLabelPlacements
|
|
7271
|
+
);
|
|
7272
|
+
const actionable = overlaps.filter((o) => {
|
|
7273
|
+
const key = this.getOverlapKey(o.trace, o.label);
|
|
7274
|
+
return !this.recentlyFailed.has(key);
|
|
7275
|
+
});
|
|
7276
|
+
if (actionable.length === 0) {
|
|
7277
|
+
this.solved = true;
|
|
7278
|
+
return;
|
|
7279
|
+
}
|
|
7280
|
+
const next = actionable[0];
|
|
7281
|
+
const traceToFix = this.outputTraces.find(
|
|
7282
|
+
(t) => t.mspPairId === next.trace.mspPairId
|
|
7283
|
+
);
|
|
7284
|
+
this.currentOverlap = next;
|
|
7285
|
+
const mergedLabel = buildMergedObstacleLabel(
|
|
7286
|
+
next.label,
|
|
7287
|
+
this.outputNetLabelPlacements.filter(
|
|
7288
|
+
(l) => l.globalConnNetId !== traceToFix.globalConnNetId
|
|
7289
|
+
)
|
|
7290
|
+
);
|
|
7291
|
+
const mergeKey = `${next.trace.mspPairId}::merged::${mergedLabel.center.x},${mergedLabel.center.y},${mergedLabel.width},${mergedLabel.height}`;
|
|
7292
|
+
const detourCount = this.detourCounts.get(mergeKey) ?? 0;
|
|
7293
|
+
if (detourCount >= MAX_DETOUR_ATTEMPTS) {
|
|
7294
|
+
this.recentlyFailed.add(this.getOverlapKey(next.trace, next.label));
|
|
7295
|
+
this.currentOverlap = null;
|
|
7296
|
+
return;
|
|
7297
|
+
}
|
|
7298
|
+
this.detourCounts.set(mergeKey, detourCount + 1);
|
|
7299
|
+
this.activeSubSolver = new SingleOverlapSolver({
|
|
7300
|
+
trace: traceToFix,
|
|
7301
|
+
label: mergedLabel,
|
|
7302
|
+
problem: this.inputProblem,
|
|
7303
|
+
paddingBuffer: PADDING_BUFFER,
|
|
7304
|
+
detourCount
|
|
7305
|
+
});
|
|
7306
|
+
}
|
|
7307
|
+
getOutput() {
|
|
7308
|
+
return {
|
|
7309
|
+
traces: this.outputTraces,
|
|
7310
|
+
netLabelPlacements: this.outputNetLabelPlacements
|
|
7311
|
+
};
|
|
7312
|
+
}
|
|
7313
|
+
getOverlapKey(trace, label) {
|
|
7314
|
+
return `${trace.mspPairId}::${label.globalConnNetId}`;
|
|
7315
|
+
}
|
|
7316
|
+
visualize() {
|
|
7317
|
+
if (this.activeSubSolver) return this.activeSubSolver.visualize();
|
|
7318
|
+
const graphics = visualizeInputProblem(this.inputProblem);
|
|
7319
|
+
if (!graphics.lines) graphics.lines = [];
|
|
7320
|
+
if (!graphics.rects) graphics.rects = [];
|
|
7321
|
+
if (!graphics.points) graphics.points = [];
|
|
7322
|
+
for (const trace of this.outputTraces) {
|
|
7323
|
+
graphics.lines.push({
|
|
7324
|
+
points: trace.tracePath,
|
|
7325
|
+
strokeColor: "purple"
|
|
7326
|
+
});
|
|
7327
|
+
}
|
|
7328
|
+
for (const label of this.outputNetLabelPlacements) {
|
|
7329
|
+
graphics.rects.push({
|
|
7330
|
+
center: label.center,
|
|
7331
|
+
width: label.width,
|
|
7332
|
+
height: label.height,
|
|
7333
|
+
fill: getColorFromString(label.globalConnNetId, 0.35),
|
|
7334
|
+
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
7335
|
+
label: `netId: ${label.netId}
|
|
7336
|
+
globalConnNetId: ${label.globalConnNetId}`
|
|
7337
|
+
});
|
|
7338
|
+
graphics.points.push({
|
|
7339
|
+
x: label.anchorPoint.x,
|
|
7340
|
+
y: label.anchorPoint.y,
|
|
7341
|
+
color: getColorFromString(label.globalConnNetId, 0.9)
|
|
7342
|
+
});
|
|
7343
|
+
}
|
|
7344
|
+
if (this.currentOverlap) {
|
|
7345
|
+
graphics.lines.push({
|
|
7346
|
+
points: this.currentOverlap.trace.tracePath,
|
|
7347
|
+
strokeColor: "red"
|
|
7348
|
+
});
|
|
7349
|
+
graphics.rects.push({
|
|
7350
|
+
center: this.currentOverlap.label.center,
|
|
7351
|
+
width: this.currentOverlap.label.width,
|
|
7352
|
+
height: this.currentOverlap.label.height,
|
|
7353
|
+
fill: "rgba(255,0,0,0.3)",
|
|
7354
|
+
strokeColor: "red"
|
|
7355
|
+
});
|
|
7356
|
+
}
|
|
7357
|
+
return graphics;
|
|
7358
|
+
}
|
|
7359
|
+
};
|
|
7360
|
+
|
|
7153
7361
|
// lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
|
|
7154
7362
|
function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
|
|
7155
7363
|
return {
|
|
@@ -7174,6 +7382,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
7174
7382
|
availableNetOrientationSolver;
|
|
7175
7383
|
vccNetLabelCornerPlacementSolver;
|
|
7176
7384
|
traceAnchoredNetLabelOverlapSolver;
|
|
7385
|
+
netLabelTraceCollisionSolver;
|
|
7177
7386
|
startTimeOfPhase;
|
|
7178
7387
|
endTimeOfPhase;
|
|
7179
7388
|
timeSpentOnPhase;
|
|
@@ -7354,6 +7563,17 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
7354
7563
|
netLabelPlacements: instance.vccNetLabelCornerPlacementSolver.outputNetLabelPlacements
|
|
7355
7564
|
}
|
|
7356
7565
|
]
|
|
7566
|
+
),
|
|
7567
|
+
definePipelineStep(
|
|
7568
|
+
"netLabelTraceCollisionSolver",
|
|
7569
|
+
NetLabelTraceCollisionSolver,
|
|
7570
|
+
(instance) => [
|
|
7571
|
+
{
|
|
7572
|
+
inputProblem: instance.inputProblem,
|
|
7573
|
+
traces: instance.availableNetOrientationSolver.traces,
|
|
7574
|
+
netLabelPlacements: instance.traceAnchoredNetLabelOverlapSolver.outputNetLabelPlacements
|
|
7575
|
+
}
|
|
7576
|
+
]
|
|
7357
7577
|
)
|
|
7358
7578
|
];
|
|
7359
7579
|
constructor(inputProblem) {
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
6
|
+
import { SingleOverlapSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver"
|
|
7
|
+
import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
8
|
+
import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
|
|
9
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
10
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Merges all labels that touch/overlap with each other (transitively) around
|
|
14
|
+
* a seed label into a single bounding-box label. This prevents the situation
|
|
15
|
+
* where fixing one label collision re-routes the trace into an adjacent label.
|
|
16
|
+
*/
|
|
17
|
+
function buildMergedObstacleLabel(
|
|
18
|
+
seedLabel: NetLabelPlacement,
|
|
19
|
+
allLabels: NetLabelPlacement[],
|
|
20
|
+
): NetLabelPlacement {
|
|
21
|
+
const TOUCH_MARGIN = 0.01
|
|
22
|
+
|
|
23
|
+
const getBounds = (l: NetLabelPlacement) =>
|
|
24
|
+
getRectBounds(l.center, l.width, l.height)
|
|
25
|
+
|
|
26
|
+
const touches = (a: NetLabelPlacement, b: NetLabelPlacement) => {
|
|
27
|
+
const ba = getBounds(a)
|
|
28
|
+
const bb = getBounds(b)
|
|
29
|
+
return (
|
|
30
|
+
ba.minX <= bb.maxX + TOUCH_MARGIN &&
|
|
31
|
+
ba.maxX >= bb.minX - TOUCH_MARGIN &&
|
|
32
|
+
ba.minY <= bb.maxY + TOUCH_MARGIN &&
|
|
33
|
+
ba.maxY >= bb.minY - TOUCH_MARGIN
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const group = new Set<NetLabelPlacement>([seedLabel])
|
|
38
|
+
let changed = true
|
|
39
|
+
while (changed) {
|
|
40
|
+
changed = false
|
|
41
|
+
for (const l of allLabels) {
|
|
42
|
+
if (group.has(l)) continue
|
|
43
|
+
if ([...group].some((g) => touches(g, l))) {
|
|
44
|
+
group.add(l)
|
|
45
|
+
changed = true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let minX = Infinity,
|
|
51
|
+
minY = Infinity,
|
|
52
|
+
maxX = -Infinity,
|
|
53
|
+
maxY = -Infinity
|
|
54
|
+
for (const l of group) {
|
|
55
|
+
const b = getBounds(l)
|
|
56
|
+
if (b.minX < minX) minX = b.minX
|
|
57
|
+
if (b.minY < minY) minY = b.minY
|
|
58
|
+
if (b.maxX > maxX) maxX = b.maxX
|
|
59
|
+
if (b.maxY > maxY) maxY = b.maxY
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const cx = (minX + maxX) / 2
|
|
63
|
+
const cy = (minY + maxY) / 2
|
|
64
|
+
const w = maxX - minX
|
|
65
|
+
const h = maxY - minY
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
...seedLabel,
|
|
69
|
+
center: { x: cx, y: cy },
|
|
70
|
+
width: w,
|
|
71
|
+
height: h,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface TraceInnerLabelOverlap {
|
|
76
|
+
trace: SolvedTracePath
|
|
77
|
+
label: NetLabelPlacement
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Detects overlaps where a trace's INTERIOR (non-endpoint) segments cross a label.
|
|
82
|
+
* Traces are allowed to boundary a label at their endpoints (pin locations),
|
|
83
|
+
* just not allowed to cross through it in the middle.
|
|
84
|
+
*/
|
|
85
|
+
function detectInnerTraceLabelOverlaps(
|
|
86
|
+
traces: SolvedTracePath[],
|
|
87
|
+
labels: NetLabelPlacement[],
|
|
88
|
+
): TraceInnerLabelOverlap[] {
|
|
89
|
+
const overlaps: TraceInnerLabelOverlap[] = []
|
|
90
|
+
for (const trace of traces) {
|
|
91
|
+
for (const label of labels) {
|
|
92
|
+
if (trace.globalConnNetId === label.globalConnNetId) continue
|
|
93
|
+
const bounds = getRectBounds(label.center, label.width, label.height)
|
|
94
|
+
const path = trace.tracePath
|
|
95
|
+
// Skip first and last segments — trace endpoints (pins) may legitimately
|
|
96
|
+
// sit inside or adjacent to labels of neighbouring nets.
|
|
97
|
+
const innerStart = 1
|
|
98
|
+
const innerEnd = path.length - 2
|
|
99
|
+
for (let i = innerStart; i < innerEnd; i++) {
|
|
100
|
+
if (segmentIntersectsRect(path[i]!, path[i + 1]!, bounds)) {
|
|
101
|
+
overlaps.push({ trace, label })
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return overlaps
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface NetLabelTraceCollisionSolverParams {
|
|
111
|
+
inputProblem: InputProblem
|
|
112
|
+
traces: SolvedTracePath[]
|
|
113
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const PADDING_BUFFER = 0.1
|
|
117
|
+
const MAX_DETOUR_ATTEMPTS = 3
|
|
118
|
+
|
|
119
|
+
export class NetLabelTraceCollisionSolver extends BaseSolver {
|
|
120
|
+
inputProblem: InputProblem
|
|
121
|
+
traces: SolvedTracePath[]
|
|
122
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
123
|
+
|
|
124
|
+
outputTraces: SolvedTracePath[]
|
|
125
|
+
outputNetLabelPlacements: NetLabelPlacement[]
|
|
126
|
+
|
|
127
|
+
override activeSubSolver: SingleOverlapSolver | null = null
|
|
128
|
+
private recentlyFailed = new Set<string>()
|
|
129
|
+
private detourCounts = new Map<string, number>()
|
|
130
|
+
|
|
131
|
+
private currentOverlap: {
|
|
132
|
+
trace: SolvedTracePath
|
|
133
|
+
label: NetLabelPlacement
|
|
134
|
+
} | null = null
|
|
135
|
+
|
|
136
|
+
constructor(params: NetLabelTraceCollisionSolverParams) {
|
|
137
|
+
super()
|
|
138
|
+
this.inputProblem = params.inputProblem
|
|
139
|
+
this.traces = params.traces
|
|
140
|
+
this.netLabelPlacements = params.netLabelPlacements
|
|
141
|
+
this.outputTraces = [...params.traces]
|
|
142
|
+
this.outputNetLabelPlacements = [...params.netLabelPlacements]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
override getConstructorParams(): ConstructorParameters<
|
|
146
|
+
typeof NetLabelTraceCollisionSolver
|
|
147
|
+
>[0] {
|
|
148
|
+
return {
|
|
149
|
+
inputProblem: this.inputProblem,
|
|
150
|
+
traces: this.traces,
|
|
151
|
+
netLabelPlacements: this.netLabelPlacements,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
override _step() {
|
|
156
|
+
if (this.activeSubSolver) {
|
|
157
|
+
this.activeSubSolver.step()
|
|
158
|
+
|
|
159
|
+
if (this.activeSubSolver.solved) {
|
|
160
|
+
const solvedPath = this.activeSubSolver.solvedTracePath
|
|
161
|
+
if (solvedPath) {
|
|
162
|
+
const idx = this.outputTraces.findIndex(
|
|
163
|
+
(t) => t.mspPairId === this.activeSubSolver!.initialTrace.mspPairId,
|
|
164
|
+
)
|
|
165
|
+
if (idx !== -1) {
|
|
166
|
+
this.outputTraces[idx] = {
|
|
167
|
+
...this.outputTraces[idx],
|
|
168
|
+
tracePath: solvedPath,
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.activeSubSolver = null
|
|
173
|
+
this.currentOverlap = null
|
|
174
|
+
} else if (this.activeSubSolver.failed) {
|
|
175
|
+
const key = this.getOverlapKey(
|
|
176
|
+
this.activeSubSolver.initialTrace,
|
|
177
|
+
this.activeSubSolver.label,
|
|
178
|
+
)
|
|
179
|
+
this.recentlyFailed.add(key)
|
|
180
|
+
this.activeSubSolver = null
|
|
181
|
+
this.currentOverlap = null
|
|
182
|
+
}
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const overlaps = detectInnerTraceLabelOverlaps(
|
|
187
|
+
this.outputTraces,
|
|
188
|
+
this.outputNetLabelPlacements,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
const actionable = overlaps.filter((o) => {
|
|
192
|
+
const key = this.getOverlapKey(o.trace, o.label)
|
|
193
|
+
return !this.recentlyFailed.has(key)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
if (actionable.length === 0) {
|
|
197
|
+
this.solved = true
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const next = actionable[0]
|
|
202
|
+
|
|
203
|
+
const traceToFix = this.outputTraces.find(
|
|
204
|
+
(t) => t.mspPairId === next.trace.mspPairId,
|
|
205
|
+
)!
|
|
206
|
+
|
|
207
|
+
this.currentOverlap = next
|
|
208
|
+
|
|
209
|
+
// Merge all touching/overlapping labels into one obstacle so that fixing
|
|
210
|
+
// one collision doesn't immediately create another with an adjacent label.
|
|
211
|
+
const mergedLabel = buildMergedObstacleLabel(
|
|
212
|
+
next.label,
|
|
213
|
+
this.outputNetLabelPlacements.filter(
|
|
214
|
+
(l) => l.globalConnNetId !== traceToFix.globalConnNetId,
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
const mergeKey = `${next.trace.mspPairId}::merged::${mergedLabel.center.x},${mergedLabel.center.y},${mergedLabel.width},${mergedLabel.height}`
|
|
219
|
+
const detourCount = this.detourCounts.get(mergeKey) ?? 0
|
|
220
|
+
if (detourCount >= MAX_DETOUR_ATTEMPTS) {
|
|
221
|
+
this.recentlyFailed.add(this.getOverlapKey(next.trace, next.label))
|
|
222
|
+
this.currentOverlap = null
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
this.detourCounts.set(mergeKey, detourCount + 1)
|
|
226
|
+
|
|
227
|
+
this.activeSubSolver = new SingleOverlapSolver({
|
|
228
|
+
trace: traceToFix,
|
|
229
|
+
label: mergedLabel,
|
|
230
|
+
problem: this.inputProblem,
|
|
231
|
+
paddingBuffer: PADDING_BUFFER,
|
|
232
|
+
detourCount,
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getOutput() {
|
|
237
|
+
return {
|
|
238
|
+
traces: this.outputTraces,
|
|
239
|
+
netLabelPlacements: this.outputNetLabelPlacements,
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private getOverlapKey(trace: SolvedTracePath, label: NetLabelPlacement) {
|
|
244
|
+
return `${trace.mspPairId}::${label.globalConnNetId}`
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
override visualize(): GraphicsObject {
|
|
248
|
+
if (this.activeSubSolver) return this.activeSubSolver.visualize()
|
|
249
|
+
|
|
250
|
+
const graphics = visualizeInputProblem(this.inputProblem)
|
|
251
|
+
if (!graphics.lines) graphics.lines = []
|
|
252
|
+
if (!graphics.rects) graphics.rects = []
|
|
253
|
+
if (!graphics.points) graphics.points = []
|
|
254
|
+
|
|
255
|
+
for (const trace of this.outputTraces) {
|
|
256
|
+
graphics.lines.push({
|
|
257
|
+
points: trace.tracePath,
|
|
258
|
+
strokeColor: "purple",
|
|
259
|
+
} as any)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
for (const label of this.outputNetLabelPlacements) {
|
|
263
|
+
graphics.rects.push({
|
|
264
|
+
center: label.center,
|
|
265
|
+
width: label.width,
|
|
266
|
+
height: label.height,
|
|
267
|
+
fill: getColorFromString(label.globalConnNetId, 0.35),
|
|
268
|
+
strokeColor: getColorFromString(label.globalConnNetId, 0.9),
|
|
269
|
+
label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
|
|
270
|
+
} as any)
|
|
271
|
+
graphics.points.push({
|
|
272
|
+
x: label.anchorPoint.x,
|
|
273
|
+
y: label.anchorPoint.y,
|
|
274
|
+
color: getColorFromString(label.globalConnNetId, 0.9),
|
|
275
|
+
} as any)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (this.currentOverlap) {
|
|
279
|
+
graphics.lines.push({
|
|
280
|
+
points: this.currentOverlap.trace.tracePath,
|
|
281
|
+
strokeColor: "red",
|
|
282
|
+
} as any)
|
|
283
|
+
graphics.rects.push({
|
|
284
|
+
center: this.currentOverlap.label.center,
|
|
285
|
+
width: this.currentOverlap.label.width,
|
|
286
|
+
height: this.currentOverlap.label.height,
|
|
287
|
+
fill: "rgba(255,0,0,0.3)",
|
|
288
|
+
strokeColor: "red",
|
|
289
|
+
} as any)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return graphics
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -25,6 +25,7 @@ import { Example28Solver } from "../Example28Solver/Example28Solver"
|
|
|
25
25
|
import { AvailableNetOrientationSolver } from "../AvailableNetOrientationSolver/AvailableNetOrientationSolver"
|
|
26
26
|
import { VccNetLabelCornerPlacementSolver } from "../VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver"
|
|
27
27
|
import { TraceAnchoredNetLabelOverlapSolver } from "../TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver"
|
|
28
|
+
import { NetLabelTraceCollisionSolver } from "../NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver"
|
|
28
29
|
|
|
29
30
|
type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
|
|
30
31
|
solverName: string
|
|
@@ -78,6 +79,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
78
79
|
availableNetOrientationSolver?: AvailableNetOrientationSolver
|
|
79
80
|
vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver
|
|
80
81
|
traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver
|
|
82
|
+
netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
|
|
81
83
|
|
|
82
84
|
startTimeOfPhase: Record<string, number>
|
|
83
85
|
endTimeOfPhase: Record<string, number>
|
|
@@ -285,6 +287,19 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
285
287
|
},
|
|
286
288
|
],
|
|
287
289
|
),
|
|
290
|
+
definePipelineStep(
|
|
291
|
+
"netLabelTraceCollisionSolver",
|
|
292
|
+
NetLabelTraceCollisionSolver,
|
|
293
|
+
(instance) => [
|
|
294
|
+
{
|
|
295
|
+
inputProblem: instance.inputProblem,
|
|
296
|
+
traces: instance.availableNetOrientationSolver!.traces,
|
|
297
|
+
netLabelPlacements:
|
|
298
|
+
instance.traceAnchoredNetLabelOverlapSolver!
|
|
299
|
+
.outputNetLabelPlacements,
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
),
|
|
288
303
|
]
|
|
289
304
|
|
|
290
305
|
constructor(inputProblem: InputProblem) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
{
|
|
2
|
+
"chips": [
|
|
3
|
+
{
|
|
4
|
+
"chipId": "schematic_component_0",
|
|
5
|
+
"center": {
|
|
6
|
+
"x": -1.4,
|
|
7
|
+
"y": -6.6
|
|
8
|
+
},
|
|
9
|
+
"width": 1.1,
|
|
10
|
+
"height": 0.388910699999999,
|
|
11
|
+
"pins": [
|
|
12
|
+
{
|
|
13
|
+
"pinId": "R7.1",
|
|
14
|
+
"x": -1.95,
|
|
15
|
+
"y": -6.6
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"pinId": "R7.2",
|
|
19
|
+
"x": -0.8499999999999999,
|
|
20
|
+
"y": -6.6
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"sectionId": "qspi"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"chipId": "schematic_component_1",
|
|
27
|
+
"center": {
|
|
28
|
+
"x": 0,
|
|
29
|
+
"y": -8.4
|
|
30
|
+
},
|
|
31
|
+
"width": 3.045,
|
|
32
|
+
"height": 1,
|
|
33
|
+
"pins": [
|
|
34
|
+
{
|
|
35
|
+
"pinId": "U5.1",
|
|
36
|
+
"x": -1.5225,
|
|
37
|
+
"y": -8.1
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"pinId": "U5.2",
|
|
41
|
+
"x": -1.5225,
|
|
42
|
+
"y": -8.3
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"pinId": "U5.3",
|
|
46
|
+
"x": -1.5225,
|
|
47
|
+
"y": -8.5
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"pinId": "U5.4",
|
|
51
|
+
"x": -1.5225,
|
|
52
|
+
"y": -8.700000000000001
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"pinId": "U5.5",
|
|
56
|
+
"x": 1.5225,
|
|
57
|
+
"y": -8.1
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"pinId": "U5.6",
|
|
61
|
+
"x": 1.5225,
|
|
62
|
+
"y": -8.3
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"pinId": "U5.7",
|
|
66
|
+
"x": 1.5225,
|
|
67
|
+
"y": -8.700000000000001
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"pinId": "U5.8",
|
|
71
|
+
"x": 1.5225,
|
|
72
|
+
"y": -8.5
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"sectionId": "qspi"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"directConnections": [],
|
|
79
|
+
"netConnections": [
|
|
80
|
+
{
|
|
81
|
+
"netId": "V3V3",
|
|
82
|
+
"pinIds": ["R7.1", "U5.8"],
|
|
83
|
+
"netLabelWidth": 0.4
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"netId": "QSPI_CS",
|
|
87
|
+
"pinIds": ["R7.2", "U5.4"],
|
|
88
|
+
"netLabelWidth": 0.7
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"netId": "QSPI_SCK",
|
|
92
|
+
"pinIds": ["U5.1"],
|
|
93
|
+
"netLabelWidth": 0.8
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"netId": "QSPI_DATA0",
|
|
97
|
+
"pinIds": ["U5.2"],
|
|
98
|
+
"netLabelWidth": 1
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"netId": "QSPI_DATA1",
|
|
102
|
+
"pinIds": ["U5.3"],
|
|
103
|
+
"netLabelWidth": 1
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"netId": "QSPI_DATA2",
|
|
107
|
+
"pinIds": ["U5.5"],
|
|
108
|
+
"netLabelWidth": 1
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"netId": "QSPI_DATA3",
|
|
112
|
+
"pinIds": ["U5.6"],
|
|
113
|
+
"netLabelWidth": 1
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"netId": "GND",
|
|
117
|
+
"pinIds": ["U5.7"],
|
|
118
|
+
"netLabelWidth": 0.3
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"availableNetLabelOrientations": {
|
|
122
|
+
"V3V3": ["y+"],
|
|
123
|
+
"QSPI_CS": ["x-", "x+"],
|
|
124
|
+
"QSPI_SCK": ["x-", "x+"],
|
|
125
|
+
"QSPI_DATA0": ["x-", "x+"],
|
|
126
|
+
"QSPI_DATA1": ["x-", "x+"],
|
|
127
|
+
"QSPI_DATA2": ["x-", "x+"],
|
|
128
|
+
"QSPI_DATA3": ["x-", "x+"],
|
|
129
|
+
"GND": ["y-"]
|
|
130
|
+
},
|
|
131
|
+
"maxMspPairDistance": 2.4
|
|
132
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="100%" height="100%" fill="white" />
|
|
3
|
+
<g>
|
|
4
|
+
<circle data-type="point" data-label="R7.1
|
|
5
|
+
x-" data-x="-1.95" data-y="-6.6" cx="102.39751311443555" cy="200.26423159121816" r="3" fill="hsl(232, 100%, 50%, 0.8)" />
|
|
6
|
+
</g>
|
|
7
|
+
<g>
|
|
8
|
+
<circle data-type="point" data-label="R7.2
|
|
9
|
+
x+" data-x="-0.8499999999999999" data-y="-6.6" cx="222.07888090149598" cy="200.26423159121816" r="3" fill="hsl(233, 100%, 50%, 0.8)" />
|
|
10
|
+
</g>
|
|
11
|
+
<g>
|
|
12
|
+
<circle data-type="point" data-label="U5.1
|
|
13
|
+
x-" data-x="-1.5225" data-y="-8.1" cx="148.91004468622495" cy="363.4660967553915" r="3" fill="hsl(203, 100%, 50%, 0.8)" />
|
|
14
|
+
</g>
|
|
15
|
+
<g>
|
|
16
|
+
<circle data-type="point" data-label="U5.2
|
|
17
|
+
x-" data-x="-1.5225" data-y="-8.3" cx="148.91004468622495" cy="385.22634544394805" r="3" fill="hsl(204, 100%, 50%, 0.8)" />
|
|
18
|
+
</g>
|
|
19
|
+
<g>
|
|
20
|
+
<circle data-type="point" data-label="U5.3
|
|
21
|
+
x-" data-x="-1.5225" data-y="-8.5" cx="148.91004468622495" cy="406.98659413250437" r="3" fill="hsl(205, 100%, 50%, 0.8)" />
|
|
22
|
+
</g>
|
|
23
|
+
<g>
|
|
24
|
+
<circle data-type="point" data-label="U5.4
|
|
25
|
+
x-" data-x="-1.5225" data-y="-8.700000000000001" cx="148.91004468622495" cy="428.7468428210609" r="3" fill="hsl(206, 100%, 50%, 0.8)" />
|
|
26
|
+
</g>
|
|
27
|
+
<g>
|
|
28
|
+
<circle data-type="point" data-label="U5.5
|
|
29
|
+
x+" data-x="1.5225" data-y="-8.1" cx="480.2098309694967" cy="363.4660967553915" r="3" fill="hsl(207, 100%, 50%, 0.8)" />
|
|
30
|
+
</g>
|
|
31
|
+
<g>
|
|
32
|
+
<circle data-type="point" data-label="U5.6
|
|
33
|
+
x+" data-x="1.5225" data-y="-8.3" cx="480.2098309694967" cy="385.22634544394805" r="3" fill="hsl(208, 100%, 50%, 0.8)" />
|
|
34
|
+
</g>
|
|
35
|
+
<g>
|
|
36
|
+
<circle data-type="point" data-label="U5.7
|
|
37
|
+
x+" data-x="1.5225" data-y="-8.700000000000001" cx="480.2098309694967" cy="428.7468428210609" r="3" fill="hsl(209, 100%, 50%, 0.8)" />
|
|
38
|
+
</g>
|
|
39
|
+
<g>
|
|
40
|
+
<circle data-type="point" data-label="U5.8
|
|
41
|
+
x+" data-x="1.5225" data-y="-8.5" cx="480.2098309694967" cy="406.98659413250437" r="3" fill="hsl(210, 100%, 50%, 0.8)" />
|
|
42
|
+
</g>
|
|
43
|
+
<g>
|
|
44
|
+
<circle data-type="point" data-label="" data-x="-2.15" data-y="-6.6" cx="80.63726442587912" cy="200.26423159121816" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
45
|
+
</g>
|
|
46
|
+
<g>
|
|
47
|
+
<circle data-type="point" data-label="" data-x="-0.8499999999999999" data-y="-6.6" cx="222.07888090149598" cy="200.26423159121816" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
48
|
+
</g>
|
|
49
|
+
<g>
|
|
50
|
+
<circle data-type="point" data-label="" data-x="-1.5225" data-y="-8.700000000000001" cx="148.91004468622495" cy="428.7468428210609" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
51
|
+
</g>
|
|
52
|
+
<g>
|
|
53
|
+
<circle data-type="point" data-label="" data-x="-1.5225" data-y="-8.1" cx="148.91004468622495" cy="363.4660967553915" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
54
|
+
</g>
|
|
55
|
+
<g>
|
|
56
|
+
<circle data-type="point" data-label="" data-x="-1.5225" data-y="-8.3" cx="148.91004468622495" cy="385.22634544394805" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
57
|
+
</g>
|
|
58
|
+
<g>
|
|
59
|
+
<circle data-type="point" data-label="" data-x="-1.5225" data-y="-8.5" cx="148.91004468622495" cy="406.98659413250437" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
60
|
+
</g>
|
|
61
|
+
<g>
|
|
62
|
+
<circle data-type="point" data-label="" data-x="1.5225" data-y="-8.1" cx="480.2098309694967" cy="363.4660967553915" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
63
|
+
</g>
|
|
64
|
+
<g>
|
|
65
|
+
<circle data-type="point" data-label="" data-x="1.5225" data-y="-8.3" cx="480.2098309694967" cy="385.22634544394805" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
66
|
+
</g>
|
|
67
|
+
<g>
|
|
68
|
+
<circle data-type="point" data-label="" data-x="1.6235" data-y="-8.901" cx="491.19875655721773" cy="450.61589275306005" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
|
|
69
|
+
</g>
|
|
70
|
+
<g>
|
|
71
|
+
<polyline data-points="-1.95,-6.6 1.5225,-8.5" data-type="line" data-label="" points="102.39751311443555,200.26423159121816 480.2098309694967,406.98659413250437" fill="none" stroke="hsl(154, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
|
|
72
|
+
</g>
|
|
73
|
+
<g>
|
|
74
|
+
<polyline data-points="-0.8499999999999999,-6.6 -1.5225,-8.700000000000001" data-type="line" data-label="" points="222.07888090149598,200.26423159121816 148.91004468622495,428.7468428210609" fill="none" stroke="hsl(172, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
|
|
75
|
+
</g>
|
|
76
|
+
<g>
|
|
77
|
+
<polyline data-points="-1.95,-6.6 -2.15,-6.6 -2.15,-7.55 1.7225000000000004,-7.55 1.7225000000000004,-7.8999999999999995 2.6235,-7.8999999999999995 2.6235,-8.499999999999998 1.5225000000000002,-8.5" data-type="line" data-label="" points="102.39751311443555,200.26423159121816 80.63726442587912,200.26423159121816 80.63726442587912,303.62541286186126 501.97007965805324,303.62541286186126 501.97007965805324,341.70584806683496 600,341.70584806683496 600,406.98659413250414 480.20983096949675,406.98659413250437" fill="none" stroke="purple" stroke-width="1" />
|
|
78
|
+
</g>
|
|
79
|
+
<g>
|
|
80
|
+
<polyline data-points="1.5225,-8.700000000000001 1.6235,-8.700000000000001 1.6235,-8.901" data-type="line" data-label="" points="480.2098309694967,428.7468428210609 491.19875655721773,428.7468428210609 491.19875655721773,450.61589275306005" fill="none" stroke="purple" stroke-width="1" />
|
|
81
|
+
</g>
|
|
82
|
+
<g>
|
|
83
|
+
<rect data-type="rect" data-label="schematic_component_0" data-x="-1.4" data-y="-6.6" x="102.39751311443555" y="179.10724771711682" width="119.68136778706042" height="42.313967748202685" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009191071428571429" />
|
|
84
|
+
</g>
|
|
85
|
+
<g>
|
|
86
|
+
<rect data-type="rect" data-label="schematic_component_1" data-x="0" data-y="-8.4" x="148.91004468622495" y="341.7058480668351" width="331.2997862832717" height="108.80124344278227" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009191071428571429" />
|
|
87
|
+
</g>
|
|
88
|
+
<g>
|
|
89
|
+
<rect data-type="rect" data-label="netId: V3V3
|
|
90
|
+
globalConnNetId: connectivity_net0" data-x="-2.15" data-y="-6.3999999999999995" x="69.75714008160091" y="156.7437342141053" width="21.76024868855646" height="43.52049737711286" fill="#ef444466" stroke="#ef4444" stroke-width="0.009191071428571429" />
|
|
91
|
+
</g>
|
|
92
|
+
<g>
|
|
93
|
+
<rect data-type="rect" data-label="netId: QSPI_CS
|
|
94
|
+
globalConnNetId: connectivity_net1" data-x="-0.4989999999999999" data-y="-6.6" x="222.18768214493878" y="189.38410724693995" width="76.1608704099475" height="21.76024868855643" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
|
|
95
|
+
</g>
|
|
96
|
+
<g>
|
|
97
|
+
<rect data-type="rect" data-label="netId: QSPI_CS
|
|
98
|
+
globalConnNetId: connectivity_net1" data-x="-1.8735" data-y="-8.700000000000001" x="72.64037303283462" y="417.8667184767828" width="76.16087040994756" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
|
|
99
|
+
</g>
|
|
100
|
+
<g>
|
|
101
|
+
<rect data-type="rect" data-label="netId: QSPI_SCK
|
|
102
|
+
globalConnNetId: connectivity_net2" data-x="-1.9234999999999998" data-y="-8.1" x="61.76024868855643" y="352.5859724111133" width="87.04099475422575" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
|
|
103
|
+
</g>
|
|
104
|
+
<g>
|
|
105
|
+
<rect data-type="rect" data-label="netId: QSPI_DATA0
|
|
106
|
+
globalConnNetId: connectivity_net3" data-x="-2.0235" data-y="-8.3" x="40" y="374.34622109966983" width="108.80124344278218" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
|
|
107
|
+
</g>
|
|
108
|
+
<g>
|
|
109
|
+
<rect data-type="rect" data-label="netId: QSPI_DATA1
|
|
110
|
+
globalConnNetId: connectivity_net4" data-x="-2.0235" data-y="-8.5" x="40" y="396.10646978822615" width="108.80124344278218" height="21.76024868855643" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
|
|
111
|
+
</g>
|
|
112
|
+
<g>
|
|
113
|
+
<rect data-type="rect" data-label="netId: QSPI_DATA2
|
|
114
|
+
globalConnNetId: connectivity_net5" data-x="2.0235" data-y="-8.1" x="480.3186322129395" y="352.5859724111133" width="108.80124344278215" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
|
|
115
|
+
</g>
|
|
116
|
+
<g>
|
|
117
|
+
<rect data-type="rect" data-label="netId: QSPI_DATA3
|
|
118
|
+
globalConnNetId: connectivity_net6" data-x="2.0235" data-y="-8.3" x="480.3186322129395" y="374.34622109966983" width="108.80124344278215" height="21.760248688556317" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009191071428571429" />
|
|
119
|
+
</g>
|
|
120
|
+
<g>
|
|
121
|
+
<rect data-type="rect" data-label="netId: GND
|
|
122
|
+
globalConnNetId: connectivity_net7" data-x="1.6235" data-y="-9.051" x="480.3186322129395" y="450.61589275306005" width="21.76024868855643" height="32.64037303283476" fill="#00000066" stroke="#000000" stroke-width="0.009191071428571429" />
|
|
123
|
+
</g>
|
|
124
|
+
<g id="crosshair" style="display: none">
|
|
125
|
+
<line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
|
|
126
|
+
<line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5" /><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text>
|
|
127
|
+
</g>
|
|
128
|
+
<script>
|
|
129
|
+
<![CDATA[
|
|
130
|
+
document.currentScript.parentElement.addEventListener('mousemove', (e) => {
|
|
131
|
+
const svg = e.currentTarget;
|
|
132
|
+
const rect = svg.getBoundingClientRect();
|
|
133
|
+
const x = e.clientX - rect.left;
|
|
134
|
+
const y = e.clientY - rect.top;
|
|
135
|
+
const crosshair = svg.getElementById('crosshair');
|
|
136
|
+
const h = svg.getElementById('crosshair-h');
|
|
137
|
+
const v = svg.getElementById('crosshair-v');
|
|
138
|
+
const coords = svg.getElementById('coordinates');
|
|
139
|
+
|
|
140
|
+
crosshair.style.display = 'block';
|
|
141
|
+
h.setAttribute('x1', '0');
|
|
142
|
+
h.setAttribute('x2', '640');
|
|
143
|
+
h.setAttribute('y1', y);
|
|
144
|
+
h.setAttribute('y2', y);
|
|
145
|
+
v.setAttribute('x1', x);
|
|
146
|
+
v.setAttribute('x2', x);
|
|
147
|
+
v.setAttribute('y1', '0');
|
|
148
|
+
v.setAttribute('y2', '640');
|
|
149
|
+
|
|
150
|
+
// Calculate real coordinates using inverse transformation
|
|
151
|
+
const matrix = {
|
|
152
|
+
"a": 108.8012434427822,
|
|
153
|
+
"c": 0,
|
|
154
|
+
"e": 314.55993782786084,
|
|
155
|
+
"b": 0,
|
|
156
|
+
"d": -108.8012434427822,
|
|
157
|
+
"f": -517.8239751311443
|
|
158
|
+
};
|
|
159
|
+
// Manually invert and apply the affine transform
|
|
160
|
+
// Since we only use translate and scale, we can directly compute:
|
|
161
|
+
// x' = (x - tx) / sx
|
|
162
|
+
// y' = (y - ty) / sy
|
|
163
|
+
const sx = matrix.a;
|
|
164
|
+
const sy = matrix.d;
|
|
165
|
+
const tx = matrix.e;
|
|
166
|
+
const ty = matrix.f;
|
|
167
|
+
const realPoint = {
|
|
168
|
+
x: (x - tx) / sx,
|
|
169
|
+
y: (y - ty) / sy // Flip y back since we used negative scale
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
|
|
173
|
+
coords.setAttribute('x', (x + 5).toString());
|
|
174
|
+
coords.setAttribute('y', (y - 5).toString());
|
|
175
|
+
});
|
|
176
|
+
document.currentScript.parentElement.addEventListener('mouseleave', () => {
|
|
177
|
+
document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
|
|
178
|
+
});
|
|
179
|
+
]]>
|
|
180
|
+
</script>
|
|
181
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { test, expect } from "bun:test"
|
|
2
|
+
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
3
|
+
import inputProblem from "../assets/example33.json"
|
|
4
|
+
import "tests/fixtures/matcher"
|
|
5
|
+
|
|
6
|
+
test("example33", () => {
|
|
7
|
+
const solver = new SchematicTracePipelineSolver(inputProblem as any)
|
|
8
|
+
|
|
9
|
+
solver.solve()
|
|
10
|
+
|
|
11
|
+
expect(solver).toMatchSolverSnapshot(import.meta.path)
|
|
12
|
+
})
|