1
0

Outlining improvements

This commit is contained in:
Bán Dénes 2022-05-29 20:25:52 +02:00
parent 5a25c1c423
commit 5e68bdb630
5 changed files with 92 additions and 80 deletions

View File

@ -6,9 +6,6 @@
### Major
- Key-level access to full anchors
- this could provide extra variables `padding`, `spread`, `splay` for custom layout purposes
- make row anchors cumulative, too (like columns), so fingers arcs and other edits can happen
- Restructure pcb point/footprint filtering
- Use the same `what`/`where` infrastructure as outlines
- Collapse params/nets/anchors into a single hierarchy from the user's POV
@ -30,12 +27,8 @@
- Allow footprints to publish outlines
- Make these usable in the `outlines` section through a new `what`
- 3D orient for cases
- Allow a generic `adjust` field for outlines that accepts an anchor
- This could swallow `origin` from `outline`
- Post-process anchor for global (post-mirror!) orient/shift/rotate for everything
- Even more extreme anchor stuff
- Checkpoints, intersects, distances, weighted combinations?
- Allow both object (as well as arrays) in multiple anchor refs
- SVG input (for individual outlines, or even combinations parsed by line color, etc.)
- And once that's done, possibly even STL or other input for cases or pcb renders
- Support text silk output to PCBs (in configurable fonts, through SVG?)
@ -44,7 +37,6 @@
- Support curves (arcs as well as Béziers) in polygons
- Add snappable line footprint
- Figure out a manual, but still reasonably comfortable routing method directly from the config
- Add filleting syntax with `@`?
- Eeschema support for pcbs
- Generate ZMK shield from config
- Export **to** KLE?
@ -53,11 +45,7 @@
- Look into kicad 5 vs. 6 output format
- Update json schema and add syntax highlight to editors
- Support different netclasses
- `round`, `pointy` and `beveled` symbolic constants for expand joint types
- Also, string shorthands like `3)`, `5>` and `10]`
- Allow a potential filter for filleting (only on angles =90°, <45°, left turn vs. right turn when going clockwise, etc.)
- Support cumulative handling of outline parts (i.e., add `fillet` as an generic option that applies to all the parts up to that point)
- Similar with adjust
### Patch

View File

@ -69,7 +69,6 @@ const anchor = exports.parse = (raw, name, points={}, default_point=new Point(),
} else {
point = anchor(raw.ref, `${name}.ref`, points, default_point, mirror)(units)
}
}
if (raw.aggregate !== undefined) {
@ -95,10 +94,7 @@ const anchor = exports.parse = (raw, name, points={}, default_point=new Point(),
// simple case: number gets added to point rotation
if (a.type(config)(units) == 'number') {
let angle = a.sane(config, name, 'number')(units)
if (point.meta.mirrored) {
angle = -angle
}
point.r += angle
point.rotate(angle, false)
// recursive case: points turns "towards" target anchor
} else {
const target = anchor(config, name, points, default_point, mirror)(units)
@ -111,10 +107,7 @@ const anchor = exports.parse = (raw, name, points={}, default_point=new Point(),
}
if (raw.shift !== undefined) {
let xyval = a.wh(raw.shift, `${name}.shift`)(units)
if (point.meta.mirrored) {
xyval[0] = -xyval[0]
}
point.shift(xyval, true)
point.shift(xyval)
}
if (raw.rotate !== undefined) {
rotator(raw.rotate, `${name}.rotate`, point)

View File

@ -1,10 +1,16 @@
const op_prefix = exports.op_prefix = str => {
const prefix = str[0]
const suffix = str.slice(1)
if (str.startsWith('+')) return {name: suffix, operation: 'add'}
if (str.startsWith('-')) return {name: suffix, operation: 'subtract'}
if (str.startsWith('~')) return {name: suffix, operation: 'intersect'}
if (str.startsWith('^')) return {name: suffix, operation: 'stack'}
return {name: str, operation: 'add'}
const result = {name: suffix, operation: 'add'}
if (prefix == '+') ; // noop
else if (prefix == '-') result.operation = 'subtract'
else if (prefix == '~') result.operation = 'intersect'
else if (prefix == '^') result.operation = 'stack'
else result.name = str // no prefix, so the name was the whole string
return result
}
exports.operation = (str, choices={}, order=Object.keys(choices)) => {

View File

@ -41,7 +41,7 @@ const rectangle = (config, name, points, outlines, units) => {
const bevel = a.sane(config.bevel || 0, `${name}.bevel`, 'number')(rec_units)
// return shape function and its units
return [(point, bound) => {
return [() => {
const error = (dim, val) => `Rectangle for "${name}" isn't ${dim} enough for its corner and bevel (${val} - 2 * ${corner} - 2 * ${bevel} <= 0)!`
const [w, h] = size
@ -66,13 +66,9 @@ const rectangle = (config, name, points, outlines, units) => {
}
if (corner > 0) rect = m.model.outline(rect, corner, 0)
rect = m.model.moveRelative(rect, [-cw/2, -ch/2])
if (bound) {
const bbox = {high: [w/2, h/2], low: [-w/2, -h/2]}
rect = binding(rect, bbox, point, rec_units)
}
rect = point.position(rect)
const bbox = {high: [w/2, h/2], low: [-w/2, -h/2]}
return rect
return [rect, bbox]
}, rec_units]
}
@ -86,14 +82,10 @@ const circle = (config, name, points, outlines, units) => {
}, units)
// return shape function and its units
return [(point, bound) => {
return [() => {
let circle = u.circle([0, 0], radius)
if (bound) {
const bbox = {high: [radius, radius], low: [-radius, -radius]}
circle = binding(circle, bbox, point, circ_units)
}
circle = point.position(circle)
return circle
const bbox = {high: [radius, radius], low: [-radius, -radius]}
return [circle, bbox]
}, circ_units]
}
@ -104,10 +96,10 @@ const polygon = (config, name, points, outlines, units) => {
const poly_points = a.sane(config.points, `${name}.points`, 'array')()
// return shape function and its units
return [(point, bound) => {
return [point => {
const parsed_points = []
// the point starts at [0, 0] as it will be positioned later
// but we keep the metadata for potential mirroring purposes
// the poly starts at [0, 0] as it will be positioned later
// but we keep the point metadata for potential mirroring purposes
let last_anchor = new Point(0, 0, 0, point.meta)
let poly_index = -1
for (const poly_point of poly_points) {
@ -116,52 +108,24 @@ const polygon = (config, name, points, outlines, units) => {
parsed_points.push(last_anchor.p)
}
let poly = u.poly(parsed_points)
if (bound) {
const bbox = u.bbox(parsed_points)
poly = binding(poly, bbox, point, units)
}
poly = point.position(poly)
return poly
const bbox = u.bbox(parsed_points)
return [poly, bbox]
}, units]
}
const outline = (config, name, points, outlines, units) => {
// prepare params
a.unexpected(config, `${name}`, ['name', 'fillet', 'expand', 'origin', 'scale'])
a.unexpected(config, `${name}`, ['name', 'origin'])
a.assert(outlines[config.name], `Field "${name}.name" does not name an existing outline!`)
const fillet = a.sane(config.fillet || 0, `${name}.fillet`, 'number')(units)
const expand = a.sane(config.expand || 0, `${name}.expand`, 'number')(units)
const joints = a.in(a.sane(config.joints || 0, `${name}.joints`, 'number')(units), `${name}.joints`, [0, 1, 2])
const origin = anchor(config.origin || {}, `${name}.origin`, points)(units)
const scale = a.sane(config.scale || 1, `${name}.scale`, 'number')(units)
// return shape function and its units
return [(point, bound) => {
return [() => {
let o = u.deepcopy(outlines[config.name])
o = origin.unposition(o)
if (scale !== 1) {
o = m.model.scale(o, scale)
}
if (fillet) {
for (const [index, chain] of m.model.findChains(o).entries()) {
o.models[`fillet_${index}`] = m.chain.fillet(chain, fillet)
}
}
if (expand) {
o = m.model.outline(o, Math.abs(expand), joints, (expand < 0), {farPoint: u.farPoint})
}
if (bound) {
const bbox = m.measure.modelExtents(o)
o = binding(o, bbox, point, units)
}
o = point.position(o)
return o
const bbox = m.measure.modelExtents(o)
return [o, bbox]
}, units]
}
@ -172,6 +136,29 @@ const whats = {
outline
}
const expand_shorthand = (config, units) => {
if (a.type(config.expand)(units) == 'string') {
const prefix = config.expand.slice(0, -1)
const suffix = config.expand.slice(-1)
let expand = suffix
let joints = 0
if (suffix == ')') ; // noop
else if (suffix == '>') joints = 1
else if (suffix == ']') joints = 2
else expand = config.expand
config.expand = parseFloat(expand)
config.joints = config.joints || joints
}
if (a.type(config.joints)(units) == 'string') {
if (config.joints == 'round') config.joints = 0
if (config.joints == 'pointy') config.joints = 1
if (config.joints == 'beveled') config.joints = 2
}
}
exports.parse = (config = {}, points = {}, units = {}) => {
// output outlines will be collected here
@ -205,10 +192,18 @@ exports.parse = (config = {}, points = {}, units = {}) => {
const what = a.in(part.what || 'outline', `${name}.what`, ['rectangle', 'circle', 'polygon', 'outline'])
const bound = !!part.bound
const mirror = a.sane(part.mirror || false, `${name}.mirror`, 'boolean')()
// `where` is delayed until we have all, potentially what-dependent units
// default where is [0, 0], as per filter parsing
const original_where = part.where // need to save, so the delete's don't get rid of it below
const where = units => filter(original_where, `${name}.where`, points, units, mirror)
const adjust = anchor(part.adjust || {}, `${name}.adjust`, points)(units)
const fillet = a.sane(part.fillet || 0, `${name}.fillet`, 'number')(units)
expand_shorthand(part, units)
const expand = a.sane(part.expand || 0, `${name}.expand`, 'number')(units)
const joints = a.in(a.sane(part.joints || 0, `${name}.joints`, 'number')(units), `${name}.joints`, [0, 1, 2])
const scale = a.sane(part.scale || 1, `${name}.scale`, 'number')(units)
// these keys are then removed, so ops can check their own unexpected keys without interference
delete part.operation
@ -216,15 +211,41 @@ exports.parse = (config = {}, points = {}, units = {}) => {
delete part.bound
delete part.mirror
delete part.where
delete part.adjust
delete part.fillet
delete part.expand
delete part.joints
delete part.scale
// a prototype "shape" maker (and its units) are computed
const [shape_maker, shape_units] = whats[what](part, name, points, outlines, units)
// and then the shape is repeated for all where positions
for (const w of where(shape_units)) {
const shape = shape_maker(w, bound)
const point = w.clone().shift(adjust.p).rotate(adjust.r, false)
let [shape, bbox] = shape_maker(point) // point is passed for mirroring metadata only...
if (bound) {
shape = binding(shape, bbox, point, shape_units)
}
shape = point.position(shape) // ...actual positioning happens here
outlines[outline_name] = operation(outlines[outline_name], shape)
}
if (scale !== 1) {
outlines[outline_name] = m.model.scale(outlines[outline_name], scale)
}
if (expand) {
outlines[outline_name] = m.model.outline(
outlines[outline_name], Math.abs(expand), joints, (expand < 0), {farPoint: u.farPoint}
)
}
if (fillet) {
for (const [index, chain] of m.model.findChains(outlines[outline_name]).entries()) {
outlines[outline_name].models[`fillet_${part_name}_${index}`] = m.chain.fillet(chain, fillet)
}
}
}
// final adjustments

View File

@ -25,6 +25,7 @@ module.exports = class Point {
}
shift(s, relative=true) {
s[0] *= this.meta.mirrored ? -1 : 1
if (relative) {
s = m.point.rotate(s, this.r)
}
@ -34,7 +35,10 @@ module.exports = class Point {
}
rotate(angle, origin=[0, 0]) {
this.p = m.point.rotate(this.p, angle, origin)
angle *= this.meta.mirrored ? -1 : 1
if (origin) {
this.p = m.point.rotate(this.p, angle, origin)
}
this.r += angle
return this
}