From 066e1a54ea8abc6a8da9a6be86a5469ec292915e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1n=20D=C3=A9nes?= Date: Sat, 4 Jul 2020 23:23:14 +0200 Subject: [PATCH] Still more outline progress --- README.md | 23 ++++++----- src/assert.js | 10 ++--- src/cli.js | 11 +++-- src/outline.js | 85 +++++++++++++++++++++++++++++++------- src/points.js | 10 +++-- test/fixtures/absolem.yaml | 55 +++++++++++++----------- 6 files changed, 132 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 6805488..d9a7bf8 100644 --- a/README.md +++ b/README.md @@ -154,12 +154,13 @@ That's where `column.rows` can help, specifying a row-override for just that col Easy. 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 -2. at the column-level -3. at the row-level -4. at the key-level +1. at the global-level (affecting all zones) +2. at the zone-level +3. at the column-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. 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. 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: @@ -201,6 +202,7 @@ Indeed: ```yaml points: zones: + key: rotate: num # default = 0 mirror: 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 - `radius: num` : the radius of the circle - `polygon` : an independent polygon primitive. Parameters: - - `ref`, `rotate`, and `shift` are the same as above - - `points: [[x, y], ...]` : the points of the polygon + - `points: [, ...]` : the points of the polygon. Each `` 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. - `ref` : a previously defined outline, see below. - `name: outline_name` : the name of the referenced outline @@ -394,7 +395,7 @@ Using these, we define exports as follows: ```yaml exports: my_name: - - op: add | sub | diff # default = add + - operation: add | subtract | intersect # default = add type: - ... @@ -481,13 +482,13 @@ case: extrude: num # default = 1 translate: [x, y, z] # 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. -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". diff --git a/src/assert.js b/src/assert.js index 0e908f1..b9d888d 100644 --- a/src/assert.js +++ b/src/assert.js @@ -38,9 +38,9 @@ const numarr = exports.numarr = (raw, name, length) => { 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] - return a.xy(raw, name) + return xy(raw, name) } exports.trbl = (raw, name) => { @@ -49,15 +49,15 @@ exports.trbl = (raw, name) => { 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']) - let a = new Point() + let a = default_point.clone() if (raw.ref !== undefined) { assert(points[raw.ref], `Unknown point reference "${raw.ref}" in anchor "${name}"!`) a = points[raw.ref].clone() } 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.y += xyval[1] } diff --git a/src/cli.js b/src/cli.js index b2c6eab..19e23ee 100644 --- a/src/cli.js +++ b/src/cli.js @@ -52,15 +52,20 @@ try { console.log('Parsing points...') const points = points_lib.parse(config.points) if (args.debug) { - fs.writeJSONSync(path.join(args.o, 'points.json'), points, {spaces: 4}) const rect = u.rect(18, 18, [-9, -9]) 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 -// 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 diff --git a/src/outline.js b/src/outline.js index 2d8b738..2747545 100644 --- a/src/outline.js +++ b/src/outline.js @@ -1,6 +1,7 @@ const m = require('makerjs') const u = require('./utils') const a = require('./assert') +const Point = require('./point') 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)!` @@ -19,7 +20,7 @@ const parse_glue = exports._parse_glue = (config = {}, points = {}) => { 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']) config[y].left = a.anchor(config[y].left, `outline.glue.${y}.left`, points) 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) - 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 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 left = {paths: {}} - let right = {paths: {}} - if (['left', 'right', 'middle', 'both'].includes(params.side)) { + let left = {models: {}} + let right = {models: {}} + if (['left', 'right', 'middle', 'both'].includes(side)) { for (const [pname, p] of Object.entries(points)) { 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 (params.side == 'right') return right + if (side == 'left') return left + if (side == 'right') return right - let glue = {paths: {}} - if (['middle', 'both', 'glue'].includes(params.side)) { + let glue = {models: {}} + if (['middle', 'both', 'glue'].includes(side)) { const get_line = (anchor) => { if (a.type(anchor) == 'number') { @@ -135,11 +136,11 @@ const parse_glue = exports._parse_glue = (config = {}, points = {}) => { glue = u.poly(waypoints) } - if (params.side == 'glue') return glue + if (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 + if (side == 'both') return both let middle = m.model.combineSubtraction(both, left) middle = m.model.combineSubtraction(both, right) @@ -151,14 +152,68 @@ exports.parse = (config = {}, points = {}) => { a.detect_unexpected(config, 'outline', ['glue', 'exports']) const glue = parse_glue(config.glue, points) - config = a.sane(config, 'outline.exports', 'object') - for (const [key, parts] of Object.entries(config)) { + const outlines = {} + + const ex = a.sane(config.exports, 'outline.exports', 'object') + for (const [key, parts] of Object.entries(ex)) { let index = 0 + let result = {models: {}} for (const part of parts) { 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.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 } \ No newline at end of file diff --git a/src/points.js b/src/points.js index b2c34c4..d454ad2 100644 --- a/src/points.js +++ b/src/points.js @@ -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 @@ -141,6 +141,7 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor) => { for (const row of Object.keys(actual_rows)) { const key = extend( default_key, + global_key, zone_wide_key, col.key, zone_wide_rows[row] || {}, @@ -198,16 +199,17 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor) => { exports.parse = (config = {}) => { - a.detect_unexpected(config, 'points', ['zones', 'rotate', 'mirror']) + a.detect_unexpected(config, 'points', ['zones', 'key', 'rotate', 'mirror']) let points = {} // getting original points 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)) { - const anchor = a.anchor(zone.anchor || new Point(), `points.zones.${zone_name}.anchor`, points) - points = Object.assign(points, render_zone(zone_name, zone, anchor)) + const anchor = a.anchor(zone.anchor || {}, `points.zones.${zone_name}.anchor`, points) + points = Object.assign(points, render_zone(zone_name, zone, anchor, global_key)) } // applying global rotation diff --git a/test/fixtures/absolem.yaml b/test/fixtures/absolem.yaml index 5eacb78..147f8f5 100644 --- a/test/fixtures/absolem.yaml +++ b/test/fixtures/absolem.yaml @@ -10,48 +10,48 @@ points: rows: bottom: home: - neighbors: [right] + bind: [,10] top: - neighbors: [right] + bind: [,10] ring: stagger: 12 rows: bottom: - neighbors: [left] + bind: [,,,10] home: - neighbors: [right] + bind: [,10] top: - neighbors: [right] + bind: [,10] middle: stagger: 5 rows: bottom: - neighbors: [both] + bind: [,10,,10] home: - neighbors: [both] + bind: [,10,,10] top: index: stagger: -6 rows: bottom: - neighbors: [right] + bind: [,10] home: - neighbors: [left] + bind: [,,,10] top: - neighbors: [left] + bind: [,,,10] inner: stagger: -2 rows: bottom: home: - neighbors: [left] + bind: [,,,10] top: - neighbors: [left] + bind: [,,,10] rows: bottom: - neighbors: [,up] + bind: [10] home: - neighbors: [,up] + bind: [10] top: thumbfan: anchor: @@ -64,46 +64,53 @@ points: origin: [9.5, -9] rows: thumb: - neighbors: [right] + bind: [,10] home: spread: 21.25 rotate: -28 origin: [11.75, -9] rows: thumb: - neighbors: [both] + bind: [,10,,10] far: rows: thumb: - neighbors: [left] + bind: [,,,10] rows: thumb: - neighbors: [,up] + bind: [10] + key: + bind: [0, 0, 0, 0] rotate: -20 mirror: ref: pinky_home distance: 223.7529778 outline: - bind: 10 glue: top: left: ref: inner_top - shift: [,.5] + shift: [, 0.5] right: ref: mirror_inner_top - shift: [,.5] + shift: [, 0.5] bottom: left: ref: far_thumb - shift: [.5] + shift: [0.5, 0] rotate: 90 right: ref: mirror_far_thumb - shift: [-.5] + shift: [-0.5, 0] rotate: 90 waypoints: - percent: 50 width: 100 - percent: 90 - width: 50 \ No newline at end of file + width: 50 + exports: + basic: + - type: keys + side: both + size: 18 + corner: .5 \ No newline at end of file