diff --git a/src/anchor.js b/src/anchor.js index 5bb0339..c24f75e 100644 --- a/src/anchor.js +++ b/src/anchor.js @@ -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) { diff --git a/src/outlines.js b/src/outlines.js index 6045948..cf86dd5 100644 --- a/src/outlines.js +++ b/src/outlines.js @@ -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) } diff --git a/src/pcbs.js b/src/pcbs.js index bf41ba8..1377a77 100644 --- a/src/pcbs.js +++ b/src/pcbs.js @@ -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 } diff --git a/src/points.js b/src/points.js index cd44c44..a2522ec 100644 --- a/src/points.js +++ b/src/points.js @@ -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 diff --git a/test/unit/anchor.js b/test/unit/anchor.js index a5ed60b..b335840 100644 --- a/test/unit/anchor.js +++ b/test/unit/anchor.js @@ -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')(),