diff --git a/lib/convert-easyeda-json-to-tscircuit-soup-json.ts b/lib/convert-easyeda-json-to-tscircuit-soup-json.ts index 0d8acfa..e76d069 100644 --- a/lib/convert-easyeda-json-to-tscircuit-soup-json.ts +++ b/lib/convert-easyeda-json-to-tscircuit-soup-json.ts @@ -292,12 +292,32 @@ export const convertEasyEdaJsonToCircuitJson = ( soupElements.push(handleHole(h, index)) }) - // Add silkscreen paths and arcs + // Add silkscreen paths, arcs and text easyEdaJson.packageDetail.dataStr.shape.forEach((shape, index) => { if (shape.type === "TRACK") { soupElements.push(handleSilkscreenPath(shape, index)) } else if (shape.type === "ARC") { soupElements.push(handleSilkscreenArc(shape, index)) + } else if (shape.type === "TEXT") { + soupElements.push( + Soup.pcb_silkscreen_text.parse({ + type: "pcb_silkscreen_text", + pcb_silkscreen_text_id: `pcb_silkscreen_text_${index + 1}`, + pcb_component_id: "pcb_component_1", + text: shape.text, + anchor_position: { + x: mil2mm(shape.x), + y: mil2mm(shape.y), + }, + anchor_alignment: { + L: "bottom_left", + C: "center", + R: "bottom_right", + }[shape.textAnchor ?? "L"], + font_size: shape.size_mm ? shape.size_mm : undefined, + layer: "top", + } as Soup.PcbSilkscreenTextInput), + ) } }) diff --git a/lib/generate-footprint-tsx.ts b/lib/generate-footprint-tsx.ts index 75267d4..856a655 100644 --- a/lib/generate-footprint-tsx.ts +++ b/lib/generate-footprint-tsx.ts @@ -17,6 +17,7 @@ export const generateFootprintTsx = ( const platedHoles = su(circuitJson).pcb_plated_hole.list() const smtPads = su(circuitJson).pcb_smtpad.list() const silkscreenPaths = su(circuitJson).pcb_silkscreen_path.list() + const silkscreenTexts = su(circuitJson).pcb_silkscreen_text.list() const elementStrings: string[] = [] @@ -60,6 +61,12 @@ export const generateFootprintTsx = ( ) } + for (const silkscreenText of silkscreenTexts) { + elementStrings.push( + ``, + ) + } + return ` ${elementStrings.join("\n")} diff --git a/lib/schemas/package-detail-shape-schema.ts b/lib/schemas/package-detail-shape-schema.ts index 3a70fb2..f1cb1e9 100644 --- a/lib/schemas/package-detail-shape-schema.ts +++ b/lib/schemas/package-detail-shape-schema.ts @@ -115,6 +115,21 @@ export const RectSchema = BaseShapeSchema.extend({ rotation: z.number().optional(), }) +export const TextSchema = BaseShapeSchema.extend({ + type: z.literal("TEXT"), + text: z.string(), + x: tenthmil, + y: tenthmil, + size_mm: z.number(), + rotation: z.number().optional(), + layer: z.number().optional(), + textAnchor: z + .enum(["L", "C", "R", ""]) + .optional() + .transform((val) => (val === "" ? undefined : val)), + font: z.string().optional(), +}) + export const PackageDetailShapeSchema = z.discriminatedUnion("type", [ TrackSchema, PadSchema, @@ -124,6 +139,7 @@ export const PackageDetailShapeSchema = z.discriminatedUnion("type", [ SVGNodeSchema, HoleSchema, RectSchema, + TextSchema, ]) const pairs = (arr: T[]): [T, T][] => { @@ -290,6 +306,22 @@ export const ShapeItemSchema = z fillStyle: fillStyle || undefined, }) } + case "TEXT": { + const [textAnchor, x, y, size, layer, id, rotation, , font, text] = + shape.data.split("~") + return TextSchema.parse({ + type: "TEXT", + text, + x, + y, + size_mm: Number(size) * 2.54, // empirically this seems to match, C5248081 is a good test case + layer: layer ? Number(layer) : undefined, + id, + rotation: rotation ? Number(rotation) : undefined, + textAnchor: textAnchor as "L" | "C" | "R" | undefined, + font: font || undefined, + }) + } default: throw new Error(`Unknown shape type: ${shape.type}`) diff --git a/tests/assets/C5248081.raweasy.json b/tests/assets/C5248081.raweasy.json new file mode 100644 index 0000000..16d9237 --- /dev/null +++ b/tests/assets/C5248081.raweasy.json @@ -0,0 +1,201 @@ +{ + "uuid": "333912d2af914b89a59f1086fd452842", + "title": "HS91L02W2C01", + "description": "", + "docType": 2, + "type": 3, + "thumb": "/component/333912d2af914b89a59f1086fd452842.png", + "lcsc": { + "id": 5960632, + "number": "C5248081" + }, + "szlcsc": { + "id": 5960632, + "number": "C5248081" + }, + "owner": { + "uuid": "0819f05c4eef4c71ace90d822a990e87", + "username": "lcsc", + "nickname": "LCSC", + "avatar": "//image.lceda.cn/avatars/2018/6/kFlrasi7W06gTdBLAqW3fkrqbDhbowynuSzkjqso.png" + }, + "tags": ["OLED Displays Modules"], + "updateTime": 1705041603, + "updated_at": "2025-01-02 11:21:31", + "dataStr": { + "head": { + "docType": "2", + "editorVersion": "6.5.39", + "x": 395, + "y": 300, + "c_para": { + "pre": "OLED?", + "name": "HS91L02W2C01", + "package": "OLED-TH_L38.0-W12.0_HS91L02W2C01", + "Supplier": "LCSC", + "Supplier Part": "C5248081", + "Manufacturer": "HS(汉昇)", + "Manufacturer Part": "HS91L02W2C01", + "JLCPCB Part Class": "Extended Part", + "Contributor": "lcsc" + }, + "puuid": "47a89f7083cc4ae28e8c42ffc894fa62", + "utime": 1702520650, + "uuid": "333912d2af914b89a59f1086fd452842", + "importFlag": 0, + "c_spiceCmd": null, + "pre": "OLED?", + "name": "HS91L02W2C01", + "hasIdFlag": true + }, + "canvas": "CA~1000~1000~#FFFFFF~yes~#CCCCCC~5~1000~1000~line~5~pixel~5~395~300", + "shape": [ + "R~375~275~2~2~30~50~#880000~1~0~none~gge1~0~", + "P~show~0~1~415~315~0~gge5~0^^415~315^^M 415 315 h -10~#000000^^1~401.3~319~0~GND~end~~~#000000^^1~405.5~314~0~1~start~~~#000000^^0~408~315^^0~M 405 312 L 402 315 L 405 318", + "P~show~0~2~415~305~0~gge6~0^^415~305^^M 415 305 h -10~#FF0000^^1~401.3~309~0~VCC~end~~~#FF0000^^1~405.5~304~0~2~start~~~#FF0000^^0~408~305^^0~M 405 302 L 402 305 L 405 308", + "P~show~0~3~415~295~0~gge7~0^^415~295^^M 415 295 h -10~#880000^^1~401.3~299~0~SCL~end~~~#0000FF^^1~405.5~294~0~3~start~~~#0000FF^^0~408~295^^0~M 405 292 L 402 295 L 405 298", + "P~show~0~4~415~285~0~gge8~0^^415~285^^M 415 285 h -10~#880000^^1~401.3~289~0~SDA~end~~~#0000FF^^1~405.5~284~0~4~start~~~#0000FF^^0~408~285^^0~M 405 282 L 402 285 L 405 288" + ], + "BBox": { + "x": 375, + "y": 274.6, + "width": 41, + "height": 50.4 + }, + "colors": [] + }, + "jlcOnSale": 1, + "datastrid": "cf2b344e7c2841908eba14c2cc47cfbb", + "verify": true, + "SMT": true, + "writable": false, + "isFavorite": false, + "packageDetail": { + "uuid": "47a89f7083cc4ae28e8c42ffc894fa62", + "title": "OLED-TH_L38.0-W12.0_HS91L02W2C01", + "docType": 4, + "updateTime": 1702952943, + "owner": { + "uuid": "0819f05c4eef4c71ace90d822a990e87", + "username": "lcsc", + "nickname": "LCSC", + "avatar": "//image.lceda.cn/avatars/2018/6/kFlrasi7W06gTdBLAqW3fkrqbDhbowynuSzkjqso.png" + }, + "datastrid": "489da674c2524cb491d0ca3de342d9d7", + "writable": false, + "dataStr": { + "head": { + "docType": "4", + "editorVersion": "6.5.39", + "newgId": true, + "c_para": { + "package": "OLED-TH_L38.0-W12.0_HS91L02W2C01", + "pre": "OLED?", + "Contributor": "lcsc", + "link": "https://atta.szlcsc.com/upload/public/pdf/source/20230727/C3E43C8E3DDA847F5681FEE7D95C153D.pdf", + "3DModel": "OLED-TH_L38.0-W12.0_HS91L02W2C01" + }, + "x": 4000, + "y": 3005, + "hasIdFlag": true, + "utime": 1702951613, + "uuid": "47a89f7083cc4ae28e8c42ffc894fa62", + "importFlag": 0, + "transformList": "", + "uuid_3d": "ce577b6b48bc443fbd8e9fda4477079f" + }, + "canvas": "CA~1000~1000~#000000~yes~#FFFFFF~10~1000~1000~line~0.003937~mm~1~45~visible~0.5~4000~3005~0~none", + "shape": [ + "SOLIDREGION~99~~M 3994.0945 2981.378 L 3994.0945 3028.622 L 4143.7005 3028.622 L 4143.7005 2981.378 Z ~solid~gge109~~~~0", + "TRACK~1~3~~3994.0945 2981.378 4143.7005 2981.378 4143.7005 3028.622 3994.0945 3028.622 3994.0945 2981.378~gge121~0", + "TEXT~L~4006.532~3027.35~0.8~0~0~3~~8~1~M 4006.5284 3020.7299 L 4007.2584 3020.3699 L 4008.3484 3019.2799 L 4008.3484 3026.9099~~gge133~~0~pinpart", + "PAD~ELLIPSE~4000~3020~6.2992~6.2992~11~~1~1.9685~~0~gge16~0~~Y~0~0~0.2~4000,3020", + "PAD~ELLIPSE~4000~3010~6.2992~6.2992~11~~2~1.9685~~0~gge46~0~~Y~0~0~0.2~4000,3010", + "PAD~ELLIPSE~4000~3000~6.2992~6.2992~11~~3~1.9685~~0~gge64~0~~Y~0~0~0.2~4000,3000", + "PAD~ELLIPSE~4000~2990~6.2992~6.2992~11~~4~1.9685~~0~gge82~0~~Y~0~0~0.2~4000,2990", + "SVGNODE~{\"gId\":\"g1_outline\",\"nodeName\":\"g\",\"nodeType\":1,\"layerid\":\"19\",\"attrs\":{\"c_width\":\"149.606\",\"c_height\":\"47.244\",\"c_rotation\":\"0,0,0\",\"z\":\"0\",\"c_origin\":\"4068.8975,3005\",\"uuid\":\"9018832d564840e08b96db89bf75c8cc\",\"c_etype\":\"outline3D\",\"id\":\"g1_outline\",\"title\":\"OLED-TH_L38.0-W12.0_HS91L02W2C01\",\"layerid\":\"19\",\"transform\":\"scale(1) translate(0, 0)\"},\"childNodes\":[{\"gId\":\"g1_outline_line0\",\"nodeName\":\"polyline\",\"nodeType\":1,\"attrs\":{\"fill\":\"none\",\"id\":\"g1_outline_line0\",\"c_shapetype\":\"line\",\"points\":\"3994.0945 2981.378 3994.0945 3014.8031 3994.0945 3028.622 4003.7401 3028.622 4143.7005 3028.622 4143.7005 3014.8031 4143.7005 2981.378 4003.7401 2981.378 3994.0945 2981.378 3994.0945 2981.378\"}}]}" + ], + "layers": [ + "1~TopLayer~#FF0000~true~false~true~", + "2~BottomLayer~#0000FF~true~false~true~", + "3~TopSilkLayer~#FFCC00~true~true~true~", + "4~BottomSilkLayer~#66CC33~true~false~true~", + "5~TopPasteMaskLayer~#808080~true~false~true~", + "6~BottomPasteMaskLayer~#800000~true~false~true~", + "7~TopSolderMaskLayer~#800080~true~false~true~0.3", + "8~BottomSolderMaskLayer~#AA00FF~true~false~true~0.3", + "9~Ratlines~#6464FF~false~false~true~", + "10~BoardOutLine~#FF00FF~true~false~true~", + "11~Multi-Layer~#C0C0C0~true~false~true~", + "12~Document~#FFFFFF~true~false~true~", + "13~TopAssembly~#33CC99~true~false~true~", + "14~BottomAssembly~#5555FF~true~false~true~", + "15~Mechanical~#F022F0~true~false~true~", + "19~3DModel~#66CCFF~true~false~true~", + "21~Inner1~#999966~false~false~false~~", + "22~Inner2~#008000~false~false~false~~", + "23~Inner3~#00FF00~false~false~false~~", + "24~Inner4~#BC8E00~false~false~false~~", + "25~Inner5~#70DBFA~false~false~false~~", + "26~Inner6~#00CC66~false~false~false~~", + "27~Inner7~#9966FF~false~false~false~~", + "28~Inner8~#800080~false~false~false~~", + "29~Inner9~#008080~false~false~false~~", + "30~Inner10~#15935F~false~false~false~~", + "31~Inner11~#000080~false~false~false~~", + "32~Inner12~#00B400~false~false~false~~", + "33~Inner13~#2E4756~false~false~false~~", + "34~Inner14~#99842F~false~false~false~~", + "35~Inner15~#FFFFAA~false~false~false~~", + "36~Inner16~#99842F~false~false~false~~", + "37~Inner17~#2E4756~false~false~false~~", + "38~Inner18~#3535FF~false~false~false~~", + "39~Inner19~#8000BC~false~false~false~~", + "40~Inner20~#43AE5F~false~false~false~~", + "41~Inner21~#C3ECCE~false~false~false~~", + "42~Inner22~#728978~false~false~false~~", + "43~Inner23~#39503F~false~false~false~~", + "44~Inner24~#0C715D~false~false~false~~", + "45~Inner25~#5A8A80~false~false~false~~", + "46~Inner26~#2B937E~false~false~false~~", + "47~Inner27~#23999D~false~false~false~~", + "48~Inner28~#45B4E3~false~false~false~~", + "49~Inner29~#215DA1~false~false~false~~", + "50~Inner30~#4564D7~false~false~false~~", + "51~Inner31~#6969E9~false~false~false~~", + "52~Inner32~#9069E9~false~false~false~~", + "99~ComponentShapeLayer~#00CCCC~true~false~true~0.4", + "100~LeadShapeLayer~#CC9999~true~false~true~", + "101~ComponentMarkingLayer~#66FFCC~true~false~true~", + "Hole~Hole~#222222~false~false~true~", + "DRCError~DRCError~#FAD609~false~false~true~" + ], + "objects": [ + "All~true~false", + "Component~true~true", + "Prefix~true~true", + "Name~true~false", + "Track~true~true", + "Pad~true~true", + "Via~true~true", + "Hole~true~true", + "Copper_Area~true~true", + "Circle~true~true", + "Arc~true~true", + "Solid_Region~true~true", + "Text~true~true", + "Image~true~true", + "Rect~true~true", + "Dimension~true~true", + "Protractor~true~true" + ], + "BBox": { + "x": 3994.1, + "y": 2981.4, + "width": 149.6, + "height": 47.2 + }, + "netColors": [] + } + } +} diff --git a/tests/convert-to-ts/C5248081-to-ts.test.ts b/tests/convert-to-ts/C5248081-to-ts.test.ts new file mode 100644 index 0000000..1d0f3a8 --- /dev/null +++ b/tests/convert-to-ts/C5248081-to-ts.test.ts @@ -0,0 +1,74 @@ +import { it, expect } from "bun:test" +import chipRawEasy from "../assets/C5248081.raweasy.json" +import { convertBetterEasyToTsx } from "lib/convert-to-typescript-component" +import { EasyEdaJsonSchema } from "lib/schemas/easy-eda-json-schema" + +it("should convert C5248081 into typescript file", async () => { + const betterEasy = EasyEdaJsonSchema.parse(chipRawEasy) + const result = await convertBetterEasyToTsx({ + betterEasy, + }) + + expect(result).not.toContain("milmm") + expect(result).not.toContain("NaNmm") + + // Add more specific assertions here based on the component + + expect(result).toMatchInlineSnapshot(` +"import { createUseComponent } from "@tscircuit/core" +import type { CommonLayoutProps } from "@tscircuit/props" + +const pinLabels = { + "pin1": [ + "pin1", + "GND" + ], + "pin2": [ + "pin2", + "VCC" + ], + "pin3": [ + "pin3", + "SCL" + ], + "pin4": [ + "pin4", + "SDA" + ] +} as const + +interface Props extends CommonLayoutProps { + name: string +} + +export const HS91L02W2C01 = (props: Props) => { + return ( + + + + + + + + } + /> + ) +} + +export const useHS91L02W2C01 = createUseComponent(HS91L02W2C01, pinLabels)" +`) +})