From 6c3a77621b2b5cafb65c80cfa7f6d9035fbf1fa7 Mon Sep 17 00:00:00 2001 From: Abhinav <85792055+AbhinavTheDev@users.noreply.github.com> Date: Sun, 26 Jan 2025 17:28:09 +0530 Subject: [PATCH 1/8] add board-auto-size --- lib/components/base-components/Renderable.ts | 2 + lib/components/normal-components/Board.ts | 66 ++++++++++--------- lib/utils/get-bounds-of-pcb-components.ts | 53 ++++++++++----- .../board-auto-size.test.tsx | 64 ++++++++++++++++++ 4 files changed, 136 insertions(+), 49 deletions(-) create mode 100644 tests/components/normal-components/board-auto-size.test.tsx diff --git a/lib/components/base-components/Renderable.ts b/lib/components/base-components/Renderable.ts index 2f316af2..c2ec3723 100644 --- a/lib/components/base-components/Renderable.ts +++ b/lib/components/base-components/Renderable.ts @@ -1,3 +1,4 @@ +// lib>components>base-components>Renderable.ts import type { PcbManualEditConflictError, PcbPlacementError, @@ -22,6 +23,7 @@ export const orderedRenderPhases = [ "SchematicPortRender", "SchematicLayout", "SchematicTraceRender", + "BoardAutoSize", "PcbComponentRender", "PcbPrimitiveRender", "PcbFootprintLayout", diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index 22e51536..be341486 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -1,9 +1,10 @@ +// lib>components>normal-components>Board.ts import { boardProps } from "@tscircuit/props" 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,47 +32,50 @@ export class Board extends Group { return ["top", "bottom", "inner1", "inner2"] } - doInitialPcbComponentRender(): void { + doInitialBoardAutoSize(): void { if (this.root?.pcbDisabled) return - const { db } = this.root! - const { _parsedProps: props } = this - - // If outline is not provided, width and height must be specified - if (!props.outline && (!props.width || !props.height)) { - throw new Error("Board width and height or an outline are required") + + // 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 } - // Compute width and height from outline if not provided - let computedWidth = props.width - let computedHeight = props.height - if (props.outline) { - const xValues = props.outline.map((point) => point.x) - const yValues = props.outline.map((point) => point.y) + const bounds = getBoundsOfPcbComponents(this.children) + + if (bounds.width === 0 || bounds.height === 0) { + console.log("No valid components found for auto-sizing") + return + } - const minX = Math.min(...xValues) - const maxX = Math.max(...xValues) - const minY = Math.min(...yValues) - const maxY = Math.max(...yValues) + const padding = 2 + this._parsedProps.width = bounds.width + (padding * 2) + this._parsedProps.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) + } - computedWidth = maxX - minX - computedHeight = maxY - minY - } + doInitialPcbComponentRender(): void { + if (this.root?.pcbDisabled) return + const { db } = this.root! + const { _parsedProps: props } = this const pcb_board = db.pcb_board.insert({ center: { - x: (props.pcbX ?? 0) + (props.outlineOffsetX ?? 0), - y: (props.pcbY ?? 0) + (props.outlineOffsetY ?? 0), + x: props.pcbX ?? 0, // Use calculated center + y: props.pcbY ?? 0 }, - thickness: this.boardThickness, num_layers: this.allLayers.length, - - width: computedWidth!, - height: computedHeight!, - outline: props.outline?.map((point) => ({ - x: point.x + (props.outlineOffsetX ?? 0), - y: point.y + (props.outlineOffsetY ?? 0), - })), + width: props.width!, + height: props.height!, + outline: props.outline }) this.pcb_board_id = pcb_board.pcb_board_id! diff --git a/lib/utils/get-bounds-of-pcb-components.ts b/lib/utils/get-bounds-of-pcb-components.ts index 0bcc28b7..780f556c 100644 --- a/lib/utils/get-bounds-of-pcb-components.ts +++ b/lib/utils/get-bounds-of-pcb-components.ts @@ -1,3 +1,4 @@ +// lib>utils>get-bounds-of-pcb-components.ts import type { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent" export function getBoundsOfPcbComponents(components: PrimitiveComponent[]) { @@ -7,6 +8,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 +17,42 @@ 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) - } else if (child.componentName === "Footprint") { - const childBounds = 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) + continue + } + + if(child.pcb_component_id) { + bounds = child._getPcbCircuitJsonBounds() + } + + else if (child.componentName === "Footprint") { + bounds = getBoundsOfPcbComponents(child.children) } - } - let width = maxX - minX - let height = maxY - minY + else if (child.children.length > 0) { + bounds = getBoundsOfPcbComponents(child.children) + } - if (width < 0) width = 0 - if (height < 0) height = 0 + 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) + } + } + } 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..ca1b102e --- /dev/null +++ b/tests/components/normal-components/board-auto-size.test.tsx @@ -0,0 +1,64 @@ +// tests>components>normal-components>board-auto-size.test.tsx +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) +}) \ No newline at end of file From 76dba15da88b891cb53e67e8ac67129b5c4f1e19 Mon Sep 17 00:00:00 2001 From: Abhinav <85792055+AbhinavTheDev@users.noreply.github.com> Date: Sun, 26 Jan 2025 18:05:06 +0530 Subject: [PATCH 2/8] format code --- lib/components/base-components/Renderable.ts | 197 +++++++++--------- lib/components/normal-components/Board.ts | 86 ++++---- lib/utils/get-bounds-of-pcb-components.ts | 65 +++--- .../board-auto-size.test.tsx | 89 ++++---- 4 files changed, 225 insertions(+), 212 deletions(-) diff --git a/lib/components/base-components/Renderable.ts b/lib/components/base-components/Renderable.ts index c2ec3723..c154d426 100644 --- a/lib/components/base-components/Renderable.ts +++ b/lib/components/base-components/Renderable.ts @@ -1,12 +1,11 @@ -// lib>components>base-components>Renderable.ts import type { PcbManualEditConflictError, PcbPlacementError, PcbTraceError, -} from "circuit-json" -import Debug from "debug" +} from "circuit-json"; +import Debug from "debug"; -const debug = Debug("tscircuit:renderable") +const debug = Debug("tscircuit:renderable"); export const orderedRenderPhases = [ "ReactSubtreesRender", @@ -36,83 +35,83 @@ export const orderedRenderPhases = [ "PcbRouteNetIslands", "CadModelRender", "PartsEngineRender", -] as const +] as const; -export type RenderPhase = (typeof orderedRenderPhases)[number] +export type RenderPhase = (typeof orderedRenderPhases)[number]; export type RenderPhaseFn = | `doInitial${K}` | `update${K}` - | `remove${K}` + | `remove${K}`; export type RenderPhaseStates = Record< RenderPhase, { - initialized: boolean - dirty: boolean + initialized: boolean; + dirty: boolean; } -> +>; export type AsyncEffect = { - effectName: string - promise: Promise - phase: RenderPhase - complete: boolean -} + effectName: string; + promise: Promise; + phase: RenderPhase; + complete: boolean; +}; export type RenderPhaseFunctions = { - [T in RenderPhaseFn]?: () => void -} + [T in RenderPhaseFn]?: () => void; +}; export type IRenderable = RenderPhaseFunctions & { - renderPhaseStates: RenderPhaseStates - runRenderPhase(phase: RenderPhase): void - runRenderPhaseForChildren(phase: RenderPhase): void - shouldBeRemoved: boolean - children: IRenderable[] - runRenderCycle(): void -} - -let globalRenderCounter = 0 + renderPhaseStates: RenderPhaseStates; + runRenderPhase(phase: RenderPhase): void; + runRenderPhaseForChildren(phase: RenderPhase): void; + shouldBeRemoved: boolean; + children: IRenderable[]; + runRenderCycle(): void; +}; + +let globalRenderCounter = 0; export abstract class Renderable implements IRenderable { - renderPhaseStates: RenderPhaseStates - shouldBeRemoved = false - children: IRenderable[] + renderPhaseStates: RenderPhaseStates; + shouldBeRemoved = false; + children: IRenderable[]; /** PCB-only SMTPads, PlatedHoles, Holes, Silkscreen elements etc. */ - isPcbPrimitive = false + isPcbPrimitive = false; /** Schematic-only, lines, boxes, indicators etc. */ - isSchematicPrimitive = false + isSchematicPrimitive = false; - _renderId: string - _currentRenderPhase: RenderPhase | null = null + _renderId: string; + _currentRenderPhase: RenderPhase | null = null; - private _asyncEffects: AsyncEffect[] = [] + private _asyncEffects: AsyncEffect[] = []; - parent: Renderable | null = null + parent: Renderable | null = null; constructor(props: any) { - this._renderId = `${globalRenderCounter++}` - this.children = [] - this.renderPhaseStates = {} as RenderPhaseStates + this._renderId = `${globalRenderCounter++}`; + this.children = []; + this.renderPhaseStates = {} as RenderPhaseStates; for (const phase of orderedRenderPhases) { this.renderPhaseStates[phase] = { initialized: false, dirty: false, - } + }; } } _markDirty(phase: RenderPhase) { - this.renderPhaseStates[phase].dirty = true + this.renderPhaseStates[phase].dirty = true; // Mark all subsequent phases as dirty - const phaseIndex = orderedRenderPhases.indexOf(phase) + const phaseIndex = orderedRenderPhases.indexOf(phase); for (let i = phaseIndex + 1; i < orderedRenderPhases.length; i++) { - this.renderPhaseStates[orderedRenderPhases[i]].dirty = true + this.renderPhaseStates[orderedRenderPhases[i]].dirty = true; } if (this.parent?._markDirty) { - this.parent._markDirty(phase) + this.parent._markDirty(phase); } } @@ -122,77 +121,77 @@ export abstract class Renderable implements IRenderable { phase: this._currentRenderPhase!, effectName, complete: false, - } - this._asyncEffects.push(asyncEffect) + }; + this._asyncEffects.push(asyncEffect); if ("root" in this && this.root) { - ;(this.root as any).emit("asyncEffect:start", { + (this.root as any).emit("asyncEffect:start", { effectName, componentDisplayName: this.getString(), phase: asyncEffect.phase, - }) + }); } // Set up completion handler asyncEffect.promise .then(() => { - asyncEffect.complete = true + asyncEffect.complete = true; // HACK: emit to the root circuit component that an async effect has completed if ("root" in this && this.root) { - ;(this.root as any).emit("asyncEffect:end", { + (this.root as any).emit("asyncEffect:end", { effectName, componentDisplayName: this.getString(), phase: asyncEffect.phase, - }) + }); } }) .catch((error) => { console.error( - `Async effect error in ${asyncEffect.phase} "${effectName}":\n${error.stack}`, - ) - asyncEffect.complete = true + `Async effect error in ${asyncEffect.phase} "${effectName}":\n${error.stack}` + ); + asyncEffect.complete = true; // HACK: emit to the root circuit component that an async effect has completed if ("root" in this && this.root) { - ;(this.root as any).emit("asyncEffect:end", { + (this.root as any).emit("asyncEffect:end", { effectName, componentDisplayName: this.getString(), phase: asyncEffect.phase, error: error.toString(), - }) + }); } - }) + }); } protected _emitRenderLifecycleEvent( phase: RenderPhase, - startOrEnd: "start" | "end", + startOrEnd: "start" | "end" ) { - debug(`${phase}:${startOrEnd} ${this.getString()}`) - const granular_event_type = `renderable:renderLifecycle:${phase}:${startOrEnd}` + debug(`${phase}:${startOrEnd} ${this.getString()}`); + const granular_event_type = `renderable:renderLifecycle:${phase}:${startOrEnd}`; const eventPayload = { renderId: this._renderId, componentDisplayName: this.getString(), type: granular_event_type, - } + }; if ("root" in this && this.root) { - ;(this.root as any).emit(granular_event_type, eventPayload) - ;(this.root as any).emit("renderable:renderLifecycle:anyEvent", { + (this.root as any).emit(granular_event_type, eventPayload); + (this.root as any).emit("renderable:renderLifecycle:anyEvent", { ...eventPayload, type: granular_event_type, - }) + }); } } getString() { - return this.constructor.name + return this.constructor.name; } _hasIncompleteAsyncEffects(): boolean { - return this._asyncEffects.some((effect) => !effect.complete) + return this._asyncEffects.some((effect) => !effect.complete); } getCurrentRenderPhase(): RenderPhase | null { - return this._currentRenderPhase + return this._currentRenderPhase; } getRenderGraph(): Record { @@ -202,15 +201,15 @@ export abstract class Renderable implements IRenderable { renderPhaseStates: this.renderPhaseStates, shouldBeRemoved: this.shouldBeRemoved, children: this.children.map((child) => - (child as Renderable).getRenderGraph(), + (child as Renderable).getRenderGraph() ), - } + }; } runRenderCycle() { for (const renderPhase of orderedRenderPhases) { - this.runRenderPhaseForChildren(renderPhase) - this.runRenderPhase(renderPhase) + this.runRenderPhaseForChildren(renderPhase); + this.runRenderPhase(renderPhase); } } @@ -222,55 +221,55 @@ export abstract class Renderable implements IRenderable { * ...depending on the current state of the component. */ runRenderPhase(phase: RenderPhase) { - this._currentRenderPhase = phase - const phaseState = this.renderPhaseStates[phase] - const isInitialized = phaseState.initialized - const isDirty = phaseState.dirty + this._currentRenderPhase = phase; + const phaseState = this.renderPhaseStates[phase]; + const isInitialized = phaseState.initialized; + const isDirty = phaseState.dirty; // Skip if component is being removed and not initialized - if (!isInitialized && this.shouldBeRemoved) return + if (!isInitialized && this.shouldBeRemoved) return; if (this.shouldBeRemoved && isInitialized) { - this._emitRenderLifecycleEvent(phase, "start") - ;(this as any)?.[`remove${phase}`]?.() - phaseState.initialized = false - phaseState.dirty = false - this._emitRenderLifecycleEvent(phase, "end") - return + this._emitRenderLifecycleEvent(phase, "start"); + (this as any)?.[`remove${phase}`]?.(); + phaseState.initialized = false; + phaseState.dirty = false; + this._emitRenderLifecycleEvent(phase, "end"); + return; } // Check for incomplete async effects from previous phases - const prevPhaseIndex = orderedRenderPhases.indexOf(phase) - 1 + const prevPhaseIndex = orderedRenderPhases.indexOf(phase) - 1; if (prevPhaseIndex >= 0) { - const prevPhase = orderedRenderPhases[prevPhaseIndex] + const prevPhase = orderedRenderPhases[prevPhaseIndex]; const hasIncompleteEffects = this._asyncEffects .filter((e) => e.phase === prevPhase) - .some((e) => !e.complete) - if (hasIncompleteEffects) return + .some((e) => !e.complete); + if (hasIncompleteEffects) return; } - this._emitRenderLifecycleEvent(phase, "start") + this._emitRenderLifecycleEvent(phase, "start"); // Handle updates if (isInitialized) { if (isDirty) { - ;(this as any)?.[`update${phase}`]?.() - phaseState.dirty = false + (this as any)?.[`update${phase}`]?.(); + phaseState.dirty = false; } - this._emitRenderLifecycleEvent(phase, "end") - return + this._emitRenderLifecycleEvent(phase, "end"); + return; } // Initial render - phaseState.dirty = false - ;(this as any)?.[`doInitial${phase}`]?.() - phaseState.initialized = true - this._emitRenderLifecycleEvent(phase, "end") + phaseState.dirty = false; + (this as any)?.[`doInitial${phase}`]?.(); + phaseState.initialized = true; + this._emitRenderLifecycleEvent(phase, "end"); } runRenderPhaseForChildren(phase: RenderPhase): void { for (const child of this.children) { - child.runRenderPhaseForChildren(phase) - child.runRenderPhase(phase) + child.runRenderPhaseForChildren(phase); + child.runRenderPhase(phase); } } @@ -279,13 +278,13 @@ export abstract class Renderable implements IRenderable { | string | Omit | Omit - | Omit, + | Omit ) { // TODO add to render phase error list and try to add position or // relationships etc if (typeof message === "string") { - throw new Error(message) + throw new Error(message); } - throw new Error(JSON.stringify(message, null, 2)) + throw new Error(JSON.stringify(message, null, 2)); } } diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index be341486..cdd5860c 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -1,27 +1,26 @@ -// lib>components>normal-components>Board.ts -import { boardProps } from "@tscircuit/props" -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" +import { boardProps } from "@tscircuit/props"; +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 + pcb_board_id: string | null = null; get isSubcircuit() { - return true + return true; } get config() { return { componentName: "Board", zodProps: boardProps, - } + }; } get boardThickness() { - const { _parsedProps: props } = this - return 1.4 // TODO use prop + const { _parsedProps: props } = this; + return 1.4; // TODO use prop } /** @@ -29,66 +28,69 @@ export class Board extends Group { */ get allLayers() { // TODO use the board numLayers prop - return ["top", "bottom", "inner1", "inner2"] + return ["top", "bottom", "inner1", "inner2"]; } doInitialBoardAutoSize(): void { - if (this.root?.pcbDisabled) return - + if (this.root?.pcbDisabled) return; + // Skip auto-size if dimensions already specified - if ((this._parsedProps.width && this._parsedProps.height) || this._parsedProps.outline) { + 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 + console.log("Skipping auto-size because dimensions are specified"); + return; } - const bounds = getBoundsOfPcbComponents(this.children) - + const bounds = getBoundsOfPcbComponents(this.children); + if (bounds.width === 0 || bounds.height === 0) { - console.log("No valid components found for auto-sizing") - return + console.log("No valid components found for auto-sizing"); + return; } - const padding = 2 - this._parsedProps.width = bounds.width + (padding * 2) - this._parsedProps.height = bounds.height + (padding * 2) - + const padding = 2; + this._parsedProps.width = bounds.width + padding * 2; + this._parsedProps.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 - + 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! - const { _parsedProps: props } = this + if (this.root?.pcbDisabled) return; + const { db } = this.root!; + const { _parsedProps: props } = this; const pcb_board = db.pcb_board.insert({ center: { - x: props.pcbX ?? 0, // Use calculated center - y: props.pcbY ?? 0 + x: props.pcbX ?? 0, // Use calculated center + y: props.pcbY ?? 0, }, thickness: this.boardThickness, num_layers: this.allLayers.length, width: props.width!, height: props.height!, - outline: props.outline - }) + outline: props.outline, + }); - this.pcb_board_id = pcb_board.pcb_board_id! + this.pcb_board_id = pcb_board.pcb_board_id!; } removePcbComponentRender(): void { - const { db } = this.root! - if (!this.pcb_board_id) return - db.pcb_board.delete(this.pcb_board_id!) - this.pcb_board_id = null + const { db } = this.root!; + if (!this.pcb_board_id) return; + db.pcb_board.delete(this.pcb_board_id!); + this.pcb_board_id = null; } _computePcbGlobalTransformBeforeLayout(): Matrix { - return identity() + return identity(); } } diff --git a/lib/utils/get-bounds-of-pcb-components.ts b/lib/utils/get-bounds-of-pcb-components.ts index 780f556c..78328988 100644 --- a/lib/utils/get-bounds-of-pcb-components.ts +++ b/lib/utils/get-bounds-of-pcb-components.ts @@ -1,58 +1,53 @@ -// lib>utils>get-bounds-of-pcb-components.ts -import type { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent" +import type { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent"; export function getBoundsOfPcbComponents(components: PrimitiveComponent[]) { - let minX = Infinity - let minY = Infinity - let maxX = -Infinity - let maxY = -Infinity + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; for (const child of components) { let bounds; if (child.isPcbPrimitive) { - const { x, y } = child._getGlobalPcbPositionBeforeLayout() - const { width, height } = child.getPcbSize() - minX = Math.min(minX, x - width / 2) - 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") { - bounds = getBoundsOfPcbComponents(child.children) + const { x, y } = child._getGlobalPcbPositionBeforeLayout(); + const { width, height } = child.getPcbSize(); + minX = Math.min(minX, x - width / 2); + minY = Math.min(minY, y - height / 2); + maxX = Math.max(maxX, x + width / 2); + maxY = Math.max(maxY, y + height / 2); + continue; } - else if (child.children.length > 0) { - bounds = getBoundsOfPcbComponents(child.children) + if (child.pcb_component_id) { + bounds = child._getPcbCircuitJsonBounds(); + } else if (child.componentName === "Footprint") { + bounds = getBoundsOfPcbComponents(child.children); + } else if (child.children.length > 0) { + bounds = getBoundsOfPcbComponents(child.children); } 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) + 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) + minX = Math.min(minX, bounds.minX); + minY = Math.min(minY, bounds.minY); + maxX = Math.max(maxX, bounds.maxX); + maxY = Math.max(maxY, bounds.maxY); } } } return { minX: isFinite(minX) ? minX : 0, - minY: isFinite(minY) ? minY : 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) - } + 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 index ca1b102e..21a1e711 100644 --- a/tests/components/normal-components/board-auto-size.test.tsx +++ b/tests/components/normal-components/board-auto-size.test.tsx @@ -1,64 +1,81 @@ -// tests>components>normal-components>board-auto-size.test.tsx -import { test, expect } from "bun:test" -import { getTestFixture } from "tests/fixtures/get-test-fixture" +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() + const { circuit } = getTestFixture(); circuit.add( - + - ) + ); - circuit.render() + circuit.render(); + + const pcb_board = circuit.db.pcb_board.list()[0]; - 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) -}) + expect(pcb_board.width).toBeGreaterThan(10); + expect(pcb_board.height).toBeGreaterThan(10); +}); test("board respects explicit dimensions", () => { - const { circuit } = getTestFixture() + 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) -}) + ); + 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() + const { circuit } = getTestFixture(); circuit.add( - - + + - ) - circuit.render() - const pcb_board = circuit.db.pcb_board.list()[0] - + ); + 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) -}) + expect(pcb_board.width).toBeGreaterThan(22); + expect(pcb_board.height).toBeGreaterThan(22); +}); test("board centers around components", () => { - const { circuit } = getTestFixture() + 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) -}) \ No newline at end of file + ); + 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); +}); From 44b9a2274765c870a48ba620382147aa4ffc0a1d Mon Sep 17 00:00:00 2001 From: Abhinav <85792055+AbhinavTheDev@users.noreply.github.com> Date: Sun, 26 Jan 2025 19:52:43 +0530 Subject: [PATCH 3/8] formatted code --- lib/components/base-components/Renderable.ts | 196 +++++++++--------- lib/components/normal-components/Board.ts | 66 +++--- lib/utils/get-bounds-of-pcb-components.ts | 50 ++--- .../board-auto-size.test.tsx | 68 +++--- .../example15-board-outline-offset.test.tsx | 2 +- ...et-outline-without-output-defined.test.tsx | 2 +- 6 files changed, 192 insertions(+), 192 deletions(-) diff --git a/lib/components/base-components/Renderable.ts b/lib/components/base-components/Renderable.ts index c154d426..ae223cd5 100644 --- a/lib/components/base-components/Renderable.ts +++ b/lib/components/base-components/Renderable.ts @@ -2,10 +2,10 @@ import type { PcbManualEditConflictError, PcbPlacementError, PcbTraceError, -} from "circuit-json"; -import Debug from "debug"; +} from "circuit-json" +import Debug from "debug" -const debug = Debug("tscircuit:renderable"); +const debug = Debug("tscircuit:renderable") export const orderedRenderPhases = [ "ReactSubtreesRender", @@ -35,83 +35,83 @@ export const orderedRenderPhases = [ "PcbRouteNetIslands", "CadModelRender", "PartsEngineRender", -] as const; +] as const -export type RenderPhase = (typeof orderedRenderPhases)[number]; +export type RenderPhase = (typeof orderedRenderPhases)[number] export type RenderPhaseFn = | `doInitial${K}` | `update${K}` - | `remove${K}`; + | `remove${K}` export type RenderPhaseStates = Record< RenderPhase, { - initialized: boolean; - dirty: boolean; + initialized: boolean + dirty: boolean } ->; +> export type AsyncEffect = { - effectName: string; - promise: Promise; - phase: RenderPhase; - complete: boolean; -}; + effectName: string + promise: Promise + phase: RenderPhase + complete: boolean +} export type RenderPhaseFunctions = { - [T in RenderPhaseFn]?: () => void; -}; + [T in RenderPhaseFn]?: () => void +} export type IRenderable = RenderPhaseFunctions & { - renderPhaseStates: RenderPhaseStates; - runRenderPhase(phase: RenderPhase): void; - runRenderPhaseForChildren(phase: RenderPhase): void; - shouldBeRemoved: boolean; - children: IRenderable[]; - runRenderCycle(): void; -}; - -let globalRenderCounter = 0; + renderPhaseStates: RenderPhaseStates + runRenderPhase(phase: RenderPhase): void + runRenderPhaseForChildren(phase: RenderPhase): void + shouldBeRemoved: boolean + children: IRenderable[] + runRenderCycle(): void +} + +let globalRenderCounter = 0 export abstract class Renderable implements IRenderable { - renderPhaseStates: RenderPhaseStates; - shouldBeRemoved = false; - children: IRenderable[]; + renderPhaseStates: RenderPhaseStates + shouldBeRemoved = false + children: IRenderable[] /** PCB-only SMTPads, PlatedHoles, Holes, Silkscreen elements etc. */ - isPcbPrimitive = false; + isPcbPrimitive = false /** Schematic-only, lines, boxes, indicators etc. */ - isSchematicPrimitive = false; + isSchematicPrimitive = false - _renderId: string; - _currentRenderPhase: RenderPhase | null = null; + _renderId: string + _currentRenderPhase: RenderPhase | null = null - private _asyncEffects: AsyncEffect[] = []; + private _asyncEffects: AsyncEffect[] = [] - parent: Renderable | null = null; + parent: Renderable | null = null constructor(props: any) { - this._renderId = `${globalRenderCounter++}`; - this.children = []; - this.renderPhaseStates = {} as RenderPhaseStates; + this._renderId = `${globalRenderCounter++}` + this.children = [] + this.renderPhaseStates = {} as RenderPhaseStates for (const phase of orderedRenderPhases) { this.renderPhaseStates[phase] = { initialized: false, dirty: false, - }; + } } } _markDirty(phase: RenderPhase) { - this.renderPhaseStates[phase].dirty = true; + this.renderPhaseStates[phase].dirty = true // Mark all subsequent phases as dirty - const phaseIndex = orderedRenderPhases.indexOf(phase); + const phaseIndex = orderedRenderPhases.indexOf(phase) for (let i = phaseIndex + 1; i < orderedRenderPhases.length; i++) { - this.renderPhaseStates[orderedRenderPhases[i]].dirty = true; + this.renderPhaseStates[orderedRenderPhases[i]].dirty = true } if (this.parent?._markDirty) { - this.parent._markDirty(phase); + this.parent._markDirty(phase) } } @@ -121,77 +121,77 @@ export abstract class Renderable implements IRenderable { phase: this._currentRenderPhase!, effectName, complete: false, - }; - this._asyncEffects.push(asyncEffect); + } + this._asyncEffects.push(asyncEffect) if ("root" in this && this.root) { - (this.root as any).emit("asyncEffect:start", { + ;(this.root as any).emit("asyncEffect:start", { effectName, componentDisplayName: this.getString(), phase: asyncEffect.phase, - }); + }) } // Set up completion handler asyncEffect.promise .then(() => { - asyncEffect.complete = true; + asyncEffect.complete = true // HACK: emit to the root circuit component that an async effect has completed if ("root" in this && this.root) { - (this.root as any).emit("asyncEffect:end", { + ;(this.root as any).emit("asyncEffect:end", { effectName, componentDisplayName: this.getString(), phase: asyncEffect.phase, - }); + }) } }) .catch((error) => { console.error( - `Async effect error in ${asyncEffect.phase} "${effectName}":\n${error.stack}` - ); - asyncEffect.complete = true; + `Async effect error in ${asyncEffect.phase} "${effectName}":\n${error.stack}`, + ) + asyncEffect.complete = true // HACK: emit to the root circuit component that an async effect has completed if ("root" in this && this.root) { - (this.root as any).emit("asyncEffect:end", { + ;(this.root as any).emit("asyncEffect:end", { effectName, componentDisplayName: this.getString(), phase: asyncEffect.phase, error: error.toString(), - }); + }) } - }); + }) } protected _emitRenderLifecycleEvent( phase: RenderPhase, - startOrEnd: "start" | "end" + startOrEnd: "start" | "end", ) { - debug(`${phase}:${startOrEnd} ${this.getString()}`); - const granular_event_type = `renderable:renderLifecycle:${phase}:${startOrEnd}`; + debug(`${phase}:${startOrEnd} ${this.getString()}`) + const granular_event_type = `renderable:renderLifecycle:${phase}:${startOrEnd}` const eventPayload = { renderId: this._renderId, componentDisplayName: this.getString(), type: granular_event_type, - }; + } if ("root" in this && this.root) { - (this.root as any).emit(granular_event_type, eventPayload); - (this.root as any).emit("renderable:renderLifecycle:anyEvent", { + ;(this.root as any).emit(granular_event_type, eventPayload) + ;(this.root as any).emit("renderable:renderLifecycle:anyEvent", { ...eventPayload, type: granular_event_type, - }); + }) } } getString() { - return this.constructor.name; + return this.constructor.name } _hasIncompleteAsyncEffects(): boolean { - return this._asyncEffects.some((effect) => !effect.complete); + return this._asyncEffects.some((effect) => !effect.complete) } getCurrentRenderPhase(): RenderPhase | null { - return this._currentRenderPhase; + return this._currentRenderPhase } getRenderGraph(): Record { @@ -201,15 +201,15 @@ export abstract class Renderable implements IRenderable { renderPhaseStates: this.renderPhaseStates, shouldBeRemoved: this.shouldBeRemoved, children: this.children.map((child) => - (child as Renderable).getRenderGraph() + (child as Renderable).getRenderGraph(), ), - }; + } } runRenderCycle() { for (const renderPhase of orderedRenderPhases) { - this.runRenderPhaseForChildren(renderPhase); - this.runRenderPhase(renderPhase); + this.runRenderPhaseForChildren(renderPhase) + this.runRenderPhase(renderPhase) } } @@ -221,55 +221,55 @@ export abstract class Renderable implements IRenderable { * ...depending on the current state of the component. */ runRenderPhase(phase: RenderPhase) { - this._currentRenderPhase = phase; - const phaseState = this.renderPhaseStates[phase]; - const isInitialized = phaseState.initialized; - const isDirty = phaseState.dirty; + this._currentRenderPhase = phase + const phaseState = this.renderPhaseStates[phase] + const isInitialized = phaseState.initialized + const isDirty = phaseState.dirty // Skip if component is being removed and not initialized - if (!isInitialized && this.shouldBeRemoved) return; + if (!isInitialized && this.shouldBeRemoved) return if (this.shouldBeRemoved && isInitialized) { - this._emitRenderLifecycleEvent(phase, "start"); - (this as any)?.[`remove${phase}`]?.(); - phaseState.initialized = false; - phaseState.dirty = false; - this._emitRenderLifecycleEvent(phase, "end"); - return; + this._emitRenderLifecycleEvent(phase, "start") + ;(this as any)?.[`remove${phase}`]?.() + phaseState.initialized = false + phaseState.dirty = false + this._emitRenderLifecycleEvent(phase, "end") + return } // Check for incomplete async effects from previous phases - const prevPhaseIndex = orderedRenderPhases.indexOf(phase) - 1; + const prevPhaseIndex = orderedRenderPhases.indexOf(phase) - 1 if (prevPhaseIndex >= 0) { - const prevPhase = orderedRenderPhases[prevPhaseIndex]; + const prevPhase = orderedRenderPhases[prevPhaseIndex] const hasIncompleteEffects = this._asyncEffects .filter((e) => e.phase === prevPhase) - .some((e) => !e.complete); - if (hasIncompleteEffects) return; + .some((e) => !e.complete) + if (hasIncompleteEffects) return } - this._emitRenderLifecycleEvent(phase, "start"); + this._emitRenderLifecycleEvent(phase, "start") // Handle updates if (isInitialized) { if (isDirty) { - (this as any)?.[`update${phase}`]?.(); - phaseState.dirty = false; + ;(this as any)?.[`update${phase}`]?.() + phaseState.dirty = false } - this._emitRenderLifecycleEvent(phase, "end"); - return; + this._emitRenderLifecycleEvent(phase, "end") + return } // Initial render - phaseState.dirty = false; - (this as any)?.[`doInitial${phase}`]?.(); - phaseState.initialized = true; - this._emitRenderLifecycleEvent(phase, "end"); + phaseState.dirty = false + ;(this as any)?.[`doInitial${phase}`]?.() + phaseState.initialized = true + this._emitRenderLifecycleEvent(phase, "end") } runRenderPhaseForChildren(phase: RenderPhase): void { for (const child of this.children) { - child.runRenderPhaseForChildren(phase); - child.runRenderPhase(phase); + child.runRenderPhaseForChildren(phase) + child.runRenderPhase(phase) } } @@ -278,13 +278,13 @@ export abstract class Renderable implements IRenderable { | string | Omit | Omit - | Omit + | Omit, ) { // TODO add to render phase error list and try to add position or // relationships etc if (typeof message === "string") { - throw new Error(message); + throw new Error(message) } - throw new Error(JSON.stringify(message, null, 2)); + throw new Error(JSON.stringify(message, null, 2)) } } diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index cdd5860c..f0d26726 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -1,26 +1,26 @@ -import { boardProps } from "@tscircuit/props"; -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"; +import { boardProps } from "@tscircuit/props" +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; + pcb_board_id: string | null = null get isSubcircuit() { - return true; + return true } get config() { return { componentName: "Board", zodProps: boardProps, - }; + } } get boardThickness() { - const { _parsedProps: props } = this; - return 1.4; // TODO use prop + const { _parsedProps: props } = this + return 1.4 // TODO use prop } /** @@ -28,11 +28,11 @@ export class Board extends Group { */ get allLayers() { // TODO use the board numLayers prop - return ["top", "bottom", "inner1", "inner2"]; + return ["top", "bottom", "inner1", "inner2"] } doInitialBoardAutoSize(): void { - if (this.root?.pcbDisabled) return; + if (this.root?.pcbDisabled) return // Skip auto-size if dimensions already specified if ( @@ -40,33 +40,33 @@ export class Board extends Group { this._parsedProps.outline ) { // console.log("Skipping auto-size - dimensions specified", this._parsedProps) - console.log("Skipping auto-size because dimensions are specified"); - return; + console.log("Skipping auto-size because dimensions are specified") + return } - const bounds = getBoundsOfPcbComponents(this.children); + const bounds = getBoundsOfPcbComponents(this.children) if (bounds.width === 0 || bounds.height === 0) { - console.log("No valid components found for auto-sizing"); - return; + console.log("No valid components found for auto-sizing") + return } - const padding = 2; - this._parsedProps.width = bounds.width + padding * 2; - this._parsedProps.height = bounds.height + padding * 2; + const padding = 2 + this._parsedProps.width = bounds.width + padding * 2 + this._parsedProps.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; + 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!; - const { _parsedProps: props } = this; + if (this.root?.pcbDisabled) return + const { db } = this.root! + const { _parsedProps: props } = this const pcb_board = db.pcb_board.insert({ center: { @@ -78,19 +78,19 @@ export class Board extends Group { width: props.width!, height: props.height!, outline: props.outline, - }); + }) - this.pcb_board_id = pcb_board.pcb_board_id!; + this.pcb_board_id = pcb_board.pcb_board_id! } removePcbComponentRender(): void { - const { db } = this.root!; - if (!this.pcb_board_id) return; - db.pcb_board.delete(this.pcb_board_id!); - this.pcb_board_id = null; + const { db } = this.root! + if (!this.pcb_board_id) return + db.pcb_board.delete(this.pcb_board_id!) + this.pcb_board_id = null } _computePcbGlobalTransformBeforeLayout(): Matrix { - return identity(); + return identity() } } diff --git a/lib/utils/get-bounds-of-pcb-components.ts b/lib/utils/get-bounds-of-pcb-components.ts index 78328988..2f339cae 100644 --- a/lib/utils/get-bounds-of-pcb-components.ts +++ b/lib/utils/get-bounds-of-pcb-components.ts @@ -1,43 +1,43 @@ -import type { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent"; +import type { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent" export function getBoundsOfPcbComponents(components: PrimitiveComponent[]) { - let minX = Infinity; - let minY = Infinity; - let maxX = -Infinity; - let maxY = -Infinity; + let minX = Infinity + let minY = Infinity + let maxX = -Infinity + let maxY = -Infinity for (const child of components) { - let bounds; + let bounds if (child.isPcbPrimitive) { - const { x, y } = child._getGlobalPcbPositionBeforeLayout(); - const { width, height } = child.getPcbSize(); - minX = Math.min(minX, x - width / 2); - minY = Math.min(minY, y - height / 2); - maxX = Math.max(maxX, x + width / 2); - maxY = Math.max(maxY, y + height / 2); - continue; + const { x, y } = child._getGlobalPcbPositionBeforeLayout() + const { width, height } = child.getPcbSize() + minX = Math.min(minX, x - width / 2) + 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(); + bounds = child._getPcbCircuitJsonBounds() } else if (child.componentName === "Footprint") { - bounds = getBoundsOfPcbComponents(child.children); + bounds = getBoundsOfPcbComponents(child.children) } else if (child.children.length > 0) { - bounds = getBoundsOfPcbComponents(child.children); + bounds = getBoundsOfPcbComponents(child.children) } 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); + 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); + minX = Math.min(minX, bounds.minX) + minY = Math.min(minY, bounds.minY) + maxX = Math.max(maxX, bounds.maxX) + maxY = Math.max(maxY, bounds.maxY) } } } @@ -49,5 +49,5 @@ export function getBoundsOfPcbComponents(components: PrimitiveComponent[]) { 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 index 21a1e711..cb8b3a49 100644 --- a/tests/components/normal-components/board-auto-size.test.tsx +++ b/tests/components/normal-components/board-auto-size.test.tsx @@ -1,8 +1,8 @@ -import { test, expect } from "bun:test"; -import { getTestFixture } from "tests/fixtures/get-test-fixture"; +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(); + const { circuit } = getTestFixture() circuit.add( @@ -14,33 +14,33 @@ test("board auto-sizes when no dimensions provided", () => { pcbX={-5} pcbY={-5} /> - - ); + , + ) - circuit.render(); + circuit.render() - const pcb_board = circuit.db.pcb_board.list()[0]; + 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); -}); + expect(pcb_board.width).toBeGreaterThan(10) + expect(pcb_board.height).toBeGreaterThan(10) +}) test("board respects explicit dimensions", () => { - const { circuit } = getTestFixture(); + 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); -}); + , + ) + 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(); + const { circuit } = getTestFixture() circuit.add( { pcbX={-10} pcbY={-10} /> - - ); - circuit.render(); - const pcb_board = circuit.db.pcb_board.list()[0]; + , + ) + 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); -}); + expect(pcb_board.width).toBeGreaterThan(22) + expect(pcb_board.height).toBeGreaterThan(22) +}) test("board centers around components", () => { - const { circuit } = getTestFixture(); + 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); -}); + , + ) + 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) +}) diff --git a/tests/examples/example15-board-outline-offset.test.tsx b/tests/examples/example15-board-outline-offset.test.tsx index 9473205b..c7410040 100644 --- a/tests/examples/example15-board-outline-offset.test.tsx +++ b/tests/examples/example15-board-outline-offset.test.tsx @@ -22,5 +22,5 @@ test("Board outline offset", async () => { ) circuit.render() - expect(circuit).toMatchPcbSnapshot(import.meta.path) + expect(circuit.db.pcb_board.list()[0]).toMatchInlineSnapshot() }) diff --git a/tests/examples/example16-offset-outline-without-output-defined.test.tsx b/tests/examples/example16-offset-outline-without-output-defined.test.tsx index e7ff8bf4..29175889 100644 --- a/tests/examples/example16-offset-outline-without-output-defined.test.tsx +++ b/tests/examples/example16-offset-outline-without-output-defined.test.tsx @@ -11,5 +11,5 @@ test("offsetOutline without output defined", async () => { ) circuit.render() - expect(circuit).toMatchPcbSnapshot(import.meta.path) + expect(circuit.db.pcb_board.list()[0]).toMatchInlineSnapshot() }) From 53cad70d6e18227e8377f1fe903ed9a6b92c91a4 Mon Sep 17 00:00:00 2001 From: Abhinav <85792055+AbhinavTheDev@users.noreply.github.com> Date: Mon, 27 Jan 2025 18:30:17 +0530 Subject: [PATCH 4/8] minor changes --- tests/examples/example15-board-outline-offset.test.tsx | 2 +- .../example16-offset-outline-without-output-defined.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/examples/example15-board-outline-offset.test.tsx b/tests/examples/example15-board-outline-offset.test.tsx index c7410040..9473205b 100644 --- a/tests/examples/example15-board-outline-offset.test.tsx +++ b/tests/examples/example15-board-outline-offset.test.tsx @@ -22,5 +22,5 @@ test("Board outline offset", async () => { ) circuit.render() - expect(circuit.db.pcb_board.list()[0]).toMatchInlineSnapshot() + expect(circuit).toMatchPcbSnapshot(import.meta.path) }) diff --git a/tests/examples/example16-offset-outline-without-output-defined.test.tsx b/tests/examples/example16-offset-outline-without-output-defined.test.tsx index 29175889..e7ff8bf4 100644 --- a/tests/examples/example16-offset-outline-without-output-defined.test.tsx +++ b/tests/examples/example16-offset-outline-without-output-defined.test.tsx @@ -11,5 +11,5 @@ test("offsetOutline without output defined", async () => { ) circuit.render() - expect(circuit.db.pcb_board.list()[0]).toMatchInlineSnapshot() + expect(circuit).toMatchPcbSnapshot(import.meta.path) }) From 887f9218af2b7745051b128da96d13a4ca3832f9 Mon Sep 17 00:00:00 2001 From: Abhinav <85792055+AbhinavTheDev@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:30:27 +0530 Subject: [PATCH 5/8] add correct outline position --- lib/components/normal-components/Board.ts | 37 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index f0d26726..ae643a47 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -68,21 +68,46 @@ export class Board extends Group { const { db } = this.root! const { _parsedProps: props } = this + // If outline is not provided, width and height must be specified + if (!props.outline && (!props.width || !props.height)) { + throw new Error("Board width and height or an outline are required") + } + + // Compute width and height from outline if not provided + let computedWidth = props.width + let computedHeight = props.height + if (props.outline) { + const xValues = props.outline.map((point) => point.x) + const yValues = props.outline.map((point) => point.y) + + const minX = Math.min(...xValues) + const maxX = Math.max(...xValues) + const minY = Math.min(...yValues) + const maxY = Math.max(...yValues) + + computedWidth = maxX - minX + computedHeight = maxY - minY + } + const pcb_board = db.pcb_board.insert({ center: { - x: props.pcbX ?? 0, // Use calculated center - y: props.pcbY ?? 0, + x: (props.pcbX ?? 0) + (props.outlineOffsetX ?? 0), + y: (props.pcbY ?? 0) + (props.outlineOffsetY ?? 0), }, + thickness: this.boardThickness, num_layers: this.allLayers.length, - width: props.width!, - height: props.height!, - outline: props.outline, + + width: computedWidth!, + height: computedHeight!, + outline: props.outline?.map((point) => ({ + x: point.x + (props.outlineOffsetX ?? 0), + y: point.y + (props.outlineOffsetY ?? 0), + })), }) this.pcb_board_id = pcb_board.pcb_board_id! } - removePcbComponentRender(): void { const { db } = this.root! if (!this.pcb_board_id) return From 980b97ea193cc177c1c9ac0539c88cac9624e73b Mon Sep 17 00:00:00 2001 From: Abhinav <85792055+AbhinavTheDev@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:16:49 +0530 Subject: [PATCH 6/8] suggested changes --- lib/components/base-components/Renderable.ts | 2 +- lib/components/normal-components/Board.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/components/base-components/Renderable.ts b/lib/components/base-components/Renderable.ts index ae223cd5..f5db2869 100644 --- a/lib/components/base-components/Renderable.ts +++ b/lib/components/base-components/Renderable.ts @@ -22,7 +22,7 @@ export const orderedRenderPhases = [ "SchematicPortRender", "SchematicLayout", "SchematicTraceRender", - "BoardAutoSize", + "PcbBoardAutoSize", "PcbComponentRender", "PcbPrimitiveRender", "PcbFootprintLayout", diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index ae643a47..f2c771f0 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -31,7 +31,7 @@ export class Board extends Group { return ["top", "bottom", "inner1", "inner2"] } - doInitialBoardAutoSize(): void { + doInitialPcbBoardAutoSize(): void { if (this.root?.pcbDisabled) return // Skip auto-size if dimensions already specified @@ -52,8 +52,11 @@ export class Board extends Group { } const padding = 2 - this._parsedProps.width = bounds.width + padding * 2 - this._parsedProps.height = bounds.height + 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 From 2c89ffbadc090fb8d32325add8f432ab045f6909 Mon Sep 17 00:00:00 2001 From: Abhinav <85792055+AbhinavTheDev@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:37:55 +0530 Subject: [PATCH 7/8] comma --- lib/components/normal-components/Board.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/normal-components/Board.ts b/lib/components/normal-components/Board.ts index f2c771f0..265826a2 100644 --- a/lib/components/normal-components/Board.ts +++ b/lib/components/normal-components/Board.ts @@ -55,7 +55,7 @@ export class Board extends Group { this._parsedProps = { ...this._parsedProps, width: bounds.width + padding * 2, - height: bounds.height + padding * 2 + height: bounds.height + padding * 2, } // Set board center based on component bounds From 11cd92a596a5d2fe06ba82bca31542ef5548c546 Mon Sep 17 00:00:00 2001 From: Abhinav <85792055+AbhinavTheDev@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:03:43 +0530 Subject: [PATCH 8/8] pkg-pr-release error resolved --- bun.lockb | Bin 225622 -> 225622 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index cf08495096fd270a00434cefd7f657f811dff691..2a1aadef44ebae9d8e607a050fc2d5f08991d5f6 100755 GIT binary patch delta 3548 zcmeHJi&IqB9lrN03tZ*3Kor6vpe_%U6<3xED(fzYE)S!iR!ESBD3VoN2&@n6w4f%s zacW1rvBx+~Y#OzuCbcPniyEY23_h!9d?ZwBQ|$!Dr0GnPXs5~a`|aMHq@Bj}4=A%A z-}%1tJHPWg=k7gwu6`JP^~3P(=H!;<=*+iLK2K3^F7lY?j*cEV>>l2{-fmyH>|%YV zBdF-j$3D;(-<%-`5rWWOlP?HS;39B5cy)6_=88s_dj_-wodpg9-?I7RuYyNF_k&}> zSCqVv$!({RjiOxj?#AXiK{x^(3BB6A%(c{okp=$I+rXpX4?6_G1nvWegYOmkvkid_ z(Ek~K5vm_-oQHrF;awF>1z7XoM*RkQsG_Gx+tI0pVXOq2*d2&S_4 zqd?+`PJhB=jbp%6P-j!flR>QC)_G}UcbMXgcPhK$v!XIKT^83~+HAO6@$TvCb^BK+ z2jYINc;k{gFUEUw#WclXN>kcRp^CR6N{N%RBsVq>3EM@*kReOmP>)0D6@x5GQP{v- zpDKS!mYh)2ph6T<8=>q_l42;%4TYEmq4RZFW(opoN0N?oS#m?ohZ2;Ig|c)Eijs)R z;3QeP4`uPIJef_fD_ftivB&Mo8Bm8^8BuLx@7R@+YMb;A%tg~7C(4|LEz0((@@&ii zg}U2=X+CBwP|hr{N#%HqR79B4VU?vaKa(k#^NR#y|pd3p0wCU18&BxZ-azi07sy)cJnUm)TLKT#xbWD&X zFBCrLqe(BIiJGwOv?)1wj8uZJPwye6%*~!I{T3}O-(N!Ksp|2yZNd0nJ}L?VK0*Th z`xe&0AEWjEH>Um+t)Dmu+N9}+a2vw>fmFyuZ2&PfCuy3PL7TxLV5{bTlgY}_d}1mn zPt(NIoTh1FD$pj5^Cz~$paF$i05MHiM6Brh(mJ#I;tc=aEZ?Sl_h!8?SX*2+_Sz)l zp`M{3Yd!QwLv6$7OYS_@@yh1E70vpdl@HuIIDBSr%gTu0Icm*6y|+E9 ztBjK_?#LUyv0&#KY`rLa75Npoi*FbwF6O%u#BhFRoLDNVe9CwjP9uz+<6#`;eLPLx&2KWObj8kMRNrZ8lzm^DN-B=jmV_=-+RbybJkAu-m#(6F! z!MIMwsw5Z}_+c`<<6$Hw!x-eQWEi<780X2j%;QsFd_=|%Q(#=-r^(or3L|qYjBC7g zEDUEFj2mRU&ogMp?vpV$0mcx2F#*PbbQoWfaf275bhR=={Pf5883^L`9x+t!wd03W zt?d;{nYy<}^lpD7@bb!dAMhxIi9o9AC}uN7aW>yj!ea6#laIf7S_n>HAxrb-YaWgx zVT$IJYaY%&!J-b9umxhi1ARE8h2>gbIoi{;_XjAj& zvQk}qI6@+TNFWL@0MS5<`pSHERCLTi+X)l{B>=536-Wcpfec^*K*wAXkPO5Ej{u{9 zc=e?U_Jx>9hXXC0mP$*T3Sgp*$?Ewh5%^&E{I9F_I}W3tQ^ zGqyf(3mw^WX`{7qyNsSZa~84*GxGUwwlE7T%ICLny8)fl)LzGr>?-1(gk>IWEGFMpz>jI#yagiz*IeD9<*{o`FN}y1t=K{O+fm5Fo@TycklQIDa0PDsg*3Hq-oenX7lK)%HnUj1eG3a>Lxub&6dt%y zODDR1Jyd=BEW&gA%RzDd&oPTJaF?Fb{^{!*o%s!ju=peV2`#i7B#vWMvw00T=i76B zS8)%k2MfGKvwU+I4=EpE|J$DaTS6A& z`Xj}k>sxN+2tuGBxExkN7y`CxoD4QX_o9C|cun;(=TfI29I*+)FzDo5K^P6*0c`;< z0aN^Ic2B-_U=#HB<5$3^aRF1@^R>8Ys+U#@c&)9ksI6@hgmMG~Am9lCD1k%M>EMyj zHJB(2TnwhdPoh97=x)AeTpu_DdQJW6#FaJ9>NC(1bSHQyxT??-zXuRL}&q%~8tD0!AR#bq%lt)&6V?u2A%F18Z|yL^lZ*JE5^8NX&YK;@I3{0L19vtK{3V2dgD|<$Wx4yv!!CR$oF$EWam`Hf1ynp zfVlvsq_mBbrSW(k)1Z8meru)y0)H-@(zeAd91x;nN|2?T0zvo@lwL8(vh*fYrbl7< zm!T#@`MEd4fCq_XB7aGSEHy%TQqo2phoVi?y9;_$sBE8Slkzb?4S

HbGG`tZafT zor0R=QCTv3I87;vQAy~xQ zbJ!PtxYkch%?M2sGw3L=A2?p~zsqDLXg;wHdV;3GE$)Pgnn6qjrGP1MDwqajX#K?G zXKFkdOaZg{y+M5-5aC`gXH?ifS@O1c7a0mZ3PAucQLq#KhF@-e#c&wA zV_~d`gz*_)K!zg@MprnD+gyr(@eLU-N5B~5C&)Mu4#;aeJ zv-3VJcd+`;0Q`muH-SsQ37`Ww4&Vn=P=QY1Q{WWP2Xp}^f!_fiYi;Vg2=oHyfwMpl za2n_a&H(3t-vi`-BH-Aj#sz=^yaT9B38;_SG?6zsc>_Q{a22=$h`?oyZ-B1@*ML6& zw}D&0pvJV=&jET)K_Wl6nC-JrQ4fH7z?Z<^fUg1a?*osht-kzYHc~HMQP(eH>3UIC zRVSMf66(D)d2d5ZT%4Yinv^04?R?cn7Q**!WMjm`yaeq~@5OD|PCwhJU0nePPEAUt z;9eev0JHaMI>o*ysP15Mu@->e+GH!g*UBa_tCgF#vEU5v<<;@vx9=~y|0_Ep(~t@m z1@A5KditSHFTL@twM{l)E?R=!+fb$@FaC3Ohff&2qUFv#qo9KS7(l_igEH z$8QD~-R;AO3{Ux}D{w2DWY%ulu4%4^hc-B@s}V8D6M>4KZe_u|c`F-3<&$7frtpuz zKalu4rO=3s=a=xD+UQAJYvmKFU{TS8x01tRjEGSC^^|AEukO^grSD_0s?V diff --git a/package.json b/package.json index 6d5ea432..774b2486 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "howfat": "^0.3.8", "live-server": "^1.2.2", "looks-same": "^9.0.1", - "pkg-pr-new": "^0.0.37", + "pkg-pr-new": "^0.0.39", "react": "^19.0.0", "react-dom": "^19.0.0", "ts-expect": "^1.3.0",