From 36417945b1fb15065cdfff92dfcebb6901a8d375 Mon Sep 17 00:00:00 2001 From: Rishabh Gupta Date: Thu, 10 Oct 2024 02:02:49 +0530 Subject: [PATCH] feat: port the function from builder --- lib/find-bounds-and-center.ts | 38 ++++++ lib/utils/get-layout-debug-object.ts | 174 +++++++++++++++++++++++++++ lib/utils/is-truthy.ts | 1 + lib/utils/string-hash.ts | 10 ++ tests/find-bounds-and-center.test.ts | 51 ++++++++ 5 files changed, 274 insertions(+) create mode 100644 lib/find-bounds-and-center.ts create mode 100644 lib/utils/get-layout-debug-object.ts create mode 100644 lib/utils/is-truthy.ts create mode 100644 lib/utils/string-hash.ts create mode 100644 tests/find-bounds-and-center.test.ts diff --git a/lib/find-bounds-and-center.ts b/lib/find-bounds-and-center.ts new file mode 100644 index 0000000..0457676 --- /dev/null +++ b/lib/find-bounds-and-center.ts @@ -0,0 +1,38 @@ +import type { AnyCircuitElement } from "circuit-json" +import { getDebugLayoutObject } from "./utils/get-layout-debug-object" +import { isTruthy } from "./utils/is-truthy" + +export const findBoundsAndCenter = ( + elements: AnyCircuitElement[], +): { center: { x: number; y: number }; width: number; height: number } => { + const debugObjects = elements + .filter((elm) => elm.type.startsWith("pcb_")) + .concat( + elements + .filter((elm) => elm.type === "pcb_trace") + .flatMap((elm) => elm.route), + ) + .map((elm) => getDebugLayoutObject(elm)) + .filter(isTruthy) + + if (debugObjects.length === 0) + return { center: { x: 0, y: 0 }, width: 0, height: 0 } + + let minX = debugObjects[0].x - debugObjects[0].width / 2 + let maxX = debugObjects[0].x + debugObjects[0].width / 2 + let minY = debugObjects[0].y - debugObjects[0].height / 2 + let maxY = debugObjects[0].y + debugObjects[0].height / 2 + + for (const obj of debugObjects.slice(1)) { + minX = Math.min(minX, obj.x - obj.width / 2) + maxX = Math.max(maxX, obj.x + obj.width / 2) + minY = Math.min(minY, obj.y - obj.height / 2) + maxY = Math.max(maxY, obj.y + obj.height / 2) + } + + const width = maxX - minX + const height = maxY - minY + const center = { x: minX + width / 2, y: minY + height / 2 } + + return { center, width, height } +} diff --git a/lib/utils/get-layout-debug-object.ts b/lib/utils/get-layout-debug-object.ts new file mode 100644 index 0000000..3040a08 --- /dev/null +++ b/lib/utils/get-layout-debug-object.ts @@ -0,0 +1,174 @@ +import { stringHash } from "./string-hash" + +export type LayoutDebugObject = { + x: number + y: number + width: number + height: number + bg_color: string + title: string + content: Object + secondary?: boolean +} + +/** + * Vendored from "nice-color-palettes" package + */ +const nice_color_palettes = [ + ["#69d2e7", "#a7dbd8", "#e0e4cc", "#f38630", "#fa6900"], + ["#fe4365", "#fc9d9a", "#f9cdad", "#c8c8a9", "#83af9b"], + ["#ecd078", "#d95b43", "#c02942", "#542437", "#53777a"], + ["#556270", "#4ecdc4", "#c7f464", "#ff6b6b", "#c44d58"], + ["#774f38", "#e08e79", "#f1d4af", "#ece5ce", "#c5e0dc"], + ["#e8ddcb", "#cdb380", "#036564", "#033649", "#031634"], + ["#490a3d", "#bd1550", "#e97f02", "#f8ca00", "#8a9b0f"], + ["#594f4f", "#547980", "#45ada8", "#9de0ad", "#e5fcc2"], + ["#00a0b0", "#6a4a3c", "#cc333f", "#eb6841", "#edc951"], + ["#e94e77", "#d68189", "#c6a49a", "#c6e5d9", "#f4ead5"], + ["#3fb8af", "#7fc7af", "#dad8a7", "#ff9e9d", "#ff3d7f"], + ["#d9ceb2", "#948c75", "#d5ded9", "#7a6a53", "#99b2b7"], + ["#ffffff", "#cbe86b", "#f2e9e1", "#1c140d", "#cbe86b"], + ["#efffcd", "#dce9be", "#555152", "#2e2633", "#99173c"], + ["#343838", "#005f6b", "#008c9e", "#00b4cc", "#00dffc"], + ["#413e4a", "#73626e", "#b38184", "#f0b49e", "#f7e4be"], + ["#ff4e50", "#fc913a", "#f9d423", "#ede574", "#e1f5c4"], + ["#99b898", "#fecea8", "#ff847c", "#e84a5f", "#2a363b"], + ["#655643", "#80bca3", "#f6f7bd", "#e6ac27", "#bf4d28"], + ["#00a8c6", "#40c0cb", "#f9f2e7", "#aee239", "#8fbe00"], + ["#351330", "#424254", "#64908a", "#e8caa4", "#cc2a41"], + ["#554236", "#f77825", "#d3ce3d", "#f1efa5", "#60b99a"], + ["#5d4157", "#838689", "#a8caba", "#cad7b2", "#ebe3aa"], + ["#8c2318", "#5e8c6a", "#88a65e", "#bfb35a", "#f2c45a"], + ["#fad089", "#ff9c5b", "#f5634a", "#ed303c", "#3b8183"], + ["#ff4242", "#f4fad2", "#d4ee5e", "#e1edb9", "#f0f2eb"], + ["#f8b195", "#f67280", "#c06c84", "#6c5b7b", "#355c7d"], + ["#d1e751", "#ffffff", "#000000", "#4dbce9", "#26ade4"], + ["#1b676b", "#519548", "#88c425", "#bef202", "#eafde6"], + ["#5e412f", "#fcebb6", "#78c0a8", "#f07818", "#f0a830"], + ["#bcbdac", "#cfbe27", "#f27435", "#f02475", "#3b2d38"], + ["#452632", "#91204d", "#e4844a", "#e8bf56", "#e2f7ce"], + ["#eee6ab", "#c5bc8e", "#696758", "#45484b", "#36393b"], + ["#f0d8a8", "#3d1c00", "#86b8b1", "#f2d694", "#fa2a00"], + ["#2a044a", "#0b2e59", "#0d6759", "#7ab317", "#a0c55f"], + ["#f04155", "#ff823a", "#f2f26f", "#fff7bd", "#95cfb7"], + ["#b9d7d9", "#668284", "#2a2829", "#493736", "#7b3b3b"], + ["#bbbb88", "#ccc68d", "#eedd99", "#eec290", "#eeaa88"], + ["#b3cc57", "#ecf081", "#ffbe40", "#ef746f", "#ab3e5b"], + ["#a3a948", "#edb92e", "#f85931", "#ce1836", "#009989"], + ["#300030", "#480048", "#601848", "#c04848", "#f07241"], + ["#67917a", "#170409", "#b8af03", "#ccbf82", "#e33258"], + ["#aab3ab", "#c4cbb7", "#ebefc9", "#eee0b7", "#e8caaf"], + ["#e8d5b7", "#0e2430", "#fc3a51", "#f5b349", "#e8d5b9"], + ["#ab526b", "#bca297", "#c5ceae", "#f0e2a4", "#f4ebc3"], + ["#607848", "#789048", "#c0d860", "#f0f0d8", "#604848"], + ["#b6d8c0", "#c8d9bf", "#dadabd", "#ecdbbc", "#fedcba"], + ["#a8e6ce", "#dcedc2", "#ffd3b5", "#ffaaa6", "#ff8c94"], + ["#3e4147", "#fffedf", "#dfba69", "#5a2e2e", "#2a2c31"], + ["#fc354c", "#29221f", "#13747d", "#0abfbc", "#fcf7c5"], + ["#cc0c39", "#e6781e", "#c8cf02", "#f8fcc1", "#1693a7"], + ["#1c2130", "#028f76", "#b3e099", "#ffeaad", "#d14334"], + ["#a7c5bd", "#e5ddcb", "#eb7b59", "#cf4647", "#524656"], + ["#dad6ca", "#1bb0ce", "#4f8699", "#6a5e72", "#563444"], + ["#5c323e", "#a82743", "#e15e32", "#c0d23e", "#e5f04c"], + ["#edebe6", "#d6e1c7", "#94c7b6", "#403b33", "#d3643b"], + ["#fdf1cc", "#c6d6b8", "#987f69", "#e3ad40", "#fcd036"], + ["#230f2b", "#f21d41", "#ebebbc", "#bce3c5", "#82b3ae"], + ["#b9d3b0", "#81bda4", "#b28774", "#f88f79", "#f6aa93"], + ["#3a111c", "#574951", "#83988e", "#bcdea5", "#e6f9bc"], + ["#5e3929", "#cd8c52", "#b7d1a3", "#dee8be", "#fcf7d3"], + ["#1c0113", "#6b0103", "#a30006", "#c21a01", "#f03c02"], + ["#000000", "#9f111b", "#b11623", "#292c37", "#cccccc"], + ["#382f32", "#ffeaf2", "#fcd9e5", "#fbc5d8", "#f1396d"], + ["#e3dfba", "#c8d6bf", "#93ccc6", "#6cbdb5", "#1a1f1e"], + ["#f6f6f6", "#e8e8e8", "#333333", "#990100", "#b90504"], + ["#1b325f", "#9cc4e4", "#e9f2f9", "#3a89c9", "#f26c4f"], + ["#a1dbb2", "#fee5ad", "#faca66", "#f7a541", "#f45d4c"], + ["#c1b398", "#605951", "#fbeec2", "#61a6ab", "#accec0"], + ["#5e9fa3", "#dcd1b4", "#fab87f", "#f87e7b", "#b05574"], + ["#951f2b", "#f5f4d7", "#e0dfb1", "#a5a36c", "#535233"], + ["#8dccad", "#988864", "#fea6a2", "#f9d6ac", "#ffe9af"], + ["#2d2d29", "#215a6d", "#3ca2a2", "#92c7a3", "#dfece6"], + ["#413d3d", "#040004", "#c8ff00", "#fa023c", "#4b000f"], + ["#eff3cd", "#b2d5ba", "#61ada0", "#248f8d", "#605063"], + ["#ffefd3", "#fffee4", "#d0ecea", "#9fd6d2", "#8b7a5e"], + ["#cfffdd", "#b4dec1", "#5c5863", "#a85163", "#ff1f4c"], + ["#9dc9ac", "#fffec7", "#f56218", "#ff9d2e", "#919167"], + ["#4e395d", "#827085", "#8ebe94", "#ccfc8e", "#dc5b3e"], + ["#a8a7a7", "#cc527a", "#e8175d", "#474747", "#363636"], + ["#f8edd1", "#d88a8a", "#474843", "#9d9d93", "#c5cfc6"], + ["#046d8b", "#309292", "#2fb8ac", "#93a42a", "#ecbe13"], + ["#f38a8a", "#55443d", "#a0cab5", "#cde9ca", "#f1edd0"], + ["#a70267", "#f10c49", "#fb6b41", "#f6d86b", "#339194"], + ["#ff003c", "#ff8a00", "#fabe28", "#88c100", "#00c176"], + ["#ffedbf", "#f7803c", "#f54828", "#2e0d23", "#f8e4c1"], + ["#4e4d4a", "#353432", "#94ba65", "#2790b0", "#2b4e72"], + ["#0ca5b0", "#4e3f30", "#fefeeb", "#f8f4e4", "#a5b3aa"], + ["#4d3b3b", "#de6262", "#ffb88c", "#ffd0b3", "#f5e0d3"], + ["#fffbb7", "#a6f6af", "#66b6ab", "#5b7c8d", "#4f2958"], + ["#edf6ee", "#d1c089", "#b3204d", "#412e28", "#151101"], + ["#9d7e79", "#ccac95", "#9a947c", "#748b83", "#5b756c"], + ["#fcfef5", "#e9ffe1", "#cdcfb7", "#d6e6c3", "#fafbe3"], + ["#9cddc8", "#bfd8ad", "#ddd9ab", "#f7af63", "#633d2e"], + ["#30261c", "#403831", "#36544f", "#1f5f61", "#0b8185"], + ["#aaff00", "#ffaa00", "#ff00aa", "#aa00ff", "#00aaff"], + ["#d1313d", "#e5625c", "#f9bf76", "#8eb2c5", "#615375"], + ["#ffe181", "#eee9e5", "#fad3b2", "#ffba7f", "#ff9c97"], + ["#73c8a9", "#dee1b6", "#e1b866", "#bd5532", "#373b44"], + ["#805841", "#dcf7f3", "#fffcdd", "#ffd8d8", "#f5a2a2"], +] + +export const getDebugLayoutObject = (lo: any): LayoutDebugObject | null => { + let { + x, + y, + width, + height, + }: { x: number; y: number; width?: number; height?: number } = { + ...lo, + ...(lo as any).size, + ...(lo as any).center, + ...(lo as any).position, + } + + if ( + lo.x1 !== undefined && + lo.x2 !== undefined && + lo.y1 !== undefined && + lo.y2 !== undefined + ) { + x = (lo.x1 + lo.x2) / 2 + y = (lo.y1 + lo.y2) / 2 + width = Math.abs(lo.x1 - lo.x2) + height = Math.abs(lo.y1 - lo.y2) + } + + const title = lo.text || lo.name || lo.source?.text || lo.source?.name || "?" + const content = lo + + if (x === undefined || y === undefined) return null + + if (width === undefined) { + if ("outer_diameter" in lo) { + width = lo.outer_diameter + height = lo.outer_diameter + } + } + + if (width === undefined || height === undefined) { + width = 0.1 + height = 0.1 + } + + return { + x, + y, + width, + height, + title, + content, + bg_color: + nice_color_palettes[ + stringHash((lo as any).type || title) % nice_color_palettes.length + ]?.[4] ?? "#f00", + } +} diff --git a/lib/utils/is-truthy.ts b/lib/utils/is-truthy.ts new file mode 100644 index 0000000..d362625 --- /dev/null +++ b/lib/utils/is-truthy.ts @@ -0,0 +1 @@ +export const isTruthy = (value: T): value is NonNullable => Boolean(value) diff --git a/lib/utils/string-hash.ts b/lib/utils/string-hash.ts new file mode 100644 index 0000000..f0c2519 --- /dev/null +++ b/lib/utils/string-hash.ts @@ -0,0 +1,10 @@ +export function stringHash(str: string) { + let hash = 0 + if (str.length == 0) return hash + for (var i = 0; i < str.length; i++) { + var char = str.charCodeAt(i) + hash = (hash << 5) - hash + char + hash = hash & hash // Convert to 32bit integer + } + return Math.abs(hash) +} diff --git a/tests/find-bounds-and-center.test.ts b/tests/find-bounds-and-center.test.ts new file mode 100644 index 0000000..b203a5b --- /dev/null +++ b/tests/find-bounds-and-center.test.ts @@ -0,0 +1,51 @@ +import { expect, test } from "bun:test" +import { findBoundsAndCenter } from "lib/find-bounds-and-center" + +test("should return default values for empty input", () => { + const result = findBoundsAndCenter([]) + expect(result).toEqual({ center: { x: 0, y: 0 }, width: 0, height: 0 }) +}) + +test("should calculate bounds and center for a single element", () => { + const elements = [ + { type: "pcb_component", x: 10, y: 20, width: 5, height: 5 }, + ] + const result = findBoundsAndCenter(elements) + expect(result).toEqual({ + center: { x: 10, y: 20 }, + width: 5, + height: 5, + }) +}) + +test("should calculate bounds and center for multiple elements", () => { + const elements = [ + { type: "pcb_component", x: 0, y: 0, width: 10, height: 10 }, + { type: "pcb_component", x: 20, y: 20, width: 10, height: 10 }, + ] + const result = findBoundsAndCenter(elements) + expect(result).toEqual({ + center: { x: 10, y: 10 }, + width: 30, + height: 30, + }) +}) + +test("should handle pcb_trace elements correctly", () => { + const elements = [ + { + type: "pcb_trace", + route: [ + { x: 0, y: 0 }, + { x: 10, y: 10 }, + ], + }, + { type: "pcb_component", x: 20, y: 20, width: 10, height: 10 }, + ] + const result = findBoundsAndCenter(elements) + expect(result).toEqual({ + center: { x: 12.475, y: 12.475 }, + width: 25.05, + height: 25.05, + }) +})