@tscircuit/pcb-viewer 1.0.0
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/.github/workflows/npm-semantic-release.yml +29 -0
- package/next-env.d.ts +5 -0
- package/package.json +30 -0
- package/release.config.js +16 -0
- package/src/PCBViewer.tsx +24 -0
- package/src/assets/alphabet.ts +70 -0
- package/src/assets/attiny-eagle.ts +843 -0
- package/src/components/CanvasElementsRenderer.tsx +15 -0
- package/src/components/CanvasPrimitiveRenderer.tsx +25 -0
- package/src/index.tsx +2 -0
- package/src/lib/Drawer.ts +168 -0
- package/src/lib/colors.ts +233 -0
- package/src/lib/convert-element-to-primitive.ts +31 -0
- package/src/lib/convert-text-to-lines.ts +31 -0
- package/src/lib/draw-eagle.ts +45 -0
- package/src/lib/draw-primitives.ts +69 -0
- package/src/lib/types.ts +54 -0
- package/src/lib/util/scale-only.ts +7 -0
- package/src/pages/eagle.tsx +20 -0
- package/src/pages/index.tsx +23 -0
- package/src/pages/primitives.tsx +64 -0
- package/src/pages/viewer.tsx +43 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CanvasPrimitiveRenderer } from "./CanvasPrimitiveRenderer"
|
|
2
|
+
import { AnyElement } from "@tscircuit/builder"
|
|
3
|
+
import { useMemo } from "react"
|
|
4
|
+
import { convertElementToPrimitives } from "../lib/convert-element-to-primitive"
|
|
5
|
+
|
|
6
|
+
export interface CanvasElementsRendererProps {
|
|
7
|
+
elements: AnyElement[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const CanvasElementsRenderer = (props: CanvasElementsRendererProps) => {
|
|
11
|
+
const primitives = useMemo(() => {
|
|
12
|
+
return props.elements.flatMap((elm) => convertElementToPrimitives(elm))
|
|
13
|
+
}, [props.elements])
|
|
14
|
+
return <CanvasPrimitiveRenderer primitives={primitives} />
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react"
|
|
2
|
+
import { drawPrimitives } from "../lib/draw-primitives"
|
|
3
|
+
import { Drawer } from "../lib/Drawer"
|
|
4
|
+
import { Primitive } from "../lib/types"
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
primitives: Primitive[]
|
|
8
|
+
defaultUnit?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CanvasPrimitiveRenderer = ({ primitives }: Props) => {
|
|
12
|
+
const ref = useRef()
|
|
13
|
+
let [width, height] = [500, 500]
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const drawer = new Drawer(ref.current)
|
|
16
|
+
drawer.clear()
|
|
17
|
+
drawPrimitives(drawer, primitives)
|
|
18
|
+
}, [primitives])
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div style={{ backgroundColor: "black" }}>
|
|
22
|
+
<canvas ref={ref} width={width} height={height}></canvas>
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { scaleOnly } from "./util/scale-only"
|
|
2
|
+
import {
|
|
3
|
+
identity,
|
|
4
|
+
Matrix,
|
|
5
|
+
applyToPoint,
|
|
6
|
+
translate,
|
|
7
|
+
compose,
|
|
8
|
+
} from "transformation-matrix"
|
|
9
|
+
import colors from "./colors"
|
|
10
|
+
import { convertTextToLines } from "./convert-text-to-lines"
|
|
11
|
+
|
|
12
|
+
export interface Aperture {
|
|
13
|
+
shape: "circle" | "square"
|
|
14
|
+
size: number
|
|
15
|
+
mode: "add" | "subtract"
|
|
16
|
+
fontSize: number
|
|
17
|
+
color: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const LAYER_NAME_TO_COLOR = {
|
|
21
|
+
// Standard colors, you shouldn't use these except for testing
|
|
22
|
+
red: "red",
|
|
23
|
+
black: "black",
|
|
24
|
+
green: "green",
|
|
25
|
+
// TODO more builtin html colors
|
|
26
|
+
|
|
27
|
+
// Common eagle names
|
|
28
|
+
top: colors.board.copper.f,
|
|
29
|
+
keepout: colors.board.background,
|
|
30
|
+
tkeepout: colors.board.b_crtyd,
|
|
31
|
+
tplace: colors.board.b_silks,
|
|
32
|
+
|
|
33
|
+
...colors.board,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const FILL_TYPES = {
|
|
37
|
+
0: "Empty",
|
|
38
|
+
1: "Solid",
|
|
39
|
+
2: "Line",
|
|
40
|
+
3: "LtSlash",
|
|
41
|
+
4: "Slash",
|
|
42
|
+
5: "BkSlash",
|
|
43
|
+
6: "LtBkSlash",
|
|
44
|
+
7: "Hatch",
|
|
45
|
+
8: "XHatch",
|
|
46
|
+
9: "Interleave",
|
|
47
|
+
10: "WideDot",
|
|
48
|
+
11: "CloseDot",
|
|
49
|
+
12: "Stipple1",
|
|
50
|
+
13: "Stipple2",
|
|
51
|
+
14: "Stipple3",
|
|
52
|
+
15: "Stipple4",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class Drawer {
|
|
56
|
+
ctx: CanvasRenderingContext2D
|
|
57
|
+
aperture: Aperture
|
|
58
|
+
transform: Matrix
|
|
59
|
+
lastPoint: { x: number; y: number }
|
|
60
|
+
|
|
61
|
+
constructor(public canvas: HTMLCanvasElement) {
|
|
62
|
+
this.canvas = canvas
|
|
63
|
+
this.ctx = canvas.getContext("2d")
|
|
64
|
+
this.transform = identity()
|
|
65
|
+
// positive is up (cartesian)
|
|
66
|
+
this.transform.d *= -1
|
|
67
|
+
this.transform = compose(this.transform, translate(0, -500))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
clear() {
|
|
71
|
+
const { ctx, canvas } = this
|
|
72
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
equip(aperature: Partial<Aperture>) {
|
|
76
|
+
this.aperture = {
|
|
77
|
+
fontSize: 0,
|
|
78
|
+
shape: "circle",
|
|
79
|
+
mode: "add",
|
|
80
|
+
size: 0,
|
|
81
|
+
color: "red",
|
|
82
|
+
...aperature,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
rect(x: number, y: number, w: number, h: number) {
|
|
87
|
+
const [x1$, y1$] = applyToPoint(this.transform, [x, y])
|
|
88
|
+
const [x2$, y2$] = applyToPoint(this.transform, [x + w, y + h])
|
|
89
|
+
this.applyAperture()
|
|
90
|
+
this.ctx.fillRect(x1$, y1$, x2$ - x1$, y2$ - y1$)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
circle(x: number, y: number, r: number) {
|
|
94
|
+
const r$ = scaleOnly(this.transform, r)
|
|
95
|
+
const [x$, y$] = applyToPoint(this.transform, [x, y])
|
|
96
|
+
this.applyAperture()
|
|
97
|
+
this.ctx.beginPath()
|
|
98
|
+
this.ctx.arc(x$, y$, r$ * 2, 0, 2 * Math.PI)
|
|
99
|
+
this.ctx.fill()
|
|
100
|
+
this.ctx.closePath()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* NOTE: This is not gerber compatible */
|
|
104
|
+
text(text: string, x: number, y: number) {
|
|
105
|
+
const [x$, y$] = applyToPoint(this.transform, [x, y])
|
|
106
|
+
this.applyAperture()
|
|
107
|
+
|
|
108
|
+
this.ctx.fillText(text, x$, y$)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
applyAperture() {
|
|
112
|
+
const { ctx, transform, aperture } = this
|
|
113
|
+
const { size, mode, color, fontSize } = aperture
|
|
114
|
+
ctx.lineWidth = scaleOnly(transform, size)
|
|
115
|
+
ctx.lineCap = "round"
|
|
116
|
+
if (mode === "add") {
|
|
117
|
+
let colorString =
|
|
118
|
+
color[0] === "#" || color.startsWith("rgb")
|
|
119
|
+
? color
|
|
120
|
+
: LAYER_NAME_TO_COLOR[color.toLowerCase()]
|
|
121
|
+
? LAYER_NAME_TO_COLOR[color.toLowerCase()]
|
|
122
|
+
: null
|
|
123
|
+
if (colorString === null) {
|
|
124
|
+
console.warn(`Color mapping for "${color}" not found`)
|
|
125
|
+
colorString = "white"
|
|
126
|
+
}
|
|
127
|
+
ctx.fillStyle = colorString
|
|
128
|
+
ctx.strokeStyle = colorString
|
|
129
|
+
} else {
|
|
130
|
+
ctx.globalCompositeOperation = "destination-out"
|
|
131
|
+
ctx.fillStyle = "rgba(0,0,0,1)"
|
|
132
|
+
ctx.strokeStyle = "rgba(0,0,0,1)"
|
|
133
|
+
}
|
|
134
|
+
ctx.font = `${scaleOnly(transform, fontSize)}px sans-serif`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
moveTo(x: number, y: number) {
|
|
138
|
+
this.lastPoint = { x, y }
|
|
139
|
+
}
|
|
140
|
+
lineTo(x: number, y: number) {
|
|
141
|
+
const [x$, y$] = applyToPoint(this.transform, [x, y])
|
|
142
|
+
const { size, shape, mode } = this.aperture
|
|
143
|
+
const size$ = scaleOnly(this.transform, size)
|
|
144
|
+
let { lastPoint, ctx } = this
|
|
145
|
+
const lastPoint$ = applyToPoint(this.transform, lastPoint)
|
|
146
|
+
|
|
147
|
+
this.applyAperture()
|
|
148
|
+
|
|
149
|
+
if (shape === "square")
|
|
150
|
+
ctx.fillRect(
|
|
151
|
+
lastPoint$.x - size$ / 2,
|
|
152
|
+
lastPoint$.y - size$ / 2,
|
|
153
|
+
size$,
|
|
154
|
+
size$
|
|
155
|
+
)
|
|
156
|
+
ctx.beginPath()
|
|
157
|
+
ctx.moveTo(lastPoint$.x, lastPoint$.y)
|
|
158
|
+
ctx.lineTo(x$, y$)
|
|
159
|
+
|
|
160
|
+
ctx.stroke()
|
|
161
|
+
ctx.closePath()
|
|
162
|
+
|
|
163
|
+
if (shape === "square")
|
|
164
|
+
ctx.fillRect(x$ - size$ / 2, y$ - size$ / 2, size$, size$)
|
|
165
|
+
|
|
166
|
+
this.lastPoint = { x, y }
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// Mix of Eagle Dark for schematics and KiCAD 2020 for PCBs
|
|
2
|
+
// https://github.com/pointhi/kicad-color-schemes/blob/master/eagle-dark/eagle-dark.jsonO
|
|
3
|
+
// https://github.com/pointhi/kicad-color-schemes/blob/master/kicad-2020/kicad_2020.json
|
|
4
|
+
export default {
|
|
5
|
+
"3d_viewer": {
|
|
6
|
+
background_bottom: "rgb(102, 102, 128)",
|
|
7
|
+
background_top: "rgb(204, 204, 230)",
|
|
8
|
+
board: "rgb(51, 43, 23)",
|
|
9
|
+
copper: "rgb(179, 156, 0)",
|
|
10
|
+
silkscreen_bottom: "rgb(230, 230, 230)",
|
|
11
|
+
silkscreen_top: "rgb(230, 230, 230)",
|
|
12
|
+
soldermask: "rgb(20, 51, 36)",
|
|
13
|
+
solderpaste: "rgb(128, 128, 128)",
|
|
14
|
+
},
|
|
15
|
+
board: {
|
|
16
|
+
anchor: "rgb(255, 38, 226)",
|
|
17
|
+
aux_items: "rgb(255, 255, 255)",
|
|
18
|
+
b_adhes: "rgb(0, 0, 132)",
|
|
19
|
+
b_crtyd: "rgb(255, 38, 226)",
|
|
20
|
+
b_fab: "rgb(88, 93, 132)",
|
|
21
|
+
b_mask: "rgba(2, 255, 238, 0.400)",
|
|
22
|
+
b_paste: "rgb(0, 194, 194)",
|
|
23
|
+
b_silks: "rgb(232, 178, 167)",
|
|
24
|
+
background: "rgb(0, 16, 35)",
|
|
25
|
+
cmts_user: "rgb(89, 148, 220)",
|
|
26
|
+
copper: {
|
|
27
|
+
b: "rgb(77, 127, 196)",
|
|
28
|
+
f: "rgb(200, 52, 52)",
|
|
29
|
+
in1: "rgb(127, 200, 127)",
|
|
30
|
+
in10: "rgb(237, 124, 51)",
|
|
31
|
+
in11: "rgb(91, 195, 235)",
|
|
32
|
+
in12: "rgb(247, 111, 142)",
|
|
33
|
+
in13: "rgb(167, 165, 198)",
|
|
34
|
+
in14: "rgb(40, 204, 217)",
|
|
35
|
+
in15: "rgb(232, 178, 167)",
|
|
36
|
+
in16: "rgb(242, 237, 161)",
|
|
37
|
+
in17: "rgb(237, 124, 51)",
|
|
38
|
+
in18: "rgb(91, 195, 235)",
|
|
39
|
+
in19: "rgb(247, 111, 142)",
|
|
40
|
+
in2: "rgb(206, 125, 44)",
|
|
41
|
+
in20: "rgb(167, 165, 198)",
|
|
42
|
+
in21: "rgb(40, 204, 217)",
|
|
43
|
+
in22: "rgb(232, 178, 167)",
|
|
44
|
+
in23: "rgb(242, 237, 161)",
|
|
45
|
+
in24: "rgb(237, 124, 51)",
|
|
46
|
+
in25: "rgb(91, 195, 235)",
|
|
47
|
+
in26: "rgb(247, 111, 142)",
|
|
48
|
+
in27: "rgb(167, 165, 198)",
|
|
49
|
+
in28: "rgb(40, 204, 217)",
|
|
50
|
+
in29: "rgb(232, 178, 167)",
|
|
51
|
+
in3: "rgb(79, 203, 203)",
|
|
52
|
+
in30: "rgb(242, 237, 161)",
|
|
53
|
+
in4: "rgb(219, 98, 139)",
|
|
54
|
+
in5: "rgb(167, 165, 198)",
|
|
55
|
+
in6: "rgb(40, 204, 217)",
|
|
56
|
+
in7: "rgb(232, 178, 167)",
|
|
57
|
+
in8: "rgb(242, 237, 161)",
|
|
58
|
+
in9: "rgb(141, 203, 129)",
|
|
59
|
+
},
|
|
60
|
+
cursor: "rgb(255, 255, 255)",
|
|
61
|
+
drc: "rgb(194, 194, 194)",
|
|
62
|
+
drc_error: "rgba(215, 91, 107, 0.800)",
|
|
63
|
+
drc_exclusion: "rgb(255, 255, 255)",
|
|
64
|
+
drc_warning: "rgba(255, 208, 66, 0.902)",
|
|
65
|
+
dwgs_user: "rgb(194, 194, 194)",
|
|
66
|
+
eco1_user: "rgb(180, 219, 210)",
|
|
67
|
+
eco2_user: "rgb(216, 200, 82)",
|
|
68
|
+
edge_cuts: "rgb(208, 210, 205)",
|
|
69
|
+
f_adhes: "rgb(132, 0, 132)",
|
|
70
|
+
f_crtyd: "rgb(255, 0, 245)",
|
|
71
|
+
f_fab: "rgb(175, 175, 175)",
|
|
72
|
+
f_mask: "rgba(216, 100, 255, 0.400)",
|
|
73
|
+
f_paste: "rgba(180, 160, 154, 0.902)",
|
|
74
|
+
f_silks: "rgb(242, 237, 161)",
|
|
75
|
+
footprint_text_back: "rgb(0, 0, 132)",
|
|
76
|
+
footprint_text_front: "rgb(194, 194, 194)",
|
|
77
|
+
footprint_text_invisible: "rgb(132, 132, 132)",
|
|
78
|
+
grid: "rgb(132, 132, 132)",
|
|
79
|
+
grid_axes: "rgb(194, 194, 194)",
|
|
80
|
+
margin: "rgb(255, 38, 226)",
|
|
81
|
+
microvia: "rgb(0, 132, 132)",
|
|
82
|
+
no_connect: "rgb(0, 0, 132)",
|
|
83
|
+
pad_back: "rgb(77, 127, 196)",
|
|
84
|
+
pad_front: "rgb(200, 52, 52)",
|
|
85
|
+
pad_plated_hole: "rgb(194, 194, 0)",
|
|
86
|
+
pad_through_hole: "rgb(227, 183, 46)",
|
|
87
|
+
plated_hole: "rgb(26, 196, 210)",
|
|
88
|
+
ratsnest: "rgba(245, 255, 213, 0.702)",
|
|
89
|
+
select_overlay: "rgb(4, 255, 67)",
|
|
90
|
+
through_via: "rgb(236, 236, 236)",
|
|
91
|
+
user_1: "rgb(194, 194, 194)",
|
|
92
|
+
user_2: "rgb(89, 148, 220)",
|
|
93
|
+
user_3: "rgb(180, 219, 210)",
|
|
94
|
+
user_4: "rgb(216, 200, 82)",
|
|
95
|
+
user_5: "rgb(194, 194, 194)",
|
|
96
|
+
user_6: "rgb(89, 148, 220)",
|
|
97
|
+
user_7: "rgb(180, 219, 210)",
|
|
98
|
+
user_8: "rgb(216, 200, 82)",
|
|
99
|
+
user_9: "rgb(232, 178, 167)",
|
|
100
|
+
via_blind_buried: "rgb(187, 151, 38)",
|
|
101
|
+
via_hole: "rgb(227, 183, 46)",
|
|
102
|
+
via_micro: "rgb(0, 132, 132)",
|
|
103
|
+
via_through: "rgb(236, 236, 236)",
|
|
104
|
+
worksheet: "rgb(200, 114, 171)",
|
|
105
|
+
},
|
|
106
|
+
gerbview: {
|
|
107
|
+
axes: "rgb(0, 0, 132)",
|
|
108
|
+
background: "rgb(0, 0, 0)",
|
|
109
|
+
dcodes: "rgb(255, 255, 255)",
|
|
110
|
+
grid: "rgb(132, 132, 132)",
|
|
111
|
+
layers: [
|
|
112
|
+
"rgb(132, 0, 0)",
|
|
113
|
+
"rgb(194, 194, 0)",
|
|
114
|
+
"rgb(194, 0, 194)",
|
|
115
|
+
"rgb(194, 0, 0)",
|
|
116
|
+
"rgb(0, 132, 132)",
|
|
117
|
+
"rgb(0, 132, 0)",
|
|
118
|
+
"rgb(0, 0, 132)",
|
|
119
|
+
"rgb(132, 132, 132)",
|
|
120
|
+
"rgb(132, 0, 132)",
|
|
121
|
+
"rgb(194, 194, 194)",
|
|
122
|
+
"rgb(132, 0, 132)",
|
|
123
|
+
"rgb(132, 0, 0)",
|
|
124
|
+
"rgb(132, 132, 0)",
|
|
125
|
+
"rgb(194, 194, 194)",
|
|
126
|
+
"rgb(0, 0, 132)",
|
|
127
|
+
"rgb(0, 132, 0)",
|
|
128
|
+
"rgb(132, 0, 0)",
|
|
129
|
+
"rgb(194, 194, 0)",
|
|
130
|
+
"rgb(194, 0, 194)",
|
|
131
|
+
"rgb(194, 0, 0)",
|
|
132
|
+
"rgb(0, 132, 132)",
|
|
133
|
+
"rgb(0, 132, 0)",
|
|
134
|
+
"rgb(0, 0, 132)",
|
|
135
|
+
"rgb(132, 132, 132)",
|
|
136
|
+
"rgb(132, 0, 132)",
|
|
137
|
+
"rgb(194, 194, 194)",
|
|
138
|
+
"rgb(132, 0, 132)",
|
|
139
|
+
"rgb(132, 0, 0)",
|
|
140
|
+
"rgb(132, 132, 0)",
|
|
141
|
+
"rgb(194, 194, 194)",
|
|
142
|
+
"rgb(0, 0, 132)",
|
|
143
|
+
"rgb(0, 132, 0)",
|
|
144
|
+
"rgb(132, 0, 0)",
|
|
145
|
+
"rgb(194, 194, 0)",
|
|
146
|
+
"rgb(194, 0, 194)",
|
|
147
|
+
"rgb(194, 0, 0)",
|
|
148
|
+
"rgb(0, 132, 132)",
|
|
149
|
+
"rgb(0, 132, 0)",
|
|
150
|
+
"rgb(0, 0, 132)",
|
|
151
|
+
"rgb(132, 132, 132)",
|
|
152
|
+
"rgb(132, 0, 132)",
|
|
153
|
+
"rgb(194, 194, 194)",
|
|
154
|
+
"rgb(132, 0, 132)",
|
|
155
|
+
"rgb(132, 0, 0)",
|
|
156
|
+
"rgb(132, 132, 0)",
|
|
157
|
+
"rgb(194, 194, 194)",
|
|
158
|
+
"rgb(0, 0, 132)",
|
|
159
|
+
"rgb(0, 132, 0)",
|
|
160
|
+
"rgb(132, 0, 0)",
|
|
161
|
+
"rgb(194, 194, 0)",
|
|
162
|
+
"rgb(194, 0, 194)",
|
|
163
|
+
"rgb(194, 0, 0)",
|
|
164
|
+
"rgb(0, 132, 132)",
|
|
165
|
+
"rgb(0, 132, 0)",
|
|
166
|
+
"rgb(0, 0, 132)",
|
|
167
|
+
"rgb(132, 132, 132)",
|
|
168
|
+
"rgb(132, 0, 132)",
|
|
169
|
+
"rgb(194, 194, 194)",
|
|
170
|
+
"rgb(132, 0, 132)",
|
|
171
|
+
"rgb(132, 0, 0)",
|
|
172
|
+
],
|
|
173
|
+
negative_objects: "rgb(132, 132, 132)",
|
|
174
|
+
worksheet: "rgb(0, 0, 132)",
|
|
175
|
+
},
|
|
176
|
+
palette: [
|
|
177
|
+
"rgb(132, 0, 0)",
|
|
178
|
+
"rgb(194, 194, 0)",
|
|
179
|
+
"rgb(194, 0, 194)",
|
|
180
|
+
"rgb(194, 0, 0)",
|
|
181
|
+
"rgb(0, 132, 132)",
|
|
182
|
+
"rgb(0, 132, 0)",
|
|
183
|
+
"rgb(0, 0, 132)",
|
|
184
|
+
"rgb(132, 132, 132)",
|
|
185
|
+
"rgb(132, 0, 132)",
|
|
186
|
+
"rgb(194, 194, 194)",
|
|
187
|
+
"rgb(132, 0, 132)",
|
|
188
|
+
"rgb(132, 0, 0)",
|
|
189
|
+
"rgb(132, 132, 0)",
|
|
190
|
+
"rgb(194, 194, 194)",
|
|
191
|
+
"rgb(0, 0, 132)",
|
|
192
|
+
"rgb(0, 132, 0)",
|
|
193
|
+
],
|
|
194
|
+
schematic: {
|
|
195
|
+
anchor: "rgb(0, 0, 255)",
|
|
196
|
+
aux_items: "rgb(0, 0, 0)",
|
|
197
|
+
background: "rgb(33, 33, 33)",
|
|
198
|
+
brightened: "rgb(204, 204, 204)",
|
|
199
|
+
bus: "rgb(0, 96, 192)",
|
|
200
|
+
bus_junction: "rgb(0, 96, 192)",
|
|
201
|
+
component_body: "rgb(44, 44, 44)",
|
|
202
|
+
component_outline: "rgb(192, 0, 0)",
|
|
203
|
+
cursor: "rgb(224, 224, 224)",
|
|
204
|
+
erc_error: "rgba(192, 48, 48, 0.800)",
|
|
205
|
+
erc_warning: "rgba(192, 140, 0, 0.800)",
|
|
206
|
+
fields: "rgb(128, 0, 160)",
|
|
207
|
+
grid: "rgb(60, 60, 60)",
|
|
208
|
+
grid_axes: "rgb(60, 60, 60)",
|
|
209
|
+
hidden: "rgb(194, 194, 194)",
|
|
210
|
+
junction: "rgb(0, 160, 0)",
|
|
211
|
+
label_global: "rgb(0, 160, 224)",
|
|
212
|
+
label_hier: "rgb(160, 160, 0)",
|
|
213
|
+
label_local: "rgb(192, 192, 192)",
|
|
214
|
+
net_name: "rgb(224, 224, 224)",
|
|
215
|
+
no_connect: "rgb(97, 43, 224)",
|
|
216
|
+
note: "rgb(192, 192, 0)",
|
|
217
|
+
override_item_colors: false,
|
|
218
|
+
pin: "rgb(192, 0, 0)",
|
|
219
|
+
pin_name: "rgb(192, 192, 192)",
|
|
220
|
+
pin_number: "rgb(192, 0, 0)",
|
|
221
|
+
reference: "rgb(192, 192, 192)",
|
|
222
|
+
shadow: "rgba(102, 179, 255, 0.800)",
|
|
223
|
+
sheet: "rgb(128, 0, 160)",
|
|
224
|
+
sheet_background: "rgba(255, 255, 255, 0.000)",
|
|
225
|
+
sheet_fields: "rgb(160, 160, 0)",
|
|
226
|
+
sheet_filename: "rgb(160, 160, 0)",
|
|
227
|
+
sheet_label: "rgb(160, 160, 0)",
|
|
228
|
+
sheet_name: "rgb(0, 160, 204)",
|
|
229
|
+
value: "rgb(192, 192, 192)",
|
|
230
|
+
wire: "rgb(0, 160, 0)",
|
|
231
|
+
worksheet: "rgb(192, 0, 0)",
|
|
232
|
+
},
|
|
233
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AnyElement } from "@tscircuit/builder"
|
|
2
|
+
import { Primitive } from "./types"
|
|
3
|
+
|
|
4
|
+
export const convertElementToPrimitives = (
|
|
5
|
+
element: AnyElement
|
|
6
|
+
): Primitive[] => {
|
|
7
|
+
switch (element.type) {
|
|
8
|
+
case "pcb_smtpad": {
|
|
9
|
+
if (element.shape === "rect") {
|
|
10
|
+
const { shape, x, y, width, height, layer } = element
|
|
11
|
+
|
|
12
|
+
return [
|
|
13
|
+
{
|
|
14
|
+
pcb_drawing_type: "rect",
|
|
15
|
+
x,
|
|
16
|
+
y,
|
|
17
|
+
w: width,
|
|
18
|
+
h: height,
|
|
19
|
+
layer: layer,
|
|
20
|
+
},
|
|
21
|
+
]
|
|
22
|
+
} else if (element.shape === "circle") {
|
|
23
|
+
console.warn(`Unsupported shape: ${element.shape} for pcb_smtpad`)
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.warn(`Unsupported element type: ${element.type}`)
|
|
30
|
+
return []
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { lineAlphabet } from "../assets/alphabet"
|
|
2
|
+
import { Line, Text } from "./types"
|
|
3
|
+
|
|
4
|
+
export const convertTextToLines = (text: Text): Line[] => {
|
|
5
|
+
const strokeWidth = text.size / 8
|
|
6
|
+
const letterWidth = text.size * 0.6
|
|
7
|
+
const letterSpace = text.size * 0.2
|
|
8
|
+
|
|
9
|
+
const lines: Line[] = []
|
|
10
|
+
for (let letterIndex = 0; letterIndex < text.text.length; letterIndex++) {
|
|
11
|
+
const letter = text.text[letterIndex]
|
|
12
|
+
const letterLines = lineAlphabet[letter.toUpperCase()]
|
|
13
|
+
if (!letterLines) continue
|
|
14
|
+
for (const { x1, y1, x2, y2 } of letterLines) {
|
|
15
|
+
lines.push({
|
|
16
|
+
pcb_drawing_type: "line",
|
|
17
|
+
x1:
|
|
18
|
+
text.x + (letterWidth + letterSpace) * letterIndex + letterWidth * x1,
|
|
19
|
+
y1: text.y + text.size * y1,
|
|
20
|
+
x2:
|
|
21
|
+
text.x + (letterWidth + letterSpace) * letterIndex + letterWidth * x2,
|
|
22
|
+
y2: text.y + text.size * y2,
|
|
23
|
+
width: strokeWidth,
|
|
24
|
+
layer: text.layer,
|
|
25
|
+
unit: text.unit,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return lines
|
|
31
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { EagleJSON, Layer } from "@tscircuit/eagle-xml-converter"
|
|
2
|
+
import {
|
|
3
|
+
compose,
|
|
4
|
+
fromDefinition,
|
|
5
|
+
fromTransformAttribute,
|
|
6
|
+
} from "transformation-matrix"
|
|
7
|
+
import { Drawer } from "./Drawer"
|
|
8
|
+
|
|
9
|
+
export const drawEagle = (drawer: Drawer, eagle: EagleJSON) => {
|
|
10
|
+
const pkg = eagle.library.packages[0]
|
|
11
|
+
|
|
12
|
+
if (eagle.grid.unit === "inch") {
|
|
13
|
+
drawer.transform = compose(
|
|
14
|
+
fromDefinition(
|
|
15
|
+
fromTransformAttribute("translate(200, 200) scale(30,-30)")
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const layerMap: Record<number, Layer> = {}
|
|
21
|
+
for (const layer of eagle.layers) {
|
|
22
|
+
layerMap[layer.number] = layer
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const smd of pkg.smd || []) {
|
|
26
|
+
drawer.equip({
|
|
27
|
+
color: layerMap[smd.layer].name,
|
|
28
|
+
})
|
|
29
|
+
drawer.rect(smd.x - smd.dx / 2, smd.y - smd.dy / 2, smd.dx, smd.dy)
|
|
30
|
+
}
|
|
31
|
+
for (const wire of pkg.wire || []) {
|
|
32
|
+
drawer.equip({
|
|
33
|
+
size: wire.width,
|
|
34
|
+
shape: "circle",
|
|
35
|
+
color: layerMap[wire.layer].name,
|
|
36
|
+
})
|
|
37
|
+
drawer.moveTo(wire.x1, wire.y1)
|
|
38
|
+
drawer.lineTo(wire.x2, wire.y2)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const circle of pkg.circle || []) {
|
|
42
|
+
drawer.equip({ color: layerMap[circle.layer].name })
|
|
43
|
+
drawer.circle(circle.x, circle.y, circle.radius)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Primitive, Line, Text, Circle, Rect } from "./types"
|
|
2
|
+
import { Drawer } from "./Drawer"
|
|
3
|
+
import { convertTextToLines } from "./convert-text-to-lines"
|
|
4
|
+
|
|
5
|
+
export const drawLine = (drawer: Drawer, line: Line) => {
|
|
6
|
+
drawer.equip({
|
|
7
|
+
size: line.width,
|
|
8
|
+
shape: line.squareCap ? "square" : "circle",
|
|
9
|
+
color: line.layer.name,
|
|
10
|
+
})
|
|
11
|
+
drawer.moveTo(line.x1, line.y1)
|
|
12
|
+
drawer.lineTo(line.x2, line.y2)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const drawText = (drawer: Drawer, text: Text) => {
|
|
16
|
+
drawer.equip({
|
|
17
|
+
fontSize: text.size,
|
|
18
|
+
color: text.layer.name,
|
|
19
|
+
})
|
|
20
|
+
// TODO handle align
|
|
21
|
+
if (text.align && text.align !== "top-left") {
|
|
22
|
+
console.warn("Unhandled text align", text.align)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Non-gerber compatible
|
|
26
|
+
// drawer.text(text.text, text.x, text.y)
|
|
27
|
+
|
|
28
|
+
const lines = convertTextToLines(text)
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
drawLine(drawer, line)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const drawRect = (drawer: Drawer, rect: Rect) => {
|
|
35
|
+
drawer.equip({
|
|
36
|
+
color: rect.layer.name,
|
|
37
|
+
})
|
|
38
|
+
if (rect.align && rect.align !== "center") {
|
|
39
|
+
console.warn("Unhandled rect align", rect.align)
|
|
40
|
+
}
|
|
41
|
+
drawer.rect(rect.x, rect.y, rect.w, rect.h)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const drawCircle = (drawer: Drawer, circle: Circle) => {
|
|
45
|
+
drawer.equip({
|
|
46
|
+
color: circle.layer.name,
|
|
47
|
+
})
|
|
48
|
+
drawer.circle(circle.x, circle.y, circle.r)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const drawPrimitive = (drawer: Drawer, primitive: Primitive) => {
|
|
52
|
+
switch (primitive.pcb_drawing_type) {
|
|
53
|
+
case "line":
|
|
54
|
+
return drawLine(drawer, primitive)
|
|
55
|
+
case "text":
|
|
56
|
+
return drawText(drawer, primitive)
|
|
57
|
+
case "rect":
|
|
58
|
+
return drawRect(drawer, primitive)
|
|
59
|
+
case "circle":
|
|
60
|
+
return drawCircle(drawer, primitive)
|
|
61
|
+
}
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Unknown primitive type: ${(primitive as any).pcb_drawing_type}`
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const drawPrimitives = (drawer: Drawer, primitives: Primitive[]) => {
|
|
68
|
+
primitives.forEach((primitive) => drawPrimitive(drawer, primitive))
|
|
69
|
+
}
|
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type AlignString =
|
|
2
|
+
| "top-left"
|
|
3
|
+
| "top-center"
|
|
4
|
+
| "top-right"
|
|
5
|
+
| "center-left"
|
|
6
|
+
| "center"
|
|
7
|
+
| "center-right"
|
|
8
|
+
| "bottom-left"
|
|
9
|
+
| "bottom-center"
|
|
10
|
+
| "bottom-right"
|
|
11
|
+
|
|
12
|
+
export interface LayerRef {
|
|
13
|
+
name: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PCBDrawingObject {
|
|
17
|
+
layer: LayerRef
|
|
18
|
+
unit?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Line extends PCBDrawingObject {
|
|
22
|
+
pcb_drawing_type: "line"
|
|
23
|
+
x1: number
|
|
24
|
+
y1: number
|
|
25
|
+
x2: number
|
|
26
|
+
y2: number
|
|
27
|
+
squareCap?: boolean
|
|
28
|
+
width: number
|
|
29
|
+
}
|
|
30
|
+
export interface Text extends PCBDrawingObject {
|
|
31
|
+
pcb_drawing_type: "text"
|
|
32
|
+
text: string
|
|
33
|
+
x: number
|
|
34
|
+
y: number
|
|
35
|
+
size: number
|
|
36
|
+
align?: AlignString
|
|
37
|
+
}
|
|
38
|
+
export interface Rect extends PCBDrawingObject {
|
|
39
|
+
pcb_drawing_type: "rect"
|
|
40
|
+
x: number
|
|
41
|
+
y: number
|
|
42
|
+
w: number
|
|
43
|
+
h: number
|
|
44
|
+
roundness?: number
|
|
45
|
+
align?: AlignString
|
|
46
|
+
}
|
|
47
|
+
export interface Circle extends PCBDrawingObject {
|
|
48
|
+
pcb_drawing_type: "circle"
|
|
49
|
+
x: number
|
|
50
|
+
y: number
|
|
51
|
+
r: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type Primitive = Line | Text | Rect | Circle
|