1
0

Further outline progress

This commit is contained in:
Bán Dénes 2020-07-03 22:29:39 +02:00
parent 8da97611e4
commit 999a5dfad8
2 changed files with 72 additions and 171 deletions

View File

@ -303,17 +303,14 @@ TODO: Absolem points here, with pics
Once the raw points are available, we want to turn them into solid, continuous outlines.
The points are enough to create properly positioned and rotated rectangles (with parametric side lengths), but they won't combine since there won't be any overlap.
So the first part of the outline generation is "binding", where we make the individual holes _bind_ to each other.
We use two, key-level declarations for this:
We use a key-level declarations for this:
```yaml
neighbors: [dir_x, dir_y]
bind: num | [num_x, num_y] | [num_t, num_r, num_b, num_l] # default = 10
bind: num | [num_x, num_y] | [num_t, num_r, num_b, num_l] # default = 0
```
Again, key-level declaration means that both of these should be specified in the `points` section, benefiting from the same extension process every key-level setting does.
The former declares the directions we want to bind in, where `dir_x` can be one of `left`, `right`, `both` or `neither`; and `dir_y` can be one of `up`, `down`, `both` or `neither`.
(If left empty, `neither` will be assumed.)
The latter declares how much we want to bind, i.e., the amount of overlap we want in that direction to make sure that we can reach the neighbor (`num` applies to all directions, `num_x` horizontally, `num_y` vertically, and the t/r/b/l versions to top/right/bottom/left, respectively).
This field declares how much we want to bind in each direction, i.e., the amount of overlap we want to make sure that we can reach the neighbor (`num` applies to all directions, `num_x` horizontally, `num_y` vertically, and the t/r/b/l versions to top/right/bottom/left, respectively).
If it's a one-piece design, we also need to "glue" the halves together (or we might want to leave some extra space for the controller on the inner side for splits).
This is where the following section comes into play:
@ -370,11 +367,11 @@ Note that this outline is still parametric, so that we can specify different wid
Now we can configure what we want to "export" as outlines from this phase, given by the combination/subtraction of the following primitives:
- `keys` : the combined outline that we've just created. Its parameters include:
- `side: left | right | both | glue | raw` : the part we want to use
- `side: left | right | middle | both | glue` : the part we want to use
- `left` and `right` are just the appropriate side of the laid out keys, without the glue.
- `middle` means an "ideal" version of the glue (meaning that instead of the `outline.glue` we defined above, we get `both` - `left` - `right`, so the _exact_ middle piece we would have needed to glue everything together
- `both` means both sides, held together by the glue
- `glue` means an "ideal" version of the glue (meaning that instead of the `outline.glue` we defined above, we get `both` - `left` - `right`, so the _exact_ middle piece we would have needed to glue everything together
- `raw` is the raw glue shape we defined above under `outline.glue`
- `glue` is just the raw glue shape we defined above under `outline.glue`
- `size: num | [num_x, num_y]` : the width/height of the rectangles to lay onto the points
- `corner: num # default = 0)` : corner radius of the rectangles
- `bevel: num # default = 0)` : corner bevel of the rectangles, can be combined with rounding
@ -382,7 +379,7 @@ Now we can configure what we want to "export" as outlines from this phase, given
- `ref: <point reference>` : what position and rotation to consider as the origin
- `rotate: num` : extra rotation
- `shift: [x, y]` : extra translation
- `size: num | [width, height]` : the size of the rectangle
- `size`, `corner` and `bevel`, just like for `keys`
- `circle` : an independent circle primitive. Parameters:
- `ref`, `rotate`, and `shift` are the same as above
- `radius: num` : the radius of the circle

View File

@ -2,150 +2,19 @@ const m = require('makerjs')
const u = require('./utils')
const a = require('./assert')
const outline = exports._outline = (points, config={}) => params => {
const rectangle = (w, h, corner, bevel, name='') => {
const error = (dim, val) => `Rectangle for "${name}" isn't ${dim} enough for its corner and bevel (${val} - ${corner} - ${bevel} <= 0)!`
const cw = w - corner - bevel
a.assert(cw >= 0, error('wide', w))
ch = h - corner - bevel
a.assert(ch >= 0, error('tall', h))
const size = a.wh(params.size || [18, 18], '')
if (!Array.isArray(size)) size = [size, size]
size = a.xy(size, `outline.exports.${params.name}.size`)
const corner = params.corner || 0
let glue = {paths: {}}
if (config.glue) {
const internal_part = (line) => {
// taking the middle part only, so that we don't interfere with corner rounding
return u.line(m.point.middle(line, 0.4), m.point.middle(line, 0.6))
}
const get_line = (def={}) => {
const ref = points[def.ref]
if (!ref) throw new Error(`Point ${def.ref} not found...`)
let from = [0, 0]
let to = [ref.meta.mirrored ? -1 : 1, 0]
// todo: position according to point to get the lines...
let point = ref.clone().shift(def.shift || [0, 0])
point.rotate(def.rotate || 0, point.add(def.origin || [0, 0]))
const rect = m.model.originate(point.rect(footprint))
line = rect.paths[def.line || 'top']
return internal_part(line)
}
assert.ok(config.glue.top)
const tll = get_line(config.glue.top.left)
const trl = get_line(config.glue.top.right)
const tip = m.path.converge(tll, trl)
const tlp = u.eq(tll.origin, tip) ? tll.end : tll.origin
const trp = u.eq(trl.origin, tip) ? trl.end : trl.origin
assert.ok(config.glue.bottom)
const bll = get_line(config.glue.bottom.left)
const brl = get_line(config.glue.bottom.right)
const bip = m.path.converge(bll, brl)
const blp = u.eq(bll.origin, bip) ? bll.end : bll.origin
const brp = u.eq(brl.origin, bip) ? brl.end : brl.origin
const left_waypoints = []
const right_waypoints = []
for (const w of config.glue.waypoints || []) {
const percent = w.percent / 100
const center_x = tip[0] + percent * (bip[0] - tip[0])
const center_y = tip[1] + percent * (bip[1] - tip[1])
const left_x = center_x - (w.left || w.width / 2)
const right_x = center_x + (w.right || w.width / 2)
left_waypoints.push([left_x, center_y])
right_waypoints.unshift([right_x, center_y])
}
const waypoints =
[trp, tip, tlp]
.concat(left_waypoints)
.concat([blp, bip, brp])
.concat(right_waypoints)
glue = u.poly(waypoints)
}
let i = 0
const keys = {}
let left_keys = {}
let right_keys = {}
for (const zone of Object.values(config.zones)) {
// interate cols in reverse order so they can
// always overlap with the growing middle patch
for (const col of zone.columns.slice().reverse()) {
for (const [pname, p] of Object.entries(points)) {
if (p.meta.col.name != col.name) continue
let from_x = -footprint / 2, to_x = footprint / 2
let from_y = -footprint / 2, to_y = footprint / 2
let bind = p.meta.bind || 10
if (!Array.isArray(bind)) {
u.assert(u.type(bind) == 'number', `Incorrect "bind" field for point "${p.meta.name}"!`)
bind = {top: bind, right: bind, bottom: bind, left: bind}
} else {
u.assert([2, 4].includes(bind.length), `The "bind" field for point "${p.meta.name}" should contain 2 or 4 elements!`)
bind.map(val => u.assert(u.type(val) == 'number', `The "bind" field for point "${p.meta.name}" should contain numbers!`))
}
const mirrored = p.meta.mirrored
const bind_x = p.meta.row.bind_x || p.meta.col.bind_x
if ((bind_x == 'left' && !mirrored) || (bind_x == 'right' && mirrored) || bind_x == 'both') {
from_x -= bind
}
if ((bind_x == 'right' && !mirrored) || (bind_x == 'left' && mirrored) || bind_x == 'both') {
to_x += bind
}
const bind_y = p.meta.row.bind_y || p.meta.col.bind_y
if (bind_y == 'down' || bind_y == 'both') {
from_y -= bind
}
if (bind_y == 'up' || bind_y == 'both') {
to_y += bind
}
let key = new m.models.RoundRectangle(to_x - from_x, to_y - from_y, corner)
key = m.model.moveRelative(key, [from_x, from_y])
key = p.position(key)
if (mirrored) {
right_keys = m.model.combineUnion(right_keys, key)
} else {
left_keys = m.model.combineUnion(left_keys, key)
}
}
}
}
u.dump_model({a: glue, b: left_keys, c: {models: right_keys}}, `all_before`)
glue = m.model.combineUnion(glue, left_keys)
u.dump_model({a: glue, b: left_keys, c: {models: right_keys}}, `all_after_left`)
glue = m.model.combineUnion(glue, right_keys)
u.dump_model({a: glue, b: {models: keys}}, `fullll`)
let res = m.models.Rectangle(w, h)
res = m.model.outline(res, bevel, 2)
res = m.model.outline(res, corner, 0)
return m.model.moveRelative(res, [corner + bevel, corner + bevel])
}
const parse_glue = exports._parse_glue = (config = {}, points = {}) => {
a.detect_unexpected(config, 'outline.glue', ['top', 'bottom', 'waypoints', 'extra'])
@ -172,14 +41,47 @@ const parse_glue = exports._parse_glue = (config = {}, points = {}) => {
return (export_name, params) => {
a.detect_unexpected(params, `outline.exports.${export_name}`, ['side', 'size', 'corner', 'bevel'])
params.side = a.in(params.side, `outline.exports.${export_name}.side`, ['left', 'right', 'both', 'glue', 'raw'])
params.size = a.wh(params.size, `outline.exports.${export_name}.size`)
params.corner = a.sane(params.corner || 0, `outline.exports.${export_name}.corner`, 'number')
params.bevel = a.sane(params.bevel || 0, `outline.exports.${export_name}.bevel`, 'number')
a.detect_unexpected(params, `${export_name}`, ['side', 'size', 'corner', 'bevel'])
const side = a.in(params.side, `${export_name}.side`, ['left', 'right', 'middle', 'both', 'glue'])
const size = a.wh(params.size, `${export_name}.size`)
const corner = a.sane(params.corner || 0, `${export_name}.corner`, 'number')
const bevel = a.sane(params.bevel || 0, `${export_name}.bevel`, 'number')
let glue
if (['both', 'glue', 'raw'].includes(params.side)) {
let left = {paths: {}}
let right = {paths: {}}
if (['left', 'right', 'middle', 'both'].includes(params.side)) {
for (const [pname, p] of Object.entries(points)) {
let from_x = -size[0] / 2, to_x = size[0] / 2
let from_y = -size[1] / 2, to_y = size[1] / 2
let bind = a.trbl(p.meta.bind || 0, `${pname}.bind`)
// if it's a mirrored key, we swap the left and right bind values
if (p.meta.mirrored) {
bind = [bind[0], bind[3], bind[2], bind[1]]
}
from_x -= bind[3]
to_x += bind[1]
from_y -= bind[2]
to_y += bind[0]
let rect = rectangle(to_x - from_x, to_y - from_y, corner, bevel, `${export_name}.size`)
rect = m.model.move(rect, [from_x, from_y])
rect = p.position(rect)
if (p.meta.mirrored) {
right = m.model.combineUnion(right, rect)
} else {
left = m.model.combineUnion(left, rect)
}
}
}
if (params.side == 'left') return left
if (params.side == 'right') return right
let glue = {paths: {}}
if (['middle', 'both', 'glue'].includes(params.side)) {
const get_line = (anchor) => {
if (a.type(anchor) == 'number') {
@ -233,18 +135,15 @@ const parse_glue = exports._parse_glue = (config = {}, points = {}) => {
glue = u.poly(waypoints)
}
if (params.side == 'glue') return glue
}
}
let both = m.model.combineUnion(u.deepcopy(left), glue)
both = m.model.combineUnion(both, u.deepcopy(right))
if (params.side == 'both') return both
const parse_exports = exports._parse_exports = (config = {}, points = {}) => {
config = a.sane(config, 'outline.exports', 'object')
for (const [key, val] of Object.entries(config)) {
params.op = a.in(params.op || 'add', `outline.exports.${key}.op`, ['add', 'sub', 'diff'])
params.type = a.in(params.type, `outline.exports.${key}.type`, ['add', 'sub', 'diff'])
let middle = m.model.combineSubtraction(both, left)
middle = m.model.combineSubtraction(both, right)
return middle
}
}
@ -253,8 +152,13 @@ exports.parse = (config = {}, points = {}) => {
const glue = parse_glue(config.glue, points)
config = a.sane(config, 'outline.exports', 'object')
for (const [key, val] of Object.entries(config)) {
params.op = a.in(params.op || 'add', `outline.exports.${key}.op`, ['add', 'sub', 'diff'])
params.type = a.in(params.type, `outline.exports.${key}.type`, ['add', 'sub', 'diff'])
for (const [key, parts] of Object.entries(config)) {
let index = 0
for (const part of parts) {
const name = `outline.exports.${key}[${++index}]`
part.op = a.in(part.op || 'add', `${name}.op`, ['add', 'sub', 'diff'])
part.type = a.in(part.type, `${name}.type`, ['keys', 'rectangle', 'circle', 'polygon', 'ref'])
}
}
}