@tscircuit/schematic-trace-solver 0.0.30 → 0.0.32
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/CLAUDE.md +8 -0
- package/dist/index.js +114 -33
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +2 -1
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts +37 -7
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts +7 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +27 -25
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/applyJogToTrace.ts +76 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +1 -1
- package/package.json +1 -1
- package/site/SchematicTracePipelineSolver/SchematicTracePipelineSolver02.page.tsx +177 -0
- package/site/SchematicTracePipelineSolver/SchematicTracePipelineSolver03.page.tsx +486 -0
- package/tests/examples/__snapshots__/example11.snap.svg +1 -1
- package/tests/examples/__snapshots__/example16.snap.svg +195 -0
- package/tests/examples/example16.test.tsx +175 -0
- package/tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro03.test.ts +490 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
- use `bunx tsc --noEmit` to type check
|
|
2
|
+
- use `bun test path/to/test.ts` to run a test
|
|
3
|
+
- use `bun add <package>` to add a package
|
|
4
|
+
|
|
5
|
+
There are two ways the user tests the algorithm:
|
|
6
|
+
|
|
7
|
+
- Running `bun test`
|
|
8
|
+
- Looking at the `*.page.tsx` files in the `site` directory via a browser
|
package/dist/index.js
CHANGED
|
@@ -607,7 +607,8 @@ var cartesian = (arrays) => arrays.length === 0 ? [[]] : arrays.reduce(
|
|
|
607
607
|
);
|
|
608
608
|
var generateElbowVariants = ({
|
|
609
609
|
baseElbow,
|
|
610
|
-
guidelines
|
|
610
|
+
guidelines,
|
|
611
|
+
maxVariants = 1e4
|
|
611
612
|
}) => {
|
|
612
613
|
const elbow = preprocessElbow(baseElbow);
|
|
613
614
|
assertOrthogonalPolyline(elbow);
|
|
@@ -692,6 +693,9 @@ var generateElbowVariants = ({
|
|
|
692
693
|
if (!seen.has(key)) {
|
|
693
694
|
seen.add(key);
|
|
694
695
|
elbowVariants.push(variant);
|
|
696
|
+
if (elbowVariants.length >= maxVariants) {
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
695
699
|
}
|
|
696
700
|
}
|
|
697
701
|
return { elbowVariants, movableSegments };
|
|
@@ -868,7 +872,8 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
|
|
|
868
872
|
);
|
|
869
873
|
const { elbowVariants, movableSegments } = generateElbowVariants({
|
|
870
874
|
baseElbow: this.baseElbow,
|
|
871
|
-
guidelines: this.guidelines
|
|
875
|
+
guidelines: this.guidelines,
|
|
876
|
+
maxVariants: 1e3
|
|
872
877
|
});
|
|
873
878
|
this.movableSegments = movableSegments;
|
|
874
879
|
const getPathLength = (pts) => {
|
|
@@ -906,6 +911,7 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
|
|
|
906
911
|
pinIdMap: this.pinIdMap,
|
|
907
912
|
chipMap: this.chipMap
|
|
908
913
|
});
|
|
914
|
+
let pathIsValid = true;
|
|
909
915
|
for (let i = 0; i < nextCandidatePath.length - 1; i++) {
|
|
910
916
|
const start = nextCandidatePath[i];
|
|
911
917
|
const end = nextCandidatePath[i + 1];
|
|
@@ -913,12 +919,19 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
|
|
|
913
919
|
const EPS2 = 1e-9;
|
|
914
920
|
for (const [, rcl] of restrictedCenterLines) {
|
|
915
921
|
if (rcl.axes.has("x") && typeof rcl.x === "number") {
|
|
916
|
-
if ((start.x - rcl.x) * (end.x - rcl.x) < -EPS2)
|
|
922
|
+
if ((start.x - rcl.x) * (end.x - rcl.x) < -EPS2) {
|
|
923
|
+
pathIsValid = false;
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
917
926
|
}
|
|
918
927
|
if (rcl.axes.has("y") && typeof rcl.y === "number") {
|
|
919
|
-
if ((start.y - rcl.y) * (end.y - rcl.y) < -EPS2)
|
|
928
|
+
if ((start.y - rcl.y) * (end.y - rcl.y) < -EPS2) {
|
|
929
|
+
pathIsValid = false;
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
920
932
|
}
|
|
921
933
|
}
|
|
934
|
+
if (!pathIsValid) break;
|
|
922
935
|
const isStartPin = this.pins.some(
|
|
923
936
|
(pin) => Math.abs(pin.x - start.x) < 1e-10 && Math.abs(pin.y - start.y) < 1e-10
|
|
924
937
|
);
|
|
@@ -939,12 +952,16 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
|
|
|
939
952
|
const onBottom = Math.abs(start.y - bounds.minY) < 1e-9;
|
|
940
953
|
const onTop = Math.abs(start.y - bounds.maxY) < 1e-9;
|
|
941
954
|
const entersInterior = onLeft && dx > EPS3 || onRight && dx < -EPS3 || onBottom && dy > EPS3 || onTop && dy < -EPS3;
|
|
942
|
-
if (entersInterior)
|
|
955
|
+
if (entersInterior) {
|
|
956
|
+
pathIsValid = false;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
943
959
|
if (!excludeChipIds.includes(startPin.chipId)) {
|
|
944
960
|
excludeChipIds.push(startPin.chipId);
|
|
945
961
|
}
|
|
946
962
|
}
|
|
947
963
|
}
|
|
964
|
+
if (!pathIsValid) break;
|
|
948
965
|
if (isEndPin) {
|
|
949
966
|
const endPin = this.pins.find(
|
|
950
967
|
(pin) => Math.abs(pin.x - end.x) < 1e-10 && Math.abs(pin.y - end.y) < 1e-10
|
|
@@ -959,21 +976,30 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
|
|
|
959
976
|
const onBottom = Math.abs(end.y - bounds.minY) < 1e-9;
|
|
960
977
|
const onTop = Math.abs(end.y - bounds.maxY) < 1e-9;
|
|
961
978
|
const entersInterior = onLeft && dx > EPS3 || onRight && dx < -EPS3 || onBottom && dy > EPS3 || onTop && dy < -EPS3;
|
|
962
|
-
if (entersInterior)
|
|
979
|
+
if (entersInterior) {
|
|
980
|
+
pathIsValid = false;
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
963
983
|
if (!excludeChipIds.includes(endPin.chipId)) {
|
|
964
984
|
excludeChipIds.push(endPin.chipId);
|
|
965
985
|
}
|
|
966
986
|
}
|
|
967
987
|
}
|
|
988
|
+
if (!pathIsValid) break;
|
|
968
989
|
const obstacleOps = { excludeChipIds };
|
|
969
990
|
const intersects = this.chipObstacleSpatialIndex.doesOrthogonalLineIntersectChip(
|
|
970
991
|
[start, end],
|
|
971
992
|
obstacleOps
|
|
972
993
|
);
|
|
973
|
-
if (intersects)
|
|
994
|
+
if (intersects) {
|
|
995
|
+
pathIsValid = false;
|
|
996
|
+
break;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (pathIsValid) {
|
|
1000
|
+
this.solvedTracePath = nextCandidatePath;
|
|
1001
|
+
this.solved = true;
|
|
974
1002
|
}
|
|
975
|
-
this.solvedTracePath = nextCandidatePath;
|
|
976
|
-
this.solved = true;
|
|
977
1003
|
}
|
|
978
1004
|
visualize() {
|
|
979
1005
|
const graphics = visualizeInputProblem(this.inputProblem, {
|
|
@@ -1095,11 +1121,11 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
|
|
|
1095
1121
|
return;
|
|
1096
1122
|
}
|
|
1097
1123
|
const connectionPair = this.queuedConnectionPairs.shift();
|
|
1098
|
-
this.currentConnectionPair = connectionPair;
|
|
1099
1124
|
if (!connectionPair) {
|
|
1100
1125
|
this.solved = true;
|
|
1101
1126
|
return;
|
|
1102
1127
|
}
|
|
1128
|
+
this.currentConnectionPair = connectionPair;
|
|
1103
1129
|
const { pins } = connectionPair;
|
|
1104
1130
|
this.activeSubSolver = new SchematicTraceSingleLineSolver({
|
|
1105
1131
|
inputProblem: this.inputProblem,
|
|
@@ -1137,6 +1163,64 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
|
|
|
1137
1163
|
}
|
|
1138
1164
|
};
|
|
1139
1165
|
|
|
1166
|
+
// lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/applyJogToTrace.ts
|
|
1167
|
+
var applyJogToTerminalSegment = ({
|
|
1168
|
+
pts,
|
|
1169
|
+
segmentIndex: si,
|
|
1170
|
+
offset,
|
|
1171
|
+
JOG_SIZE,
|
|
1172
|
+
EPS: EPS2 = 1e-6
|
|
1173
|
+
}) => {
|
|
1174
|
+
if (si !== 0 && si !== pts.length - 2) return;
|
|
1175
|
+
const start = pts[si];
|
|
1176
|
+
const end = pts[si + 1];
|
|
1177
|
+
const isVertical = Math.abs(start.x - end.x) < EPS2;
|
|
1178
|
+
const isHorizontal = Math.abs(start.y - end.y) < EPS2;
|
|
1179
|
+
if (!isVertical && !isHorizontal) return;
|
|
1180
|
+
const segDir = isVertical ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
|
|
1181
|
+
if (si === 0) {
|
|
1182
|
+
if (isVertical) {
|
|
1183
|
+
const jogY = start.y + segDir * JOG_SIZE;
|
|
1184
|
+
pts.splice(
|
|
1185
|
+
1,
|
|
1186
|
+
1,
|
|
1187
|
+
{ x: start.x, y: jogY },
|
|
1188
|
+
{ x: start.x + offset, y: jogY },
|
|
1189
|
+
{ x: end.x + offset, y: end.y }
|
|
1190
|
+
);
|
|
1191
|
+
} else {
|
|
1192
|
+
const jogX = start.x + segDir * JOG_SIZE;
|
|
1193
|
+
pts.splice(
|
|
1194
|
+
1,
|
|
1195
|
+
1,
|
|
1196
|
+
{ x: jogX, y: start.y },
|
|
1197
|
+
{ x: jogX, y: start.y + offset },
|
|
1198
|
+
{ x: end.x, y: end.y + offset }
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
} else {
|
|
1202
|
+
if (isVertical) {
|
|
1203
|
+
const jogY = end.y - segDir * JOG_SIZE;
|
|
1204
|
+
pts.splice(
|
|
1205
|
+
si,
|
|
1206
|
+
1,
|
|
1207
|
+
{ x: start.x + offset, y: start.y },
|
|
1208
|
+
{ x: end.x + offset, y: jogY },
|
|
1209
|
+
{ x: end.x, y: jogY }
|
|
1210
|
+
);
|
|
1211
|
+
} else {
|
|
1212
|
+
const jogX = end.x - segDir * JOG_SIZE;
|
|
1213
|
+
pts.splice(
|
|
1214
|
+
si,
|
|
1215
|
+
1,
|
|
1216
|
+
{ x: start.x, y: start.y + offset },
|
|
1217
|
+
{ x: jogX, y: end.y + offset },
|
|
1218
|
+
{ x: jogX, y: end.y }
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1140
1224
|
// lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts
|
|
1141
1225
|
var TraceOverlapIssueSolver = class extends BaseSolver {
|
|
1142
1226
|
overlappingTraceSegments;
|
|
@@ -1180,33 +1264,30 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
|
|
|
1180
1264
|
const current = this.correctedTraceMap[original.mspPairId] ?? original;
|
|
1181
1265
|
const pts = current.tracePath.map((p) => ({ ...p }));
|
|
1182
1266
|
const segIdxs = Array.from(segIdxSet).sort((a, b) => a - b);
|
|
1183
|
-
const
|
|
1184
|
-
const
|
|
1185
|
-
for (const si of
|
|
1267
|
+
const segIdxsRev = Array.from(segIdxSet).sort((a, b) => a - b).reverse();
|
|
1268
|
+
const JOG_SIZE = this.SHIFT_DISTANCE;
|
|
1269
|
+
for (const si of segIdxsRev) {
|
|
1186
1270
|
if (si < 0 || si >= pts.length - 1) continue;
|
|
1187
|
-
if (si === 0 || si === pts.length - 2)
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1271
|
+
if (si === 0 || si === pts.length - 2) {
|
|
1272
|
+
applyJogToTerminalSegment({
|
|
1273
|
+
pts,
|
|
1274
|
+
segmentIndex: si,
|
|
1275
|
+
offset,
|
|
1276
|
+
JOG_SIZE,
|
|
1277
|
+
EPS: EPS2
|
|
1278
|
+
});
|
|
1279
|
+
} else {
|
|
1280
|
+
const start = pts[si];
|
|
1281
|
+
const end = pts[si + 1];
|
|
1282
|
+
const isVertical = Math.abs(start.x - end.x) < EPS2;
|
|
1283
|
+
const isHorizontal = Math.abs(start.y - end.y) < EPS2;
|
|
1284
|
+
if (!isVertical && !isHorizontal) continue;
|
|
1285
|
+
if (isVertical) {
|
|
1195
1286
|
start.x += offset;
|
|
1196
|
-
appliedX.add(si);
|
|
1197
|
-
}
|
|
1198
|
-
if (!appliedX.has(si + 1)) {
|
|
1199
1287
|
end.x += offset;
|
|
1200
|
-
|
|
1201
|
-
}
|
|
1202
|
-
} else if (isHorizontal) {
|
|
1203
|
-
if (!appliedY.has(si)) {
|
|
1288
|
+
} else {
|
|
1204
1289
|
start.y += offset;
|
|
1205
|
-
appliedY.add(si);
|
|
1206
|
-
}
|
|
1207
|
-
if (!appliedY.has(si + 1)) {
|
|
1208
1290
|
end.y += offset;
|
|
1209
|
-
appliedY.add(si + 1);
|
|
1210
1291
|
}
|
|
1211
1292
|
}
|
|
1212
1293
|
}
|
|
@@ -1295,7 +1376,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
1295
1376
|
return islands;
|
|
1296
1377
|
}
|
|
1297
1378
|
findNextOverlapIssue() {
|
|
1298
|
-
const EPS2 =
|
|
1379
|
+
const EPS2 = 2e-3;
|
|
1299
1380
|
const netIds = Object.keys(this.traceNetIslands);
|
|
1300
1381
|
for (let i = 0; i < netIds.length; i++) {
|
|
1301
1382
|
for (let j = i + 1; j < netIds.length; j++) {
|
|
@@ -101,13 +101,14 @@ export class SchematicTraceLinesSolver extends BaseSolver {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
const connectionPair = this.queuedConnectionPairs.shift()
|
|
104
|
-
this.currentConnectionPair = connectionPair!
|
|
105
104
|
|
|
106
105
|
if (!connectionPair) {
|
|
107
106
|
this.solved = true
|
|
108
107
|
return
|
|
109
108
|
}
|
|
110
109
|
|
|
110
|
+
this.currentConnectionPair = connectionPair
|
|
111
|
+
|
|
111
112
|
const { pins } = connectionPair
|
|
112
113
|
|
|
113
114
|
this.activeSubSolver = new SchematicTraceSingleLineSolver({
|
|
@@ -97,6 +97,7 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
|
|
|
97
97
|
const { elbowVariants, movableSegments } = generateElbowVariants({
|
|
98
98
|
baseElbow: this.baseElbow,
|
|
99
99
|
guidelines: this.guidelines,
|
|
100
|
+
maxVariants: 1000,
|
|
100
101
|
})
|
|
101
102
|
|
|
102
103
|
this.movableSegments = movableSegments
|
|
@@ -144,6 +145,9 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
|
|
|
144
145
|
chipMap: this.chipMap,
|
|
145
146
|
})
|
|
146
147
|
|
|
148
|
+
// Check if this candidate path is valid
|
|
149
|
+
let pathIsValid = true
|
|
150
|
+
|
|
147
151
|
for (let i = 0; i < nextCandidatePath.length - 1; i++) {
|
|
148
152
|
const start = nextCandidatePath[i]
|
|
149
153
|
const end = nextCandidatePath[i + 1]
|
|
@@ -156,14 +160,22 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
|
|
|
156
160
|
for (const [, rcl] of restrictedCenterLines) {
|
|
157
161
|
if (rcl.axes.has("x") && typeof rcl.x === "number") {
|
|
158
162
|
// segment strictly crosses vertical center line
|
|
159
|
-
if ((start.x - rcl.x) * (end.x - rcl.x) < -EPS)
|
|
163
|
+
if ((start.x - rcl.x) * (end.x - rcl.x) < -EPS) {
|
|
164
|
+
pathIsValid = false
|
|
165
|
+
break
|
|
166
|
+
}
|
|
160
167
|
}
|
|
161
168
|
if (rcl.axes.has("y") && typeof rcl.y === "number") {
|
|
162
169
|
// segment strictly crosses horizontal center line
|
|
163
|
-
if ((start.y - rcl.y) * (end.y - rcl.y) < -EPS)
|
|
170
|
+
if ((start.y - rcl.y) * (end.y - rcl.y) < -EPS) {
|
|
171
|
+
pathIsValid = false
|
|
172
|
+
break
|
|
173
|
+
}
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
|
|
177
|
+
if (!pathIsValid) break
|
|
178
|
+
|
|
167
179
|
// Always exclude chips that contain the start or end points of this segment
|
|
168
180
|
// if those points are actually pin locations
|
|
169
181
|
const isStartPin = this.pins.some(
|
|
@@ -197,13 +209,18 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
|
|
|
197
209
|
(onRight && dx < -EPS) ||
|
|
198
210
|
(onBottom && dy > EPS) ||
|
|
199
211
|
(onTop && dy < -EPS)
|
|
200
|
-
if (entersInterior)
|
|
212
|
+
if (entersInterior) {
|
|
213
|
+
pathIsValid = false
|
|
214
|
+
break
|
|
215
|
+
}
|
|
201
216
|
if (!excludeChipIds.includes(startPin.chipId)) {
|
|
202
217
|
excludeChipIds.push(startPin.chipId)
|
|
203
218
|
}
|
|
204
219
|
}
|
|
205
220
|
}
|
|
206
221
|
|
|
222
|
+
if (!pathIsValid) break
|
|
223
|
+
|
|
207
224
|
if (isEndPin) {
|
|
208
225
|
const endPin = this.pins.find(
|
|
209
226
|
(pin) =>
|
|
@@ -224,24 +241,37 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
|
|
|
224
241
|
(onRight && dx < -EPS) ||
|
|
225
242
|
(onBottom && dy > EPS) ||
|
|
226
243
|
(onTop && dy < -EPS)
|
|
227
|
-
if (entersInterior)
|
|
244
|
+
if (entersInterior) {
|
|
245
|
+
pathIsValid = false
|
|
246
|
+
break
|
|
247
|
+
}
|
|
228
248
|
if (!excludeChipIds.includes(endPin.chipId)) {
|
|
229
249
|
excludeChipIds.push(endPin.chipId)
|
|
230
250
|
}
|
|
231
251
|
}
|
|
232
252
|
}
|
|
233
253
|
|
|
254
|
+
if (!pathIsValid) break
|
|
255
|
+
|
|
234
256
|
const obstacleOps = { excludeChipIds }
|
|
235
257
|
const intersects =
|
|
236
258
|
this.chipObstacleSpatialIndex.doesOrthogonalLineIntersectChip(
|
|
237
259
|
[start, end],
|
|
238
260
|
obstacleOps,
|
|
239
261
|
)
|
|
240
|
-
if (intersects)
|
|
262
|
+
if (intersects) {
|
|
263
|
+
pathIsValid = false
|
|
264
|
+
break
|
|
265
|
+
}
|
|
241
266
|
}
|
|
242
267
|
|
|
243
|
-
this
|
|
244
|
-
|
|
268
|
+
// If this path is valid, use it as the solution
|
|
269
|
+
if (pathIsValid) {
|
|
270
|
+
this.solvedTracePath = nextCandidatePath
|
|
271
|
+
this.solved = true
|
|
272
|
+
}
|
|
273
|
+
// If this path is invalid, continue to next step to try the next candidate
|
|
274
|
+
// The next _step() call will try the next candidate path
|
|
245
275
|
}
|
|
246
276
|
|
|
247
277
|
override visualize(): GraphicsObject {
|
|
@@ -260,9 +260,11 @@ const cartesian = <T>(arrays: T[][]): T[][] =>
|
|
|
260
260
|
export const generateElbowVariants = ({
|
|
261
261
|
baseElbow,
|
|
262
262
|
guidelines,
|
|
263
|
+
maxVariants = 10000,
|
|
263
264
|
}: {
|
|
264
265
|
baseElbow: Point[]
|
|
265
266
|
guidelines: Guideline[]
|
|
267
|
+
maxVariants?: number
|
|
266
268
|
}): {
|
|
267
269
|
elbowVariants: Array<Point[]>
|
|
268
270
|
movableSegments: Array<MovableSegment>
|
|
@@ -395,6 +397,11 @@ export const generateElbowVariants = ({
|
|
|
395
397
|
if (!seen.has(key)) {
|
|
396
398
|
seen.add(key)
|
|
397
399
|
elbowVariants.push(variant)
|
|
400
|
+
|
|
401
|
+
// Stop if we've hit the maximum number of variants
|
|
402
|
+
if (elbowVariants.length >= maxVariants) {
|
|
403
|
+
break
|
|
404
|
+
}
|
|
398
405
|
}
|
|
399
406
|
}
|
|
400
407
|
|
package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { GraphicsObject } from "graphics-debug"
|
|
|
2
2
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
3
|
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
4
4
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import { applyJogToTerminalSegment } from "./applyJogToTrace"
|
|
5
6
|
|
|
6
7
|
type ConnNetId = string
|
|
7
8
|
|
|
@@ -83,38 +84,39 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
83
84
|
|
|
84
85
|
const segIdxs = Array.from(segIdxSet).sort((a, b) => a - b)
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
const segIdxsRev = Array.from(segIdxSet)
|
|
88
|
+
.sort((a, b) => a - b)
|
|
89
|
+
.reverse()
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
if (si < 0 || si >= pts.length - 1) continue
|
|
92
|
-
// Do not move the first or last segment since they connect directly to pins
|
|
93
|
-
if (si === 0 || si === pts.length - 2) continue
|
|
94
|
-
const start = pts[si]!
|
|
95
|
-
const end = pts[si + 1]!
|
|
96
|
-
const isVertical = Math.abs(start.x - end.x) < EPS
|
|
97
|
-
const isHorizontal = Math.abs(start.y - end.y) < EPS
|
|
91
|
+
const JOG_SIZE = this.SHIFT_DISTANCE
|
|
98
92
|
|
|
99
|
-
|
|
93
|
+
// Process from end to start to keep indices valid after splicing
|
|
94
|
+
for (const si of segIdxsRev) {
|
|
95
|
+
if (si < 0 || si >= pts.length - 1) continue
|
|
100
96
|
|
|
101
|
-
if (
|
|
102
|
-
|
|
97
|
+
if (si === 0 || si === pts.length - 2) {
|
|
98
|
+
applyJogToTerminalSegment({
|
|
99
|
+
pts,
|
|
100
|
+
segmentIndex: si,
|
|
101
|
+
offset,
|
|
102
|
+
JOG_SIZE,
|
|
103
|
+
EPS,
|
|
104
|
+
})
|
|
105
|
+
} else {
|
|
106
|
+
// Internal segment - shift both points
|
|
107
|
+
const start = pts[si]!
|
|
108
|
+
const end = pts[si + 1]!
|
|
109
|
+
const isVertical = Math.abs(start.x - end.x) < EPS
|
|
110
|
+
const isHorizontal = Math.abs(start.y - end.y) < EPS
|
|
111
|
+
if (!isVertical && !isHorizontal) continue
|
|
112
|
+
|
|
113
|
+
if (isVertical) {
|
|
103
114
|
start.x += offset
|
|
104
|
-
appliedX.add(si)
|
|
105
|
-
}
|
|
106
|
-
if (!appliedX.has(si + 1)) {
|
|
107
115
|
end.x += offset
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
} else if (isHorizontal) {
|
|
111
|
-
if (!appliedY.has(si)) {
|
|
116
|
+
} else {
|
|
117
|
+
// Horizontal
|
|
112
118
|
start.y += offset
|
|
113
|
-
appliedY.add(si)
|
|
114
|
-
}
|
|
115
|
-
if (!appliedY.has(si + 1)) {
|
|
116
119
|
end.y += offset
|
|
117
|
-
appliedY.add(si + 1)
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
|
|
3
|
+
export const applyJogToTerminalSegment = ({
|
|
4
|
+
pts,
|
|
5
|
+
segmentIndex: si,
|
|
6
|
+
offset,
|
|
7
|
+
JOG_SIZE,
|
|
8
|
+
EPS = 1e-6,
|
|
9
|
+
}: {
|
|
10
|
+
pts: Point[]
|
|
11
|
+
segmentIndex: number
|
|
12
|
+
offset: number
|
|
13
|
+
JOG_SIZE: number
|
|
14
|
+
EPS?: number
|
|
15
|
+
}) => {
|
|
16
|
+
if (si !== 0 && si !== pts.length - 2) return
|
|
17
|
+
|
|
18
|
+
const start = pts[si]!
|
|
19
|
+
const end = pts[si + 1]!
|
|
20
|
+
const isVertical = Math.abs(start.x - end.x) < EPS
|
|
21
|
+
const isHorizontal = Math.abs(start.y - end.y) < EPS
|
|
22
|
+
if (!isVertical && !isHorizontal) return
|
|
23
|
+
|
|
24
|
+
const segDir = isVertical
|
|
25
|
+
? end.y > start.y
|
|
26
|
+
? 1
|
|
27
|
+
: -1
|
|
28
|
+
: end.x > start.x
|
|
29
|
+
? 1
|
|
30
|
+
: -1
|
|
31
|
+
|
|
32
|
+
if (si === 0) {
|
|
33
|
+
if (isVertical) {
|
|
34
|
+
const jogY = start.y + segDir * JOG_SIZE
|
|
35
|
+
pts.splice(
|
|
36
|
+
1,
|
|
37
|
+
1,
|
|
38
|
+
{ x: start.x, y: jogY },
|
|
39
|
+
{ x: start.x + offset, y: jogY },
|
|
40
|
+
{ x: end.x + offset, y: end.y },
|
|
41
|
+
)
|
|
42
|
+
} else {
|
|
43
|
+
// Horizontal
|
|
44
|
+
const jogX = start.x + segDir * JOG_SIZE
|
|
45
|
+
pts.splice(
|
|
46
|
+
1,
|
|
47
|
+
1,
|
|
48
|
+
{ x: jogX, y: start.y },
|
|
49
|
+
{ x: jogX, y: start.y + offset },
|
|
50
|
+
{ x: end.x, y: end.y + offset },
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
// si === pts.length - 2
|
|
55
|
+
if (isVertical) {
|
|
56
|
+
const jogY = end.y - segDir * JOG_SIZE
|
|
57
|
+
pts.splice(
|
|
58
|
+
si,
|
|
59
|
+
1,
|
|
60
|
+
{ x: start.x + offset, y: start.y },
|
|
61
|
+
{ x: end.x + offset, y: jogY },
|
|
62
|
+
{ x: end.x, y: jogY },
|
|
63
|
+
)
|
|
64
|
+
} else {
|
|
65
|
+
// Horizontal
|
|
66
|
+
const jogX = end.x - segDir * JOG_SIZE
|
|
67
|
+
pts.splice(
|
|
68
|
+
si,
|
|
69
|
+
1,
|
|
70
|
+
{ x: start.x, y: start.y + offset },
|
|
71
|
+
{ x: jogX, y: end.y + offset },
|
|
72
|
+
{ x: jogX, y: end.y },
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -89,7 +89,7 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
89
89
|
overlappingTraceSegments: Array<OverlappingTraceSegmentLocator>
|
|
90
90
|
} | null {
|
|
91
91
|
// Detect the next set of overlapping segments between two different net islands.
|
|
92
|
-
const EPS =
|
|
92
|
+
const EPS = 2e-3
|
|
93
93
|
|
|
94
94
|
const netIds = Object.keys(this.traceNetIslands)
|
|
95
95
|
// Compare each pair of different nets
|