diff --git a/README.md b/README.md index 9aa3dd5..742a3e8 100644 --- a/README.md +++ b/README.md @@ -310,7 +310,9 @@ neighbors: [dir_x, dir_y] bind: num | [num_x, num_y] | [num_t, num_r, num_b, num_l] # default = 10 ``` -The former declares the directions we want to bind in, where `dir_x` can be one of `left`, `right`, or `both`; and `dir_y` can be one of `up`, `down`, or `both`. +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). 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). @@ -319,11 +321,11 @@ This is where the following section comes into play: ```yaml glue: top: - left: - right: | num + left: + right: | num bottom: - left: - right: | num + left: + right: | num waypoints: - percent: num width: num | [num_left, num_right] @@ -333,13 +335,12 @@ glue: - ... ``` -...where a `` looks like: +...where an `` is the same as it was for points: ```yaml ref: -shift: [x, y] -rotate: num -origin: [x, y] +shift: [x, y] # default = [0, 0] +rotate: num # default = 0 ``` The section's `top` and `bottom` are both formatted the same, and describe the center line's top and bottom intersections, respectively. @@ -359,6 +360,7 @@ If this is insufficient (maybe because it would leave holes), the `waypoints` ca Here, `percent` means the y coordinate along the centerline (going from the top intersection to the bottom intersection), and `width` means the offset on the x axis. If this is somehow _still_ insufficient (or there were problems with the binding phase), we can specify additional primitive shapes under the `extra` key (similarly to how we would use them in the exports; see below). +These are then added to what we have so far to finish out the glue.
@@ -376,7 +378,9 @@ Now we can configure what we want to "export" as outlines from this phase, given - `side: left | right` : the side we want - `glue` : just the glue, but the "ideal" version of it. This means that instead of the `glue` we defined above, we get `all` - `left` - `right`, so the _exact_ middle piece we would have needed to glue everything together. Parameters: - everything we could specify for `all` (since those are needed for the calculation) - - `side: left | right | both # default = both)` : optionally, we could choose only one side of the glue as well + - `raw: boolean # default = false)` : optionally, we could choose to get the "raw" (i.e., the non-idealized) glue as well +- `ref` : a previously defined outline, see below. + - `name: outline_name` : the name of the referenced outline Additionally, we can use primitive shapes: @@ -384,8 +388,7 @@ Additionally, we can use primitive shapes: - `ref: ` : what position and rotation to consider as the origin - `rotate: num` : extra rotation - `shift: [x, y]` : extra translation - - `width: num` : the width of the rectangle - - `height: num` : the height of the rectangle + - `size: num | [width, height]` : the size of the rectangle - `circle` : an independent circle primitive. Parameters: - `ref`, `rotate`, and `shift` are the same as above - `radius: num` : the radius of the circle @@ -405,7 +408,7 @@ exports: ``` Operations are performed in order, and the resulting shape is exported as an output. -Additionally, it is going to be available to further export declarations under the name specified (`my_name`, in this case). +Additionally, it is going to be available to further export declarations (using the `ref` type) under the name specified (`my_name`, in this case). If we only want to use it as a building block for further exports, we can start the name with an underscore (e.g., `_my_name`) to prevent it from being actually exported. diff --git a/src/assert.js b/src/assert.js index d25a008..5464b8d 100644 --- a/src/assert.js +++ b/src/assert.js @@ -24,16 +24,28 @@ const detect_unexpected = exports.detect_unexpected = (obj, name, expected) => { } } -const xy = exports.xy = (raw, name) => { - assert(type(raw) == 'array' && raw.length == 2, `Field "${name}" should be an array of length 2!`) - const x = raw[0] || 0 - const y = raw[1] || 0 - assert(type(x) == 'number' && type(y) == 'number', `Field "${name}" should contain numbers!`) - return {x, y} +const numarr = exports.numarr = (raw, name, length) => { + assert(type(raw) == 'array' && raw.length == length, `Field "${name}" should be an array of length ${length}!`) + raw = raw.map(val => val || 0) + raw.map(val => assert(type(val) == 'number', `Field "${name}" should contain numbers!`)) + return raw } -exports.anchor = (raw, name, points={}) => { - detect_unexpected(raw, name, ['ref', 'shift', 'rotate']) +const xy = exports.xy = (raw, name) => numarr(raw, name, 2) + +exports.wh = (raw, name) => { + if (!Array.isArray(raw)) raw = [raw, raw] + return a.xy(raw, name) +} + +exports.trbl = (raw, name) => { + if (!Array.isArray(raw)) raw = [raw, raw, raw, raw] + if (raw.length == 2) raw = [raw[1], raw[0], raw[1], raw[0]] + return numarr(raw, name, 4) +} + +exports.anchor = (raw, name, points={}, check_unexpected=true) => { + if (check_unexpected) detect_unexpected(raw, name, ['ref', 'shift', 'rotate']) let a = new Point() if (raw.ref !== undefined) { assert(points[raw.ref], `Unknown point reference "${raw.ref}" in anchor "${name}"!`) @@ -41,8 +53,8 @@ exports.anchor = (raw, name, points={}) => { } if (raw.shift !== undefined) { const xyval = xy(raw.shift, name + '.shift') - a.x += xyval.x - a.y += xyval.y + a.x += xyval[0] + a.y += xyval[1] } if (raw.rotate !== undefined) { a.r += sane(raw.rotate || 0, name + '.rotate', 'number') diff --git a/src/cli.js b/src/cli.js index 50dba27..b2c6eab 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,33 +1,26 @@ #!/usr/bin/env node -const m = require('makerjs') +// libs + const fs = require('fs-extra') const path = require('path') const yaml = require('js-yaml') const yargs = require('yargs') +// internals + const u = require('./utils') +const io = require('./io') const points_lib = require('./points') -// const outline_lib = require('./outline') +const outline_lib = require('./outline') -const dump_model = (model, file='model') => { - const assembly = m.model.originate({ - models: u.deepcopy(model), - units: 'mm' - }) - - fs.mkdirpSync(path.dirname(`${file}.dxf`)) - fs.writeFileSync(`${file}.dxf`, m.exporter.toDXF(assembly)) - if (args.debug) { - fs.writeJSONSync(`${file}.json`, assembly, {spaces: 4}) - } -} +// command line args const args = yargs .option('config', { alias: 'c', demandOption: true, - describe: 'Config yaml file', + describe: 'Config yaml/json file', type: 'string' }) .option('output', { @@ -37,8 +30,9 @@ const args = yargs type: 'string' }) .option('debug', { + alias: 'd', default: false, - hidden: true, + describe: 'Debug mode', type: 'boolean' }) .argv @@ -46,15 +40,28 @@ const args = yargs fs.mkdirpSync(args.o) const config_parser = args.c.endsWith('.yaml') ? yaml.load : JSON.parse -const config = config_parser(fs.readFileSync(args.c).toString()) +let config +try { + config = config_parser(fs.readFileSync(args.c).toString()) +} catch (err) { + throw new Error(`Malformed input "${args.c}": ${err}`) +} +// points + +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 size = 14 - const rect = u.rect(size, size, [-size/2, -size/2]) - const points_demo = outline_lib.layout(points, rect) - dump_model(points_demo, path.join(args.o, 'points_demo')) + 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) } +// outlines + +// console.log('Generating outlines...') + +// goodbye + console.log('Done.') diff --git a/src/io.js b/src/io.js new file mode 100644 index 0000000..5de648d --- /dev/null +++ b/src/io.js @@ -0,0 +1,18 @@ +const m = require('makerjs') +const fs = require('fs-extra') +const path = require('path') + +const u = require('./utils') + +exports.dump_model = (model, file='model', debug=false) => { + const assembly = m.model.originate({ + models: u.deepcopy(model), + units: 'mm' + }) + + fs.mkdirpSync(path.dirname(`${file}.dxf`)) + fs.writeFileSync(`${file}.dxf`, m.exporter.toDXF(assembly)) + if (debug) { + fs.writeJSONSync(`${file}.json`, assembly, {spaces: 4}) + } +} \ No newline at end of file diff --git a/src/outline.js b/src/outline.js index a547eb9..09aa38d 100644 --- a/src/outline.js +++ b/src/outline.js @@ -1,20 +1,14 @@ const m = require('makerjs') const u = require('./utils') - -const layout = exports.layout = (points, shape) => { - const shapes = {} - for (const [pname, p] of Object.entries(points)) { - shapes[pname] = p.position(u.deepcopy(shape)) - } - return {layout: {models: shapes}} -} +const a = require('./assert') const outline = exports._outline = (points, config={}) => params => { - let size = params.size || [18, 18] + 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 @@ -142,3 +136,39 @@ const outline = exports._outline = (points, config={}) => params => { 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`) +} + + + + + + + + + +const parse_glue = exports._parse_glue = (config = {}, points = {}) => { + + a.detect_unexpected(config, 'outline.glue', ['top', 'bottom', 'waypoints', 'extra']) + + for (const y in ['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') { + config[y].right = a.anchor(config[y].right, `outline.glue.${y}.right`, points) + } + } + + + + config.bottom = a.sane(config.bottom, 'outline.glue.bottom', 'object') + config.bottom.left = a.anchor(config.bottom.left, 'outline.glue.bottom.left', points) + if (a.type(config.bottom.right) != 'number') { + config.bottom.right = a.anchor(config.bottom.right, 'outline.glue.bottom.right', points) + } + +} + +exports.parse = (config = {}, points = {}) => { + a.detect_unexpected(config, 'outline', ['glue', 'exports']) + const glue = parse_glue(config.glue, points) +} \ No newline at end of file diff --git a/src/points.js b/src/points.js index 8a78a26..b2c34c4 100644 --- a/src/points.js +++ b/src/points.js @@ -8,7 +8,7 @@ const extend_pair = exports._extend_pair = (to, from) => { if (from === undefined || from === null) return to if (to_type != from_type) return from if (from_type == 'object') { - const res = {} + const res = u.deepcopy(to) for (const key of Object.keys(from)) { res[key] = extend_pair(to[key], from[key]) } @@ -147,16 +147,6 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor) => { col.rows[row] || {} ) - require('fs-extra').writeJSONSync('arst.json', { - default_key, - zone_wide_key, - col_key: col.key, - zone_wide_rows: zone_wide_rows[row] || {}, - col_rows: col.rows[row] || {}, - result: key - }, {spaces: 4}) - throw 28 - key.name = key.name || `${col_name}_${row}` key.shift = a.xy(key.shift, `${key.name}.shift`) key.rotate = a.sane(key.rotate, `${key.name}.rotate`, 'number') @@ -206,7 +196,9 @@ const render_zone = exports._render_zone = (zone_name, zone, anchor) => { -exports.parse = (config) => { +exports.parse = (config = {}) => { + + a.detect_unexpected(config, 'points', ['zones', 'rotate', 'mirror']) let points = {} @@ -263,4 +255,12 @@ exports.parse = (config) => { } return filtered +} + +exports.position = (points, shape) => { + const shapes = {} + for (const [pname, p] of Object.entries(points)) { + shapes[pname] = p.position(u.deepcopy(shape)) + } + return {layout: {models: shapes}} } \ No newline at end of file