Outlining improvements
This commit is contained in:
parent
5a25c1c423
commit
5e68bdb630
5 changed files with 92 additions and 80 deletions
12
roadmap.md
12
roadmap.md
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)) => {
|
||||
|
|
127
src/outlines.js
127
src/outlines.js
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue