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
|
return ref
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchor = exports.parse = (raw, name, points={}, check_unexpected=true, default_point=new Point(), mirror=false) => units => {
|
const aggregator_common = ['parts', 'method']
|
||||||
if (a.type(raw)() == 'array') {
|
|
||||||
|
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
|
// recursive call with incremental default_point mods, according to `affect`s
|
||||||
let current = default_point.clone()
|
let current = default_point.clone()
|
||||||
|
let index = 1
|
||||||
for (const step of raw) {
|
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
|
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()
|
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 (raw.ref !== undefined) {
|
||||||
if (a.type(raw.ref)() == 'array') {
|
// base case, resolve directly
|
||||||
// averaging multiple anchors
|
if (a.type(raw.ref)() == 'string') {
|
||||||
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 {
|
|
||||||
const parsed_ref = mirror_ref(raw.ref, mirror)
|
const parsed_ref = mirror_ref(raw.ref, mirror)
|
||||||
a.assert(points[parsed_ref], `Unknown point reference "${parsed_ref}" in anchor "${name}"!`)
|
a.assert(points[parsed_ref], `Unknown point reference "${parsed_ref}" in anchor "${name}"!`)
|
||||||
point = points[parsed_ref].clone()
|
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) {
|
if (raw.orient !== undefined) {
|
||||||
let angle = a.sane(raw.orient, `${name}.orient`, 'number')(units)
|
let angle = a.sane(raw.orient, `${name}.orient`, 'number')(units)
|
||||||
if (point.meta.mirrored) {
|
if (point.meta.mirrored) {
|
||||||
|
@ -77,5 +125,6 @@ const anchor = exports.parse = (raw, name, points={}, check_unexpected=true, def
|
||||||
point[aff] = candidate[aff]
|
point[aff] = candidate[aff]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return point
|
return point
|
||||||
}
|
}
|
|
@ -121,7 +121,7 @@ exports.parse = (config, name, points={}, units={}, include_mirrors=false) => {
|
||||||
result.push(anchor(config, name, points)(units))
|
result.push(anchor(config, name, points)(units))
|
||||||
if (include_mirrors) {
|
if (include_mirrors) {
|
||||||
// this is strict: if the ref of the anchor doesn't have a mirror pair, it will error out
|
// 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
|
// 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
|
let poly_index = -1
|
||||||
for (const poly_point of poly_points) {
|
for (const poly_point of poly_points) {
|
||||||
const poly_name = `${name}.points[${++poly_index}]`
|
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)
|
parsed_points.push(last_anchor.p)
|
||||||
}
|
}
|
||||||
let poly = u.poly(parsed_points)
|
let poly = u.poly(parsed_points)
|
||||||
|
|
|
@ -156,7 +156,7 @@ const footprint = exports._footprint = (config, name, points, point, net_indexer
|
||||||
// config sanitization
|
// config sanitization
|
||||||
a.unexpected(config, name, ['type', 'anchor', 'nets', 'anchors', 'params'])
|
a.unexpected(config, name, ['type', 'anchor', 'nets', 'anchors', 'params'])
|
||||||
const type = a.in(config.type, `${name}.type`, Object.keys(footprint_types))
|
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 nets = a.sane(config.nets || {}, `${name}.nets`, 'object')()
|
||||||
const anchors = a.sane(config.anchors || {}, `${name}.anchors`, 'object')()
|
const anchors = a.sane(config.anchors || {}, `${name}.anchors`, 'object')()
|
||||||
const params = a.sane(config.params || {}, `${name}.params`, '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) => {
|
parsed_params.xy = (x, y) => {
|
||||||
const new_anchor = anchor_lib.parse({
|
const new_anchor = anchor_lib.parse({
|
||||||
shift: [x, -y]
|
shift: [x, -y]
|
||||||
}, '_internal_footprint_xy', points, true, anchor)(units)
|
}, '_internal_footprint_xy', points, anchor)(units)
|
||||||
return `${new_anchor.x} ${-new_anchor.y}`
|
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
|
// parsing anchor-type parameters
|
||||||
parsed_params.anchors = {}
|
parsed_params.anchors = {}
|
||||||
for (const [anchor_name, anchor_config] of Object.entries(prep.extend(fp.anchors || {}, 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_anchor.y = -parsed_anchor.y
|
||||||
parsed_params.anchors[anchor_name] = parsed_anchor
|
parsed_params.anchors[anchor_name] = parsed_anchor
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ outlines:
|
||||||
middle_circle:
|
middle_circle:
|
||||||
what: circle
|
what: circle
|
||||||
where:
|
where:
|
||||||
ref:
|
aggregate.parts:
|
||||||
- matrix
|
- matrix
|
||||||
- mirror_matrix
|
- mirror_matrix
|
||||||
radius: 15
|
radius: 15
|
||||||
|
|
|
@ -11,7 +11,7 @@ outlines:
|
||||||
bound: false
|
bound: false
|
||||||
middle_poly:
|
middle_poly:
|
||||||
what: polygon
|
what: polygon
|
||||||
where.ref:
|
where.aggregate.parts:
|
||||||
- matrix
|
- matrix
|
||||||
- mirror_matrix
|
- mirror_matrix
|
||||||
points:
|
points:
|
||||||
|
|
|
@ -12,7 +12,7 @@ outlines:
|
||||||
middle_rect:
|
middle_rect:
|
||||||
what: rectangle
|
what: rectangle
|
||||||
where:
|
where:
|
||||||
ref:
|
aggregate.parts:
|
||||||
- matrix
|
- matrix
|
||||||
- mirror_matrix
|
- mirror_matrix
|
||||||
shift: [0, sy/2]
|
shift: [0, sy/2]
|
||||||
|
|
|
@ -6,8 +6,8 @@ describe('Anchor', function() {
|
||||||
|
|
||||||
const points = {
|
const points = {
|
||||||
o: new Point(0, 0, 0, {label: 'o'}),
|
o: new Point(0, 0, 0, {label: 'o'}),
|
||||||
ten: new Point(10, 10, 10, {label: 'ten'}),
|
ten: new Point(10, 10, -90, {label: 'ten'}),
|
||||||
mirror: new Point(20, 0, 0, {mirrored: true})
|
mirror_ten: new Point(-10, 10, 90, {mirrored: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
it('params', function() {
|
it('params', function() {
|
||||||
|
@ -16,28 +16,45 @@ describe('Anchor', function() {
|
||||||
parse({}, 'name')(),
|
parse({}, 'name')(),
|
||||||
[0, 0, 0, {}]
|
[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
|
// single reference
|
||||||
check(
|
check(
|
||||||
parse({ref: 'o'}, 'name', points)(),
|
parse({ref: 'o'}, 'name', points)(),
|
||||||
[0, 0, 0, {label: 'o'}]
|
[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)
|
// average of multiple references (metadata gets ignored)
|
||||||
check(
|
check(
|
||||||
parse({ref: ['o', 'ten']}, 'name', points)(),
|
parse({
|
||||||
[5, 5, 5, {}]
|
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)
|
// shift should respect mirrored points (and invert along the x axis)
|
||||||
check(
|
check(
|
||||||
parse({ref: 'mirror', shift: [1, 1]}, 'name', points)(),
|
parse({ref: 'mirror_ten', shift: [1, 1]}, 'name', points)(),
|
||||||
[19, 1, 0, {mirrored: true}]
|
[-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() {
|
it('array', function() {
|
||||||
// basic multi-anchor
|
// basic multi-anchor
|
||||||
check(
|
check(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue