1
0

Support full anchors in outline shapes

This commit is contained in:
Bán Dénes 2021-07-18 16:03:45 +02:00
parent 677fae0072
commit 26128f8db7
5 changed files with 68 additions and 50 deletions

View File

@ -2,12 +2,23 @@ const u = require('./utils')
const a = require('./assert')
const Point = require('./point')
const anchor = module.exports = (raw, name, points={}, check_unexpected=true, default_point=new Point()) => units => {
const mirror_ref = exports.mirror = (ref, mirror) => {
if (mirror) {
if (ref.startsWith('mirror_')) {
return ref.substring(7)
} else {
return 'mirror_' + ref
}
}
return ref
}
const anchor = exports.parse = (raw, name, points={}, check_unexpected=true, default_point=new Point(), mirror=false) => units => {
if (a.type(raw)() == 'array') {
// recursive call with incremental default_point mods, according to `affect`s
let current = default_point.clone()
for (const step of raw) {
current = anchor(step, name, points, check_unexpected, current)(units)
current = anchor(step, name, points, check_unexpected, current, mirror)(units)
}
return current
}
@ -19,16 +30,18 @@ const anchor = module.exports = (raw, name, points={}, check_unexpected=true, de
let x = 0, y = 0, r = 0
const len = raw.ref.length
for (const ref of raw.ref) {
a.assert(points[ref], `Unknown point reference "${ref}" in anchor "${name}"!`)
const resolved = points[ref]
const parsed_ref = mirror_ref(ref, mirror)
a.assert(points[parsed_ref], `Unknown point reference "${parsed_ref}" in anchor "${name}"!`)
const resolved = points[parsed_ref]
x += resolved.x
y += resolved.y
r += resolved.r
}
point = new Point(x / len, y / len, r / len)
} else {
a.assert(points[raw.ref], `Unknown point reference "${raw.ref}" in anchor "${name}"!`)
point = points[raw.ref].clone()
const parsed_ref = mirror_ref(raw.ref, mirror)
a.assert(points[parsed_ref], `Unknown point reference "${parsed_ref}" in anchor "${name}"!`)
point = points[parsed_ref].clone()
}
}
if (raw.orient !== undefined) {

View File

@ -4,7 +4,7 @@ const a = require('./assert')
const o = require('./operation')
const Point = require('./point')
const prep = require('./prepare')
const make_anchor = require('./anchor')
const anchor_lib = require('./anchor')
const rectangle = (w, h, corner, bevel, name='') => {
const error = (dim, val) => `Rectangle for "${name}" isn't ${dim} enough for its corner and bevel (${val} - 2 * ${corner} - 2 * ${bevel} <= 0)!`
@ -41,9 +41,9 @@ const layout = exports._layout = (config = {}, points = {}, units = {}) => {
for (const y of ['top', 'bottom']) {
a.unexpected(gval[y], `outlines.glue.${gkey}.${y}`, ['left', 'right'])
gval[y].left = make_anchor(gval[y].left, `outlines.glue.${gkey}.${y}.left`, points)
gval[y].left = anchor_lib.parse(gval[y].left, `outlines.glue.${gkey}.${y}.left`, points)
if (a.type(gval[y].right)(units) != 'number') {
gval[y].right = make_anchor(gval[y].right, `outlines.glue.${gkey}.${y}.right`, points)
gval[y].right = anchor_lib.parse(gval[y].right, `outlines.glue.${gkey}.${y}.right`, points)
}
}
@ -148,7 +148,7 @@ const layout = exports._layout = (config = {}, points = {}, units = {}) => {
return u.line([anchor, -1000], [anchor, 1000])
}
// if it wasn't a number, then it's a (possibly relative) achor
// if it wasn't a number, then it's a (possibly relative) anchor
const from = anchor(relative_units).clone()
const to = from.clone().shift([from.meta.mirrored ? -1 : 1, 0])
@ -244,43 +244,45 @@ exports.parse = (config = {}, points = {}, units = {}) => {
let arg
let anchor
const anchor_def = part.anchor || {}
switch (part.type) {
case 'keys':
arg = layout_fn(part, name, expected)
break
case 'rectangle':
a.unexpected(part, name, expected.concat(['ref', 'shift', 'rotate', 'size', 'corner', 'bevel', 'mirror']))
a.unexpected(part, name, expected.concat(['anchor', 'size', 'corner', 'bevel', 'mirror']))
const size = a.wh(part.size, `${name}.size`)(units)
const rec_units = prep.extend({
sx: size[0],
sy: size[1]
}, units)
anchor = make_anchor(part, name, points, false)(rec_units)
anchor = anchor_lib.parse(anchor_def, `${name}.anchor`, points)(rec_units)
const corner = a.sane(part.corner || 0, `${name}.corner`, 'number')(rec_units)
const bevel = a.sane(part.bevel || 0, `${name}.bevel`, 'number')(rec_units)
const rect_mirror = a.sane(part.mirror || false, `${name}.mirror`, 'boolean')()
const rect = rectangle(size[0], size[1], corner, bevel, name)
arg = anchor.position(u.deepcopy(rect))
if (rect_mirror) {
const mirror_part = u.deepcopy(part)
a.assert(mirror_part.ref, `Field "${name}.ref" must be speficied if mirroring is required!`)
mirror_part.ref = `mirror_${mirror_part.ref}`
anchor = make_anchor(mirror_part, name, points, false)(rec_units)
const mirror_anchor = u.deepcopy(anchor_def)
a.assert(mirror_anchor.ref, `Field "${name}.anchor.ref" must be speficied if mirroring is required!`)
anchor = anchor_lib.parse(mirror_anchor, `${name}.anchor --> mirror`, points, undefined, undefined, true)(rec_units)
const mirror_rect = m.model.moveRelative(u.deepcopy(rect), [-size[0], 0])
arg = u.union(arg, anchor.position(mirror_rect))
}
break
case 'circle':
a.unexpected(part, name, expected.concat(['ref', 'shift', 'rotate', 'radius', 'mirror']))
anchor = make_anchor(part, name, points, false)(units)
a.unexpected(part, name, expected.concat(['anchor', 'radius', 'mirror']))
const radius = a.sane(part.radius, `${name}.radius`, 'number')(units)
const circle_units = prep.extend({
r: radius
}, units)
anchor = anchor_lib.parse(anchor_def, `${name}.anchor`, points)(circle_units)
const circle_mirror = a.sane(part.mirror || false, `${name}.mirror`, 'boolean')()
arg = u.circle(anchor.p, radius)
if (circle_mirror) {
const mirror_part = u.deepcopy(part)
a.assert(mirror_part.ref, `Field "${name}.ref" must be speficied if mirroring is required!`)
mirror_part.ref = `mirror_${mirror_part.ref}`
anchor = make_anchor(mirror_part, name, points, false)(units)
const mirror_anchor = u.deepcopy(anchor_def)
a.assert(mirror_anchor.ref, `Field "${name}.anchor.ref" must be speficied if mirroring is required!`)
anchor = anchor_lib.parse(mirror_anchor, `${name}.anchor --> mirror`, points, undefined, undefined, true)(circle_units)
arg = u.union(arg, u.circle(anchor.p, radius))
}
break
@ -294,12 +296,15 @@ exports.parse = (config = {}, points = {}, units = {}) => {
let last_anchor = new Point()
let poly_index = 0
for (const poly_point of poly_points) {
if (poly_index == 0 && poly_mirror) {
a.assert(poly_point.ref, `Field "${name}.ref" must be speficied if mirroring is required!`)
poly_mirror_x = (points[poly_point.ref].x + points[`mirror_${poly_point.ref}`].x) / 2
}
const poly_name = `${name}.points[${++poly_index}]`
last_anchor = make_anchor(poly_point, poly_name, points, true, last_anchor)(units)
if (poly_index == 1 && poly_mirror) {
a.assert(poly_point.ref, `Field "${poly_name}.ref" must be speficied if mirroring is required!`)
const mirrored_ref = anchor_lib.mirror(poly_point.ref, poly_mirror)
a.assert(points[poly_point.ref], `Field "${poly_name}.ref" does not name an existing point!`)
a.assert(points[mirrored_ref], `The mirror of field "${poly_name}.ref" ("${mirrored_ref}") does not name an existing point!`)
poly_mirror_x = (points[poly_point.ref].x + points[mirrored_ref].x) / 2
}
last_anchor = anchor_lib.parse(poly_point, poly_name, points, true, last_anchor)(units)
parsed_points.push(last_anchor.p)
mirror_points.push(last_anchor.clone().mirror(poly_mirror_x).p)
}

View File

@ -1,7 +1,7 @@
const m = require('makerjs')
const a = require('./assert')
const prep = require('./prepare')
const make_anchor = require('./anchor')
const anchor_lib = require('./anchor')
const kicad_prefix = `
(kicad_pcb (version 20171130) (host pcbnew 5.1.6)
@ -156,7 +156,7 @@ const footprint = exports._footprint = (config, name, points, point, net_indexer
// config sanitization
a.unexpected(config, name, ['type', 'anchor', 'nets', 'anchors', 'params'])
const type = a.in(config.type, `${name}.type`, Object.keys(footprint_types))
let anchor = make_anchor(config.anchor || {}, `${name}.anchor`, points, true, point)(units)
let anchor = anchor_lib.parse(config.anchor || {}, `${name}.anchor`, points, true, point)(units)
const nets = a.sane(config.nets || {}, `${name}.nets`, 'object')()
const anchors = a.sane(config.anchors || {}, `${name}.anchors`, 'object')()
const params = a.sane(config.params || {}, `${name}.params`, 'object')()
@ -187,7 +187,7 @@ const footprint = exports._footprint = (config, name, points, point, net_indexer
parsed_params.at = `(at ${anchor.x} ${-anchor.y} ${anchor.r})`
parsed_params.rot = anchor.r
parsed_params.xy = (x, y) => {
const new_anchor = make_anchor({
const new_anchor = anchor_lib.parse({
shift: [x, -y]
}, '_internal_footprint_xy', points, true, anchor)(units)
return `${new_anchor.x} ${-new_anchor.y}`
@ -224,7 +224,7 @@ const footprint = exports._footprint = (config, name, points, point, net_indexer
// parsing anchor-type parameters
parsed_params.anchors = {}
for (const [anchor_name, anchor_config] of Object.entries(prep.extend(fp.anchors || {}, anchors))) {
let parsed_anchor = make_anchor(anchor_config || {}, `${name}.anchors.${anchor_name}`, points, true, anchor)(units)
let parsed_anchor = anchor_lib.parse(anchor_config || {}, `${name}.anchors.${anchor_name}`, points, true, anchor)(units)
parsed_anchor.y = -parsed_anchor.y
parsed_params.anchors[anchor_name] = parsed_anchor
}

View File

@ -2,7 +2,7 @@ const m = require('makerjs')
const u = require('./utils')
const a = require('./assert')
const prep = require('./prepare')
const make_anchor = require('./anchor')
const anchor_lib = require('./anchor')
const push_rotation = exports._push_rotation = (list, angle, origin) => {
let candidate = origin
@ -194,7 +194,7 @@ const parse_axis = exports._parse_axis = (config, name, points, units) => {
const mirror_obj = a.sane(config || {}, name, 'object')()
const distance = a.sane(mirror_obj.distance || 0, `${name}.distance`, 'number')(units)
delete mirror_obj.distance
let axis = make_anchor(mirror_obj, name, points)(units).x
let axis = anchor_lib.parse(mirror_obj, name, points)(units).x
axis += distance / 2
return axis
} else return config
@ -235,7 +235,7 @@ exports.parse = (config, units) => {
for (let [zone_name, zone] of Object.entries(zones)) {
// extracting keys that are handled here, not at the zone render level
const anchor = make_anchor(zone.anchor || {}, `points.zones.${zone_name}.anchor`, all_points)(units)
const anchor = anchor_lib.parse(zone.anchor || {}, `points.zones.${zone_name}.anchor`, all_points)(units)
const rotate = a.sane(zone.rotate || 0, `points.zones.${zone_name}.rotate`, 'number')(units)
const mirror = zone.mirror
delete zone.anchor

View File

@ -1,4 +1,4 @@
const anchor = require('../../src/anchor')
const {parse} = require('../../src/anchor')
const Point = require('../../src/point')
const {check} = require('../helpers/point')
@ -13,17 +13,17 @@ describe('Anchor', function() {
it('params', function() {
// an empty anchor definition leads to the default point
check(
anchor({}, 'name')(),
parse({}, 'name')(),
[0, 0, 0, {}]
)
// unexpected check can be disabled
check(
anchor({unexpected_key: true}, 'name', {}, false)(),
parse({unexpected_key: true}, 'name', {}, false)(),
[0, 0, 0, {}]
)
// default point can be overridden
check(
anchor({}, 'name', {}, true, new Point(1, 1))(),
parse({}, 'name', {}, true, new Point(1, 1))(),
[1, 1, 0, {}]
)
})
@ -31,12 +31,12 @@ describe('Anchor', function() {
it('ref', function() {
// single reference
check(
anchor({ref: 'o'}, 'name', points)(),
parse({ref: 'o'}, 'name', points)(),
[0, 0, 0, {label: 'o'}]
)
// average of multiple references (metadata gets ignored)
check(
anchor({ref: ['o', 'ten']}, 'name', points)(),
parse({ref: ['o', 'ten']}, 'name', points)(),
[5, 5, 5, {}]
)
})
@ -44,12 +44,12 @@ describe('Anchor', function() {
it('shift', function() {
// normal shift
check(
anchor({shift: [1, 1]}, 'name')(),
parse({shift: [1, 1]}, 'name')(),
[1, 1, 0, {}]
)
// shift should respect mirrored points (and invert along the x axis)
check(
anchor({ref: 'mirror', shift: [1, 1]}, 'name', points)(),
parse({ref: 'mirror', shift: [1, 1]}, 'name', points)(),
[19, 1, 0, {mirrored: true}]
)
})
@ -57,13 +57,13 @@ describe('Anchor', function() {
it('orient', function() {
// an orient by itself is equal to rotation
check(
anchor({orient: 10}, 'name')(),
parse({orient: 10}, 'name')(),
[0, 0, 10, {}]
)
// orient acts before shifting
// so when we orient to the right, an upward shift goes to the right
check(
anchor({orient: -90, shift: [0, 1]}, 'name')(),
parse({orient: -90, shift: [0, 1]}, 'name')(),
[1, 0, -90, {}]
)
})
@ -71,13 +71,13 @@ describe('Anchor', function() {
it('rotate', function() {
// basic rotation
check(
anchor({rotate: 10}, 'name')(),
parse({rotate: 10}, 'name')(),
[0, 0, 10, {}]
)
// rotate acts *after* shifting
// so even tho we rotate to the right, an upward shift does go upward
check(
anchor({shift: [0, 1], rotate: -90}, 'name')(),
parse({shift: [0, 1], rotate: -90}, 'name')(),
[0, 1, -90, {}]
)
})
@ -85,16 +85,16 @@ describe('Anchor', function() {
it('affect', function() {
// affect can restrict which point fields (x, y, r) are affected by the transformations
check(
anchor({orient: -90, shift: [0, 1], rotate: 10, affect: 'r'}, 'name')(),
parse({orient: -90, shift: [0, 1], rotate: 10, affect: 'r'}, 'name')(),
[0, 0, -80, {}]
)
check(
anchor({orient: -90, shift: [0, 1], rotate: 10, affect: 'xy'}, 'name')(),
parse({orient: -90, shift: [0, 1], rotate: 10, affect: 'xy'}, 'name')(),
[1, 0, 0, {}]
)
// affects can also be arrays (example same as above)
check(
anchor({orient: -90, shift: [0, 1], rotate: 10, affect: ['x', 'y']}, 'name')(),
parse({orient: -90, shift: [0, 1], rotate: 10, affect: ['x', 'y']}, 'name')(),
[1, 0, 0, {}]
)
})
@ -102,7 +102,7 @@ describe('Anchor', function() {
it('array', function() {
// basic multi-anchor
check(
anchor([
parse([
{shift: [1, 1]},
{rotate: 10}
], 'name')(),