-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathutility_router.js
250 lines (238 loc) · 8.35 KB
/
utility_router.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// Copyright (c) 2025 Marco Massarelli
//
// SPDX-License-Identifier: MIT
//
// To view a copy of this license, visit https://opensource.org/license/mit/
//
// Author: @yanshay + @ceoloide improvements
//
// Description:
// This footprint provides basic routing capabilities including laying routes (traces) and vias.
//
// It is useful especially since there are tens of keys per keyboard which have exact same routing
// and whenever a change needs to be made to Ergogen config, rerouting is required. This footprint
// allows compact routing declaration in Ergogen directly.
//
// For a more complete description, consult the original documentation at: https://github.com/yanshay/ergogen-stuff/blob/main/docs/router.md
//
// A KiCad Plugin exists in order to facilitate the configuration of this footprint:
//
// Example:
// row_route:
// what: ceoloide/utility_router
// where: true
// params:
// net: "{{row_net}}"
// route: "f(-8.275,5.1)(-8.275,7.26)"
//
// Params:
// net: default is no net
// allows specifying a net for all routes in this footprint. To route multiple different nets,
// you would need several different footprint configurations. However, if this parameter is left
// to the default value then KiCad will fill in the missing nets when a file is opened in KiCad.
// Support for multiple nets is included, but depends on an unmerged Ergogen PR. For details see
// https://github.com/ergogen/ergogen/pull/109
// width: default 0.250mm
// the default trace width to be used. Not recommended to go below 0.15mm (JLCPC min is 0.127mm).
// via_size: default is 0.6
// allows to define the size of the via. Not recommended below 0.56 (JLCPCB minimum),
// or above 0.8 (KiCad default), to avoid overlap or DRC errors
// via_drill: default is 0.3
// allows to define the size of the drill. Not recommended below 0.3 (JLCPCB minimum),
// or above 0.4 (KiCad default), to avoid overlap or DRC errors
// locked: default is false
// sets the traces and vias as locked in KiCad. Locked objects may not be manipulated
// or moved, and cannot be selected unless the Locked Items option is enabled in the
// Selection Filter panel in KiCad. Useful for a faster workflow. If using autorouting
// solutins like Freerouting, locking can prevent the traces and vias from being
// replaced.
// routes: default empty / no routes
// an array of routes based on the syntax described below, each stands by its own except they all
// share other params (net, ...)
// route: default empty / no route
// allows adding a single route using the syntax described below, but on a single row and in a more
// concise format
//
// Route syntax:
// A route is a string that describe how to route using one letter commands and positions. It follows to
// some extent KiCad key presses to make it easy to remember. Valid commands are as follows:
//
// b - route on the back layer - there is no default layer to avoid mistakes
// f - route on the front layer
// v - place a via and switch layer
// x or | - start a new route (if layer is set, stays on the same layer, just like in KiCad)
// (x_pos,y_pos) - route to the given position (relative to the Ergogen point). If it is the first
// occurence in the route or if after x command then it places the cursor in the specific point.
// <net_name> - the name of a net to use for the following segment. Currently unsupported in mainline
// Ergogen, until https://github.com/ergogen/ergogen/pull/109 is merged.
//
// @ceoloide's improvements:
// - Replace `get_at_coordinates` and `adjust_point` with native Ergogen `eaxy()`
// - Refresh `via` and `segment` syntax to align with KiCad 8
//
// Special credit to @infused-kim for the adjust_point() function in the original code.
module.exports = {
params: {
net: { type: "net", value: "" },
width: { type: "number", value: 0.25 },
via_size: { type: "number", value: 0.6 },
via_drill: { type: "number", value: 0.3 },
locked: false,
routes: { type: "array", value: [] },
route: { type: "string", value: "" },
},
body: (p) => {
/*
Reference (KiCad 8):
(segment
(start 108.8 108)
(end 109.7 108)
(width 0.2)
(locked yes)
(layer "F.Cu")
(net 0)
)
*/
const get_segment = (start, end, layer, net) => {
if (!layer) {
throw new Error(
"Can't place segment before layer is set, use 'f' or 'b', to set starting layer"
)
}
return `
(segment
(start ${p.eaxy(start[0], start[1])})
(end ${p.eaxy(end[0], end[1])})
(width ${p.width})
(locked ${p.locked ? 'yes' : 'no'})
(layer ${layer})
(net ${net})
)`
}
/*
Reference (KiCad 8):
(via
(at -7.775 -5.95)
(size 0.6)
(drill 0.3)
(layers "F.Cu" "B.Cu")
(locked yes)
(net 2)
)
*/
const get_via = (pos, net) => {
if (!pos) {
throw new Error(
"Can't place via when position is not set, use (x,y) to set position"
)
}
return `
(via
(at ${p.eaxy(pos[0], pos[1])})
(size ${p.via_size})
(drill ${p.via_drill})
(layers "F.Cu" "B.Cu")
(locked ${p.locked ? 'yes' : 'no'})
(net ${net})
)`
}
const parse_tuple = (t) => {
let str_tuple = JSON.parse(t.replace(/\(/g, "[").replace(/\)/g, "]"))
let num_tuple = str_tuple.map((v) => Number(v))
if (isNaN(num_tuple[0] || isNaN(num_tuple[1]))) {
throw new Error(`Invalid position encountered: ${str_tuple}`)
}
return num_tuple
}
const get_traces = (route, net) => {
let traces = ""
let layer = undefined
let start = undefined // [x, y]
for (let i = 0; i < route.length; i++) {
let command = route[i].toLowerCase()
switch (command) {
case "f":
layer = "F.Cu"
break
case "b":
layer = "B.Cu"
break
case "v":
traces = traces + get_via(start, net) + "\n"
switch (layer) {
case "F.Cu":
layer = "B.Cu"
break
case "B.Cu":
layer = "F.Cu"
break
}
break
case "(":
let tuple_str = "("
let parenthesis_idx = i
for (i = i + 1; i < route.length; i++) {
let ch = route[i]
tuple_str += ch
if (route[i] == ")") {
break
}
if (i > route.length) {
throw new Error(
`Unclosed position parenthesis in ${route} at character position ${parenthesis_idx}`
)
}
}
let pos = parse_tuple(tuple_str)
if (start) {
traces = traces + get_segment(start, pos, layer, net) + "\n"
}
start = pos
break
case "<":
if(typeof p.global_net !== 'Function') {
throw new Error(
`Global nets are not yet supported (character position ${i}). See https://github.com/ergogen/ergogen/pull/109`
)
}
let net_name = ""
let lt_idx = i
for (i = i + 1; i < route.length; i++) {
let ch = route[i]
if (route[i] == ">") {
break
}
net_name += ch
if (i > route.length) {
throw new Error(
`Unclosed net parenthesis in ${route} at character position ${lt_idx}`
)
}
}
net = p.global_net(net_name)
case "x":
case "|":
start = undefined
break
default:
throw new Error(`Unsupported character '${command}' at position ${i}.`)
}
}
return traces
}
const get_routes_traces = (routes, net) => {
let routes_traces = routes.reduce((acc_traces, route) => {
return acc_traces + get_traces(route, net)
}, "")
return routes_traces
}
let combined_traces = ""
if (p.route) {
combined_traces += get_traces(p.route, p.net.index)
}
if (p.routes) {
combined_traces += get_routes_traces(p.routes, p.net.index)
}
return combined_traces
},
}