Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: tscircuit/core
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.0.357
Choose a base ref
...
head repository: tscircuit/core
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.0.358
Choose a head ref
  • 2 commits
  • 7 files changed
  • 2 contributors

Commits on Mar 23, 2025

  1. v0.0.357

    actions-user committed Mar 23, 2025
    Copy the full SHA
    5714fa1 View commit details

Commits on Mar 25, 2025

  1. DRC Implementation for trace margin, emit DRC violations as pcb trace…

    … errors (#716)
    
    * add deps
    
    * checkpoint, tentative drc implementation
    
    * drc detection
    
    * wip start over to allow autorouter def
    
    * make it easier to create test autorouters
    seveibar authored Mar 25, 2025
    Copy the full SHA
    87ac7bd View commit details
16 changes: 14 additions & 2 deletions bun.lock
Original file line number Diff line number Diff line change
@@ -6,8 +6,10 @@
"dependencies": {
"@lume/kiwi": "^0.4.3",
"@tscircuit/capacity-autorouter": "^0.0.42",
"@tscircuit/checks": "^0.0.30",
"@tscircuit/circuit-json-util": "^0.0.45",
"@tscircuit/infgrid-ijump-astar": "^0.0.33",
"@tscircuit/math-utils": "^0.0.9",
"@tscircuit/math-utils": "^0.0.12",
"@tscircuit/props": "^0.0.163",
"@tscircuit/schematic-autolayout": "^0.0.6",
"@tscircuit/soup-util": "^0.0.41",
@@ -211,6 +213,10 @@

"@tscircuit/capacity-autorouter": ["@tscircuit/capacity-autorouter@0.0.42", "", { "peerDependencies": { "typescript": "^5.7.3" } }, "sha512-bvqqpIbwFwSb0f+5nCOc7JwY0eM3nfv214Q2G2iGa9okuxUBP3ydRnjWLbkMO7aYz7AXFgaKcxH89KJZtf7NWQ=="],

"@tscircuit/checks": ["@tscircuit/checks@0.0.30", "", { "dependencies": { "@tscircuit/math-utils": "^0.0.12", "circuit-json-to-connectivity-map": "^0.0.19" }, "peerDependencies": { "circuit-json": "*", "typescript": "^5.5.3" } }, "sha512-2kB/XDSIWlJCfQqqHzGgStLWsaBEqPIyP5PyLHh79OUNswQPFglHHoYtNsOmcuGR/JpQni6TXg9Ngp/BBdNGIA=="],

"@tscircuit/circuit-json-util": ["@tscircuit/circuit-json-util@0.0.45", "", { "dependencies": { "parsel-js": "^1.1.2" }, "peerDependencies": { "circuit-json": "*", "transformation-matrix": "*", "zod": "*" } }, "sha512-zIcI5Fp1UllIm/JsjJsXhmgRDYReDUddJtylh5PZnkRK3ZVkMj+HV34A39qGHeYDg3bhf/89OQoxz+1fL68jug=="],

"@tscircuit/core": ["@tscircuit/core@0.0.325", "", { "dependencies": { "@lume/kiwi": "^0.4.3", "@tscircuit/footprinter": "^0.0.135", "@tscircuit/infgrid-ijump-astar": "^0.0.33", "@tscircuit/math-utils": "^0.0.9", "@tscircuit/props": "^0.0.152", "@tscircuit/schematic-autolayout": "^0.0.6", "@tscircuit/soup-util": "^0.0.41", "circuit-json": "^0.0.139", "circuit-json-to-connectivity-map": "^0.0.17", "format-si-unit": "^0.0.3", "nanoid": "^5.0.7", "performance-now": "^2.1.0", "react-reconciler": "^0.31.0", "react-reconciler-18": "npm:react-reconciler@0.29.2", "schematic-symbols": "^0.0.121", "transformation-matrix": "^2.16.1", "zod": "^3.23.8" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-rhnPwYyEQxkewis/7W+YC+mXwndras6fyFh3syHfaW5Z8zTC3njbXiGkFPWtExrWAV2+AG0g/uL6NfQ+eDCA8Q=="],

"@tscircuit/footprinter": ["@tscircuit/footprinter@0.0.140", "", { "dependencies": { "@tscircuit/mm": "^0.0.8", "zod": "^3.23.8" }, "peerDependencies": { "circuit-json": "*" } }, "sha512-M7Tvbp8IAt1JpWRUZr2OS5rRMZxY0stk1frhR80CYQPsyWzwifh9pgdNKOqApCJ0MPmy4rmPey5BVGtAZ+gIDw=="],
@@ -225,7 +231,7 @@

"@tscircuit/manual-edit-events": ["@tscircuit/manual-edit-events@0.0.6", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-PLgy+/Dsw1YcnNVNqfieNGTNIaRKRJ70Jt2LcSMljwaBOtsiOwtdzj24xCYO9XzJUZc7opKytShMlx863PehTQ=="],

"@tscircuit/math-utils": ["@tscircuit/math-utils@0.0.9", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-sPzfXndijet8z29X6f5vnSZddiso2tRg7m6rB+268bVj60mxnxUMD14rKuMlLn6n84fMOpD/X7pRTZUfi6M+Tg=="],
"@tscircuit/math-utils": ["@tscircuit/math-utils@0.0.12", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-pYk39tdEdgyaoT2kHd+1QKVVfztOKVn+BoyxTH9HREWaPsf3C90VkY2blRUKS26VcEzvcbxKlURW0JGkUgqlOg=="],

"@tscircuit/mm": ["@tscircuit/mm@0.0.8", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-nl7nxE7AhARbKuobflI0LUzoir7+wJyvwfPw6bzA/O0Q3YTcH3vBkU/Of+V/fp6ht+AofiCXj7YAH9E446138Q=="],

@@ -1079,8 +1085,12 @@

"@octokit/request-error/@octokit/types": ["@octokit/types@13.8.0", "", { "dependencies": { "@octokit/openapi-types": "^23.0.1" } }, "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A=="],

"@tscircuit/checks/circuit-json-to-connectivity-map": ["circuit-json-to-connectivity-map@0.0.19", "", { "dependencies": { "@tscircuit/math-utils": "^0.0.9" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-EJgeuBjCCvlKC+CjnR+yKppSi+/027x3uvXr0PR77iofR629BnVDgWv/0wKLJU7YlklcbpyUvOPyPCE1k2+WQQ=="],

"@tscircuit/core/@tscircuit/footprinter": ["@tscircuit/footprinter@0.0.135", "", { "dependencies": { "@tscircuit/mm": "^0.0.8", "zod": "^3.23.8" }, "peerDependencies": { "circuit-json": "*" } }, "sha512-+8MasMGTNQL/NINnlWk2v6zw/NDerfdIBG3uqhrTr0r7VOHh4lK1ymTkQR7IeoypkxTIHYGB9wHbOt39XAF3mg=="],

"@tscircuit/core/@tscircuit/math-utils": ["@tscircuit/math-utils@0.0.9", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-sPzfXndijet8z29X6f5vnSZddiso2tRg7m6rB+268bVj60mxnxUMD14rKuMlLn6n84fMOpD/X7pRTZUfi6M+Tg=="],

"@tscircuit/core/@tscircuit/props": ["@tscircuit/props@0.0.152", "", { "peerDependencies": { "@tscircuit/layout": "*", "circuit-json": "*", "react": "*", "zod": "*" } }, "sha512-K5HnYDA4yf3MVJyU4nElOh3E89D48bAAxO35CfKusJsjrmrdnSNWKKm9PYtJZrcGrlT9sZ2v6EQ3Qxr80Qp4BQ=="],

"@tscircuit/core/circuit-json": ["circuit-json@0.0.139", "", { "dependencies": { "nanoid": "^5.0.7", "zod": "^3.23.6" } }, "sha512-mdS1lWWsSlWRy0lzO/Yx7GgdgbBgGIpUTZQqyVeF+YeEAUejPEz1pieXAiDa1YQI0bf3TmTeF7KNfrsd1zcxtA=="],
@@ -1223,6 +1233,8 @@

"@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@23.0.1", "", {}, "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g=="],

"@tscircuit/checks/circuit-json-to-connectivity-map/@tscircuit/math-utils": ["@tscircuit/math-utils@0.0.9", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-sPzfXndijet8z29X6f5vnSZddiso2tRg7m6rB+268bVj60mxnxUMD14rKuMlLn6n84fMOpD/X7pRTZUfi6M+Tg=="],

"class-utils/define-property/is-descriptor": ["is-descriptor@0.1.7", "", { "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" } }, "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg=="],

"concurrently/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
1 change: 1 addition & 0 deletions lib/components/base-components/Renderable.ts
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ export const orderedRenderPhases = [
"PcbTraceRender",
"PcbTraceHintRender",
"PcbRouteNetIslands",
"PcbDesignRuleChecks",
"CadModelRender",
"PartsEngineRender",
] as const
11 changes: 11 additions & 0 deletions lib/components/normal-components/Board.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { boardProps } from "@tscircuit/props"
import { type Matrix, identity } from "transformation-matrix"
import { Group } from "../primitive-components/Group/Group"
import { checkEachPcbTraceNonOverlapping } from "@tscircuit/checks"

export class Board extends Group<typeof boardProps> {
pcb_board_id: string | null = null
@@ -143,4 +144,14 @@ export class Board extends Group<typeof boardProps> {
_computePcbGlobalTransformBeforeLayout(): Matrix {
return identity()
}

doInitialPcbDesignRuleChecks() {
if (this.root?.pcbDisabled) return
if (this.getInheritedProperty("routingDisabled")) return
const { db } = this.root!
const errors = checkEachPcbTraceNonOverlapping(db.toArray())
for (const error of errors) {
db.pcb_trace_error.insert(error)
}
}
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@tscircuit/core",
"type": "module",
"version": "0.0.356",
"version": "0.0.357",
"types": "dist/index.d.ts",
"main": "dist/index.js",
"module": "dist/index.js",
@@ -54,8 +54,10 @@
"dependencies": {
"@lume/kiwi": "^0.4.3",
"@tscircuit/capacity-autorouter": "^0.0.42",
"@tscircuit/checks": "^0.0.30",
"@tscircuit/circuit-json-util": "^0.0.45",
"@tscircuit/infgrid-ijump-astar": "^0.0.33",
"@tscircuit/math-utils": "^0.0.9",
"@tscircuit/math-utils": "^0.0.12",
"@tscircuit/props": "^0.0.163",
"@tscircuit/schematic-autolayout": "^0.0.6",
"@tscircuit/soup-util": "^0.0.41",
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
133 changes: 133 additions & 0 deletions tests/features/drc-error-detection.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { test, expect } from "bun:test"
import { getTestFixture } from "../fixtures/get-test-fixture"
import { createBasicAutorouter } from "../fixtures/createBasicAutorouter"
import type {
SimpleRouteJson,
SimplifiedPcbTrace,
} from "lib/utils/autorouting/SimpleRouteJson"
import { checkEachPcbTraceNonOverlapping } from "@tscircuit/checks"

/**
* Test for the Design Rule Check (DRC) phase
*
* This test creates a simple circuit with intentionally crossing traces
* to trigger DRC errors. It verifies that the DRC phase correctly identifies
* and reports trace overlaps as errors.
*/
test("design rule check detects crossing traces", async () => {
const { circuit } = getTestFixture()

// Create a circuit with traces that will cross each other on the same layer
circuit.add(
<board
width="20mm"
height="20mm"
// Create a custom autorouter that generates crossing traces
autorouter={{
algorithmFn: createBasicAutorouter(
async (simpleRouteJson: SimpleRouteJson) => {
// Create crossing traces that will trigger DRC errors
return [
// Horizontal trace from left to right
{
type: "pcb_trace",
pcb_trace_id: "trace_horizontal",
connection_name: "source_trace_0",
route: [
{
route_type: "wire",
x: -3.5,
y: 0,
width: 0.15,
layer: "top",
},
{
route_type: "wire",
x: 3.5,
y: 0,
width: 0.15,
layer: "top",
},
],
},
// Vertical trace from bottom to top (crosses the horizontal trace)
{
type: "pcb_trace",
pcb_trace_id: "trace_vertical",
connection_name: "source_trace_1",
route: [
{
route_type: "wire",
x: 0,
y: -3,
width: 0.15,
layer: "top",
},
{
route_type: "wire",
x: 0,
y: 3,
width: 0.15,
layer: "top",
},
],
},
]
},
),
}}
>
{/* Four resistors forming a cross pattern */}
<resistor
name="R1"
footprint="0402"
resistance="10k"
pcbX={-3}
pcbY={0}
/>
<resistor name="R2" footprint="0402" resistance="10k" pcbX={3} pcbY={0} />
<resistor
name="R3"
footprint="0402"
resistance="10k"
pcbX={0}
pcbY={-3}
/>
<resistor name="R4" footprint="0402" resistance="10k" pcbX={0} pcbY={3} />

{/* Connect resistors with traces that will cross in the center */}
<trace from=".R1 > .pin1" to=".R2 > .pin2" />
<trace from=".R3 > .pin1" to=".R4 > .pin2" />
</board>,
)

// Ensure the circuit is fully rendered
await circuit.renderUntilSettled()

// Get the circuit JSON and manually run the DRC check
const circuitJson = circuit.getCircuitJson()

// Run the DRC check function directly
const drcErrors = checkEachPcbTraceNonOverlapping(circuitJson)

// Insert the DRC errors into the database for visualization
for (const error of drcErrors) {
circuit.db.pcb_trace_error.insert(error)
}

// Verify that at least one DRC error was detected
expect(drcErrors.length).toBeGreaterThan(0)

// Check for trace overlap DRC error
const traceOverlapError = drcErrors.find(
(error) =>
error.message.includes("overlaps with trace") &&
error.pcb_trace_id === "trace_horizontal" &&
error.pcb_trace_error_id === "overlap_trace_horizontal_trace_vertical",
)

expect(traceOverlapError).toBeDefined()

// Save the rendered PCB with errors as a snapshot
expect(circuit).toMatchPcbSnapshot(import.meta.path)
})
103 changes: 103 additions & 0 deletions tests/fixtures/createBasicAutorouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type {
AutorouterCompleteEvent,
AutorouterErrorEvent,
AutorouterProgressEvent,
GenericLocalAutorouter,
} from "../../lib/utils/autorouting/GenericLocalAutorouter"
import type {
SimpleRouteJson,
SimplifiedPcbTrace,
} from "../../lib/utils/autorouting/SimpleRouteJson"

/**
* Creates a basic autorouter for testing purposes
*
* @param routeGeneratorFn Function that generates traces from the input SimpleRouteJson
* @returns An object with an algorithmFn that returns a GenericLocalAutorouter implementation
*/
export function createBasicAutorouter(
routeGeneratorFn: (input: SimpleRouteJson) => Promise<SimplifiedPcbTrace[]>,
) {
return async (
simpleRouteJson: SimpleRouteJson,
): Promise<GenericLocalAutorouter> => {
// Create event handlers
const eventHandlers = {
complete: [] as Array<(ev: AutorouterCompleteEvent) => void>,
error: [] as Array<(ev: AutorouterErrorEvent) => void>,
progress: [] as Array<(ev: AutorouterProgressEvent) => void>,
}

// Create the autorouter instance
const autorouter: GenericLocalAutorouter = {
input: simpleRouteJson,
isRouting: false,

async start() {
if (this.isRouting) return
this.isRouting = true

// Generate traces using the provided function
try {
const traces = await routeGeneratorFn(this.input)

// Emit a progress event
for (const handler of eventHandlers.progress) {
handler({
type: "progress",
steps: 1,
progress: 1,
phase: "complete",
})
}

// Emit the complete event with generated traces
setTimeout(() => {
this.isRouting = false
for (const handler of eventHandlers.complete) {
handler({
type: "complete",
traces,
})
}
}, 0)
} catch (error) {
// Handle any errors
this.isRouting = false
for (const handler of eventHandlers.error) {
handler({
type: "error",
error: error instanceof Error ? error : new Error(String(error)),
})
}
}
},

stop(): void {
this.isRouting = false
},

on(event: "complete" | "error" | "progress", callback: any): void {
if (event === "complete") {
eventHandlers.complete.push(
callback as (ev: AutorouterCompleteEvent) => void,
)
} else if (event === "error") {
eventHandlers.error.push(
callback as (ev: AutorouterErrorEvent) => void,
)
} else if (event === "progress") {
eventHandlers.progress.push(
callback as (ev: AutorouterProgressEvent) => void,
)
}
},

solveSync(): SimplifiedPcbTrace[] {
throw new Error("Not implemented")
},
}

return autorouter
}
}