diff --git a/bun.lockb b/bun.lockb index 23a78b2..bb625f8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/lib/Project.ts b/lib/Project.ts index 26b755b..762f84f 100644 --- a/lib/Project.ts +++ b/lib/Project.ts @@ -108,7 +108,7 @@ export class Circuit { return identity() } - computePcbGlobalTransform(): Matrix { + _computePcbGlobalTransformBeforeLayout(): Matrix { return identity() } @@ -124,4 +124,7 @@ export class Circuit { } } +/** + * @deprecated + */ export const Project = Circuit diff --git a/lib/components/base-components/NormalComponent.ts b/lib/components/base-components/NormalComponent.ts index 021fc30..f309ab7 100644 --- a/lib/components/base-components/NormalComponent.ts +++ b/lib/components/base-components/NormalComponent.ts @@ -46,6 +46,8 @@ export class NormalComponent< > extends PrimitiveComponent { reactSubtrees: Array = [] + isPrimitiveContainer = true + constructor(props: z.input) { super(props) this._addChildrenFromStringFootprint() @@ -199,7 +201,7 @@ export class NormalComponent< const { db } = this.root! const { _parsedProps: props } = this const pcb_component = db.pcb_component.insert({ - center: this.getGlobalPcbPosition(), + center: this._getGlobalPcbPositionBeforeLayout(), // width/height are computed in the PcbComponentSizeCalculation phase width: 0, height: 0, @@ -226,7 +228,7 @@ export class NormalComponent< for (const child of this.children) { if (child.isPcbPrimitive) { - const { x, y } = child.getGlobalPcbPosition() + const { x, y } = child._getGlobalPcbPositionBeforeLayout() const { width, height } = child.getPcbSize() minX = Math.min(minX, x - width / 2) minY = Math.min(minY, y - height / 2) diff --git a/lib/components/base-components/PrimitiveComponent.ts b/lib/components/base-components/PrimitiveComponent.ts index a068bbc..4867e59 100644 --- a/lib/components/base-components/PrimitiveComponent.ts +++ b/lib/components/base-components/PrimitiveComponent.ts @@ -63,6 +63,18 @@ export abstract class PrimitiveComponent< ) } + /** + * A primitive container is a component that contains one or more ports and + * primitive components that are designed to interact. + * + * For example a resistor contains ports and smtpads that interact, so the + * resistor is a primitive container. Inside a primitive container, the ports + * and pads are likely to reference each other and look for eachother during + * the port matching phase. + * + */ + isPrimitiveContainer = false + source_group_id: string | null = null source_component_id: string | null = null schematic_component_id: string | null = null @@ -120,9 +132,9 @@ export abstract class PrimitiveComponent< * components, including this component's translation and rotation. * * This is used to compute this component's position as well as all children - * components positions + * components positions before layout is applied */ - computePcbGlobalTransform(): Matrix { + _computePcbGlobalTransformBeforeLayout(): Matrix { const { _parsedProps: props } = this const manualPlacement = this.getSubcircuit()._getManualPlacementForComponent(this) @@ -134,7 +146,7 @@ export abstract class PrimitiveComponent< this.props.pcbY === undefined ) { return compose( - this.parent?.computePcbGlobalTransform() ?? identity(), + this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(), compose( translate(manualPlacement.x, manualPlacement.y), rotate(((props.pcbRotation ?? 0) * Math.PI) / 180), @@ -143,11 +155,46 @@ export abstract class PrimitiveComponent< } return compose( - this.parent?.computePcbGlobalTransform() ?? identity(), + this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(), this.computePcbPropsTransform(), ) } + getPrimitiveContainer(): PrimitiveComponent | null { + if (this.isPrimitiveContainer) return this + return this.parent?.getPrimitiveContainer?.() ?? null + } + + /** + * Compute the bounds of this component the circuit json elements associated + * with it. + */ + _getCircuitJsonBounds(): { + center: { x: number; y: number } + bounds: { left: number; top: number; right: number; bottom: number } + width: number + height: number + } { + return { + center: { x: 0, y: 0 }, + bounds: { left: 0, top: 0, right: 0, bottom: 0 }, + width: 0, + height: 0, + } + } + + /** + * Set the position of this component from the layout solver. This method + * should operate using CircuitJson associated with this component, like + * _getCircuitJsonBounds it can be called multiple times as different + * parents apply layout to their children. + */ + _setPositionFromLayout(newCenter: { x: number; y: number }) { + throw new Error( + `_setPositionFromLayout not implemented for ${this.componentName}`, + ) + } + /** * Computes a transformation matrix from the props of this component for * schematic components @@ -200,7 +247,7 @@ export abstract class PrimitiveComponent< for (const position of placementConfig.positions) { if (isMatchingSelector(component, position.selector)) { const center = applyToPoint( - this.computePcbGlobalTransform(), + this._computePcbGlobalTransformBeforeLayout(), position.center, ) return center @@ -210,11 +257,14 @@ export abstract class PrimitiveComponent< return null } - getGlobalPcbPosition(): { x: number; y: number } { - return applyToPoint(this.computePcbGlobalTransform(), { x: 0, y: 0 }) + _getGlobalPcbPositionBeforeLayout(): { x: number; y: number } { + return applyToPoint(this._computePcbGlobalTransformBeforeLayout(), { + x: 0, + y: 0, + }) } - getGlobalSchematicPosition(): { x: number; y: number } { + _getGlobalSchematicPositionBeforeLayout(): { x: number; y: number } { return applyToPoint(this.computeSchematicGlobalTransform(), { x: 0, y: 0 }) } diff --git a/lib/components/base-components/Renderable.ts b/lib/components/base-components/Renderable.ts index ac97071..eadd5be 100644 --- a/lib/components/base-components/Renderable.ts +++ b/lib/components/base-components/Renderable.ts @@ -2,13 +2,13 @@ import type { PCBPlacementError, PCBTraceError } from "@tscircuit/soup" import { Component, createElement, type ReactElement } from "react" export const orderedRenderPhases = [ - "ReactSubtreesRender", // probably going to be removed b/c subtrees should render instantly + "ReactSubtreesRender", "InitializePortsFromChildren", "CreateNetsFromProps", "CreateTracesFromProps", "SourceRender", "SourceParentAttachment", - "PortDiscovery", // probably going to be removed b/c port discovery can always be done on prop change + "PortDiscovery", "PortMatching", "SourceTraceRender", "SchematicComponentRender", @@ -16,8 +16,9 @@ export const orderedRenderPhases = [ "SchematicPortRender", "SchematicTraceRender", "PcbComponentRender", - "PcbPortRender", "PcbPrimitiveRender", + "PcbFootprintLayout", + "PcbPortRender", "PcbParentAttachment", "PcbLayout", "PcbTraceRender", diff --git a/lib/components/index.ts b/lib/components/index.ts index 8ca1c61..d091af9 100644 --- a/lib/components/index.ts +++ b/lib/components/index.ts @@ -16,3 +16,4 @@ export { Chip } from "./normal-components/Chip" export { Jumper } from "./normal-components/Jumper" export { SilkscreenPath } from "./primitive-components/SilkscreenPath" export { PlatedHole } from "./primitive-components/PlatedHole" +export { Constraint } from "./primitive-components/Constraint" diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index 0a54617..961e69f 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -42,7 +42,7 @@ export class Board extends Group { this.pcb_board_id = null } - computePcbGlobalTransform(): Matrix { + _computePcbGlobalTransformBeforeLayout(): Matrix { return identity() } } diff --git a/lib/components/primitive-components/Constraint.ts b/lib/components/primitive-components/Constraint.ts new file mode 100644 index 0000000..e4b359b --- /dev/null +++ b/lib/components/primitive-components/Constraint.ts @@ -0,0 +1,49 @@ +import { constraintProps } from "@tscircuit/props" +import { z } from "zod" +import { PrimitiveComponent } from "../base-components/PrimitiveComponent" + +export class Constraint extends PrimitiveComponent { + get config() { + return { + zodProps: constraintProps, + } + } + + constructor(props: z.input) { + super(props) + if ("xdist" in props || "ydist" in props) { + if (!("edgeToEdge" in props) && !("centerToCenter" in props)) { + throw new Error( + "edgeToEdge, centerToCenter (or from*/to* props) must be set for xdist or ydist for ", + ) + } + } + } + + _getAllReferencedComponents(): { + componentsWithSelectors: Array<{ + component: PrimitiveComponent + selector: string + }> + } { + const componentsWithSelectors: Array<{ + component: PrimitiveComponent + selector: string + }> = [] + + for (const key of ["left", "right", "top", "bottom"]) { + if (key in this._parsedProps) { + const selector = (this._parsedProps as any)[key] as string + const component = this.getSubcircuit().selectOne(selector) + if (component) { + componentsWithSelectors.push({ + selector, + component, + }) + } + } + } + + return { componentsWithSelectors } + } +} diff --git a/lib/components/primitive-components/Footprint.ts b/lib/components/primitive-components/Footprint.ts index 456a78c..8664cdd 100644 --- a/lib/components/primitive-components/Footprint.ts +++ b/lib/components/primitive-components/Footprint.ts @@ -1,4 +1,161 @@ import type { footprintProps } from "@tscircuit/props" import { PrimitiveComponent } from "../base-components/PrimitiveComponent" +import type { Constraint } from "./Constraint" +import * as kiwi from "@lume/kiwi" +import Debug from "debug" -export class Footprint extends PrimitiveComponent {} +const debug = Debug("tscircuit:core:footprint") + +export class Footprint extends PrimitiveComponent { + /** + * A footprint is a constrainedlayout, the db elements are adjusted according + * to any constraints that are defined. + */ + doInitialPcbFootprintLayout() { + const constraints = this.children.filter( + (child) => child.componentName === "Constraint", + ) as Constraint[] + + if (constraints.length === 0) return + + const involvedComponents = constraints + .flatMap( + (constraint) => + constraint._getAllReferencedComponents().componentsWithSelectors, + ) + .map(({ component, selector }) => ({ + component, + selector, + bounds: component._getCircuitJsonBounds(), + })) + + function getComponentDetails(selector: string) { + return involvedComponents.find(({ selector: s }) => s === selector) + } + + const solver = new kiwi.Solver() + + const kVars: { [varName: string]: kiwi.Variable } = {} + function getKVar(name: string) { + if (!(name in kVars)) { + kVars[name] = new kiwi.Variable(name) + } + return kVars[name] + } + + // 1. Create kiwi variables to represent each component's center + for (const { selector, bounds } of involvedComponents) { + const kvx = getKVar(`${selector}_x`) + const kvy = getKVar(`${selector}_y`) + solver.addEditVariable(kvx, kiwi.Strength.weak) + solver.addEditVariable(kvy, kiwi.Strength.weak) + solver.suggestValue(kvx, bounds.center.x) + solver.suggestValue(kvy, bounds.center.y) + } + + // 2. Add kiwi constraints using the parsed constraint properties + for (const constraint of constraints) { + const props = constraint._parsedProps + + if ("xdist" in props) { + const { xdist, left, right, edgeToEdge, centerToCenter } = props + const leftVar = getKVar(`${left}_x`) + const rightVar = getKVar(`${right}_x`) + const leftBounds = getComponentDetails(left)?.bounds! + const rightBounds = getComponentDetails(right)?.bounds! + + if (centerToCenter) { + // right - left = xdist + const expr = new kiwi.Expression(rightVar, [-1, leftVar]) + solver.addConstraint( + new kiwi.Constraint( + expr, + kiwi.Operator.Eq, + props.xdist, + kiwi.Strength.required, + ), + ) + } else if (edgeToEdge) { + // rightEdge - leftEdge = xdist + // right + rightBounds.width/2 - left - leftBounds.width/2 = xdist + const expr = new kiwi.Expression( + rightVar, + rightBounds.width / 2, + [-1, leftVar], + -leftBounds.width / 2, + ) + solver.addConstraint( + new kiwi.Constraint( + expr, + kiwi.Operator.Eq, + props.xdist, + kiwi.Strength.required, + ), + ) + } + } + + // 3. Solve the system of equations + solver.updateVariables() + if (debug.enabled) { + console.log("Solution to layout constraints:") + console.table( + Object.entries(kVars).map(([key, kvar]) => ({ + var: key, + val: kvar.value(), + })), + ) + } + + // 3.1 Compute the global offset. There are different ways to do this: + // - If any component has a fixed position, then that can be used as the + // origin to determine the offset of all other components + // - If no component has a fixed position, then we recenter everything + // using the new bounds of all the involved components + + // TODO determine if there's a fixed component + + // Determine the new bounds all the involved components and compute the + // bounds of this footprint + const bounds = { + left: Infinity, + right: -Infinity, + top: -Infinity, + bottom: Infinity, + } + for (const { + selector, + bounds: { width, height }, + } of involvedComponents) { + const kvx = getKVar(`${selector}_x`) + const kvy = getKVar(`${selector}_y`) + + const newLeft = kvx.value() - width / 2 + const newRight = kvx.value() + width / 2 + const newTop = kvy.value() + height / 2 + const newBottom = kvy.value() - height / 2 + + bounds.left = Math.min(bounds.left, newLeft) + bounds.right = Math.max(bounds.right, newRight) + bounds.top = Math.max(bounds.top, newTop) + bounds.bottom = Math.min(bounds.bottom, newBottom) + } + + // Compute the global offset, we can use this to recenter each component + const globalOffset = { + x: -(bounds.right + bounds.left) / 2, + y: -(bounds.top + bounds.bottom) / 2, + } + + // 4. Update the component positions + for (const { component, selector } of involvedComponents) { + const kvx = getKVar(`${selector}_x`) + const kvy = getKVar(`${selector}_y`) + component._setPositionFromLayout({ + x: kvx.value() + globalOffset.x, + y: kvy.value() + globalOffset.y, + }) + } + } + } +} diff --git a/lib/components/primitive-components/Net.ts b/lib/components/primitive-components/Net.ts index 47d87ed..88bb677 100644 --- a/lib/components/primitive-components/Net.ts +++ b/lib/components/primitive-components/Net.ts @@ -109,10 +109,10 @@ export class Net extends PrimitiveComponent { 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(), + port._getGlobalPcbPositionBeforeLayout(), ) const Bpositions: Array<{ x: number; y: number }> = B.ports.map((port) => - port.getGlobalPcbPosition(), + port._getGlobalPcbPositionBeforeLayout(), ) let closestDist = Infinity diff --git a/lib/components/primitive-components/PlatedHole.ts b/lib/components/primitive-components/PlatedHole.ts index 2b26461..81c116c 100644 --- a/lib/components/primitive-components/PlatedHole.ts +++ b/lib/components/primitive-components/PlatedHole.ts @@ -49,7 +49,7 @@ export class PlatedHole extends PrimitiveComponent { const { db } = this.root! const { _parsedProps: props } = this if (!props.portHints) return - const position = this.getGlobalPcbPosition() + const position = this._getGlobalPcbPositionBeforeLayout() if (props.shape === "circle") { const plated_hole_input: PCBPlatedHoleInput = { pcb_component_id: this.parent?.pcb_component_id!, diff --git a/lib/components/primitive-components/Port.ts b/lib/components/primitive-components/Port.ts index 9c0672d..482177e 100644 --- a/lib/components/primitive-components/Port.ts +++ b/lib/components/primitive-components/Port.ts @@ -32,7 +32,7 @@ export class Port extends PrimitiveComponent { this.matchedComponents = [] } - getGlobalPcbPosition(): { x: number; y: number } { + _getGlobalPcbPositionBeforeLayout(): { x: number; y: number } { const matchedPcbElm = this.matchedComponents.find((c) => c.isPcbPrimitive) if (!matchedPcbElm) { @@ -41,10 +41,10 @@ export class Port extends PrimitiveComponent { ) } - return matchedPcbElm?.getGlobalPcbPosition() ?? { x: 0, y: 0 } + return matchedPcbElm?._getGlobalPcbPositionBeforeLayout() ?? { x: 0, y: 0 } } - getGlobalSchematicPosition(): { x: number; y: number } { + _getGlobalSchematicPositionBeforeLayout(): { x: number; y: number } { if (!this.schematicSymbolPortDef) { return applyToPoint(this.parent!.computeSchematicGlobalTransform(), { x: 0, @@ -146,11 +146,6 @@ export class Port extends PrimitiveComponent { this.source_component_id = this.parent?.source_component_id } - /** - * For PcbPorts, we use the parent attachment phase to determine where to place - * the pcb_port (prior to this phase, the smtpad/platedhole isn't guaranteed - * to exist) - */ doInitialPcbPortRender(): void { const { db } = this.root! const { matchedComponents } = this @@ -173,19 +168,19 @@ export class Port extends PrimitiveComponent { const pcbMatch: any = pcbMatches[0] - if ("getGlobalPcbPosition" in pcbMatch) { + if ("_getCircuitJsonBounds" in pcbMatch) { const pcb_port = db.pcb_port.insert({ pcb_component_id: this.parent?.pcb_component_id!, layers: ["top"], - ...pcbMatch.getGlobalPcbPosition(), + ...pcbMatch._getCircuitJsonBounds().center, source_port_id: this.source_port_id!, }) this.pcb_port_id = pcb_port.pcb_port_id } else { throw new Error( - `${pcbMatch.getString()} does not have a getGlobalPcbPosition method (needed for pcb_port placement)`, + `${pcbMatch.getString()} does not have a _getGlobalPcbPositionBeforeLayout method (needed for pcb_port placement)`, ) } } @@ -196,8 +191,8 @@ export class Port extends PrimitiveComponent { if (!this.parent) return - const center = this.getGlobalSchematicPosition() - const parentCenter = this.parent?.getGlobalSchematicPosition() + const center = this._getGlobalSchematicPositionBeforeLayout() + const parentCenter = this.parent?._getGlobalSchematicPositionBeforeLayout() this.facingDirection = getRelativeDirection(parentCenter, center) diff --git a/lib/components/primitive-components/SilkscreenPath.ts b/lib/components/primitive-components/SilkscreenPath.ts index c95804b..3c9a354 100644 --- a/lib/components/primitive-components/SilkscreenPath.ts +++ b/lib/components/primitive-components/SilkscreenPath.ts @@ -24,7 +24,7 @@ export class SilkscreenPath extends PrimitiveComponent< ) } - const transform = this.computePcbGlobalTransform() + const transform = this._computePcbGlobalTransformBeforeLayout() const pcb_silkscreen_path = db.pcb_silkscreen_path.insert({ pcb_component_id: this.parent?.pcb_component_id!, diff --git a/lib/components/primitive-components/SmtPad.ts b/lib/components/primitive-components/SmtPad.ts index 90dabef..a3d3cd6 100644 --- a/lib/components/primitive-components/SmtPad.ts +++ b/lib/components/primitive-components/SmtPad.ts @@ -32,8 +32,8 @@ export class SmtPad extends PrimitiveComponent { } doInitialPortMatching(): void { - const parentPorts = (this.parent?.children ?? []).filter( - (c) => c.componentName === "Port", + const parentPorts = this.getPrimitiveContainer()?.selectAll( + "port", ) as Port[] if (!this.props.portHints) { @@ -53,8 +53,10 @@ export class SmtPad extends PrimitiveComponent { const { db } = this.root! const { _parsedProps: props } = this if (!props.portHints) return - const position = this.getGlobalPcbPosition() - const decomposedMat = decomposeTSR(this.computePcbGlobalTransform()) + const position = this._getGlobalPcbPositionBeforeLayout() + const decomposedMat = decomposeTSR( + this._computePcbGlobalTransformBeforeLayout(), + ) const isRotated90 = Math.abs(decomposedMat.rotation.angle * (180 / Math.PI) - 90) < 0.01 let pcb_smtpad: PCBSMTPad | null = null @@ -94,4 +96,52 @@ export class SmtPad extends PrimitiveComponent { this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id } } + + _getCircuitJsonBounds(): { + center: { x: number; y: number } + bounds: { left: number; top: number; right: number; bottom: number } + width: number + height: number + } { + const { db } = this.root! + const smtpad = db.pcb_smtpad.get(this.pcb_smtpad_id!)! + + if (smtpad.shape === "rect") { + return { + center: { x: smtpad.x, y: smtpad.y }, + bounds: { + left: smtpad.x - smtpad.width / 2, + top: smtpad.y - smtpad.height / 2, + right: smtpad.x + smtpad.width / 2, + bottom: smtpad.y + smtpad.height / 2, + }, + width: smtpad.width, + height: smtpad.height, + } + } + if (smtpad.shape === "circle") { + return { + center: { x: smtpad.x, y: smtpad.y }, + bounds: { + left: smtpad.x - smtpad.radius, + top: smtpad.y - smtpad.radius, + right: smtpad.x + smtpad.radius, + bottom: smtpad.y + smtpad.radius, + }, + width: smtpad.radius * 2, + height: smtpad.radius * 2, + } + } + throw new Error( + `circuitJson bounds calculation not implemented for shape "${(smtpad as any).shape}"`, + ) + } + + _setPositionFromLayout(newCenter: { x: number; y: number }) { + const { db } = this.root! + db.pcb_smtpad.update(this.pcb_smtpad_id!, { + x: newCenter.x, + y: newCenter.y, + }) + } } diff --git a/lib/components/primitive-components/Trace.ts b/lib/components/primitive-components/Trace.ts index 472adbe..c64d354 100644 --- a/lib/components/primitive-components/Trace.ts +++ b/lib/components/primitive-components/Trace.ts @@ -38,7 +38,7 @@ type PcbRouteObjective = | { layers: string[]; x: number; y: number; via?: boolean } const portToObjective = (port: Port): PcbRouteObjective => { - const portPosition = port.getGlobalPcbPosition() + const portPosition = port._getGlobalPcbPositionBeforeLayout() return { ...portPosition, layers: port.getAvailablePcbLayers(), @@ -476,7 +476,7 @@ export class Trace extends PrimitiveComponent { for (const { port } of ports) { connection.pointsToConnect.push( projectPointInDirection( - port.getGlobalSchematicPosition(), + port._getGlobalSchematicPositionBeforeLayout(), port.facingDirection!, 0.1501, ), diff --git a/lib/components/primitive-components/TraceHint.ts b/lib/components/primitive-components/TraceHint.ts index cad480d..d09c41f 100644 --- a/lib/components/primitive-components/TraceHint.ts +++ b/lib/components/primitive-components/TraceHint.ts @@ -46,7 +46,7 @@ export class TraceHint extends PrimitiveComponent { if (!offsets) return [] - const globalTransform = this.computePcbGlobalTransform() + const globalTransform = this._computePcbGlobalTransformBeforeLayout() return offsets.map( (offset): RouteHintPoint => ({ diff --git a/lib/fiber/intrinsic-jsx.ts b/lib/fiber/intrinsic-jsx.ts index 469e9e2..7868d4f 100644 --- a/lib/fiber/intrinsic-jsx.ts +++ b/lib/fiber/intrinsic-jsx.ts @@ -39,6 +39,8 @@ declare global { pcbtrace: Props.PcbTraceProps fabricationnotetext: Props.FabricationNoteTextProps fabricationnotepath: Props.FabricationNotePathProps + constraint: Props.ConstraintProps + constrainedlayout: Props.ConstrainedLayoutProps jscad: any } } diff --git a/lib/utils/getClosest.ts b/lib/utils/getClosest.ts index fb489e6..f996de9 100644 --- a/lib/utils/getClosest.ts +++ b/lib/utils/getClosest.ts @@ -1,10 +1,16 @@ export type PointLike = - | { getGlobalPcbPosition: () => { x: number; y: number } } + | { _getGlobalPcbPositionBeforeLayout: () => { x: number; y: number } } | { x: number; y: number } const getDistance = (a: PointLike, b: PointLike) => { - const aPos = "getGlobalPcbPosition" in a ? a.getGlobalPcbPosition() : a - const bPos = "getGlobalPcbPosition" in b ? b.getGlobalPcbPosition() : b + const aPos = + "_getGlobalPcbPositionBeforeLayout" in a + ? a._getGlobalPcbPositionBeforeLayout() + : a + const bPos = + "_getGlobalPcbPositionBeforeLayout" in b + ? b._getGlobalPcbPositionBeforeLayout() + : b return Math.sqrt((aPos.x - bPos.x) ** 2 + (aPos.y - bPos.y) ** 2) } diff --git a/package.json b/package.json index 46e21dd..dad27c5 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,12 @@ "@tscircuit/layout": "^0.0.28", "@tscircuit/log-soup": "^1.0.2", "@types/bun": "latest", + "@types/debug": "^4.1.12", "@types/react": "^18.3.3", "@types/react-reconciler": "^0.28.8", "bun-match-svg": "0.0.2", "circuit-to-svg": "^0.0.18", + "debug": "^4.3.6", "howfat": "^0.3.8", "looks-same": "^9.0.1", "tsup": "^8.2.4" @@ -31,8 +33,9 @@ "typescript": "^5.0.0" }, "dependencies": { + "@lume/kiwi": "^0.4.3", "@tscircuit/infgrid-ijump-astar": "^0.0.6", - "@tscircuit/props": "^0.0.52", + "@tscircuit/props": "^0.0.58", "@tscircuit/soup": "^0.0.58", "@tscircuit/soup-util": "0.0.18", "footprinter": "^0.0.44", diff --git a/tests/components/base-components/normal-component.test.tsx b/tests/components/base-components/normal-component.test.tsx index 37ed8f4..13ac0f5 100644 --- a/tests/components/base-components/normal-component.test.tsx +++ b/tests/components/base-components/normal-component.test.tsx @@ -27,10 +27,9 @@ it("should be able to get ports from Footprint class", () => { footprint.add( new SmtPad({ - pcbX: 0, - pcbY: 0, layer: "top", shape: "circle", + radius: 0.2, portHints: ["pin1"], }), ) diff --git a/tests/components/normal-components/board-layout.test.tsx b/tests/components/normal-components/board-layout.test.tsx index d6517f5..525dbf8 100644 --- a/tests/components/normal-components/board-layout.test.tsx +++ b/tests/components/normal-components/board-layout.test.tsx @@ -37,8 +37,8 @@ test("board with manual layout edits", () => { expect(resistor).not.toBeNull() expect(capacitor).not.toBeNull() - const resistorPosition = resistor!.getGlobalPcbPosition() - const capacitorPosition = capacitor!.getGlobalPcbPosition() + const resistorPosition = resistor!._getGlobalPcbPositionBeforeLayout() + const capacitorPosition = capacitor!._getGlobalPcbPositionBeforeLayout() expect(resistorPosition.x).toBeCloseTo(5, 1) expect(resistorPosition.y).toBeCloseTo(5, 1) @@ -48,7 +48,7 @@ test("board with manual layout edits", () => { const r1SmtpadPositions = project .selectAll(".R1 > smtpad") - .map((elm) => elm.getGlobalPcbPosition()) + .map((elm) => elm._getGlobalPcbPositionBeforeLayout()) expect(Math.abs(r1SmtpadPositions[0].x - 5)).toBeLessThan(1) expect(Math.abs(r1SmtpadPositions[1].x - 5)).toBeLessThan(1) diff --git a/tests/components/primitive-components/__snapshots__/footprint-layout-pcb.snap.svg b/tests/components/primitive-components/__snapshots__/footprint-layout-pcb.snap.svg new file mode 100644 index 0000000..244b0a1 --- /dev/null +++ b/tests/components/primitive-components/__snapshots__/footprint-layout-pcb.snap.svg @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/tests/components/primitive-components/footprint-layout.test.tsx b/tests/components/primitive-components/footprint-layout.test.tsx new file mode 100644 index 0000000..fd70548 --- /dev/null +++ b/tests/components/primitive-components/footprint-layout.test.tsx @@ -0,0 +1,49 @@ +import { test, expect } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("footprint layout", () => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + } + /> + , + ) + + circuit.render() + + const smtpads = circuit.db.pcb_smtpad.list() + + expect(Math.abs(smtpads[0].x - smtpads[1].x)).toBeCloseTo(4, 1) + + // Should be centered about 0 + expect(Math.abs(smtpads[0].x + smtpads[1].x) / 2).toBeCloseTo(0, 1) + + const pcbPorts = circuit.db.pcb_port.list() + + expect(pcbPorts.length).toBe(2) + + expect(pcbPorts[0].x).toBeOneOf([-2, 2]) + expect(pcbPorts[1].x).toBeOneOf([-2, 2]) + + expect(circuit.getCircuitJson()).toMatchPcbSnapshot(import.meta.path) +})