diff --git a/lib/components/base-components/Renderable.ts b/lib/components/base-components/Renderable.ts index 2f316af2..f5db2869 100644 --- a/lib/components/base-components/Renderable.ts +++ b/lib/components/base-components/Renderable.ts @@ -22,6 +22,7 @@ export const orderedRenderPhases = [ "SchematicPortRender", "SchematicLayout", "SchematicTraceRender", + "PcbBoardAutoSize", "PcbComponentRender", "PcbPrimitiveRender", "PcbFootprintLayout", diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index 22e51536..067bb439 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -3,6 +3,7 @@ import type { z } from "zod" import { NormalComponent } from "../base-components/NormalComponent/NormalComponent" import { identity, type Matrix } from "transformation-matrix" import { Group } from "../primitive-components/Group/Group" +import { getBoundsOfPcbComponents } from "lib/utils/get-bounds-of-pcb-components" export class Board extends Group { pcb_board_id: string | null = null @@ -31,6 +32,40 @@ export class Board extends Group { return ["top", "bottom", "inner1", "inner2"] } + doInitialPcbBoardAutoSize(): void { + if (this.root?.pcbDisabled) return + + // Skip auto-size if dimensions already specified + if ( + (this._parsedProps.width && this._parsedProps.height) || + this._parsedProps.outline + ) { + // console.log("Skipping auto-size - dimensions specified", this._parsedProps) + console.log("Skipping auto-size because dimensions are specified") + return + } + + const bounds = getBoundsOfPcbComponents(this.children) + + if (bounds.width === 0 || bounds.height === 0) { + console.log("No valid components found for auto-sizing") + return + } + + const padding = 2 + this._parsedProps = { + ...this._parsedProps, + width: bounds.width + padding * 2, + height: bounds.height + padding * 2, + } + + // Set board center based on component bounds + this._parsedProps.pcbX = (bounds.minX + bounds.maxX) / 2 + this._parsedProps.pcbY = (bounds.minY + bounds.maxY) / 2 + + // console.log("Auto-sized dimensions:", bounds.width, bounds.height) + // console.log("Center position:", this._parsedProps.pcbX, this._parsedProps.pcbY) + } doInitialPcbComponentRender(): void { if (this.root?.pcbDisabled) return const { db } = this.root! diff --git a/lib/utils/get-bounds-of-pcb-components.ts b/lib/utils/get-bounds-of-pcb-components.ts index 0bcc28b7..2f339cae 100644 --- a/lib/utils/get-bounds-of-pcb-components.ts +++ b/lib/utils/get-bounds-of-pcb-components.ts @@ -7,6 +7,8 @@ export function getBoundsOfPcbComponents(components: PrimitiveComponent[]) { let maxY = -Infinity for (const child of components) { + let bounds + if (child.isPcbPrimitive) { const { x, y } = child._getGlobalPcbPositionBeforeLayout() const { width, height } = child.getPcbSize() @@ -14,28 +16,38 @@ export function getBoundsOfPcbComponents(components: PrimitiveComponent[]) { minY = Math.min(minY, y - height / 2) maxX = Math.max(maxX, x + width / 2) maxY = Math.max(maxY, y + height / 2) + continue + } + + if (child.pcb_component_id) { + bounds = child._getPcbCircuitJsonBounds() } else if (child.componentName === "Footprint") { - const childBounds = getBoundsOfPcbComponents(child.children) + bounds = getBoundsOfPcbComponents(child.children) + } else if (child.children.length > 0) { + bounds = getBoundsOfPcbComponents(child.children) + } - minX = Math.min(minX, childBounds.minX) - minY = Math.min(minY, childBounds.minY) - maxX = Math.max(maxX, childBounds.maxX) - maxY = Math.max(maxY, childBounds.maxY) + if (bounds) { + if ("bounds" in bounds) { + minX = Math.min(minX, bounds.bounds.left) + minY = Math.min(minY, bounds.bounds.top) + maxX = Math.max(maxX, bounds.bounds.right) + maxY = Math.max(maxY, bounds.bounds.bottom) + } else { + minX = Math.min(minX, bounds.minX) + minY = Math.min(minY, bounds.minY) + maxX = Math.max(maxX, bounds.maxX) + maxY = Math.max(maxY, bounds.maxY) + } } } - let width = maxX - minX - let height = maxY - minY - - if (width < 0) width = 0 - if (height < 0) height = 0 - return { - minX, - minY, - maxX, - maxY, - width, - height, + minX: isFinite(minX) ? minX : 0, + minY: isFinite(minY) ? minY : 0, + maxX: isFinite(maxX) ? maxX : 0, + maxY: isFinite(maxY) ? maxY : 0, + width: Math.max(0, maxX - minX), + height: Math.max(0, maxY - minY), } } diff --git a/tests/components/normal-components/board-auto-size.test.tsx b/tests/components/normal-components/board-auto-size.test.tsx new file mode 100644 index 00000000..cb8b3a49 --- /dev/null +++ b/tests/components/normal-components/board-auto-size.test.tsx @@ -0,0 +1,81 @@ +import { test, expect } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("board auto-sizes when no dimensions provided", () => { + const { circuit } = getTestFixture() + + circuit.add( + + + + , + ) + + circuit.render() + + const pcb_board = circuit.db.pcb_board.list()[0] + + // Board should be larger than component bounds + expect(pcb_board.width).toBeGreaterThan(10) + expect(pcb_board.height).toBeGreaterThan(10) +}) + +test("board respects explicit dimensions", () => { + const { circuit } = getTestFixture() + circuit.add( + + + , + ) + circuit.render() + const pcb_board = circuit.db.pcb_board.list()[0] + expect(pcb_board.width).toBe(50) + expect(pcb_board.height).toBe(50) +}) + +test("board auto-sizes with nested components", () => { + const { circuit } = getTestFixture() + circuit.add( + + + + , + ) + circuit.render() + const pcb_board = circuit.db.pcb_board.list()[0] + + // Should be at least 20mm (component spread) + padding + expect(pcb_board.width).toBeGreaterThan(22) + expect(pcb_board.height).toBeGreaterThan(22) +}) + +test("board centers around components", () => { + const { circuit } = getTestFixture() + circuit.add( + + + , + ) + circuit.render() + const pcb_board = circuit.db.pcb_board.list()[0] + expect(pcb_board.center.x).toBe(5) + expect(pcb_board.center.y).toBe(0) +})