Skip to content

Commit

Permalink
Add support for parsing silkscreen text (#146)
Browse files Browse the repository at this point in the history
* wip c5248081

* initial parsing working

* wip

* progress on text conversion

* add empirical size tweaking
  • Loading branch information
seveibar authored Jan 6, 2025
1 parent ca74da0 commit 2a5da8a
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 1 deletion.
22 changes: 21 additions & 1 deletion lib/convert-easyeda-json-to-tscircuit-soup-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
}
})

Expand Down
7 changes: 7 additions & 0 deletions lib/generate-footprint-tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = []

Expand Down Expand Up @@ -60,6 +61,12 @@ export const generateFootprintTsx = (
)
}

for (const silkscreenText of silkscreenTexts) {
elementStrings.push(
`<silkscreentext text="${silkscreenText.text}" pcbX="${mmStr(silkscreenText.anchor_position.x)}" pcbY="${mmStr(silkscreenText.anchor_position.y)}" anchorAlignment="${silkscreenText.anchor_alignment}" ${silkscreenText.font_size ? `fontSize="${mmStr(silkscreenText.font_size)}"` : ""} />`,
)
}

return `
<footprint>
${elementStrings.join("\n")}
Expand Down
32 changes: 32 additions & 0 deletions lib/schemas/package-detail-shape-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -124,6 +139,7 @@ export const PackageDetailShapeSchema = z.discriminatedUnion("type", [
SVGNodeSchema,
HoleSchema,
RectSchema,
TextSchema,
])

const pairs = <T>(arr: T[]): [T, T][] => {
Expand Down Expand Up @@ -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}`)
Expand Down
201 changes: 201 additions & 0 deletions tests/assets/C5248081.raweasy.json
Original file line number Diff line number Diff line change
@@ -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": []
}
}
}
74 changes: 74 additions & 0 deletions tests/convert-to-ts/C5248081-to-ts.test.ts
Original file line number Diff line number Diff line change
@@ -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 (
<chip
{...props}
cadModel={{
objUrl: "https://modelcdn.tscircuit.com/easyeda_models/download?uuid=9018832d564840e08b96db89bf75c8cc&pn=C5248081",
rotationOffset: { x: 0, y: 0, z: 0 },
positionOffset: { x: 0, y: 0, z: 0 },
}}
pinLabels={pinLabels}
supplierPartNumbers={{
"jlcpcb": [
"C5248081"
]
}}
manufacturerPartNumber="HS91L02W2C01"
footprint={<footprint>
<platedhole portHints={["pin1"]} pcbX="0mm" pcbY="-3.8099999999999454mm" outerDiameter="1.5999967999999998mm" holeDiameter="0.9999979999999999mm" shape="circle" />
<platedhole portHints={["pin2"]} pcbX="0mm" pcbY="-1.2699999999999818mm" outerDiameter="1.5999967999999998mm" holeDiameter="0.9999979999999999mm" shape="circle" />
<platedhole portHints={["pin3"]} pcbX="0mm" pcbY="1.2699999999999818mm" outerDiameter="1.5999967999999998mm" holeDiameter="0.9999979999999999mm" shape="circle" />
<platedhole portHints={["pin4"]} pcbX="0mm" pcbY="3.810000000000059mm" outerDiameter="1.5999967999999998mm" holeDiameter="0.9999979999999999mm" shape="circle" />
<silkscreenpath route={[{"x":-1.4999970000000076,"y":5.999987999999917},{"x":36.49992699999984,"y":5.999987999999917},{"x":36.49992699999984,"y":-5.999987999999917},{"x":-1.4999970000000076,"y":-5.999987999999917},{"x":-1.4999970000000076,"y":5.999987999999917}]} />
<silkscreentext text="1" pcbX="1.659127999999896mm" pcbY="-5.676899999999932mm" anchorAlignment="bottom_left" fontSize="2.032mm" />
</footprint>}
/>
)
}
export const useHS91L02W2C01 = createUseComponent(HS91L02W2C01, pinLabels)"
`)
})

0 comments on commit 2a5da8a

Please sign in to comment.