easyeda 0.0.2 → 0.0.3
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.
- package/dist/cli/main.cjs +6 -3
- package/dist/cli/main.cjs.map +1 -1
- package/package.json +6 -3
- package/ai-assist-docs/soup-reference.md +0 -1325
- package/bun.lockb +0 -0
- package/cli/main.ts +0 -67
- package/dist/index.cjs +0 -29196
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1553
- package/lib/convert-easyeda-json-to-tscircuit-soup-json.ts +0 -204
- package/lib/fetch-easyeda-json.ts +0 -66
- package/lib/index.ts +0 -2
- package/lib/math/arc-utils.ts +0 -170
- package/lib/schemas/easy-eda-json-schema.ts +0 -158
- package/lib/schemas/package-detail-shape-schema.ts +0 -251
- package/lib/schemas/single-letter-shape-schema.ts +0 -201
- package/renovate.json +0 -6
- package/scripts/get-easyeda-json.ts +0 -12
- package/tests/assets/a555-timer-dip.raweasy.json +0 -244
- package/tests/assets/a555-timer-smd.raweasy.json +0 -226
- package/tests/assets/esp32.raweasy.json +0 -333
- package/tests/assets/usb-c.raweasy.json +0 -263
- package/tests/convert-to-soup-tests/a555timer-smd.test.ts +0 -18
- package/tests/convert-to-soup-tests/convert-easyeda-to-tscircuit-soup.test.ts +0 -49
- package/tests/convert-to-soup-tests/convert-usb-c-to-soup.test.ts +0 -18
- package/tests/convert-to-soup-tests/esp32-to-soup.test.ts +0 -13
- package/tests/parse-tests/parse-555-timer-json.test.ts +0 -9
- package/tests/parse-tests/parse-esp32.test.ts +0 -9
- package/tests/parse-tests/parse-usb-c.test.ts +0 -9
- package/tests/parse-tests/single-letter-shape-schema.test.ts +0 -30
- package/tsconfig.json +0 -28
|
@@ -1,204 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
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
DELETED
package/lib/math/arc-utils.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
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>
|