From 51f627efd310399e879bf7963978123118065e49 Mon Sep 17 00:00:00 2001 From: seveibar Date: Sun, 1 Sep 2024 21:37:20 -0700 Subject: [PATCH 1/2] implement pullupFor and pullupTo props for resistors --- lib/Project.ts | 11 ++-- .../base-components/PrimitiveComponent.ts | 50 +++++++++++++++++-- lib/components/base-components/Renderable.ts | 1 + lib/components/normal-components/Board.ts | 4 ++ lib/components/normal-components/Resistor.ts | 18 +++++++ lib/components/primitive-components/Trace.ts | 19 +++++-- tests/__snapshots__/example1.snap.svg | 2 +- .../base-components/select-methods.test.tsx | 4 +- .../normal-components/chip-aliases.test.tsx | 2 +- .../normal-components/chip.test.tsx | 2 +- tests/examples/example1.test.tsx | 19 ++++--- .../extend-expect-circuit-snapshot.ts | 6 +-- tests/fixtures/get-test-fixture.ts | 5 +- tests/readme.test.tsx | 4 +- 14 files changed, 116 insertions(+), 31 deletions(-) diff --git a/lib/Project.ts b/lib/Project.ts index 3ea90e9a..b8b34b0c 100644 --- a/lib/Project.ts +++ b/lib/Project.ts @@ -6,7 +6,7 @@ import { isValidElement, type ReactElement } from "react" import { createInstanceFromReactElement } from "./fiber/create-instance-from-react-element" import { identity, type Matrix } from "transformation-matrix" -export class Project { +export class Circuit { rootComponent: PrimitiveComponent | null = null children: PrimitiveComponent[] db: SoupUtilObjects @@ -115,7 +115,12 @@ export class Project { return this.rootComponent?.selectAll(selector) ?? [] } - selectOne(selector: string): PrimitiveComponent | null { - return this.rootComponent?.selectOne(selector) ?? null + selectOne( + selector: string, + opts: { type?: "component" | "port" }, + ): PrimitiveComponent | null { + return this.rootComponent?.selectOne(selector, opts) ?? null } } + +export const Project = Circuit diff --git a/lib/components/base-components/PrimitiveComponent.ts b/lib/components/base-components/PrimitiveComponent.ts index 5214c1b9..210806a9 100644 --- a/lib/components/base-components/PrimitiveComponent.ts +++ b/lib/components/base-components/PrimitiveComponent.ts @@ -1,5 +1,5 @@ import type { AnySoupElement, AnySourceComponent } from "@tscircuit/soup" -import type { Project } from "../../Project" +import type { Circuit } from "../../Project" import type { ZodType } from "zod" import { z } from "zod" import { symbols, type SchSymbol, type BaseSymbolName } from "schematic-symbols" @@ -40,7 +40,7 @@ export abstract class PrimitiveComponent< } } - project: Project | null = null + project: Circuit | null = null props: z.input _parsedProps: z.infer @@ -49,6 +49,21 @@ 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 + * components share the same names) unless you explicitly break out some ports + */ + get isOpaqueGroup() { + return ( + Boolean(this.props.opaque) || + // Implied opaque group for top-level group + (this.lowercaseComponentName === "group" && + this?.parent?.props?.name === "$root") + ) + } + source_group_id: string | null = null source_component_id: string | null = null schematic_component_id: string | null = null @@ -70,7 +85,7 @@ export abstract class PrimitiveComponent< } } - setProject(project: Project) { + setProject(project: Circuit) { this.project = project for (const c of this.children) { c.setProject(project) @@ -197,6 +212,27 @@ export abstract class PrimitiveComponent< component.shouldBeRemoved = true } + getOpaqueGroupSelector(): 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}` + } + + getFullPathSelector(): string { + const name = this._parsedProps.name + const endPart = name + ? `${this.lowercaseComponentName}.${name}` + : this.lowercaseComponentName + const parentSelector = this.parent?.getFullPathSelector?.() + if (!parentSelector) return endPart + return `${parentSelector} > ${endPart}` + } + getNameAndAliases(): string[] { return [ this._parsedProps.name, @@ -230,6 +266,14 @@ export abstract class PrimitiveComponent< return false } + getOpaqueGroup(): PrimitiveComponent { + if (this.isOpaqueGroup) return this + const group = this.parent?.getOpaqueGroup() + if (!group) + throw new Error("Component is not inside an opaque group (no board?)") + return group + } + selectAll(selector: string): PrimitiveComponent[] { const parts = selector.trim().split(/\s+/) let results: PrimitiveComponent[] = [this] diff --git a/lib/components/base-components/Renderable.ts b/lib/components/base-components/Renderable.ts index 4a6b33e7..2b274b98 100644 --- a/lib/components/base-components/Renderable.ts +++ b/lib/components/base-components/Renderable.ts @@ -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 + "CreateTracesFromProps", "SourceRender", "SourceParentAttachment", "PortDiscovery", // probably going to be removed b/c port discovery can always be done on prop change diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index 92f3a9bd..d5a51a55 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -6,6 +6,10 @@ import { identity, type Matrix } from "transformation-matrix" export class Board extends NormalComponent { pcb_board_id: string | null = null + get isOpaqueGroup() { + return true + } + get config() { return { zodProps: boardProps, diff --git a/lib/components/normal-components/Resistor.ts b/lib/components/normal-components/Resistor.ts index 310354f6..e4c00dc4 100644 --- a/lib/components/normal-components/Resistor.ts +++ b/lib/components/normal-components/Resistor.ts @@ -2,6 +2,8 @@ import { resistorProps } from "@tscircuit/props" import type { PassivePorts, Ftype, BaseSymbolName } from "lib/utils/constants" import { NormalComponent } from "../base-components/NormalComponent" import type { SourceSimpleResistorInput } from "@tscircuit/soup" +import { z } from "zod" +import { Trace } from "../primitive-components/Trace" export class Resistor extends NormalComponent< typeof resistorProps, @@ -18,6 +20,22 @@ export class Resistor extends NormalComponent< pin1 = this.portMap.pin1 pin2 = this.portMap.pin2 + doInitialCreateTracesFromProps() { + if (this.props.pullupFor && this.props.pullupTo) { + this.add( + new Trace({ + from: `${this.getOpaqueGroupSelector()} > port.1`, + to: this.props.pullupFor, + }), + ) + this.add( + new Trace({ + from: `${this.getOpaqueGroupSelector()} > port.2`, + to: this.props.pullupTo, + }), + ) + } + } doInitialSourceRender() { const { db } = this.project! const { _parsedProps: props } = this diff --git a/lib/components/primitive-components/Trace.ts b/lib/components/primitive-components/Trace.ts index d1437dca..780c51ec 100644 --- a/lib/components/primitive-components/Trace.ts +++ b/lib/components/primitive-components/Trace.ts @@ -79,21 +79,25 @@ export class Trace extends PrimitiveComponent { const ports = portSelectors.map((selector) => ({ selector, - port: parent.selectOne(selector, { type: "port" }) as Port, + port: + (this.getOpaqueGroup().selectOne(selector, { type: "port" }) as Port) ?? + null, })) for (const { selector, port } of ports) { if (!port) { const parentSelector = selector.replace(/\>.*$/, "") - const targetComponent = parent.selectOne(parentSelector) + const targetComponent = this.getOpaqueGroup().selectOne(parentSelector) if (!targetComponent) { this.renderError(`Could not find port for selector "${selector}"`) } else { this.renderError( - `Could not find port for selector "${selector}"\nsearched component ${targetComponent.getString()}, which has ports:${targetComponent.children + `Could not find port for selector "${selector}"\nsearched component ${targetComponent.getString()}, which has ports: ${targetComponent.children .filter((c) => c.componentName === "Port") - .map((c) => ` ${c.getString()}`) - .join("\n")}`, + .map( + (c) => `${c.getString()}(${c.getNameAndAliases().join(",")})`, + ) + .join(" & ")}`, ) } } @@ -173,6 +177,11 @@ export class Trace extends PrimitiveComponent { const { solution } = autoroute(pcbElements.concat([source_trace])) // TODO for some reason, the solution gets duplicated inside ijump-astar const inputPcbTrace = solution[0] + + if (!inputPcbTrace) { + // TODO render error indicating we could not find a route + return + } const pcb_trace = db.pcb_trace.insert(inputPcbTrace as any) this.pcb_trace_id = pcb_trace.pcb_trace_id diff --git a/tests/__snapshots__/example1.snap.svg b/tests/__snapshots__/example1.snap.svg index 2847a93e..7f85a85a 100644 --- a/tests/__snapshots__/example1.snap.svg +++ b/tests/__snapshots__/example1.snap.svg @@ -4,4 +4,4 @@ .pcb-hole { fill: #FF00FF; } .pcb-pad { fill: #FF0000; } .pcb-boundary { fill: none; stroke: #f2eda1; stroke-width: 4.800000000000001; } - \ No newline at end of file + \ No newline at end of file diff --git a/tests/components/base-components/select-methods.test.tsx b/tests/components/base-components/select-methods.test.tsx index 95c2024d..a1ef7b6b 100644 --- a/tests/components/base-components/select-methods.test.tsx +++ b/tests/components/base-components/select-methods.test.tsx @@ -1,9 +1,9 @@ import { it, expect } from "bun:test" -import { Project } from "lib/Project" +import { Circuit } from "lib/Project" import "lib/register-catalogue" it("should correctly use selectAll and selectOne methods", () => { - const project = new Project() + const project = new Circuit() project.add( diff --git a/tests/components/normal-components/chip-aliases.test.tsx b/tests/components/normal-components/chip-aliases.test.tsx index 31d6f600..7d00a333 100644 --- a/tests/components/normal-components/chip-aliases.test.tsx +++ b/tests/components/normal-components/chip-aliases.test.tsx @@ -1,5 +1,5 @@ import { it, expect } from "bun:test" -import { Project } from "lib/Project" +import { Circuit } from "lib/Project" import { Chip } from "lib/components/normal-components/Chip" import "lib/register-catalogue" import { getTestFixture } from "tests/fixtures/get-test-fixture" diff --git a/tests/components/normal-components/chip.test.tsx b/tests/components/normal-components/chip.test.tsx index 5dbe3834..98bceeee 100644 --- a/tests/components/normal-components/chip.test.tsx +++ b/tests/components/normal-components/chip.test.tsx @@ -1,5 +1,5 @@ import { it, expect } from "bun:test" -import { Project } from "lib/Project" +import { Circuit } from "lib/Project" import { Chip } from "lib/components/normal-components/Chip" import "lib/register-catalogue" import { getTestFixture } from "tests/fixtures/get-test-fixture" diff --git a/tests/examples/example1.test.tsx b/tests/examples/example1.test.tsx index 946436a3..aaa31745 100644 --- a/tests/examples/example1.test.tsx +++ b/tests/examples/example1.test.tsx @@ -2,8 +2,8 @@ import { test, expect } from "bun:test" import { getTestFixture } from "tests/fixtures/get-test-fixture" test("example1", async () => { - const { project, logSoup } = getTestFixture() - project.add( + const { circuit, logSoup } = getTestFixture() + circuit.add( { footprint="0402" resistance="10k" pullupFor=".U1 port.2" - pullupTo="net.5v" - pcbX={4} + // pullupTo="net.5v" + pullupTo=".J1 pin.1" + pcbX={-4} pcbY={0} - pcbRotation={90} + pcbRotation={-90} /> { footprint="0603" decouplingFor=".U1 port.PWR" decouplingTo="net.GND" - pcbX={-5} + pcbX={4} pcbY={0} pcbRotation={90} /> @@ -41,10 +42,12 @@ test("example1", async () => { , ) - project.render() + circuit.render() + + await logSoup("example1") await expect( - project.getSvg({ + circuit.getSvg({ view: "pcb", layer: "top", }), diff --git a/tests/fixtures/extend-expect-circuit-snapshot.ts b/tests/fixtures/extend-expect-circuit-snapshot.ts index 3aaedbb7..dc986f04 100644 --- a/tests/fixtures/extend-expect-circuit-snapshot.ts +++ b/tests/fixtures/extend-expect-circuit-snapshot.ts @@ -4,7 +4,7 @@ import * as fs from "node:fs" import * as path from "node:path" import looksSame from "looks-same" import type { AnySoupElement } from "@tscircuit/soup" -import { Project } from "lib/Project" +import { Circuit } from "lib/Project" async function saveSnapshotOfSoup({ soup, @@ -77,7 +77,7 @@ expect.extend({ ): Promise { return saveSnapshotOfSoup({ soup: - received instanceof Project + received instanceof Circuit ? received.getSoup() : (received as AnySoupElement[]), testPath: args[0], @@ -95,7 +95,7 @@ expect.extend({ ): Promise { return saveSnapshotOfSoup({ soup: - received instanceof Project + received instanceof Circuit ? received.getSoup() : (received as AnySoupElement[]), testPath: args[0], diff --git a/tests/fixtures/get-test-fixture.ts b/tests/fixtures/get-test-fixture.ts index faa10829..ffd76176 100644 --- a/tests/fixtures/get-test-fixture.ts +++ b/tests/fixtures/get-test-fixture.ts @@ -1,13 +1,14 @@ -import { Project } from "lib/Project" +import { Circuit } from "lib/Project" import { logSoup } from "@tscircuit/log-soup" import "lib/register-catalogue" import "./extend-expect-circuit-snapshot" export const getTestFixture = () => { - const project = new Project() + const project = new Circuit() return { project, + circuit: project, logSoup: async (nameOfTest: string) => { if (process.env.CI) return if (!project.rootComponent?.renderPhaseStates.SourceRender.initialized) { diff --git a/tests/readme.test.tsx b/tests/readme.test.tsx index 49b69564..6679717a 100644 --- a/tests/readme.test.tsx +++ b/tests/readme.test.tsx @@ -1,10 +1,10 @@ import { it, expect } from "bun:test" -import { Board, Resistor, Project } from "../index" +import { Board, Resistor, Circuit } from "../index" import { Led } from "lib/components/normal-components/Led" import "lib/register-catalogue" it("should create soup with various elements", () => { - const project = new Project() + const project = new Circuit() const board = new Board({ width: "10mm", From d09fda6c14b7dde3ee7674ba46d921a1f21453dd Mon Sep 17 00:00:00 2001 From: seveibar Date: Sun, 1 Sep 2024 21:46:32 -0700 Subject: [PATCH 2/2] support for decouplingTo and decouplingFor props --- lib/components/normal-components/Capacitor.ts | 45 +++++++++++++++++-- lib/components/primitive-components/Trace.ts | 3 ++ tests/__snapshots__/example1.snap.svg | 2 +- tests/examples/example1.test.tsx | 7 +-- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/components/normal-components/Capacitor.ts b/lib/components/normal-components/Capacitor.ts index 8ceedc57..90d29bb9 100644 --- a/lib/components/normal-components/Capacitor.ts +++ b/lib/components/normal-components/Capacitor.ts @@ -1,7 +1,8 @@ -import { ledProps } from "@tscircuit/props" -import { PrimitiveComponent } from "../base-components/PrimitiveComponent" +import { capacitorProps, ledProps } from "@tscircuit/props" import { FTYPE, SYMBOL } from "lib/utils/constants" import { NormalComponent } from "../base-components/NormalComponent" +import type { capacitance, SourceSimpleCapacitorInput } from "@tscircuit/soup" +import { Trace } from "../primitive-components/Trace" type PortNames = | "1" @@ -13,7 +14,10 @@ type PortNames = | "anode" | "cathode" -export class Capacitor extends NormalComponent { +export class Capacitor extends NormalComponent< + typeof capacitorProps, + PortNames +> { get config() { return { // schematicSymbolName: BASE_SYMBOLS.capacitor, @@ -21,4 +25,39 @@ export class Capacitor extends NormalComponent { sourceFtype: FTYPE.simple_capacitor, } } + + pin1 = this.portMap.pin1 + pin2 = this.portMap.pin2 + + doInitialCreateTracesFromProps() { + if (this.props.decouplingFor && this.props.decouplingTo) { + this.add( + new Trace({ + from: `${this.getOpaqueGroupSelector()} > port.1`, + to: this.props.decouplingFor, + }), + ) + this.add( + new Trace({ + from: `${this.getOpaqueGroupSelector()} > port.2`, + to: this.props.decouplingTo, + }), + ) + } + } + + doInitialSourceRender() { + const { db } = this.project! + const { _parsedProps: props } = this + const source_component = db.source_component.insert({ + ftype: "simple_capacitor", + name: props.name, + // @ts-ignore + manufacturer_part_number: props.manufacturerPartNumber ?? props.mfn, + supplier_part_numbers: props.supplierPartNumbers, + + capacitance: props.capacitance, + } as SourceSimpleCapacitorInput) + this.source_component_id = source_component.source_component_id + } } diff --git a/lib/components/primitive-components/Trace.ts b/lib/components/primitive-components/Trace.ts index 780c51ec..454e5c5c 100644 --- a/lib/components/primitive-components/Trace.ts +++ b/lib/components/primitive-components/Trace.ts @@ -180,6 +180,9 @@ export class Trace extends PrimitiveComponent { if (!inputPcbTrace) { // TODO render error indicating we could not find a route + console.log( + `Failed to find route ffrom ${ports[0].port} to ${ports[1].port} render error!`, + ) return } const pcb_trace = db.pcb_trace.insert(inputPcbTrace as any) diff --git a/tests/__snapshots__/example1.snap.svg b/tests/__snapshots__/example1.snap.svg index 7f85a85a..3973c90d 100644 --- a/tests/__snapshots__/example1.snap.svg +++ b/tests/__snapshots__/example1.snap.svg @@ -4,4 +4,4 @@ .pcb-hole { fill: #FF00FF; } .pcb-pad { fill: #FF0000; } .pcb-boundary { fill: none; stroke: #f2eda1; stroke-width: 4.800000000000001; } - \ No newline at end of file + \ No newline at end of file diff --git a/tests/examples/example1.test.tsx b/tests/examples/example1.test.tsx index aaa31745..7ef89865 100644 --- a/tests/examples/example1.test.tsx +++ b/tests/examples/example1.test.tsx @@ -4,7 +4,7 @@ import { getTestFixture } from "tests/fixtures/get-test-fixture" test("example1", async () => { const { circuit, logSoup } = getTestFixture() circuit.add( - + { capacitance="10uF" footprint="0603" decouplingFor=".U1 port.PWR" - decouplingTo="net.GND" + // decouplingTo="net.GND" + decouplingTo=".J1 pin.4" pcbX={4} pcbY={0} - pcbRotation={90} + pcbRotation={-90} />