1
0

Still more outline progress

This commit is contained in:
Bán Dénes 2020-07-04 23:23:14 +02:00
parent 999a5dfad8
commit 066e1a54ea
6 changed files with 132 additions and 62 deletions

View File

@ -154,12 +154,13 @@ That's where `column.rows` can help, specifying a row-override for just that col
Easy. Easy.
Now for the trickier part: keys. Now for the trickier part: keys.
There are four ways to set key-related options (again, to minimize the need for repetition): There are five ways to set key-related options (again, to minimize the need for repetition):
1. at the zone-level 1. at the global-level (affecting all zones)
2. at the column-level 2. at the zone-level
3. at the row-level 3. at the column-level
4. at the key-level 4. at the row-level
5. at the key-level
These "extend" each other in this order so by the time we reach a specific key, every level had an opportunity to modify something. These "extend" each other in this order so by the time we reach a specific key, every level had an opportunity to modify something.
Note that unlike the overriding for rows, key-related extension is additive. Note that unlike the overriding for rows, key-related extension is additive.
@ -172,7 +173,7 @@ But if `key = {a: 1}` is extended by `key = {b: 2}`, the result is `key = {a: 1,
Lastly, while there are a few key-specific attributes that have special meaning in the context of points (listed below), any key with any data can be specified here. Lastly, while there are a few key-specific attributes that have special meaning in the context of points (listed below), any key with any data can be specified here.
This can be useful for storing arbitrary meta-info about the keys, or just configuring later stages with key-level parameters. This can be useful for storing arbitrary meta-info about the keys, or just configuring later stages with key-level parameters.
So, for example, when the outline phase specifies `bind` as a key-level parameter (see below), it means that the global value can be extended just like any other key-level attribute. So, for example, when the outline phase specifies `bind` as a key-level parameter (see below), it means that it can be specified just like any other key-level attribute.
Now for the "official" key-level attributes: Now for the "official" key-level attributes:
@ -201,6 +202,7 @@ Indeed:
```yaml ```yaml
points: points:
zones: <what we talked about so far...> zones: <what we talked about so far...>
key: <global key def>
rotate: num # default = 0 rotate: num # default = 0
mirror: mirror:
axis: num # default = 0 axis: num # default = 0
@ -384,8 +386,7 @@ Now we can configure what we want to "export" as outlines from this phase, given
- `ref`, `rotate`, and `shift` are the same as above - `ref`, `rotate`, and `shift` are the same as above
- `radius: num` : the radius of the circle - `radius: num` : the radius of the circle
- `polygon` : an independent polygon primitive. Parameters: - `polygon` : an independent polygon primitive. Parameters:
- `ref`, `rotate`, and `shift` are the same as above - `points: [<point_def>, ...]` : the points of the polygon. Each `<point_def>` can have its own `ref` and `shift`, all of which are still the same as above. If `ref` is unspecified, the previous point's will be assumed. For the first, it's `[0, 0]` by default.
- `points: [[x, y], ...]` : the points of the polygon
- `ref` : a previously defined outline, see below. - `ref` : a previously defined outline, see below.
- `name: outline_name` : the name of the referenced outline - `name: outline_name` : the name of the referenced outline
@ -394,7 +395,7 @@ Using these, we define exports as follows:
```yaml ```yaml
exports: exports:
my_name: my_name:
- op: add | sub | diff # default = add - operation: add | subtract | intersect # default = add
type: <one of the types> type: <one of the types>
<type-specific params> <type-specific params>
- ... - ...
@ -481,13 +482,13 @@ case:
extrude: num # default = 1 extrude: num # default = 1
translate: [x, y, z] # default = [0, 0, 0] translate: [x, y, z] # default = [0, 0, 0]
rotate: [ax, ay, az] # default = [0, 0, 0] rotate: [ax, ay, az] # default = [0, 0, 0]
op: add | sub | diff # default = add operation: add | subtract | intersect # default = add
- ... - ...
... ...
``` ```
`outline` specifies which outline to import onto the xy plane, while `extrude` specifies how much it should be extruded along the z axis. `outline` specifies which outline to import onto the xy plane, while `extrude` specifies how much it should be extruded along the z axis.
After that, the object is `translate`d, `rotate`d, and combined with what we have so far according to `op`. After that, the object is `translate`d, `rotate`d, and combined with what we have so far according to `operation`.
If we only want to use an object as a building block for further objects, we can employ the same "start with an underscore" trick we learned at the outlines section to make it "private". If we only want to use an object as a building block for further objects, we can employ the same "start with an underscore" trick we learned at the outlines section to make it "private".

View File

@ -38,9 +38,9 @@ const numarr = exports.numarr = (raw, name, length) => {
const xy = exports.xy = (raw, name) => numarr(raw, name, 2) const xy = exports.xy = (raw, name) => numarr(raw, name, 2)
exports.wh = (raw, name) => { const wh = exports.wh = (raw, name) => {
if (!Array.isArray(raw)) raw = [raw, raw] if (!Array.isArray(raw)) raw = [raw, raw]
return a.xy(raw, name) return xy(raw, name)
} }
exports.trbl = (raw, name) => { exports.trbl = (raw, name) => {
@ -49,15 +49,15 @@ exports.trbl = (raw, name) => {
return numarr(raw, name, 4) return numarr(raw, name, 4)
} }
exports.anchor = (raw, name, points={}, check_unexpected=true) => { exports.anchor = (raw, name, points={}, check_unexpected=true, default_point=new Point()) => {
if (check_unexpected) detect_unexpected(raw, name, ['ref', 'shift', 'rotate']) if (check_unexpected) detect_unexpected(raw, name, ['ref', 'shift', 'rotate'])
let a = new Point() let a = default_point.clone()
if (raw.ref !== undefined) { if (raw.ref !== undefined) {
assert(points[raw.ref], `Unknown point reference "${raw.ref}" in anchor "${name}"!`) assert(points[raw.ref], `Unknown point reference "${raw.ref}" in anchor "${name}"!`)
a = points[raw.ref].clone() a = points[raw.ref].clone()
} }
if (raw.shift !== undefined) { if (raw.shift !== undefined) {
const xyval = xy(raw.shift, name + '.shift') const xyval = wh(raw.shift || [0, 0], name + '.shift')
a.x += xyval[0] a.x += xyval[0]
a.y += xyval[1] a.y += xyval[1]
} }

View File

@ -52,15 +52,20 @@ try {
console.log('Parsing points...') console.log('Parsing points...')
const points = points_lib.parse(config.points) const points = points_lib.parse(config.points)
if (args.debug) { if (args.debug) {
fs.writeJSONSync(path.join(args.o, 'points.json'), points, {spaces: 4})
const rect = u.rect(18, 18, [-9, -9]) const rect = u.rect(18, 18, [-9, -9])
const points_demo = points_lib.position(points, rect) const points_demo = points_lib.position(points, rect)
io.dump_model(points_demo, path.join(args.o, 'points_demo'), args.debug) io.dump_model(points_demo, path.join(args.o, 'points/points_demo'), args.debug)
fs.writeJSONSync(path.join(args.o, 'points/points.json'), points, {spaces: 4})
} }
// outlines // outlines
// console.log('Generating outlines...') console.log('Generating outlines...')
const outlines = outline_lib.parse(config.outline, points)
for (const [name, outline] of Object.entries(outlines)) {
if (!args.debug && name.startsWith('_')) continue
io.dump_model(outline, path.join(args.o, `outline/${name}`), args.debug)
}
// goodbye // goodbye

View File

@ -1,6 +1,7 @@
const m = require('makerjs') const m = require('makerjs')
const u = require('./utils') const u = require('./utils')
const a = require('./assert') const a = require('./assert')
const Point = require('./point')
const rectangle = (w, h, corner, bevel, name='') => { 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 error = (dim, val) => `Rectangle for "${name}" isn't ${dim} enough for its corner and bevel (${val} - ${corner} - ${bevel} <= 0)!`
@ -19,7 +20,7 @@ const parse_glue = exports._parse_glue = (config = {}, points = {}) => {
a.detect_unexpected(config, 'outline.glue', ['top', 'bottom', 'waypoints', 'extra']) a.detect_unexpected(config, 'outline.glue', ['top', 'bottom', 'waypoints', 'extra'])
for (const y in ['top', 'bottom']) { for (const y of ['top', 'bottom']) {
a.detect_unexpected(config[y], `outline.glue.${y}`, ['left', 'right']) a.detect_unexpected(config[y], `outline.glue.${y}`, ['left', 'right'])
config[y].left = a.anchor(config[y].left, `outline.glue.${y}.left`, points) config[y].left = a.anchor(config[y].left, `outline.glue.${y}.left`, points)
if (a.type(config[y].right) != 'number') { if (a.type(config[y].right) != 'number') {
@ -39,17 +40,17 @@ const parse_glue = exports._parse_glue = (config = {}, points = {}) => {
// TODO: handle glue.extra (or revoke it from the docs) // TODO: handle glue.extra (or revoke it from the docs)
return (export_name, params) => { return (params, export_name, expected) => {
a.detect_unexpected(params, `${export_name}`, ['side', 'size', 'corner', 'bevel']) a.detect_unexpected(params, `${export_name}`, expected.concat(['side', 'size', 'corner', 'bevel']))
const side = a.in(params.side, `${export_name}.side`, ['left', 'right', 'middle', 'both', 'glue']) const side = a.in(params.side, `${export_name}.side`, ['left', 'right', 'middle', 'both', 'glue'])
const size = a.wh(params.size, `${export_name}.size`) const size = a.wh(params.size, `${export_name}.size`)
const corner = a.sane(params.corner || 0, `${export_name}.corner`, 'number') const corner = a.sane(params.corner || 0, `${export_name}.corner`, 'number')
const bevel = a.sane(params.bevel || 0, `${export_name}.bevel`, 'number') const bevel = a.sane(params.bevel || 0, `${export_name}.bevel`, 'number')
let left = {paths: {}} let left = {models: {}}
let right = {paths: {}} let right = {models: {}}
if (['left', 'right', 'middle', 'both'].includes(params.side)) { if (['left', 'right', 'middle', 'both'].includes(side)) {
for (const [pname, p] of Object.entries(points)) { for (const [pname, p] of Object.entries(points)) {
let from_x = -size[0] / 2, to_x = size[0] / 2 let from_x = -size[0] / 2, to_x = size[0] / 2
@ -77,11 +78,11 @@ const parse_glue = exports._parse_glue = (config = {}, points = {}) => {
} }
} }
} }
if (params.side == 'left') return left if (side == 'left') return left
if (params.side == 'right') return right if (side == 'right') return right
let glue = {paths: {}} let glue = {models: {}}
if (['middle', 'both', 'glue'].includes(params.side)) { if (['middle', 'both', 'glue'].includes(side)) {
const get_line = (anchor) => { const get_line = (anchor) => {
if (a.type(anchor) == 'number') { if (a.type(anchor) == 'number') {
@ -135,11 +136,11 @@ const parse_glue = exports._parse_glue = (config = {}, points = {}) => {
glue = u.poly(waypoints) glue = u.poly(waypoints)
} }
if (params.side == 'glue') return glue if (side == 'glue') return glue
let both = m.model.combineUnion(u.deepcopy(left), glue) let both = m.model.combineUnion(u.deepcopy(left), glue)
both = m.model.combineUnion(both, u.deepcopy(right)) both = m.model.combineUnion(both, u.deepcopy(right))
if (params.side == 'both') return both if (side == 'both') return both
let middle = m.model.combineSubtraction(both, left) let middle = m.model.combineSubtraction(both, left)
middle = m.model.combineSubtraction(both, right) middle = m.model.combineSubtraction(both, right)
@ -151,14 +152,68 @@ exports.parse = (config = {}, points = {}) => {
a.detect_unexpected(config, 'outline', ['glue', 'exports']) a.detect_unexpected(config, 'outline', ['glue', 'exports'])
const glue = parse_glue(config.glue, points) const glue = parse_glue(config.glue, points)
config = a.sane(config, 'outline.exports', 'object') const outlines = {}
for (const [key, parts] of Object.entries(config)) {
const ex = a.sane(config.exports, 'outline.exports', 'object')
for (const [key, parts] of Object.entries(ex)) {
let index = 0 let index = 0
let result = {models: {}}
for (const part of parts) { for (const part of parts) {
const name = `outline.exports.${key}[${++index}]` const name = `outline.exports.${key}[${++index}]`
part.op = a.in(part.op || 'add', `${name}.op`, ['add', 'sub', 'diff']) const expected = ['type', 'operation']
part.type = a.in(part.type, `${name}.type`, ['keys', 'rectangle', 'circle', 'polygon', 'ref']) part.type = a.in(part.type, `${name}.type`, ['keys', 'rectangle', 'circle', 'polygon', 'ref'])
part.operation = a.in(part.operation || 'add', `${name}.operation`, ['add', 'subtract', 'intersect'])
let op = m.model.combineUnion
if (part.operation == 'subtract') op = m.model.combineSubtraction
else if (part.operation == 'intersect') op = m.model.combineIntersection
let arg
let anchor
switch (part.type) {
case 'keys':
arg = glue(part, name, expected)
break
case 'rectangle':
a.detect_unexpected(part, name, expected.concat(['ref', 'shift', 'rotate', 'size', 'corner', 'bevel']))
anchor = a.anchor(part, name, points, false)
const size = a.wh(part.size, `${name}.size`)
const corner = a.sane(part.corner || 0, `${name}.corner`, 'number')
const bevel = a.sane(part.bevel || 0, `${name}.bevel`, 'number')
arg = rectangle(size[0], size[1], corner, bevel, name)
arg = m.model.move(arg, [-size[0]/2, -size[1]/2]) // center
arg = anchor.position(arg)
break
case 'circle':
a.detect_unexpected(part, name, expected.concat(['ref', 'shift', 'rotate', 'radius']))
anchor = a.anchor(part, name, points, false)
const radius = a.sane(part.radius, `${name}.radius`, 'number')
arg = u.circle(anchor.p, radius)
break
case 'polygon':
a.detect_unexpected(part, name, expected.concat(['points']))
const poly_points = a.sane(part.points, `${name}.points`, 'array')
const parsed_points = []
let last_anchor = new Point()
let poly_index = 0
for (const poly_point of poly_points) {
const poly_name = `${name}.points[${++poly_index}]`
const anchor = a.anchor(point, point_name, points, true, last_anchor)
parsed_points.push(anchor.p)
}
arg = u.poly(parsed_points)
break
case 'ref':
a.assert(outlines[part.name], `Field "${name}.name" does not name an existing outline!`)
arg = outlines[part.name]
break
}
result = op(result, arg)
} }
outlines[key] = result
} }
return outlines
} }

View File

@ -42,7 +42,7 @@ const push_rotation = exports._push_rotation = (list, angle, origin) => {
}) })
} }
const render_zone = exports._render_zone = (zone_name, zone, anchor) => { const render_zone = exports._render_zone = (zone_name, zone, anchor, global_key) => {
// zone-wide sanitization // zone-wide sanitization
@ -141,6 +141,7 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor) => {
for (const row of Object.keys(actual_rows)) { for (const row of Object.keys(actual_rows)) {
const key = extend( const key = extend(
default_key, default_key,
global_key,
zone_wide_key, zone_wide_key,
col.key, col.key,
zone_wide_rows[row] || {}, zone_wide_rows[row] || {},
@ -198,16 +199,17 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor) => {
exports.parse = (config = {}) => { exports.parse = (config = {}) => {
a.detect_unexpected(config, 'points', ['zones', 'rotate', 'mirror']) a.detect_unexpected(config, 'points', ['zones', 'key', 'rotate', 'mirror'])
let points = {} let points = {}
// getting original points // getting original points
const zones = a.sane(config.zones || {}, 'points.zones', 'object') const zones = a.sane(config.zones || {}, 'points.zones', 'object')
const global_key = a.sane(config.key || {}, 'points.key', 'object')
for (const [zone_name, zone] of Object.entries(zones)) { for (const [zone_name, zone] of Object.entries(zones)) {
const anchor = a.anchor(zone.anchor || new Point(), `points.zones.${zone_name}.anchor`, points) const anchor = a.anchor(zone.anchor || {}, `points.zones.${zone_name}.anchor`, points)
points = Object.assign(points, render_zone(zone_name, zone, anchor)) points = Object.assign(points, render_zone(zone_name, zone, anchor, global_key))
} }
// applying global rotation // applying global rotation

View File

@ -10,48 +10,48 @@ points:
rows: rows:
bottom: bottom:
home: home:
neighbors: [right] bind: [,10]
top: top:
neighbors: [right] bind: [,10]
ring: ring:
stagger: 12 stagger: 12
rows: rows:
bottom: bottom:
neighbors: [left] bind: [,,,10]
home: home:
neighbors: [right] bind: [,10]
top: top:
neighbors: [right] bind: [,10]
middle: middle:
stagger: 5 stagger: 5
rows: rows:
bottom: bottom:
neighbors: [both] bind: [,10,,10]
home: home:
neighbors: [both] bind: [,10,,10]
top: top:
index: index:
stagger: -6 stagger: -6
rows: rows:
bottom: bottom:
neighbors: [right] bind: [,10]
home: home:
neighbors: [left] bind: [,,,10]
top: top:
neighbors: [left] bind: [,,,10]
inner: inner:
stagger: -2 stagger: -2
rows: rows:
bottom: bottom:
home: home:
neighbors: [left] bind: [,,,10]
top: top:
neighbors: [left] bind: [,,,10]
rows: rows:
bottom: bottom:
neighbors: [,up] bind: [10]
home: home:
neighbors: [,up] bind: [10]
top: top:
thumbfan: thumbfan:
anchor: anchor:
@ -64,46 +64,53 @@ points:
origin: [9.5, -9] origin: [9.5, -9]
rows: rows:
thumb: thumb:
neighbors: [right] bind: [,10]
home: home:
spread: 21.25 spread: 21.25
rotate: -28 rotate: -28
origin: [11.75, -9] origin: [11.75, -9]
rows: rows:
thumb: thumb:
neighbors: [both] bind: [,10,,10]
far: far:
rows: rows:
thumb: thumb:
neighbors: [left] bind: [,,,10]
rows: rows:
thumb: thumb:
neighbors: [,up] bind: [10]
key:
bind: [0, 0, 0, 0]
rotate: -20 rotate: -20
mirror: mirror:
ref: pinky_home ref: pinky_home
distance: 223.7529778 distance: 223.7529778
outline: outline:
bind: 10
glue: glue:
top: top:
left: left:
ref: inner_top ref: inner_top
shift: [,.5] shift: [, 0.5]
right: right:
ref: mirror_inner_top ref: mirror_inner_top
shift: [,.5] shift: [, 0.5]
bottom: bottom:
left: left:
ref: far_thumb ref: far_thumb
shift: [.5] shift: [0.5, 0]
rotate: 90 rotate: 90
right: right:
ref: mirror_far_thumb ref: mirror_far_thumb
shift: [-.5] shift: [-0.5, 0]
rotate: 90 rotate: 90
waypoints: waypoints:
- percent: 50 - percent: 50
width: 100 width: 100
- percent: 90 - percent: 90
width: 50 width: 50
exports:
basic:
- type: keys
side: both
size: 18
corner: .5