diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index 84c2b0e02..b06d486fc 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -1,3 +1,4 @@ +import { getBoardCenterFromAnchor } from "../../utils/boards/get-board-center-from-anchor" import { boardProps } from "@tscircuit/props" import { type Matrix, identity } from "transformation-matrix" import { Group } from "../primitive-components/Group/Group" @@ -157,6 +158,14 @@ export class Board extends Group { updateBounds(pcbGroup.center, pcbGroup.width, pcbGroup.height) } + if (props.boardAnchorPosition) { + const { x, y } = props.boardAnchorPosition + minX = Math.min(minX, x) + minY = Math.min(minY, y) + maxX = Math.max(maxX, x) + maxY = Math.max(maxY, y) + } + // Add padding around components const padding = 2 @@ -165,7 +174,7 @@ export class Board extends Group { const computedHeight = hasComponents ? maxY - minY + padding * 2 : 0 // Center the board around the components or use (0,0) for empty boards - const center = { + let center = { x: hasComponents ? (minX + maxX) / 2 + (props.outlineOffsetX ?? 0) : (props.outlineOffsetX ?? 0), @@ -174,7 +183,6 @@ export class Board extends Group { : (props.outlineOffsetY ?? 0), } - // Update the board dimensions, preserving any explicit dimension provided // by the user while auto-calculating the missing one. const finalWidth = props.width ?? computedWidth const finalHeight = props.height ?? computedHeight @@ -266,6 +274,17 @@ export class Board extends Group { y: (props.pcbY ?? 0) + (props.outlineOffsetY ?? 0), } + const { boardAnchorPosition, boardAnchorAlignment } = props + + if (boardAnchorPosition) { + center = getBoardCenterFromAnchor({ + boardAnchorPosition, + boardAnchorAlignment: boardAnchorAlignment ?? "center", + width: computedWidth, + height: computedHeight, + }) + } + // Compute width and height from outline if not provided if (props.outline) { const xValues = props.outline.map((point) => point.x) diff --git a/lib/utils/boards/get-board-center-from-anchor.ts b/lib/utils/boards/get-board-center-from-anchor.ts new file mode 100644 index 000000000..d7c41051f --- /dev/null +++ b/lib/utils/boards/get-board-center-from-anchor.ts @@ -0,0 +1,57 @@ +export const getBoardCenterFromAnchor = ({ + boardAnchorPosition, + boardAnchorAlignment, + width, + height, +}: { + boardAnchorPosition: { x: number; y: number } + boardAnchorAlignment: string + width: number + height: number +}) => { + const { x: ax, y: ay } = boardAnchorPosition + + let cx = ax + let cy = ay + + switch (boardAnchorAlignment) { + case "top_left": + cx = ax + width / 2 + cy = ay - height / 2 + break + case "top_right": + cx = ax - width / 2 + cy = ay - height / 2 + break + case "bottom_left": + cx = ax + width / 2 + cy = ay + height / 2 + break + case "bottom_right": + cx = ax - width / 2 + cy = ay + height / 2 + break + case "top": + cx = ax + cy = ay - height / 2 + break + case "bottom": + cx = ax + cy = ay + height / 2 + break + case "left": + cx = ax + width / 2 + cy = ay + break + case "right": + cx = ax - width / 2 + cy = ay + break + case "center": + default: + // center is the default + break + } + + return { x: cx, y: cy } +} diff --git a/tests/examples/__snapshots__/example34-board-anchor1-pcb.snap.svg b/tests/examples/__snapshots__/example34-board-anchor1-pcb.snap.svg new file mode 100644 index 000000000..e608ec587 --- /dev/null +++ b/tests/examples/__snapshots__/example34-board-anchor1-pcb.snap.svg @@ -0,0 +1 @@ +R1(0,0)board.anchor: top_left @ (0,0) \ No newline at end of file diff --git a/tests/examples/__snapshots__/example34-board-anchor2-pcb.snap.svg b/tests/examples/__snapshots__/example34-board-anchor2-pcb.snap.svg new file mode 100644 index 000000000..286169d81 --- /dev/null +++ b/tests/examples/__snapshots__/example34-board-anchor2-pcb.snap.svg @@ -0,0 +1 @@ +R1(0,0)board.anchor: bottom_right @ (10,10) \ No newline at end of file diff --git a/tests/examples/__snapshots__/example34-board-anchor3-pcb.snap.svg b/tests/examples/__snapshots__/example34-board-anchor3-pcb.snap.svg new file mode 100644 index 000000000..1b1ff458b --- /dev/null +++ b/tests/examples/__snapshots__/example34-board-anchor3-pcb.snap.svg @@ -0,0 +1 @@ +R1(0,0)board.anchor: center @ (5,5) \ No newline at end of file diff --git a/tests/examples/__snapshots__/example34-board-anchor4-autosize-pcb.snap.svg b/tests/examples/__snapshots__/example34-board-anchor4-autosize-pcb.snap.svg new file mode 100644 index 000000000..2cd79f531 --- /dev/null +++ b/tests/examples/__snapshots__/example34-board-anchor4-autosize-pcb.snap.svg @@ -0,0 +1 @@ +R1R2(0,0)board.anchor: top_left @ (0,0) [autosized] \ No newline at end of file diff --git a/tests/examples/example34-board-anchor1.test.tsx b/tests/examples/example34-board-anchor1.test.tsx new file mode 100644 index 000000000..ada4d9cbf --- /dev/null +++ b/tests/examples/example34-board-anchor1.test.tsx @@ -0,0 +1,31 @@ +import { describe, it, expect } from "bun:test" +import { getTestFixture } from "../fixtures/get-test-fixture" + +describe("Board Anchor", () => { + it("should anchor the board to the top-left", async () => { + const { circuit } = getTestFixture() + const board = ( + + + + + + + ) + + circuit.add(board) + + await circuit.render() + + expect(circuit).toMatchPcbSnapshot(import.meta.path) + }) +}) diff --git a/tests/examples/example34-board-anchor2.test.tsx b/tests/examples/example34-board-anchor2.test.tsx new file mode 100644 index 000000000..d6031d41c --- /dev/null +++ b/tests/examples/example34-board-anchor2.test.tsx @@ -0,0 +1,32 @@ +import { describe, it, expect } from "bun:test" +import { getTestFixture } from "../fixtures/get-test-fixture" + +describe("Board Anchor", () => { + it("should anchor the board to the bottom-right", async () => { + const { circuit } = getTestFixture() + const board = ( + + + + + + + + ) + + circuit.add(board) + + await circuit.render() + + expect(circuit).toMatchPcbSnapshot(import.meta.path) + }) +}) diff --git a/tests/examples/example34-board-anchor3.test.tsx b/tests/examples/example34-board-anchor3.test.tsx new file mode 100644 index 000000000..fadeb99c6 --- /dev/null +++ b/tests/examples/example34-board-anchor3.test.tsx @@ -0,0 +1,28 @@ +import { describe, it, expect } from "bun:test" +import { getTestFixture } from "../fixtures/get-test-fixture" + +describe("Board Anchor", () => { + it("should anchor the board to the center", async () => { + const { circuit } = getTestFixture() + const board = ( + + + + + + + + ) + + circuit.add(board) + + await circuit.render() + + expect(circuit).toMatchPcbSnapshot(import.meta.path) + }) +}) diff --git a/tests/examples/example34-board-anchor4-autosize.test.tsx b/tests/examples/example34-board-anchor4-autosize.test.tsx new file mode 100644 index 000000000..56e0e23c2 --- /dev/null +++ b/tests/examples/example34-board-anchor4-autosize.test.tsx @@ -0,0 +1,42 @@ +import { describe, it, expect } from "bun:test" +import { getTestFixture } from "../fixtures/get-test-fixture" + +describe("Board Anchor", () => { + it("should auto-size the board and anchor correctly", async () => { + const { circuit } = getTestFixture() + const board = ( + + + + + + + + ) + + circuit.add(board) + + await circuit.render() + + expect(circuit).toMatchPcbSnapshot(import.meta.path) + }) +})