@tscircuit/schematic-trace-solver 0.0.6 → 0.0.8

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.js CHANGED
@@ -7150,9 +7150,292 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
7150
7150
  }
7151
7151
  };
7152
7152
 
7153
- // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts
7153
+ // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts
7154
7154
  var NET_LABEL_HORIZONTAL_WIDTH = 0.4;
7155
7155
  var NET_LABEL_HORIZONTAL_HEIGHT = 0.2;
7156
+ function getDimsForOrientation(orientation) {
7157
+ if (orientation === "y+" || orientation === "y-") {
7158
+ return {
7159
+ width: NET_LABEL_HORIZONTAL_HEIGHT,
7160
+ height: NET_LABEL_HORIZONTAL_WIDTH
7161
+ };
7162
+ }
7163
+ return {
7164
+ width: NET_LABEL_HORIZONTAL_WIDTH,
7165
+ height: NET_LABEL_HORIZONTAL_HEIGHT
7166
+ };
7167
+ }
7168
+ function getCenterFromAnchor(anchor, orientation, width, height) {
7169
+ switch (orientation) {
7170
+ case "x+":
7171
+ return { x: anchor.x + width / 2, y: anchor.y };
7172
+ case "x-":
7173
+ return { x: anchor.x - width / 2, y: anchor.y };
7174
+ case "y+":
7175
+ return { x: anchor.x, y: anchor.y + height / 2 };
7176
+ case "y-":
7177
+ return { x: anchor.x, y: anchor.y - height / 2 };
7178
+ }
7179
+ }
7180
+ function getRectBounds(center, w, h) {
7181
+ return {
7182
+ minX: center.x - w / 2,
7183
+ minY: center.y - h / 2,
7184
+ maxX: center.x + w / 2,
7185
+ maxY: center.y + h / 2
7186
+ };
7187
+ }
7188
+
7189
+ // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts
7190
+ function segmentIntersectsRect(p1, p2, rect, EPS = 1e-9) {
7191
+ const isVert = Math.abs(p1.x - p2.x) < EPS;
7192
+ const isHorz = Math.abs(p1.y - p2.y) < EPS;
7193
+ if (!isVert && !isHorz) return false;
7194
+ if (isVert) {
7195
+ const x = p1.x;
7196
+ if (x < rect.minX - EPS || x > rect.maxX + EPS) return false;
7197
+ const segMinY = Math.min(p1.y, p2.y);
7198
+ const segMaxY = Math.max(p1.y, p2.y);
7199
+ const overlap = Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY);
7200
+ return overlap > EPS;
7201
+ } else {
7202
+ const y = p1.y;
7203
+ if (y < rect.minY - EPS || y > rect.maxY + EPS) return false;
7204
+ const segMinX = Math.min(p1.x, p2.x);
7205
+ const segMaxX = Math.max(p1.x, p2.x);
7206
+ const overlap = Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX);
7207
+ return overlap > EPS;
7208
+ }
7209
+ }
7210
+ function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex) {
7211
+ for (const [pairId, solved] of Object.entries(inputTraceMap)) {
7212
+ const pts = solved.tracePath;
7213
+ for (let i = 0; i < pts.length - 1; i++) {
7214
+ if (pairId === hostPathId && i === hostSegIndex) continue;
7215
+ if (segmentIntersectsRect(pts[i], pts[i + 1], bounds)) return true;
7216
+ }
7217
+ }
7218
+ return false;
7219
+ }
7220
+
7221
+ // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/host.ts
7222
+ function lengthOfTrace(path) {
7223
+ let sum = 0;
7224
+ const pts = path.tracePath;
7225
+ for (let i = 0; i < pts.length - 1; i++) {
7226
+ sum += Math.abs(pts[i + 1].x - pts[i].x) + Math.abs(pts[i + 1].y - pts[i].y);
7227
+ }
7228
+ return sum;
7229
+ }
7230
+ function chooseHostTraceForGroup(params) {
7231
+ const { inputProblem, inputTraceMap, globalConnNetId, fallbackTrace } = params;
7232
+ const chipsById = Object.fromEntries(
7233
+ inputProblem.chips.map((c) => [c.chipId, c])
7234
+ );
7235
+ const groupTraces = Object.values(inputTraceMap).filter(
7236
+ (t) => t.globalConnNetId === globalConnNetId
7237
+ );
7238
+ const chipIdsInGroup = /* @__PURE__ */ new Set();
7239
+ for (const t of groupTraces) {
7240
+ chipIdsInGroup.add(t.pins[0].chipId);
7241
+ chipIdsInGroup.add(t.pins[1].chipId);
7242
+ }
7243
+ let largestChipId = null;
7244
+ let largestPinCount = -1;
7245
+ for (const id of chipIdsInGroup) {
7246
+ const chip = chipsById[id];
7247
+ const count = chip?.pins?.length ?? 0;
7248
+ if (count > largestPinCount) {
7249
+ largestPinCount = count;
7250
+ largestChipId = id;
7251
+ }
7252
+ }
7253
+ const hostCandidates = largestChipId == null ? [] : groupTraces.filter(
7254
+ (t) => t.pins[0].chipId === largestChipId || t.pins[1].chipId === largestChipId
7255
+ );
7256
+ if (hostCandidates.length > 0) {
7257
+ return hostCandidates.reduce(
7258
+ (a, b) => lengthOfTrace(a) >= lengthOfTrace(b) ? a : b
7259
+ );
7260
+ }
7261
+ return fallbackTrace;
7262
+ }
7263
+
7264
+ // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/anchors.ts
7265
+ function anchorsForSegment(a, b) {
7266
+ return [
7267
+ { x: a.x, y: a.y },
7268
+ { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 },
7269
+ { x: b.x, y: b.y }
7270
+ ];
7271
+ }
7272
+
7273
+ // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts
7274
+ function solveNetLabelPlacementForPortOnlyPin(params) {
7275
+ const {
7276
+ inputProblem,
7277
+ inputTraceMap,
7278
+ chipObstacleSpatialIndex,
7279
+ overlappingSameNetTraceGroup,
7280
+ availableOrientations
7281
+ } = params;
7282
+ const pinId = overlappingSameNetTraceGroup.portOnlyPinId;
7283
+ if (!pinId) {
7284
+ return {
7285
+ placement: null,
7286
+ testedCandidates: [],
7287
+ error: "No portOnlyPinId provided"
7288
+ };
7289
+ }
7290
+ let pin = null;
7291
+ for (const chip of inputProblem.chips) {
7292
+ const p = chip.pins.find((pp) => pp.pinId === pinId);
7293
+ if (p) {
7294
+ pin = { x: p.x, y: p.y };
7295
+ break;
7296
+ }
7297
+ }
7298
+ if (!pin) {
7299
+ return {
7300
+ placement: null,
7301
+ testedCandidates: [],
7302
+ error: `Port-only pin not found: ${pinId}`
7303
+ };
7304
+ }
7305
+ const orientations = availableOrientations.length > 0 ? availableOrientations : ["x+", "x-", "y+", "y-"];
7306
+ const anchor = { x: pin.x, y: pin.y };
7307
+ const outwardOf = (o) => o === "x+" ? { x: 1, y: 0 } : o === "x-" ? { x: -1, y: 0 } : o === "y+" ? { x: 0, y: 1 } : { x: 0, y: -1 };
7308
+ const testedCandidates = [];
7309
+ for (const orientation of orientations) {
7310
+ const { width, height } = getDimsForOrientation(orientation);
7311
+ const baseCenter = getCenterFromAnchor(anchor, orientation, width, height);
7312
+ const outward = outwardOf(orientation);
7313
+ const offset = 1e-3;
7314
+ const center = {
7315
+ x: baseCenter.x + outward.x * offset,
7316
+ y: baseCenter.y + outward.y * offset
7317
+ };
7318
+ const bounds = getRectBounds(center, width, height);
7319
+ const chips = chipObstacleSpatialIndex.getChipsInBounds(bounds);
7320
+ if (chips.length > 0) {
7321
+ testedCandidates.push({
7322
+ center,
7323
+ width,
7324
+ height,
7325
+ bounds,
7326
+ anchor,
7327
+ orientation,
7328
+ status: "chip-collision",
7329
+ hostSegIndex: -1
7330
+ });
7331
+ continue;
7332
+ }
7333
+ if (rectIntersectsAnyTrace(
7334
+ bounds,
7335
+ inputTraceMap,
7336
+ "",
7337
+ -1
7338
+ )) {
7339
+ testedCandidates.push({
7340
+ center,
7341
+ width,
7342
+ height,
7343
+ bounds,
7344
+ anchor,
7345
+ orientation,
7346
+ status: "trace-collision",
7347
+ hostSegIndex: -1
7348
+ });
7349
+ continue;
7350
+ }
7351
+ testedCandidates.push({
7352
+ center,
7353
+ width,
7354
+ height,
7355
+ bounds,
7356
+ anchor,
7357
+ orientation,
7358
+ status: "ok",
7359
+ hostSegIndex: -1
7360
+ });
7361
+ const placement = {
7362
+ globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
7363
+ dcConnNetId: void 0,
7364
+ orientation,
7365
+ anchorPoint: anchor,
7366
+ width,
7367
+ height,
7368
+ center
7369
+ };
7370
+ return { placement, testedCandidates };
7371
+ }
7372
+ return {
7373
+ placement: null,
7374
+ testedCandidates,
7375
+ error: "Could not place net label at port without collisions"
7376
+ };
7377
+ }
7378
+
7379
+ // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts
7380
+ function visualizeSingleNetLabelPlacementSolver(solver) {
7381
+ const graphics = visualizeInputProblem(solver.inputProblem);
7382
+ const groupId = solver.overlappingSameNetTraceGroup.globalConnNetId;
7383
+ const host = chooseHostTraceForGroup({
7384
+ inputProblem: solver.inputProblem,
7385
+ inputTraceMap: solver.inputTraceMap,
7386
+ globalConnNetId: groupId,
7387
+ fallbackTrace: solver.overlappingSameNetTraceGroup.overlappingTraces
7388
+ });
7389
+ const groupStroke = getColorFromString(groupId, 0.9);
7390
+ const groupFill = getColorFromString(groupId, 0.5);
7391
+ for (const trace of Object.values(solver.inputTraceMap)) {
7392
+ if (trace.globalConnNetId !== groupId) continue;
7393
+ const isHost = host ? trace.mspPairId === host.mspPairId : false;
7394
+ graphics.lines.push({
7395
+ points: trace.tracePath,
7396
+ strokeColor: isHost ? groupStroke : groupFill,
7397
+ strokeWidth: isHost ? 6e-3 : 3e-3,
7398
+ strokeDash: isHost ? void 0 : "4 2"
7399
+ });
7400
+ }
7401
+ for (const c of solver.testedCandidates) {
7402
+ const fill = c.status === "ok" ? "rgba(0, 180, 0, 0.25)" : c.status === "chip-collision" ? "rgba(220, 0, 0, 0.25)" : c.status === "trace-collision" ? "rgba(220, 140, 0, 0.25)" : "rgba(120, 120, 120, 0.15)";
7403
+ const stroke = c.status === "ok" ? "green" : c.status === "chip-collision" ? "red" : c.status === "trace-collision" ? "orange" : "gray";
7404
+ graphics.rects.push({
7405
+ center: {
7406
+ x: (c.bounds.minX + c.bounds.maxX) / 2,
7407
+ y: (c.bounds.minY + c.bounds.maxY) / 2
7408
+ },
7409
+ width: c.width,
7410
+ height: c.height,
7411
+ fill,
7412
+ strokeColor: stroke
7413
+ });
7414
+ graphics.points.push({
7415
+ x: c.anchor.x,
7416
+ y: c.anchor.y,
7417
+ color: stroke
7418
+ });
7419
+ }
7420
+ if (solver.netLabelPlacement) {
7421
+ const p = solver.netLabelPlacement;
7422
+ graphics.rects.push({
7423
+ center: p.center,
7424
+ width: p.width,
7425
+ height: p.height,
7426
+ fill: "rgba(0, 128, 255, 0.35)",
7427
+ strokeColor: "blue"
7428
+ });
7429
+ graphics.points.push({
7430
+ x: p.anchorPoint.x,
7431
+ y: p.anchorPoint.y,
7432
+ color: "blue"
7433
+ });
7434
+ }
7435
+ return graphics;
7436
+ }
7437
+
7438
+ // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts
7156
7439
  var SingleNetLabelPlacementSolver = class extends BaseSolver {
7157
7440
  inputProblem;
7158
7441
  inputTraceMap;
@@ -7169,260 +7452,127 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
7169
7452
  this.availableOrientations = params.availableOrientations;
7170
7453
  this.chipObstacleSpatialIndex = params.inputProblem._chipObstacleSpatialIndex ?? new ChipObstacleSpatialIndex(params.inputProblem.chips);
7171
7454
  }
7172
- getDimsForOrientation(orientation) {
7173
- if (orientation === "y+" || orientation === "y-") {
7174
- return {
7175
- width: NET_LABEL_HORIZONTAL_HEIGHT,
7176
- height: NET_LABEL_HORIZONTAL_WIDTH
7177
- };
7178
- }
7179
- return {
7180
- width: NET_LABEL_HORIZONTAL_WIDTH,
7181
- height: NET_LABEL_HORIZONTAL_HEIGHT
7182
- };
7183
- }
7184
- getCenterFromAnchor(anchor, orientation, width, height) {
7185
- switch (orientation) {
7186
- case "x+":
7187
- return { x: anchor.x + width / 2, y: anchor.y };
7188
- case "x-":
7189
- return { x: anchor.x - width / 2, y: anchor.y };
7190
- case "y+":
7191
- return { x: anchor.x, y: anchor.y + height / 2 };
7192
- case "y-":
7193
- return { x: anchor.x, y: anchor.y - height / 2 };
7194
- }
7195
- }
7196
- getRectBounds(center, w, h) {
7197
- return {
7198
- minX: center.x - w / 2,
7199
- minY: center.y - h / 2,
7200
- maxX: center.x + w / 2,
7201
- maxY: center.y + h / 2
7202
- };
7203
- }
7204
- segmentIntersectsRect(p1, p2, rect, EPS = 1e-9) {
7205
- const isVert = Math.abs(p1.x - p2.x) < EPS;
7206
- const isHorz = Math.abs(p1.y - p2.y) < EPS;
7207
- if (!isVert && !isHorz) return false;
7208
- if (isVert) {
7209
- const x = p1.x;
7210
- if (x < rect.minX - EPS || x > rect.maxX + EPS) return false;
7211
- const segMinY = Math.min(p1.y, p2.y);
7212
- const segMaxY = Math.max(p1.y, p2.y);
7213
- const overlap = Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY);
7214
- return overlap > EPS;
7215
- } else {
7216
- const y = p1.y;
7217
- if (y < rect.minY - EPS || y > rect.maxY + EPS) return false;
7218
- const segMinX = Math.min(p1.x, p2.x);
7219
- const segMaxX = Math.max(p1.x, p2.x);
7220
- const overlap = Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX);
7221
- return overlap > EPS;
7222
- }
7223
- }
7224
- rectIntersectsAnyTrace(bounds, hostPathId, hostSegIndex) {
7225
- for (const [pairId, solved] of Object.entries(this.inputTraceMap)) {
7226
- const pts = solved.tracePath;
7227
- for (let i = 0; i < pts.length - 1; i++) {
7228
- if (pairId === hostPathId && i === hostSegIndex) continue;
7229
- if (this.segmentIntersectsRect(pts[i], pts[i + 1], bounds))
7230
- return true;
7231
- }
7232
- }
7233
- return false;
7234
- }
7235
7455
  _step() {
7236
7456
  if (this.netLabelPlacement) {
7237
7457
  this.solved = true;
7238
7458
  return;
7239
7459
  }
7240
7460
  if (this.overlappingSameNetTraceGroup.portOnlyPinId) {
7241
- const pinId = this.overlappingSameNetTraceGroup.portOnlyPinId;
7242
- let pin = null;
7243
- for (const chip of this.inputProblem.chips) {
7244
- const p = chip.pins.find((pp) => pp.pinId === pinId);
7245
- if (p) {
7246
- pin = { x: p.x, y: p.y };
7247
- break;
7248
- }
7249
- }
7250
- if (!pin) {
7251
- this.failed = true;
7252
- this.error = `Port-only pin not found: ${pinId}`;
7253
- return;
7254
- }
7255
- const orientations2 = this.availableOrientations.length > 0 ? this.availableOrientations : ["x+", "x-", "y+", "y-"];
7256
- const anchor = { x: pin.x, y: pin.y };
7257
- const outwardOf = (o) => o === "x+" ? { x: 1, y: 0 } : o === "x-" ? { x: -1, y: 0 } : o === "y+" ? { x: 0, y: 1 } : { x: 0, y: -1 };
7258
- for (const orientation of orientations2) {
7259
- const { width, height } = this.getDimsForOrientation(orientation);
7260
- const baseCenter = this.getCenterFromAnchor(
7261
- anchor,
7262
- orientation,
7263
- width,
7264
- height
7265
- );
7266
- const outward = outwardOf(orientation);
7267
- const offset = 1e-3;
7268
- const center = {
7269
- x: baseCenter.x + outward.x * offset,
7270
- y: baseCenter.y + outward.y * offset
7271
- };
7272
- const bounds = this.getRectBounds(center, width, height);
7273
- const chips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds);
7274
- if (chips.length > 0) {
7275
- this.testedCandidates.push({
7276
- center,
7277
- width,
7278
- height,
7279
- bounds,
7280
- anchor,
7281
- orientation,
7282
- status: "chip-collision",
7283
- hostSegIndex: -1
7284
- });
7285
- continue;
7286
- }
7287
- if (this.rectIntersectsAnyTrace(bounds, "", -1)) {
7288
- this.testedCandidates.push({
7289
- center,
7290
- width,
7291
- height,
7292
- bounds,
7293
- anchor,
7294
- orientation,
7295
- status: "trace-collision",
7296
- hostSegIndex: -1
7297
- });
7298
- continue;
7299
- }
7300
- this.testedCandidates.push({
7301
- center,
7302
- width,
7303
- height,
7304
- bounds,
7305
- anchor,
7306
- orientation,
7307
- status: "ok",
7308
- hostSegIndex: -1
7309
- });
7310
- this.netLabelPlacement = {
7311
- globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
7312
- dcConnNetId: void 0,
7313
- orientation,
7314
- anchorPoint: anchor,
7315
- width,
7316
- height,
7317
- center
7318
- };
7461
+ const res = solveNetLabelPlacementForPortOnlyPin({
7462
+ inputProblem: this.inputProblem,
7463
+ inputTraceMap: this.inputTraceMap,
7464
+ chipObstacleSpatialIndex: this.chipObstacleSpatialIndex,
7465
+ overlappingSameNetTraceGroup: this.overlappingSameNetTraceGroup,
7466
+ availableOrientations: this.availableOrientations
7467
+ });
7468
+ this.testedCandidates.push(...res.testedCandidates);
7469
+ if (res.placement) {
7470
+ this.netLabelPlacement = res.placement;
7319
7471
  this.solved = true;
7320
7472
  return;
7321
7473
  }
7322
7474
  this.failed = true;
7323
- this.error = "Could not place net label at port without collisions";
7475
+ this.error = res.error ?? "Could not place net label at port without collisions";
7324
7476
  return;
7325
7477
  }
7326
7478
  const groupId = this.overlappingSameNetTraceGroup.globalConnNetId;
7327
- const chipsById = Object.fromEntries(
7328
- this.inputProblem.chips.map((c) => [c.chipId, c])
7329
- );
7330
- const groupTraces = Object.values(this.inputTraceMap).filter(
7331
- (t) => t.globalConnNetId === groupId
7332
- );
7333
- const chipIdsInGroup = /* @__PURE__ */ new Set();
7334
- for (const t of groupTraces) {
7335
- chipIdsInGroup.add(t.pins[0].chipId);
7336
- chipIdsInGroup.add(t.pins[1].chipId);
7337
- }
7338
- let largestChipId = null;
7339
- let largestPinCount = -1;
7340
- for (const id of chipIdsInGroup) {
7341
- const chip = chipsById[id];
7342
- const count = chip?.pins?.length ?? 0;
7343
- if (count > largestPinCount) {
7344
- largestPinCount = count;
7345
- largestChipId = id;
7346
- }
7347
- }
7348
- const lengthOf = (path) => {
7349
- let sum = 0;
7350
- const pts2 = path.tracePath;
7351
- for (let i = 0; i < pts2.length - 1; i++) {
7352
- sum += Math.abs(pts2[i + 1].x - pts2[i].x) + Math.abs(pts2[i + 1].y - pts2[i].y);
7353
- }
7354
- return sum;
7355
- };
7356
- const hostCandidates = largestChipId == null ? [] : groupTraces.filter(
7357
- (t) => t.pins[0].chipId === largestChipId || t.pins[1].chipId === largestChipId
7358
- );
7359
- let host = hostCandidates.length > 0 ? hostCandidates.reduce((a, b) => lengthOf(a) >= lengthOf(b) ? a : b) : this.overlappingSameNetTraceGroup.overlappingTraces;
7479
+ let host = chooseHostTraceForGroup({
7480
+ inputProblem: this.inputProblem,
7481
+ inputTraceMap: this.inputTraceMap,
7482
+ globalConnNetId: groupId,
7483
+ fallbackTrace: this.overlappingSameNetTraceGroup.overlappingTraces
7484
+ });
7360
7485
  if (!host) {
7361
7486
  this.failed = true;
7362
7487
  this.error = "No host trace found for net label placement";
7363
7488
  return;
7364
7489
  }
7365
- let pts = host.tracePath.slice();
7366
- if (largestChipId && host) {
7367
- const largePin = host.pins[0].chipId === largestChipId ? host.pins[0] : host.pins[1];
7368
- const d0 = Math.abs(pts[0].x - largePin.x) + Math.abs(pts[0].y - largePin.y);
7369
- const dL = Math.abs(pts[pts.length - 1].x - largePin.x) + Math.abs(pts[pts.length - 1].y - largePin.y);
7370
- if (d0 > dL) {
7371
- pts = pts.slice().reverse();
7372
- }
7373
- }
7490
+ const tracesToScanBase = Object.values(this.inputTraceMap).filter(
7491
+ (t) => t.globalConnNetId === groupId
7492
+ );
7493
+ const tracesToScan = this.availableOrientations.length === 1 ? [
7494
+ host,
7495
+ ...tracesToScanBase.filter((t) => t.mspPairId !== host.mspPairId)
7496
+ ] : [host];
7374
7497
  const orientations = this.availableOrientations.length > 0 ? this.availableOrientations : ["x+", "x-", "y+", "y-"];
7498
+ const singleOrientationMode = this.availableOrientations.length === 1;
7499
+ const scoreFor = (orientation, anchor) => {
7500
+ switch (orientation) {
7501
+ case "y+":
7502
+ return anchor.y;
7503
+ case "y-":
7504
+ return -anchor.y;
7505
+ case "x+":
7506
+ return anchor.x;
7507
+ case "x-":
7508
+ return -anchor.x;
7509
+ }
7510
+ };
7511
+ let bestCandidate = null;
7512
+ let bestScore = -Infinity;
7375
7513
  const EPS = 1e-6;
7376
- const anchorsForSegment = (a, b) => {
7377
- return [
7378
- { x: a.x, y: a.y },
7379
- { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 },
7380
- { x: b.x, y: b.y }
7381
- ];
7382
- };
7383
- for (let si = 0; si < pts.length - 1; si++) {
7384
- const a = pts[si];
7385
- const b = pts[si + 1];
7386
- const isH = Math.abs(a.y - b.y) < EPS;
7387
- const isV = Math.abs(a.x - b.x) < EPS;
7388
- if (!isH && !isV) continue;
7389
- const segmentAllowed = isH ? ["y+", "y-"] : ["x+", "x-"];
7390
- const candidateOrients = orientations.filter(
7391
- (o) => segmentAllowed.includes(o)
7392
- );
7393
- if (candidateOrients.length === 0) continue;
7394
- const anchors = anchorsForSegment(a, b);
7395
- for (const anchor of anchors) {
7396
- for (const orientation of candidateOrients) {
7397
- const { width, height } = this.getDimsForOrientation(orientation);
7398
- const center = this.getCenterFromAnchor(
7399
- anchor,
7400
- orientation,
7401
- width,
7402
- height
7403
- );
7404
- const outward = orientation === "x+" ? { x: 1, y: 0 } : orientation === "x-" ? { x: -1, y: 0 } : orientation === "y+" ? { x: 0, y: 1 } : { x: 0, y: -1 };
7405
- const offset = 1e-4;
7406
- const testCenter = {
7407
- x: center.x + outward.x * offset,
7408
- y: center.y + outward.y * offset
7409
- };
7410
- const bounds = this.getRectBounds(testCenter, width, height);
7411
- const chips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds);
7412
- if (chips.length > 0) {
7413
- this.testedCandidates.push({
7414
- center: testCenter,
7415
- width,
7416
- height,
7417
- bounds,
7514
+ for (const curr of tracesToScan) {
7515
+ const pts = curr.tracePath.slice();
7516
+ for (let si = 0; si < pts.length - 1; si++) {
7517
+ const a = pts[si];
7518
+ const b = pts[si + 1];
7519
+ const isH = Math.abs(a.y - b.y) < EPS;
7520
+ const isV = Math.abs(a.x - b.x) < EPS;
7521
+ if (!isH && !isV) continue;
7522
+ const segmentAllowed = isH ? ["y+", "y-"] : ["x+", "x-"];
7523
+ const candidateOrients = orientations.filter(
7524
+ (o) => segmentAllowed.includes(o)
7525
+ );
7526
+ if (candidateOrients.length === 0) continue;
7527
+ const anchors = anchorsForSegment(a, b);
7528
+ for (const anchor of anchors) {
7529
+ for (const orientation of candidateOrients) {
7530
+ const { width, height } = getDimsForOrientation(orientation);
7531
+ const center = getCenterFromAnchor(
7418
7532
  anchor,
7419
7533
  orientation,
7420
- status: "chip-collision",
7421
- hostSegIndex: si
7422
- });
7423
- continue;
7424
- }
7425
- if (this.rectIntersectsAnyTrace(bounds, host.mspPairId, si)) {
7534
+ width,
7535
+ height
7536
+ );
7537
+ const outward = orientation === "x+" ? { x: 1, y: 0 } : orientation === "x-" ? { x: -1, y: 0 } : orientation === "y+" ? { x: 0, y: 1 } : { x: 0, y: -1 };
7538
+ const offset = 1e-4;
7539
+ const testCenter = {
7540
+ x: center.x + outward.x * offset,
7541
+ y: center.y + outward.y * offset
7542
+ };
7543
+ const bounds = getRectBounds(testCenter, width, height);
7544
+ const chips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds);
7545
+ if (chips.length > 0) {
7546
+ this.testedCandidates.push({
7547
+ center: testCenter,
7548
+ width,
7549
+ height,
7550
+ bounds,
7551
+ anchor,
7552
+ orientation,
7553
+ status: "chip-collision",
7554
+ hostSegIndex: si
7555
+ });
7556
+ continue;
7557
+ }
7558
+ if (rectIntersectsAnyTrace(
7559
+ bounds,
7560
+ this.inputTraceMap,
7561
+ curr.mspPairId,
7562
+ si
7563
+ )) {
7564
+ this.testedCandidates.push({
7565
+ center: testCenter,
7566
+ width,
7567
+ height,
7568
+ bounds,
7569
+ anchor,
7570
+ orientation,
7571
+ status: "trace-collision",
7572
+ hostSegIndex: si
7573
+ });
7574
+ continue;
7575
+ }
7426
7576
  this.testedCandidates.push({
7427
7577
  center: testCenter,
7428
7578
  width,
@@ -7430,121 +7580,58 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
7430
7580
  bounds,
7431
7581
  anchor,
7432
7582
  orientation,
7433
- status: "trace-collision",
7583
+ status: "ok",
7434
7584
  hostSegIndex: si
7435
7585
  });
7436
- continue;
7586
+ if (singleOrientationMode) {
7587
+ const s = scoreFor(orientation, anchor);
7588
+ if (s > bestScore + 1e-9) {
7589
+ bestScore = s;
7590
+ bestCandidate = {
7591
+ anchor,
7592
+ orientation,
7593
+ width,
7594
+ height,
7595
+ center,
7596
+ hostSegIndex: si,
7597
+ dcConnNetId: curr.dcConnNetId
7598
+ };
7599
+ }
7600
+ continue;
7601
+ }
7602
+ this.netLabelPlacement = {
7603
+ globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
7604
+ dcConnNetId: curr.dcConnNetId,
7605
+ orientation,
7606
+ anchorPoint: anchor,
7607
+ width,
7608
+ height,
7609
+ center
7610
+ };
7611
+ this.solved = true;
7612
+ return;
7437
7613
  }
7438
- this.testedCandidates.push({
7439
- center: testCenter,
7440
- width,
7441
- height,
7442
- bounds,
7443
- anchor,
7444
- orientation,
7445
- status: "ok",
7446
- hostSegIndex: si
7447
- });
7448
- this.netLabelPlacement = {
7449
- globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
7450
- dcConnNetId: host.dcConnNetId,
7451
- orientation,
7452
- anchorPoint: anchor,
7453
- width,
7454
- height,
7455
- center
7456
- };
7457
- this.solved = true;
7458
- return;
7459
7614
  }
7460
7615
  }
7461
7616
  }
7617
+ if (singleOrientationMode && bestCandidate) {
7618
+ this.netLabelPlacement = {
7619
+ globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
7620
+ dcConnNetId: bestCandidate.dcConnNetId,
7621
+ orientation: bestCandidate.orientation,
7622
+ anchorPoint: bestCandidate.anchor,
7623
+ width: bestCandidate.width,
7624
+ height: bestCandidate.height,
7625
+ center: bestCandidate.center
7626
+ };
7627
+ this.solved = true;
7628
+ return;
7629
+ }
7462
7630
  this.failed = true;
7463
7631
  this.error = "Could not place net label without collisions";
7464
7632
  }
7465
7633
  visualize() {
7466
- const graphics = visualizeInputProblem(this.inputProblem);
7467
- const groupId = this.overlappingSameNetTraceGroup.globalConnNetId;
7468
- const chipsById = Object.fromEntries(
7469
- this.inputProblem.chips.map((c) => [c.chipId, c])
7470
- );
7471
- const groupTraces = Object.values(this.inputTraceMap).filter(
7472
- (t) => t.globalConnNetId === groupId
7473
- );
7474
- const chipIdsInGroup = /* @__PURE__ */ new Set();
7475
- for (const t of groupTraces) {
7476
- chipIdsInGroup.add(t.pins[0].chipId);
7477
- chipIdsInGroup.add(t.pins[1].chipId);
7478
- }
7479
- let largestChipId = null;
7480
- let largestPinCount = -1;
7481
- for (const id of chipIdsInGroup) {
7482
- const chip = chipsById[id];
7483
- const count = chip?.pins?.length ?? 0;
7484
- if (count > largestPinCount) {
7485
- largestPinCount = count;
7486
- largestChipId = id;
7487
- }
7488
- }
7489
- const lengthOf = (path) => {
7490
- let sum = 0;
7491
- const pts = path.tracePath;
7492
- for (let i = 0; i < pts.length - 1; i++) {
7493
- sum += Math.abs(pts[i + 1].x - pts[i].x) + Math.abs(pts[i + 1].y - pts[i].y);
7494
- }
7495
- return sum;
7496
- };
7497
- const hostCandidates = largestChipId == null ? [] : groupTraces.filter(
7498
- (t) => t.pins[0].chipId === largestChipId || t.pins[1].chipId === largestChipId
7499
- );
7500
- const host = hostCandidates.length > 0 ? hostCandidates.reduce((a, b) => lengthOf(a) >= lengthOf(b) ? a : b) : this.overlappingSameNetTraceGroup.overlappingTraces;
7501
- const groupStroke = getColorFromString(groupId, 0.9);
7502
- const groupFill = getColorFromString(groupId, 0.5);
7503
- for (const trace of Object.values(this.inputTraceMap)) {
7504
- if (trace.globalConnNetId !== groupId) continue;
7505
- const isHost = host ? trace.mspPairId === host.mspPairId : false;
7506
- graphics.lines.push({
7507
- points: trace.tracePath,
7508
- strokeColor: isHost ? groupStroke : groupFill,
7509
- strokeWidth: isHost ? 6e-3 : 3e-3,
7510
- strokeDash: isHost ? void 0 : "4 2"
7511
- });
7512
- }
7513
- for (const c of this.testedCandidates) {
7514
- const fill = c.status === "ok" ? "rgba(0, 180, 0, 0.25)" : c.status === "chip-collision" ? "rgba(220, 0, 0, 0.25)" : c.status === "trace-collision" ? "rgba(220, 140, 0, 0.25)" : "rgba(120, 120, 120, 0.15)";
7515
- const stroke = c.status === "ok" ? "green" : c.status === "chip-collision" ? "red" : c.status === "trace-collision" ? "orange" : "gray";
7516
- graphics.rects.push({
7517
- center: {
7518
- x: (c.bounds.minX + c.bounds.maxX) / 2,
7519
- y: (c.bounds.minY + c.bounds.maxY) / 2
7520
- },
7521
- width: c.width,
7522
- height: c.height,
7523
- fill,
7524
- strokeColor: stroke
7525
- });
7526
- graphics.points.push({
7527
- x: c.anchor.x,
7528
- y: c.anchor.y,
7529
- color: stroke
7530
- });
7531
- }
7532
- if (this.netLabelPlacement) {
7533
- const p = this.netLabelPlacement;
7534
- graphics.rects.push({
7535
- center: p.center,
7536
- width: p.width,
7537
- height: p.height,
7538
- fill: "rgba(0, 128, 255, 0.35)",
7539
- strokeColor: "blue"
7540
- });
7541
- graphics.points.push({
7542
- x: p.anchorPoint.x,
7543
- y: p.anchorPoint.y,
7544
- color: "blue"
7545
- });
7546
- }
7547
- return graphics;
7634
+ return visualizeSingleNetLabelPlacementSolver(this);
7548
7635
  }
7549
7636
  };
7550
7637
 
@@ -7891,6 +7978,31 @@ var correctPinsInsideChips = (problem) => {
7891
7978
  }
7892
7979
  };
7893
7980
 
7981
+ // lib/solvers/SchematicTracePipelineSolver/expandChipsToFitPins.ts
7982
+ var expandChipsToFitPins = (problem) => {
7983
+ for (const chip of problem.chips) {
7984
+ const halfWidth = chip.width / 2;
7985
+ const halfHeight = chip.height / 2;
7986
+ let maxDx = 0;
7987
+ let maxDy = 0;
7988
+ for (const pin of chip.pins) {
7989
+ const dx = Math.abs(pin.x - chip.center.x);
7990
+ const dy = Math.abs(pin.y - chip.center.y);
7991
+ if (dx > maxDx) maxDx = dx;
7992
+ if (dy > maxDy) maxDy = dy;
7993
+ }
7994
+ const newHalfWidth = Math.max(halfWidth, maxDx);
7995
+ const newHalfHeight = Math.max(halfHeight, maxDy);
7996
+ if (newHalfWidth > halfWidth || newHalfHeight > halfHeight) {
7997
+ chip.width = newHalfWidth * 2;
7998
+ chip.height = newHalfHeight * 2;
7999
+ for (const pin of chip.pins) {
8000
+ pin._facingDirection = void 0;
8001
+ }
8002
+ }
8003
+ }
8004
+ };
8005
+
7894
8006
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
7895
8007
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
7896
8008
  return {
@@ -8002,6 +8114,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
8002
8114
  ...original,
8003
8115
  _chipObstacleSpatialIndex: void 0
8004
8116
  });
8117
+ expandChipsToFitPins(cloned);
8005
8118
  correctPinsInsideChips(cloned);
8006
8119
  return cloned;
8007
8120
  }