easyeda 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +59 -0
  2. package/ai-assist-docs/soup-reference.md +1325 -0
  3. package/bun.lockb +0 -0
  4. package/cli/main.ts +67 -0
  5. package/dist/cli/main.cjs +29264 -0
  6. package/dist/cli/main.cjs.map +1 -0
  7. package/dist/cli/main.d.cts +1 -0
  8. package/dist/index.cjs +29196 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +1553 -0
  11. package/dist/lib/index.cjs +29196 -0
  12. package/dist/lib/index.cjs.map +1 -0
  13. package/dist/lib/index.d.cts +1553 -0
  14. package/lib/convert-easyeda-json-to-tscircuit-soup-json.ts +204 -0
  15. package/lib/fetch-easyeda-json.ts +66 -0
  16. package/lib/index.ts +2 -0
  17. package/lib/math/arc-utils.ts +170 -0
  18. package/lib/schemas/easy-eda-json-schema.ts +158 -0
  19. package/lib/schemas/package-detail-shape-schema.ts +251 -0
  20. package/lib/schemas/single-letter-shape-schema.ts +201 -0
  21. package/package.json +31 -0
  22. package/renovate.json +6 -0
  23. package/scripts/get-easyeda-json.ts +12 -0
  24. package/tests/assets/a555-timer-dip.raweasy.json +244 -0
  25. package/tests/assets/a555-timer-smd.raweasy.json +226 -0
  26. package/tests/assets/esp32.raweasy.json +333 -0
  27. package/tests/assets/usb-c.raweasy.json +263 -0
  28. package/tests/convert-to-soup-tests/a555timer-smd.test.ts +18 -0
  29. package/tests/convert-to-soup-tests/convert-easyeda-to-tscircuit-soup.test.ts +49 -0
  30. package/tests/convert-to-soup-tests/convert-usb-c-to-soup.test.ts +18 -0
  31. package/tests/convert-to-soup-tests/esp32-to-soup.test.ts +13 -0
  32. package/tests/parse-tests/parse-555-timer-json.test.ts +9 -0
  33. package/tests/parse-tests/parse-esp32.test.ts +9 -0
  34. package/tests/parse-tests/parse-usb-c.test.ts +9 -0
  35. package/tests/parse-tests/single-letter-shape-schema.test.ts +30 -0
  36. package/tsconfig.json +28 -0
@@ -0,0 +1,204 @@
1
+ import {
2
+ PadSchema,
3
+ TrackSchema,
4
+ ArcSchema,
5
+ SVGNodeSchema,
6
+ } from "./schemas/package-detail-shape-schema"
7
+ import { z } from "zod"
8
+ import type { EasyEdaJson } from "./schemas/easy-eda-json-schema"
9
+ import type {
10
+ AnySoupElement,
11
+ PCBSMTPad,
12
+ PcbSilkscreenPath,
13
+ PCBPlatedHole,
14
+ PCBPlatedHoleInput,
15
+ } from "@tscircuit/soup"
16
+ import {
17
+ any_source_component,
18
+ pcb_smtpad,
19
+ pcb_silkscreen_path,
20
+ pcb_plated_hole,
21
+ } from "@tscircuit/soup"
22
+ import * as Soup from "@tscircuit/soup"
23
+ import { generateArcFromSweep, generateArcPathWithMid } from "./math/arc-utils"
24
+ import { transformPCBElements } from "@tscircuit/builder"
25
+ import { scale } from "transformation-matrix"
26
+
27
+ const handleSilkscreenPath = (
28
+ track: z.infer<typeof TrackSchema>,
29
+ index: number
30
+ ) => {
31
+ return pcb_silkscreen_path.parse({
32
+ type: "pcb_silkscreen_path",
33
+ pcb_silkscreen_path_id: `pcb_silkscreen_path_${index + 1}`,
34
+ pcb_component_id: "pcb_component_1",
35
+ layer: "top", // Assuming all silkscreen is on top layer
36
+ route: track.points.map((point) => ({ x: point.x, y: point.y })),
37
+ stroke_width: track.width,
38
+ })
39
+ }
40
+
41
+ const handleSilkscreenArc = (arc: z.infer<typeof ArcSchema>, index: number) => {
42
+ const arcPath = generateArcFromSweep(
43
+ arc.start.x,
44
+ arc.start.y,
45
+ arc.end.x,
46
+ arc.end.y,
47
+ arc.radiusX,
48
+ arc.largeArc,
49
+ arc.sweepDirection === "CW"
50
+ )
51
+
52
+ return pcb_silkscreen_path.parse({
53
+ type: "pcb_silkscreen_path",
54
+ pcb_silkscreen_path_id: `pcb_silkscreen_arc_${index + 1}`,
55
+ pcb_component_id: "pcb_component_1",
56
+ layer: "top", // Assuming all silkscreen is on top layer
57
+ route: arcPath,
58
+ stroke_width: arc.width,
59
+ })
60
+ }
61
+
62
+ interface Options {
63
+ useModelCdn?: boolean
64
+ }
65
+
66
+ export const convertEasyEdaJsonToTscircuitSoupJson = (
67
+ easyEdaJson: EasyEdaJson,
68
+ { useModelCdn }: Options = {}
69
+ ): AnySoupElement[] => {
70
+ const soupElements: AnySoupElement[] = []
71
+
72
+ // Add source component
73
+ const source_component = any_source_component.parse({
74
+ type: "source_component",
75
+ source_component_id: "source_component_1",
76
+ name: "U1",
77
+ ftype: "simple_bug",
78
+ })
79
+
80
+ const pcb_component = Soup.pcb_component.parse({
81
+ type: "pcb_component",
82
+ pcb_component_id: "pcb_component_1",
83
+ source_component_id: "source_component_1",
84
+ name: "U1",
85
+ ftype: "simple_bug",
86
+ width: 0, // TODO compute width
87
+ height: 0, // TODO compute height
88
+ rotation: 0,
89
+ center: { x: 0, y: 0 },
90
+ layer: "top",
91
+ } as Soup.PCBComponentInput)
92
+
93
+ soupElements.push(source_component, pcb_component)
94
+
95
+ // Add source ports and pcb_smtpads
96
+ easyEdaJson.packageDetail.dataStr.shape
97
+ .filter((shape): shape is z.infer<typeof PadSchema> => shape.type === "PAD")
98
+ .forEach((pad, index) => {
99
+ const portNumber = pad.number.toString()
100
+
101
+ // Add source port
102
+ soupElements.push({
103
+ type: "source_port",
104
+ source_port_id: `source_port_${index + 1}`,
105
+ source_component_id: "source_component_1",
106
+ name: portNumber,
107
+ })
108
+
109
+ if (pad.holeRadius !== undefined && pad.holeRadius !== 0) {
110
+ // Add pcb_plated_hole
111
+ soupElements.push(
112
+ pcb_plated_hole.parse({
113
+ type: "pcb_plated_hole",
114
+ pcb_plated_hole_id: `pcb_plated_hole_${index + 1}`,
115
+ shape: "circle",
116
+ x: pad.center.x,
117
+ y: pad.center.y,
118
+ hole_diameter: pad.holeRadius * 2,
119
+ outer_diameter: pad.width,
120
+ radius: pad.holeRadius,
121
+ port_hints: [portNumber],
122
+ pcb_component_id: "pcb_component_1",
123
+ pcb_port_id: `pcb_port_${index + 1}`,
124
+ layers: ["top"],
125
+ } as PCBPlatedHoleInput)
126
+ )
127
+ } else {
128
+ // Add pcb_smtpad
129
+ let soupShape: PCBSMTPad["shape"] | undefined
130
+ if (pad.shape === "RECT") {
131
+ soupShape = "rect"
132
+ } else if (pad.shape === "ELLIPSE") {
133
+ // This is just a bug
134
+ soupShape = "rect"
135
+ } else if (pad.shape === "OVAL") {
136
+ // OVAL is often a rect, especially when holeRadius is 0
137
+ soupShape = "rect"
138
+ }
139
+ if (!soupShape) {
140
+ throw new Error(`unknown pad.shape: "${pad.shape}"`)
141
+ }
142
+ soupElements.push(
143
+ pcb_smtpad.parse({
144
+ type: "pcb_smtpad",
145
+ pcb_smtpad_id: `pcb_smtpad_${index + 1}`,
146
+ shape: soupShape,
147
+ x: pad.center.x,
148
+ y: pad.center.y,
149
+ ...(soupShape === "rect"
150
+ ? { width: pad.width, height: pad.height }
151
+ : { radius: Math.min(pad.width, pad.height) / 2 }),
152
+ layer: "top",
153
+ port_hints: [portNumber],
154
+ pcb_component_id: "pcb_component_1",
155
+ pcb_port_id: `pcb_port_${index + 1}`,
156
+ } as PCBSMTPad)
157
+ )
158
+ }
159
+ })
160
+
161
+ // Add silkscreen paths and arcs
162
+ easyEdaJson.packageDetail.dataStr.shape.forEach((shape, index) => {
163
+ if (shape.type === "TRACK") {
164
+ soupElements.push(handleSilkscreenPath(shape, index))
165
+ } else if (shape.type === "ARC") {
166
+ soupElements.push(handleSilkscreenArc(shape, index))
167
+ }
168
+ })
169
+
170
+ // easyeda uses a flipped Y axis ( tscircuit = y+ is up, easyeda = y- is up )
171
+ transformPCBElements(soupElements, scale(1, -1))
172
+
173
+ // TODO Change pcb_component width & height
174
+
175
+ // TODO compute pcb center based on all elements and transform elements such
176
+ // that the center is (0,0)
177
+
178
+ // Add 3d component
179
+ const objFileUuid = easyEdaJson.packageDetail.dataStr.shape.find(
180
+ (a): a is z.input<typeof SVGNodeSchema> =>
181
+ Boolean(a.type === "SVGNODE" && a.svgData.attrs?.uuid)
182
+ )?.svgData?.attrs?.uuid
183
+
184
+ const objFileUrl = objFileUuid
185
+ ? useModelCdn
186
+ ? `https://modelcdn.tscircuit.com/easyeda_models/download?uuid=${objFileUuid}&pn=${easyEdaJson.lcsc.number}`
187
+ : `https://modules.easyeda.com/3dmodel/${objFileUuid}`
188
+ : undefined
189
+
190
+ if (objFileUrl !== undefined) {
191
+ soupElements.push(
192
+ Soup.cad_component.parse({
193
+ type: "cad_component",
194
+ cad_component_id: "cad_component_1",
195
+ source_component_id: "source_component_1",
196
+ pcb_component_id: "pcb_component_1",
197
+ position: { x: 0, y: 0, z: 0 },
198
+ model_obj_url: objFileUrl,
199
+ } as Soup.CadComponentInput)
200
+ )
201
+ }
202
+
203
+ return soupElements
204
+ }
@@ -0,0 +1,66 @@
1
+ export async function fetchEasyEDAComponent(
2
+ jlcpcbPartNumber: string
3
+ ): Promise<any> {
4
+ const searchUrl = "https://easyeda.com/api/components/search"
5
+ const componentUrl = (uuid: string) =>
6
+ `https://easyeda.com/api/components/${uuid}?version=6.4.7&uuid=${uuid}&datastrid=`
7
+
8
+ const searchHeaders = {
9
+ authority: "easyeda.com",
10
+ pragma: "no-cache",
11
+ "cache-control": "no-cache",
12
+ accept: "application/json, text/javascript, */*; q=0.01",
13
+ "x-requested-with": "XMLHttpRequest",
14
+ "user-agent":
15
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
16
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
17
+ origin: "https://easyeda.com",
18
+ "sec-fetch-site": "same-origin",
19
+ "sec-fetch-mode": "cors",
20
+ "sec-fetch-dest": "empty",
21
+ referer: "https://easyeda.com/editor",
22
+ "accept-language": "cs,en;q=0.9,sk;q=0.8,en-GB;q=0.7",
23
+ cookie: "<PUT your cookies here>",
24
+ }
25
+
26
+ const searchData = `type=3&doctype%5B%5D=2&uid=0819f05c4eef4c71ace90d822a990e87&returnListStyle=classifyarr&wd=${jlcpcbPartNumber}&version=6.4.7`
27
+
28
+ // Perform the search request
29
+ const searchResponse = await fetch(searchUrl, {
30
+ method: "POST",
31
+ headers: searchHeaders,
32
+ body: searchData,
33
+ })
34
+
35
+ if (!searchResponse.ok) {
36
+ throw new Error("Failed to search for the component")
37
+ }
38
+
39
+ const searchResult = await searchResponse.json()
40
+ if (!searchResult.success || !searchResult.result.lists.lcsc.length) {
41
+ throw new Error("Component not found")
42
+ }
43
+
44
+ const componentUUID = searchResult.result.lists.lcsc[0].uuid
45
+
46
+ // Perform the component fetch request
47
+ const componentResponse = await fetch(componentUrl(componentUUID), {
48
+ method: "GET",
49
+ headers: {
50
+ ...searchHeaders,
51
+ referer: `https://easyeda.com/editor?uuid=${componentUUID}`,
52
+ },
53
+ })
54
+
55
+ if (!componentResponse.ok) {
56
+ throw new Error("Failed to fetch the component details")
57
+ }
58
+
59
+ const componentResult = await componentResponse.json()
60
+ return componentResult
61
+ }
62
+
63
+ // Usage example
64
+ // fetchEasyEDAComponent("C558438")
65
+ // .then((component) => console.log(component))
66
+ // .catch((error) => console.error(error))
package/lib/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./convert-easyeda-json-to-tscircuit-soup-json"
2
+ export * from "./fetch-easyeda-json"
@@ -0,0 +1,170 @@
1
+ export interface Point {
2
+ x: number
3
+ y: number
4
+ }
5
+
6
+ export function calculateCenter(start: Point, mid: Point, end: Point): Point {
7
+ const mid1 = { x: (start.x + mid.x) / 2, y: (start.y + mid.y) / 2 }
8
+ const mid2 = { x: (mid.x + end.x) / 2, y: (mid.y + end.y) / 2 }
9
+
10
+ const slope1 = -(start.x - mid.x) / (start.y - mid.y)
11
+ const slope2 = -(mid.x - end.x) / (mid.y - end.y)
12
+
13
+ const centerX =
14
+ (mid1.y - mid2.y + slope2 * mid2.x - slope1 * mid1.x) / (slope2 - slope1)
15
+ const centerY = mid1.y + slope1 * (centerX - mid1.x)
16
+
17
+ return { x: centerX, y: centerY }
18
+ }
19
+
20
+ function calculateRadius(center: Point, point: Point): number {
21
+ return Math.sqrt((center.x - point.x) ** 2 + (center.y - point.y) ** 2)
22
+ }
23
+
24
+ function calculateAngle(center: Point, point: Point): number {
25
+ return Math.atan2(point.y - center.y, point.x - center.x)
26
+ }
27
+
28
+ export const getArcLength = (start: Point, mid: Point, end: Point) => {
29
+ const center = calculateCenter(start, mid, end)
30
+ const radius = calculateRadius(center, start)
31
+
32
+ const angleStart = calculateAngle(center, start)
33
+ const angleEnd = calculateAngle(center, end)
34
+
35
+ let angleDelta = angleEnd - angleStart
36
+ if (angleDelta < 0) {
37
+ angleDelta += 2 * Math.PI
38
+ }
39
+
40
+ return radius * angleDelta
41
+ }
42
+
43
+ export function generateArcPathWithMid(
44
+ start: Point,
45
+ mid: Point,
46
+ end: Point,
47
+ numPoints: number
48
+ ): Point[] {
49
+ const center = calculateCenter(start, mid, end)
50
+ const radius = calculateRadius(center, start)
51
+
52
+ const angleStart = calculateAngle(center, start)
53
+ const angleEnd = calculateAngle(center, end)
54
+
55
+ let angleDelta = angleEnd - angleStart
56
+ if (angleDelta < 0) {
57
+ angleDelta += 2 * Math.PI
58
+ }
59
+
60
+ const path: Point[] = []
61
+
62
+ for (let i = 0; i <= numPoints; i++) {
63
+ const angle = angleStart + (i / numPoints) * angleDelta
64
+ const x = center.x + radius * Math.cos(angle)
65
+ const y = center.y + radius * Math.sin(angle)
66
+ // Check for NaN or Infinity values
67
+ if (!isNaN(x) && isFinite(x) && !isNaN(y) && isFinite(y)) {
68
+ path.push({ x, y })
69
+ }
70
+ }
71
+
72
+ // If the path is empty, add the start and end points
73
+ if (path.length === 0) {
74
+ path.push(start, end)
75
+ }
76
+
77
+ return path
78
+ }
79
+
80
+ /**
81
+ This syntax describes an SVG path command for drawing an arc. Let's break down each part:
82
+
83
+ 1. "M 3923.0512 2968.5197":
84
+ - M: Move to
85
+ - 3923.0512: X-coordinate
86
+ - 2968.5197: Y-coordinate
87
+
88
+ 2. "A 2.5 2.5 0 0 0 3923.0512 2963.5197":
89
+ - A: Arc command
90
+ - 2.5: X-radius of the ellipse
91
+ - 2.5: Y-radius of the ellipse
92
+ - 0: X-axis rotation (in degrees)
93
+ - 0: Large arc flag (0 for small arc, 1 for large arc)
94
+ - 0: Sweep flag (0 for counterclockwise, 1 for clockwise)
95
+ - 3923.0512: End X-coordinate
96
+ - 2963.5197: End Y-coordinate
97
+
98
+ This command draws an arc starting at (3923.0512, 2968.5197) and ending at (3923.0512, 2963.5197), using a circular path with a radius of 2.5 units.
99
+ */
100
+ export function generateArcFromSweep(
101
+ startX: number,
102
+ startY: number,
103
+ endX: number,
104
+ endY: number,
105
+ radius: number,
106
+ largeArcFlag: boolean,
107
+ sweepFlag: boolean
108
+ ): Point[] {
109
+ const start: Point = { x: startX, y: startY }
110
+ const end: Point = { x: endX, y: endY }
111
+
112
+ // Calculate the midpoint between start and end
113
+ const midX = (startX + endX) / 2
114
+ const midY = (startY + endY) / 2
115
+
116
+ // Calculate the distance between start and end
117
+ const dx = endX - startX
118
+ const dy = endY - startY
119
+ const distance = Math.sqrt(dx * dx + dy * dy)
120
+
121
+ // If the distance is zero or the radius is too small, return a straight line
122
+ if (distance === 0 || radius < distance / 2) {
123
+ return [start, end]
124
+ }
125
+
126
+ // Calculate the center of the arc
127
+ const h = Math.sqrt(radius * radius - (distance * distance) / 4)
128
+ const angle = Math.atan2(dy, dx)
129
+ const centerX = midX + h * Math.sin(angle) * (sweepFlag ? 1 : -1)
130
+ const centerY = midY - h * Math.cos(angle) * (sweepFlag ? 1 : -1)
131
+
132
+ // Calculate start and end angles
133
+ const startAngle = Math.atan2(startY - centerY, startX - centerX)
134
+ let endAngle = Math.atan2(endY - centerY, endX - centerX)
135
+
136
+ // Adjust end angle based on sweep and large arc flags
137
+ if (!sweepFlag && endAngle > startAngle) {
138
+ endAngle -= 2 * Math.PI
139
+ } else if (sweepFlag && endAngle < startAngle) {
140
+ endAngle += 2 * Math.PI
141
+ }
142
+
143
+ if (
144
+ (!largeArcFlag && Math.abs(endAngle - startAngle) > Math.PI) ||
145
+ (largeArcFlag && Math.abs(endAngle - startAngle) < Math.PI)
146
+ ) {
147
+ if (endAngle > startAngle) {
148
+ endAngle -= 2 * Math.PI
149
+ } else {
150
+ endAngle += 2 * Math.PI
151
+ }
152
+ }
153
+
154
+ // Generate points along the arc
155
+ const numPoints = Math.max(
156
+ 2,
157
+ Math.ceil(Math.abs(endAngle - startAngle) * radius)
158
+ )
159
+ const path: Point[] = []
160
+
161
+ for (let i = 0; i <= numPoints; i++) {
162
+ const t = i / numPoints
163
+ const angle = startAngle + t * (endAngle - startAngle)
164
+ const x = centerX + radius * Math.cos(angle)
165
+ const y = centerY + radius * Math.sin(angle)
166
+ path.push({ x, y })
167
+ }
168
+
169
+ return path
170
+ }
@@ -0,0 +1,158 @@
1
+ import { z } from "zod"
2
+ import {
3
+ PackageDetailShapeSchema,
4
+ ShapeItemSchema,
5
+ } from "./package-detail-shape-schema"
6
+ import { SingleLetterShapeSchema } from "./single-letter-shape-schema"
7
+
8
+ export const maybeNumber = z
9
+ .any()
10
+ .transform((k) => (k === "nan" || Number.isNaN(k) ? null : k))
11
+ .pipe(z.number().nullable().optional())
12
+
13
+ export const SzlcscSchema = z.object({
14
+ id: z.number(),
15
+ number: z.string(),
16
+ step: z.number().optional(),
17
+ min: z.number().optional(),
18
+ price: z.number().optional(),
19
+ stock: z.number().optional(),
20
+ url: z.string().url().optional(),
21
+ image: z.string().optional().optional(),
22
+ })
23
+
24
+ export const LcscSchema = z.object({
25
+ id: z.number(),
26
+ number: z.string(),
27
+ step: z.number().optional(),
28
+ min: z.number().optional(),
29
+ price: z.number().optional(),
30
+ stock: z.number().optional(),
31
+ url: z.string().url().optional(),
32
+ })
33
+
34
+ export const OwnerSchema = z.object({
35
+ uuid: z.string(),
36
+ username: z.string(),
37
+ nickname: z.string(),
38
+ avatar: z.string(),
39
+ })
40
+
41
+ export const HeadSchema = z.object({
42
+ docType: z.string(),
43
+ editorVersion: z.string(),
44
+ c_para: z.record(z.string(), z.string()),
45
+ x: z.number(),
46
+ y: z.number(),
47
+ puuid: z.string().optional(),
48
+ uuid: z.string(),
49
+ utime: z.number(),
50
+ importFlag: z.number(),
51
+ c_spiceCmd: z.any().optional(),
52
+ hasIdFlag: z.boolean(),
53
+ })
54
+
55
+ export const BBoxSchema = z.object({
56
+ x: z.number(),
57
+ y: z.number(),
58
+ width: z.number(),
59
+ height: z.number(),
60
+ })
61
+
62
+ export const LayerItemSchema = z.object({
63
+ name: z.string(),
64
+ color: z.string(),
65
+ visible: z.boolean(),
66
+ active: z.boolean(),
67
+ config: z.boolean(),
68
+ transparency: z.boolean(),
69
+ })
70
+
71
+ export const ObjectItemSchema = z.object({
72
+ name: z.string(),
73
+ visible: z.boolean(),
74
+ locked: z.boolean(),
75
+ })
76
+
77
+ export const DataStrSchema = z.object({
78
+ head: HeadSchema,
79
+ canvas: z.string(),
80
+ shape: z.array(SingleLetterShapeSchema),
81
+ BBox: BBoxSchema,
82
+ colors: z.array(z.unknown()),
83
+ })
84
+
85
+ export const PackageDetailDataStrSchema = z.object({
86
+ head: HeadSchema,
87
+ canvas: z.string(),
88
+ shape: z
89
+ .array(z.string())
90
+ .transform((shapes) =>
91
+ shapes.map((shape) => {
92
+ const [type, ...data] = shape.split("~")
93
+ return ShapeItemSchema.parse({ type, data: data.join("~") })
94
+ })
95
+ )
96
+ .pipe(z.array(PackageDetailShapeSchema)),
97
+ layers: z.array(z.string()).transform((layers) =>
98
+ layers.map((layer) => {
99
+ const [name, color, visible, active, config, transparency] =
100
+ layer.split("~")
101
+ return LayerItemSchema.parse({
102
+ name,
103
+ color,
104
+ visible: visible === "true",
105
+ active: active === "true",
106
+ config: config === "true",
107
+ transparency: transparency === "true",
108
+ })
109
+ })
110
+ ),
111
+ objects: z.array(z.string()).transform((objects) =>
112
+ objects.map((obj) => {
113
+ const [name, visible, locked] = obj.split("~")
114
+ return ObjectItemSchema.parse({
115
+ name,
116
+ visible: visible === "true",
117
+ locked: locked === "true",
118
+ })
119
+ })
120
+ ),
121
+ BBox: BBoxSchema,
122
+ netColors: z.array(z.unknown()).optional(),
123
+ })
124
+
125
+ export const PackageDetailSchema = z.object({
126
+ uuid: z.string(),
127
+ title: z.string(),
128
+ docType: z.number(),
129
+ updateTime: z.number(),
130
+ owner: OwnerSchema,
131
+ datastrid: z.string(),
132
+ writable: z.boolean(),
133
+ dataStr: PackageDetailDataStrSchema,
134
+ })
135
+
136
+ export const EasyEdaJsonSchema = z.object({
137
+ uuid: z.string(),
138
+ title: z.string(),
139
+ description: z.string(),
140
+ docType: z.number(),
141
+ type: z.number(),
142
+ szlcsc: SzlcscSchema,
143
+ lcsc: LcscSchema,
144
+ owner: OwnerSchema,
145
+ tags: z.array(z.string()),
146
+ updateTime: z.number(),
147
+ updated_at: z.string(),
148
+ dataStr: DataStrSchema,
149
+ verify: z.boolean(),
150
+ SMT: z.boolean(),
151
+ datastrid: z.string(),
152
+ jlcOnSale: z.number(),
153
+ writable: z.boolean(),
154
+ isFavorite: z.boolean(),
155
+ packageDetail: PackageDetailSchema,
156
+ })
157
+
158
+ export type EasyEdaJson = z.infer<typeof EasyEdaJsonSchema>