Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Net Implemenation, PcbRouteNetIslands render phase, rename OpaqueGroup to Subcircuit #26

Merged
merged 12 commits into from
Sep 4, 2024
1 change: 1 addition & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"style": {
"noNonNullAssertion": "off",
"useImportType": "off",
"useNumberNamespace": "off",
"useFilenamingConvention": {
"level": "error",
"options": {
Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion lib/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class Circuit {

selectOne(
selector: string,
opts: { type?: "component" | "port" },
opts?: { type?: "component" | "port" },
): PrimitiveComponent | null {
return this.rootComponent?.selectOne(selector, opts) ?? null
}
Expand Down
15 changes: 15 additions & 0 deletions lib/components/base-components/NormalComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "lib/fiber/create-instance-from-react-element"
import { getPortFromHints } from "lib/utils/getPortFromHints"
import { createComponentsFromSoup } from "lib/utils/createComponentsFromSoup"
import { Net } from "../primitive-components/Net"

export type PortMap<T extends string> = {
[K in T]: Port
Expand Down Expand Up @@ -307,6 +308,20 @@ export class NormalComponent<
return newPorts
}

_createNetsFromProps(propsWithConnections: (string | undefined | null)[]) {
for (const prop of propsWithConnections) {
if (typeof prop === "string" && prop.startsWith("net.")) {
if (!this.getSubcircuit().selectOne(prop)) {
this.getSubcircuit().add(
new Net({
name: prop.split(".")[1],
}),
)
}
}
}
}

/**
* Use data from our props to create ports for this component.
*
Expand Down
25 changes: 14 additions & 11 deletions lib/components/base-components/PrimitiveComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ export abstract class PrimitiveComponent<
externallyAddedAliases: string[]

/**
* An opaque group is a self-contained subcircuit. All the selectors inside
* an opaque group are relative to the group. You can have multiple opaque
* groups and their selectors will not interact with each other (even if the
* An subcircuit is self-contained. All the selectors inside
* a subcircuit are relative to the subcircuit group. You can have multiple
* subcircuits and their selectors will not interact with each other (even if the
* components share the same names) unless you explicitly break out some ports
*/
get isOpaqueGroup() {
get isSubcircuit() {
return (
Boolean(this.props.opaque) ||
Boolean(this.props.subcircuit) ||
// Implied opaque group for top-level group
(this.lowercaseComponentName === "group" &&
this?.parent?.props?.name === "$root")
Expand Down Expand Up @@ -212,15 +212,15 @@ export abstract class PrimitiveComponent<
component.shouldBeRemoved = true
}

getOpaqueGroupSelector(): string {
getSubcircuitSelector(): string {
const name = this._parsedProps.name
const endPart = name
? `${this.lowercaseComponentName}.${name}`
: this.lowercaseComponentName

if (!this.parent) return endPart
if (this.parent.isOpaqueGroup) return endPart
return `${this.parent.getOpaqueGroupSelector()} > ${endPart}`
if (this.parent.isSubcircuit) return endPart
return `${this.parent.getSubcircuitSelector()} > ${endPart}`
}

getFullPathSelector(): string {
Expand Down Expand Up @@ -266,9 +266,9 @@ export abstract class PrimitiveComponent<
return false
}

getOpaqueGroup(): PrimitiveComponent {
if (this.isOpaqueGroup) return this
const group = this.parent?.getOpaqueGroup()
getSubcircuit(): PrimitiveComponent {
if (this.isSubcircuit) return this
const group = this.parent?.getSubcircuit()
if (!group)
throw new Error("Component is not inside an opaque group (no board?)")
return group
Expand Down Expand Up @@ -349,6 +349,9 @@ export abstract class PrimitiveComponent<
if (parent?.props?.name && props?.name) {
return `<${cname}#${this._renderId}(.${parent?.props.name}>.${props?.name}) />`
}
if (props?.from && props?.to) {
return `<${cname}#${this._renderId}(from:${props.from} to:${props?.to}) />`
}
if (props?.name) {
return `<${cname}#${this._renderId} name=".${props?.name}" />`
}
Expand Down
2 changes: 2 additions & 0 deletions lib/components/base-components/Renderable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Component, createElement, type ReactElement } from "react"

export const orderedRenderPhases = [
"ReactSubtreesRender", // probably going to be removed b/c subtrees should render instantly
"CreateNetsFromProps",
"CreateTracesFromProps",
"SourceRender",
"SourceParentAttachment",
Expand All @@ -19,6 +20,7 @@ export const orderedRenderPhases = [
"PcbParentAttachment",
"PcbLayout",
"PcbTraceRender",
"PcbRouteNetIslands",
"CadModelRender",
"PcbAnalysis",
] as const
Expand Down
5 changes: 3 additions & 2 deletions lib/components/normal-components/Board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { boardProps } from "@tscircuit/props"
import type { z } from "zod"
import { NormalComponent } from "../base-components/NormalComponent"
import { identity, type Matrix } from "transformation-matrix"
import { Group } from "../primitive-components/Group"

export class Board extends NormalComponent<typeof boardProps> {
export class Board extends Group<typeof boardProps> {
pcb_board_id: string | null = null

get isOpaqueGroup() {
get isSubcircuit() {
return true
}

Expand Down
11 changes: 9 additions & 2 deletions lib/components/normal-components/Capacitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,24 @@ export class Capacitor extends NormalComponent<
pin1 = this.portMap.pin1
pin2 = this.portMap.pin2

doInitialCreateNetsFromProps() {
this._createNetsFromProps([
this.props.decouplingFor,
this.props.decouplingTo,
])
}

doInitialCreateTracesFromProps() {
if (this.props.decouplingFor && this.props.decouplingTo) {
this.add(
new Trace({
from: `${this.getOpaqueGroupSelector()} > port.1`,
from: `${this.getSubcircuitSelector()} > port.1`,
to: this.props.decouplingFor,
}),
)
this.add(
new Trace({
from: `${this.getOpaqueGroupSelector()} > port.2`,
from: `${this.getSubcircuitSelector()} > port.2`,
to: this.props.decouplingTo,
}),
)
Expand Down
10 changes: 8 additions & 2 deletions lib/components/normal-components/Resistor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NormalComponent } from "../base-components/NormalComponent"
import type { SourceSimpleResistorInput } from "@tscircuit/soup"
import { z } from "zod"
import { Trace } from "../primitive-components/Trace"
import { Net } from "../primitive-components/Net"

export class Resistor extends NormalComponent<
typeof resistorProps,
Expand All @@ -20,22 +21,27 @@ export class Resistor extends NormalComponent<
pin1 = this.portMap.pin1
pin2 = this.portMap.pin2

doInitialCreateNetsFromProps() {
this._createNetsFromProps([this.props.pullupFor, this.props.pullupTo])
}

doInitialCreateTracesFromProps() {
if (this.props.pullupFor && this.props.pullupTo) {
this.add(
new Trace({
from: `${this.getOpaqueGroupSelector()} > port.1`,
from: `${this.getSubcircuitSelector()} > port.1`,
to: this.props.pullupFor,
}),
)
this.add(
new Trace({
from: `${this.getOpaqueGroupSelector()} > port.2`,
from: `${this.getSubcircuitSelector()} > port.2`,
to: this.props.pullupTo,
}),
)
}
}

doInitialSourceRender() {
const { db } = this.project!
const { _parsedProps: props } = this
Expand Down
8 changes: 6 additions & 2 deletions lib/components/primitive-components/Group.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { groupProps } from "@tscircuit/props"
import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
import { compose, identity } from "transformation-matrix"
import { z } from "zod"
import { NormalComponent } from "../base-components/NormalComponent"

export class Group extends PrimitiveComponent<typeof groupProps> {
export class Group<
Props extends z.ZodType<any, any, any> = typeof groupProps,
> extends NormalComponent<Props> {
get config() {
return {
zodProps: groupProps,
zodProps: groupProps as unknown as Props,
}
}
}
159 changes: 159 additions & 0 deletions lib/components/primitive-components/Net.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,171 @@
import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
import { z } from "zod"
import type { Port } from "./Port"
import type { Trace } from "./Trace"
import { pairs } from "lib/utils/pairs"
import type { AnySoupElement, SourceTrace } from "@tscircuit/soup"
import { autoroute } from "@tscircuit/infgrid-ijump-astar"

export const netProps = z.object({
name: z.string(),
})

export class Net extends PrimitiveComponent<typeof netProps> {
source_net_id?: string

getPortSelector() {
return `net.${this.props.name}`
}

doInitialSourceComponentRender(): void {
const { db } = this.project!
const { _parsedProps: props } = this

const net = db.source_net.insert({
name: props.name,
member_source_group_ids: [],
})

this.source_net_id = net.source_net_id
}

/**
* Get all ports connected to this net.
*
* TODO currently we're not checking for indirect connections (traces that are
* connected to other traces that are in turn connected to the net)
*/
getAllConnectedPorts(): Port[] {
const allPorts = this.getSubcircuit().selectAll("port") as Port[]
const connectedPorts: Port[] = []

for (const port of allPorts) {
const traces = port._getDirectlyConnectedTraces()

for (const trace of traces) {
if (trace._isExplicitlyConnectedToNet(this)) {
connectedPorts.push(port)
break
}
}
}

return connectedPorts
}

/**
* Get all traces that are directly connected to this net, i.e. they list
* this net in their path, from, or to props
*/
_getAllDirectlyConnectedTraces(): Trace[] {
const allTraces = this.getSubcircuit().selectAll("trace") as Trace[]
const connectedTraces: Trace[] = []

for (const trace of allTraces) {
if (trace._isExplicitlyConnectedToNet(this)) {
connectedTraces.push(trace)
}
}

return connectedTraces
}

/**
* Add PCB Traces to connect net islands together. A net island is a set of
* ports that are connected to each other. If a there are multiple net islands
* that means that the net is not fully connected and we need to add traces
* such that the nets are fully connected
*/
doInitialPcbRouteNetIslands(): void {
const { db } = this.project!
const { _parsedProps: props } = this

const traces = this._getAllDirectlyConnectedTraces().filter(
(trace) => (trace._portsRoutedOnPcb?.length ?? 0) > 0,
)

const islands: Array<{ ports: Port[]; traces: Trace[] }> = []

for (const trace of traces) {
const tracePorts = trace._portsRoutedOnPcb
const traceIsland = islands.find((island) =>
tracePorts.some((port) => island.ports.includes(port)),
)
if (!traceIsland) {
islands.push({ ports: [...tracePorts], traces: [trace] })
continue
}
traceIsland.traces.push(trace)
traceIsland.ports.push(...tracePorts)
}

if (islands.length === 0) {
return
}

// Connect islands together by looking at each pair of islands and adding
// a trace between them
const islandPairs = pairs(islands)
for (const [A, B] of islandPairs) {
// Find two closest ports on the island
const Apositions: Array<{ x: number; y: number }> = A.ports.map((port) =>
port.getGlobalPcbPosition(),
)
const Bpositions: Array<{ x: number; y: number }> = B.ports.map((port) =>
port.getGlobalPcbPosition(),
)

let closestDist = Infinity
let closestPair: [number, number] = [-1, -1]
for (let i = 0; i < Apositions.length; i++) {
const Apos = Apositions[i]
for (let j = 0; j < Bpositions.length; j++) {
const Bpos = Bpositions[j]
const dist = Math.sqrt(
(Apos.x - Bpos.x) ** 2 + (Apos.y - Bpos.y) ** 2,
)
if (dist < closestDist) {
closestDist = dist
closestPair = [i, j]
}
}
}

const Aport = A.ports[closestPair[0]]
const Bport = B.ports[closestPair[1]]

const pcbElements: AnySoupElement[] = db
.toArray()
.filter(
(elm) =>
elm.type === "pcb_smtpad" ||
elm.type === "pcb_trace" ||
elm.type === "pcb_plated_hole" ||
elm.type === "pcb_hole" ||
elm.type === "source_port" ||
elm.type === "pcb_port",
)

const { solution } = autoroute(
pcbElements.concat([
{
type: "source_trace",
source_trace_id: "__net_trace_tmp",
connected_source_port_ids: [
Aport.source_port_id!,
Bport.source_port_id!,
],
} as SourceTrace,
]),
)

const trace = solution[0]
if (!trace) {
this.renderError("Failed to route net islands")
return
}

db.pcb_trace.insert(trace as any)
}
}
}
Loading
Loading