Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 84 additions & 5 deletions lib/data-structures/ChipObstacleSpatialIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,101 @@ export class ChipObstacleSpatialIndex {

doesOrthogonalLineIntersectChip(
line: [Point, Point],
margin = 0,
opts: {
excludeChipIds?: string[]
eps?: number
} = {},
): boolean {
// Fast path when no margin is applied
if (margin === 0) {
const excludeChipIds = opts.excludeChipIds ?? []
const eps = opts.eps ?? 0
const [p1, p2] = line
const { x: x1, y: y1 } = p1
const { x: x2, y: y2 } = p2

const minX = Math.min(x1, x2)
const minY = Math.min(y1, y2)
const maxX = Math.max(x1, x2)
const maxY = Math.max(y1, y2)

const chips = this.getChipsInBounds({
minX: minX - eps,
minY: minY - eps,
maxX: maxX + eps,
maxY: maxY + eps,
}).filter((chip) => !excludeChipIds.includes(chip.chipId))

const isVertical = Math.abs(x1 - x2) < eps
const isHorizontal = Math.abs(y1 - y2) < eps

for (const chip of chips) {
const {
minX: cMinX,
minY: cMinY,
maxX: cMaxX,
maxY: cMaxY,
} = chip.bounds

if (isVertical) {
const x = x1
if (x <= cMinX + eps || x >= cMaxX - eps) continue
const overlap = Math.min(maxY, cMaxY) - Math.max(minY, cMinY)
if (overlap > eps) return true
} else if (isHorizontal) {
const y = y1
if (y <= cMinY + eps || y >= cMaxY - eps) continue
const overlap = Math.min(maxX, cMaxX) - Math.max(minX, cMinX)
if (overlap > eps) return true
}
}

return false
}

const excludeChipIds = opts.excludeChipIds ?? []
const eps = opts.eps ?? 0
const [p1, p2] = line
const { x: x1, y: y1 } = p1
const { x: x2, y: y2 } = p2

const minX = Math.min(x1, x2)
const minY = Math.min(y1, y2)
const maxX = Math.max(x1, x2)
const maxY = Math.max(y1, y2)

const searchMargin = eps + Math.max(0, margin)

const chips = this.getChipsInBounds({
minX: Math.min(x1, x2),
minY: Math.min(y1, y2),
maxX: Math.max(x1, x2),
maxY: Math.max(y1, y2),
minX: minX - searchMargin,
minY: minY - searchMargin,
maxX: maxX + searchMargin,
maxY: maxY + searchMargin,
}).filter((chip) => !excludeChipIds.includes(chip.chipId))

return chips.length > 0
const isVertical = Math.abs(x1 - x2) < eps
const isHorizontal = Math.abs(y1 - y2) < eps

for (const chip of chips) {
const cMinX = chip.bounds.minX - margin
const cMinY = chip.bounds.minY - margin
const cMaxX = chip.bounds.maxX + margin
const cMaxY = chip.bounds.maxY + margin

if (isVertical) {
const x = x1
if (x <= cMinX + eps || x >= cMaxX - eps) continue
const overlap = Math.min(maxY, cMaxY) - Math.max(minY, cMinY)
if (overlap > eps) return true
} else if (isHorizontal) {
const y = y1
if (y <= cMinY + eps || y >= cMaxY - eps) continue
const overlap = Math.min(maxX, cMaxX) - Math.max(minX, cMinX)
if (overlap > eps) return true
}
}

return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,35 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
pin._facingDirection = getPinDirection(pin, chip)
}
}

const [pin1, pin2] = this.pins

// Attempt direct straight-line connection if pins are aligned
const EPS = 1e-6
const isDirectVertical = Math.abs(pin1.x - pin2.x) < EPS
const isDirectHorizontal = Math.abs(pin1.y - pin2.y) < EPS
if (isDirectVertical || isDirectHorizontal) {
const directPath: [Point, Point] = [
{ x: pin1.x, y: pin1.y },
{ x: pin2.x, y: pin2.y },
]
const excludeChipIds = Array.from(new Set(this.pins.map((p) => p.chipId)))
const intersects =
this.chipObstacleSpatialIndex.doesOrthogonalLineIntersectChip(
directPath,
-EPS,
{ excludeChipIds, eps: EPS },
)
if (!intersects) {
this.solvedTracePath = directPath
this.solved = true
this.baseElbow = directPath
this.movableSegments = []
this.allCandidatePaths = [directPath]
this.queuedCandidatePaths = [directPath]
return
}
}

this.baseElbow = calculateElbow(
{
x: pin1.x,
Expand Down Expand Up @@ -148,6 +175,8 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
// Check if this candidate path is valid
let pathIsValid = true

const COLLISION_EPS = 1e-6

for (let i = 0; i < nextCandidatePath.length - 1; i++) {
const start = nextCandidatePath[i]
const end = nextCandidatePath[i + 1]
Expand Down Expand Up @@ -253,10 +282,11 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {

if (!pathIsValid) break

const obstacleOps = { excludeChipIds }
const obstacleOps = { excludeChipIds, eps: COLLISION_EPS }
const intersects =
this.chipObstacleSpatialIndex.doesOrthogonalLineIntersectChip(
[start, end],
0,
obstacleOps,
)
if (intersects) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { pathKey, shiftSegmentOrth } from "./pathOps"

type PathKey = string

const EPS = 1e-6

export class SchematicTraceSingleLineSolver2 extends BaseSolver {
pins: MspConnectionPair["pins"]
inputProblem: InputProblem
Expand Down Expand Up @@ -61,8 +63,28 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
this.obstacles = getObstacleRects(this.inputProblem)
this.rectById = new Map(this.obstacles.map((r) => [r.chipId, r]))

// Build initial elbow path
const [pin1, pin2] = this.pins

// Attempt direct straight-line connection if pins are aligned
const isDirectVertical = Math.abs(pin1.x - pin2.x) < EPS
const isDirectHorizontal = Math.abs(pin1.y - pin2.y) < EPS
if (isDirectVertical || isDirectHorizontal) {
const directPath: Point[] = [
{ x: pin1.x, y: pin1.y },
{ x: pin2.x, y: pin2.y },
]
const collision = findFirstCollision(directPath, this.obstacles, {
eps: EPS,
})
if (!collision) {
this.solvedTracePath = directPath
this.solved = true
this.baseElbow = directPath
this.aabb = aabbFromPoints(directPath[0]!, directPath[1]!)
return
}
}
// Build initial elbow path
this.baseElbow = calculateElbow(
{
x: pin1.x,
Expand Down Expand Up @@ -129,7 +151,7 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {

const { path, collisionChipIds } = state

const collision = findFirstCollision(path, this.obstacles)
const collision = findFirstCollision(path, this.obstacles, { eps: EPS })

if (!collision) {
this.solvedTracePath = path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ export const segmentIntersectsRect = (

if (vert) {
const x = a.x
if (x < r.minX - eps || x > r.maxX + eps) return false
if (x <= r.minX + eps || x >= r.maxX - eps) return false
const segMinY = Math.min(a.y, b.y)
const segMaxY = Math.max(a.y, b.y)
const overlap = Math.min(segMaxY, r.maxY) - Math.max(segMinY, r.minY)
return overlap > eps
} else {
const y = a.y
if (y < r.minY - eps || y > r.maxY + eps) return false
if (y <= r.minY + eps || y >= r.maxY - eps) return false
const segMinX = Math.min(a.x, b.x)
const segMaxX = Math.max(a.x, b.x)
const overlap = Math.min(segMaxX, r.maxX) - Math.max(segMinX, r.minX)
Expand All @@ -40,15 +40,17 @@ export const findFirstCollision = (
rects: ChipWithBounds[],
opts: {
excludeRectIdsForSegment?: (segIndex: number) => Set<string>
eps?: number
} = {},
): { segIndex: number; rect: ChipWithBounds } | null => {
const eps = opts.eps ?? EPS
for (let i = 0; i < pts.length - 1; i++) {
const a = pts[i]!
const b = pts[i + 1]!
const excluded = opts.excludeRectIdsForSegment?.(i) ?? new Set<string>()
for (const r of rects) {
if (excluded.has(r.chipId)) continue
if (segmentIntersectsRect(a, b, r)) {
if (segmentIntersectsRect(a, b, r, eps)) {
return { segIndex: i, rect: r }
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { SchematicTraceSingleLineSolver } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver"
import { test, expect } from "bun:test"

test("SchematicTraceSingleLineSolver traces direct line when scraping chip edge", () => {
const chipA = {
chipId: "A",
center: { x: 0, y: 0 },
width: 1,
height: 1,
pins: [{ pinId: "A.1", x: 0.5, y: 0 }],
}
const chipB = {
chipId: "B",
center: { x: 3, y: 0 },
width: 1,
height: 1,
pins: [{ pinId: "B.1", x: 2.5, y: 0 }],
}
const chipC = {
chipId: "C",
center: { x: 1.5, y: 0.5 },
width: 1,
height: 1,
pins: [],
}

const inputProblem = {
chips: [chipA, chipB, chipC],
directConnections: [],
netConnections: [],
availableNetLabelOrientations: {},
}

const solver = new SchematicTraceSingleLineSolver({
pins: [
{ pinId: "A.1", x: 0.5, y: 0, chipId: "A" },
{ pinId: "B.1", x: 2.5, y: 0, chipId: "B" },
],
guidelines: [],
inputProblem: inputProblem as any,
chipMap: { A: chipA, B: chipB, C: chipC },
})

solver.solve()

expect(solver.solved).toBe(true)
expect(solver.solvedTracePath).toEqual([
{ x: 0.5, y: 0 },
{ x: 2.5, y: 0 },
])
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
import { test, expect } from "bun:test"

test("SchematicTraceSingleLineSolver2 traces direct horizontal line when possible", () => {
const chipA = {
chipId: "A",
center: { x: 0, y: 0 },
width: 1,
height: 1,
pins: [
{
pinId: "A.1",
x: 0.5,
y: 0,
},
],
}
const chipB = {
chipId: "B",
center: { x: 3, y: 0 },
width: 1,
height: 1,
pins: [
{
pinId: "B.1",
x: 2.5,
y: 0,
},
],
}
const chipC = {
chipId: "C",
center: { x: 1.5, y: 0.5 },
width: 1,
height: 1,
pins: [],
}

const input = {
chipMap: {
A: chipA,
B: chipB,
C: chipC,
},
pins: [
{ pinId: "A.1", x: 0.5, y: 0, chipId: "A" },
{ pinId: "B.1", x: 2.5, y: 0, chipId: "B" },
],
inputProblem: {
chips: [chipA, chipB, chipC],
},
}

const solver = new SchematicTraceSingleLineSolver2(input as any)
solver.solve()

expect(solver.solved).toBe(true)
expect(solver.solvedTracePath).toEqual([
{ x: 0.5, y: 0 },
{ x: 2.5, y: 0 },
])
})
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading