Anchor recursivization
This commit is contained in:
parent
b8c71bef0f
commit
6dc6b5d8e9
8 changed files with 119 additions and 45 deletions
|
@ -12,37 +12,85 @@ const mirror_ref = exports.mirror = (ref, mirror=true) => {
|
|||
return ref
|
||||
}
|
||||
|
||||
const anchor = exports.parse = (raw, name, points={}, check_unexpected=true, default_point=new Point(), mirror=false) => units => {
|
||||
if (a.type(raw)() == 'array') {
|
||||
const aggregator_common = ['parts', 'method']
|
||||
|
||||
const aggregators = {
|
||||
average: (config, name, parts) => {
|
||||
a.unexpected(config, name, aggregator_common)
|
||||
let x = 0, y = 0, r = 0
|
||||
const len = parts.length
|
||||
for (const part of parts) {
|
||||
x += part.x
|
||||
y += part.y
|
||||
r += part.r
|
||||
}
|
||||
return new Point(x / len, y / len, r / len)
|
||||
}
|
||||
}
|
||||
|
||||
const anchor = exports.parse = (raw, name, points={}, default_point=new Point(), mirror=false) => units => {
|
||||
|
||||
//
|
||||
// Anchor type handling
|
||||
//
|
||||
|
||||
if (a.type(raw)() == 'string') {
|
||||
raw = {ref: raw}
|
||||
}
|
||||
|
||||
else if (a.type(raw)() == 'array') {
|
||||
// recursive call with incremental default_point mods, according to `affect`s
|
||||
let current = default_point.clone()
|
||||
let index = 1
|
||||
for (const step of raw) {
|
||||
current = anchor(step, name, points, check_unexpected, current, mirror)(units)
|
||||
current = anchor(step, `${name}[${index++}]`, points, current, mirror)(units)
|
||||
}
|
||||
return current
|
||||
}
|
||||
if (check_unexpected) a.unexpected(raw, name, ['ref', 'orient', 'shift', 'rotate', 'affect'])
|
||||
|
||||
a.unexpected(raw, name, ['ref', 'aggregate', 'orient', 'shift', 'rotate', 'affect'])
|
||||
|
||||
//
|
||||
// Reference or aggregate handling
|
||||
//
|
||||
|
||||
let point = default_point.clone()
|
||||
if (raw.ref !== undefined && raw.aggregate !== undefined) {
|
||||
throw new Error(`Fields "ref" and "aggregate" cannot appear together in anchor "${name}"!`)
|
||||
}
|
||||
|
||||
if (raw.ref !== undefined) {
|
||||
if (a.type(raw.ref)() == 'array') {
|
||||
// averaging multiple anchors
|
||||
let x = 0, y = 0, r = 0
|
||||
const len = raw.ref.length
|
||||
for (const ref of raw.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 {
|
||||
// base case, resolve directly
|
||||
if (a.type(raw.ref)() == 'string') {
|
||||
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()
|
||||
// recursive case
|
||||
} else {
|
||||
point = anchor(raw.ref, `${name}.ref`, points, default_point, mirror)(units)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (raw.aggregate !== undefined) {
|
||||
raw.aggregate = a.sane(raw.aggregate, `${name}.aggregate`, 'object')()
|
||||
raw.aggregate.method = a.sane(raw.aggregate.method || 'average', `${name}.aggregate.method`, 'string')()
|
||||
a.assert(aggregators[raw.aggregate.method], `Unknown aggregator method "${raw.aggregate.method}" in anchor "${name}"!`)
|
||||
raw.aggregate.parts = a.sane(raw.aggregate.parts || [], `${name}.aggregate.parts`, 'array')()
|
||||
|
||||
const parts = []
|
||||
let index = 1
|
||||
for (const part of raw.aggregate.parts) {
|
||||
parts.push(anchor(part, `${name}.aggregate.parts[${index++}]`, points, default_point, mirror)(units))
|
||||
}
|
||||
|
||||
point = aggregators[raw.aggregate.method](raw.aggregate, `${name}.aggregate`, parts)
|
||||
}
|
||||
|
||||
//
|
||||
// Actual orient/shift/rotate/affect handling
|
||||
//
|
||||
|
||||
if (raw.orient !== undefined) {
|
||||
let angle = a.sane(raw.orient, `${name}.orient`, 'number')(units)
|
||||
if (point.meta.mirrored) {
|
||||
|
@ -77,5 +125,6 @@ const anchor = exports.parse = (raw, name, points={}, check_unexpected=true, def
|
|||
point[aff] = candidate[aff]
|
||||
}
|
||||
}
|
||||
|
||||
return point
|
||||
}
|
|
@ -121,7 +121,7 @@ exports.parse = (config, name, points={}, units={}, include_mirrors=false) => {
|
|||
result.push(anchor(config, name, points)(units))
|
||||
if (include_mirrors) {
|
||||
// this is strict: if the ref of the anchor doesn't have a mirror pair, it will error out
|
||||
result.push(anchor(config, name, points, true, undefined, true)(units))
|
||||
result.push(anchor(config, name, points, undefined, true)(units))
|
||||
}
|
||||
|
||||
// otherwise, it is treated as a condition to filter all available points
|
||||
|
|
|
@ -112,7 +112,7 @@ const polygon = (config, name, points, outlines, units) => {
|
|||
let poly_index = -1
|
||||
for (const poly_point of poly_points) {
|
||||
const poly_name = `${name}.points[${++poly_index}]`
|
||||
last_anchor = anchor(poly_point, poly_name, points, true, last_anchor)(units)
|
||||
last_anchor = anchor(poly_point, poly_name, points, last_anchor)(units)
|
||||
parsed_points.push(last_anchor.p)
|
||||
}
|
||||
let poly = u.poly(parsed_points)
|
||||
|
|
|
@ -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 = anchor_lib.parse(config.anchor || {}, `${name}.anchor`, points, true, point)(units)
|
||||
let anchor = anchor_lib.parse(config.anchor || {}, `${name}.anchor`, points, 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')()
|
||||
|
@ -189,7 +189,7 @@ const footprint = exports._footprint = (config, name, points, point, net_indexer
|
|||
parsed_params.xy = (x, y) => {
|
||||
const new_anchor = anchor_lib.parse({
|
||||
shift: [x, -y]
|
||||
}, '_internal_footprint_xy', points, true, anchor)(units)
|
||||
}, '_internal_footprint_xy', points, 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 = anchor_lib.parse(anchor_config || {}, `${name}.anchors.${anchor_name}`, points, true, anchor)(units)
|
||||
let parsed_anchor = anchor_lib.parse(anchor_config || {}, `${name}.anchors.${anchor_name}`, points, anchor)(units)
|
||||
parsed_anchor.y = -parsed_anchor.y
|
||||
parsed_params.anchors[anchor_name] = parsed_anchor
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ outlines:
|
|||
middle_circle:
|
||||
what: circle
|
||||
where:
|
||||
ref:
|
||||
aggregate.parts:
|
||||
- matrix
|
||||
- mirror_matrix
|
||||
radius: 15
|
||||
|
|
|
@ -11,7 +11,7 @@ outlines:
|
|||
bound: false
|
||||
middle_poly:
|
||||
what: polygon
|
||||
where.ref:
|
||||
where.aggregate.parts:
|
||||
- matrix
|
||||
- mirror_matrix
|
||||
points:
|
||||
|
|
|
@ -12,7 +12,7 @@ outlines:
|
|||
middle_rect:
|
||||
what: rectangle
|
||||
where:
|
||||
ref:
|
||||
aggregate.parts:
|
||||
- matrix
|
||||
- mirror_matrix
|
||||
shift: [0, sy/2]
|
||||
|
|
|
@ -6,8 +6,8 @@ describe('Anchor', function() {
|
|||
|
||||
const points = {
|
||||
o: new Point(0, 0, 0, {label: 'o'}),
|
||||
ten: new Point(10, 10, 10, {label: 'ten'}),
|
||||
mirror: new Point(20, 0, 0, {mirrored: true})
|
||||
ten: new Point(10, 10, -90, {label: 'ten'}),
|
||||
mirror_ten: new Point(-10, 10, 90, {mirrored: true})
|
||||
}
|
||||
|
||||
it('params', function() {
|
||||
|
@ -16,28 +16,45 @@ describe('Anchor', function() {
|
|||
parse({}, 'name')(),
|
||||
[0, 0, 0, {}]
|
||||
)
|
||||
// unexpected check can be disabled
|
||||
check(
|
||||
parse({unexpected_key: true}, 'name', {}, false)(),
|
||||
[0, 0, 0, {}]
|
||||
)
|
||||
// default point can be overridden
|
||||
check(
|
||||
parse({}, 'name', {}, true, new Point(1, 1))(),
|
||||
[1, 1, 0, {}]
|
||||
)
|
||||
})
|
||||
|
||||
it('ref', function() {
|
||||
// single reference
|
||||
check(
|
||||
parse({ref: 'o'}, 'name', points)(),
|
||||
[0, 0, 0, {label: 'o'}]
|
||||
)
|
||||
// default point can be overridden
|
||||
check(
|
||||
parse({}, 'name', {}, new Point(1, 1))(),
|
||||
[1, 1, 0, {}]
|
||||
)
|
||||
// mirrored references can be forced
|
||||
check(
|
||||
parse({ref: 'ten'}, 'name', points, undefined, true)(),
|
||||
[-10, 10, 90, {mirrored: true}]
|
||||
)
|
||||
})
|
||||
|
||||
it('recursive', function() {
|
||||
// recursive references are supported (keeping metadata)
|
||||
check(
|
||||
parse({
|
||||
ref: {
|
||||
ref: 'o',
|
||||
shift: [2, 2]
|
||||
}
|
||||
}, 'name', points)(),
|
||||
[2, 2, 0, {label: 'o'}]
|
||||
)
|
||||
})
|
||||
|
||||
it('aggregate', function() {
|
||||
// average of multiple references (metadata gets ignored)
|
||||
check(
|
||||
parse({ref: ['o', 'ten']}, 'name', points)(),
|
||||
[5, 5, 5, {}]
|
||||
parse({
|
||||
aggregate: {
|
||||
parts: ['o', 'ten']
|
||||
}
|
||||
}, 'name', points)(),
|
||||
[5, 5, -45, {}]
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -49,8 +66,8 @@ describe('Anchor', function() {
|
|||
)
|
||||
// shift should respect mirrored points (and invert along the x axis)
|
||||
check(
|
||||
parse({ref: 'mirror', shift: [1, 1]}, 'name', points)(),
|
||||
[19, 1, 0, {mirrored: true}]
|
||||
parse({ref: 'mirror_ten', shift: [1, 1]}, 'name', points)(),
|
||||
[-11, 9, 90, {mirrored: true}]
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -99,6 +116,14 @@ describe('Anchor', function() {
|
|||
)
|
||||
})
|
||||
|
||||
it('string', function() {
|
||||
// basic string form
|
||||
check(
|
||||
parse('ten', 'name', points)(),
|
||||
[10, 10, -90, {label: 'ten'}]
|
||||
)
|
||||
})
|
||||
|
||||
it('array', function() {
|
||||
// basic multi-anchor
|
||||
check(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue