diff --git a/.gitignore b/.gitignore index 5b1c675..d70ba49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -*.dxf \ No newline at end of file +*.dxf +*.json \ No newline at end of file diff --git a/absolem.js b/absolem.js index 60b413e..d09a49d 100644 --- a/absolem.js +++ b/absolem.js @@ -3,6 +3,9 @@ const path = require('path') const yaml = require('js-yaml') const yargs = require('yargs') +const points_lib = require('./helpers/points') +const outline_lib = require('./helpers/outline') + const args = yargs .option('config', { alias: 'c', @@ -16,8 +19,13 @@ const args = yargs describe: 'Output folder', type: 'string' }) - .option('outline', { + .option('debug', { default: false, + hidden: true, + type: 'boolean' + }) + .option('outline', { + default: true, describe: 'Generate 2D outlines', type: 'boolean' }) @@ -34,12 +42,20 @@ const args = yargs .argv if (!args.outline && !args.pcb && !args.case) { - yargs.showHelp("log") + yargs.showHelp('log') console.log('Nothing to do...') process.exit(0) } const config = yaml.load(fs.readFileSync(args.c).toString()) -const points = require('./helpers/points').parse(config) +const points = points_lib.parse(config) -console.log(points) \ No newline at end of file +if (args.debug) { + points_lib.dump(points) +} + +// if (args.outline) { +// outline_lib.draw(points, config) +// } + +console.log('Done.') \ No newline at end of file diff --git a/config/regular.yaml b/config/regular.yaml index feb94bc..b790a1f 100644 --- a/config/regular.yaml +++ b/config/regular.yaml @@ -1,39 +1,56 @@ -anchor: - angle: -5 -columns: - - name: pinky - rotate: 5 - origin: [7, -7] - - name: ring - stagger: 12 - - name: middle - stagger: 5 - - name: index - stagger: -6 - - name: inner - stagger: -2 -rows: - - name: bottom - - name: home - - name: top -thumbfan: - - name: palmward - anchor: +zones: + matrix: + anchor: + angle: 5 + columns: + - name: pinky + rotate: -5 + origin: [7, -7] + - name: ring + stagger: 12 + - name: middle + stagger: 5 + - name: index + stagger: -6 + - name: inner + stagger: -2 + rows: + - name: bottom + - name: home + - name: top + thumbfan: + anchor: ref: inner_bottom shift: [-7, -19] - column: ring - padding: 21.25 - rotate: 28 - origin: [9.5, -9] - - name: home - column: middle - padding: 21.25 - rotate: 28 - origin: [11.75, -9] - - name: outward - column: index -angle: 20 + columns: + - name: near + column_wire: ring + padding: 21.25 + rotate: -28 + origin: [9.5, -9] + - name: home + column_wire: middle + padding: 21.25 + rotate: -28 + origin: [11.75, -9] + - name: far + column_wire: index + rows: + - name: thumb +angle: -20 mirror: - column: pinky - row: bottom - distance: 250 \ No newline at end of file + ref: pinky_bottom + # The mk1's origin was the bottom left corner of the bottom pinky key. + # But it later got rotated by the bottom *right* corner as the pinky angle + # and then rotated again for the inter-half angle so [0, 0] was nowhere on + # the actual result. + # + # Since the new origin is the center of the pinky bottom, we have to convert + # the old, round 250 width to this new coordinate system if we want backward + # compatibility. The following snippet was used to arrive at 233.5881016. + # + # old_origin = new Point(7, 7) + # old_origin.rotate(5, [14, 0]) + # old_origin.rotate(-20, [0, 0]) + # new_width = 250 - (2 * old_origin.x) + distance: 233.5881016 \ No newline at end of file diff --git a/config/squished.yaml b/config/squished.yaml new file mode 100644 index 0000000..0c47cd0 --- /dev/null +++ b/config/squished.yaml @@ -0,0 +1,79 @@ +zones: + matrix: + anchor: + angle: 5 + columns: + - name: pinky + rotate: -5 + origin: [7, -7] + - name: ring + stagger: 12 + - name: middle + stagger: 5 + - name: index + stagger: -6 + padding: 16 + - name: inner + stagger: -2 + rows: + - name: bottom + padding: 16 + - name: home + padding: 16 + - name: top + thumbfan: + anchor: + ref: inner_bottom + shift: [-4, -22] + columns: + - name: wrong_near + padding: 21.25 + rotate: -28 + origin: [9.5, -9] + skip: yes + - name: home + column_wire: middle + rotate: -28 + origin: [9.5, -9] + - name: far + column_wire: index + rows: + - name: thumb + thumbfan_reverse_pass: + anchor: + ref: home_thumb + reverse: yes + columns: + - name: home_again + rotate: 28 + origin: [-9.5, -9] + skip: yes + - name: near + padding: 16 + column_wire: ring + # TODO: this is just a test for Dan + - name: nearer + column_wire: pinky + rows: + - name: thumb +angle: -20 +mirror: + ref: pinky_bottom + # The mk1's origin was the bottom left corner of the bottom pinky key. + # But it later got rotated by the bottom *right* corner as the pinky angle + # and then rotated again for the inter-half angle so [0, 0] was nowhere on + # the actual result. + # + # Since the new origin is the center of the pinky bottom, we have to convert + # the old, round 250 width to this new coordinate system if we want backward + # compatibility. The following snippet was used to arrive at 233.5881016. + # + # old_origin = new Point(7, 7) + # old_origin.rotate(5, [14, 0]) + # old_origin.rotate(-20, [0, 0]) + # new_width = 250 - (2 * old_origin.x) + # + # TODO: Hahaaa, the squishing fails, because the width reference points are + # NOT stationary! gotta move to distances between some other points + # (definitely something from among the HOME positions!!!) + distance: 233.5881016 \ No newline at end of file diff --git a/helpers/outline.js b/helpers/outline.js new file mode 100644 index 0000000..4faf6b8 --- /dev/null +++ b/helpers/outline.js @@ -0,0 +1,20 @@ +const m = require('makerjs') +const fs = require('fs-extra') + +const outline = (points, radius, expansion=5, patches=[]) => { + +} + +exports.draw = (points, config) => { + const lefts = {} + const rights = {} + for (const [k, p] of Object.entries(points)) { + if (p.meta.mirrored) { + rights[k] = p + } else { + lefts[k] = p + } + } + + +} \ No newline at end of file diff --git a/helpers/points.js b/helpers/points.js index e8cc15c..9b9d0c6 100644 --- a/helpers/points.js +++ b/helpers/points.js @@ -1,91 +1,61 @@ const m = require('makerjs') const fs = require('fs-extra') +const Point = exports.Point = class Point { + constructor(x=0, y=0, r=0, meta={}) { + if (Array.isArray(x)) { + this.x = x[0] + this.y = x[1] + this.r = 0 + this.meta = {} + } else { + this.x = x + this.y = y + this.r = r + this.meta = meta + } + } + get p() { + return [this.x, this.y] + } + set p(val) { + [this.x, this.y] = val + } -// TODO: refactor this to use the new config format + add(a) { + const res = this.clone() + res.x += a[0] + res.y += a[1] + return res + } -// const column = (func, col) => ({ -// models: { -// bottom: up(func(col, 'bottom'), 0 * (side + padding)), -// middle: up(func(col, 'middle'), 1 * (side + padding)), -// top: up(func(col, 'top'), 2 * (side + padding)) -// } -// }) + shift(s) { + this.x += s[0] + this.y += s[1] + return this + } -// const matrix = (func) => { -// const models = {} -// let i = 0 -// let sum = 0 -// for (const {name, shift} of columns) { -// let col = column(func, name) -// sum += shift -// if (name == 'pinky') { -// col = rotate(col, pinky_angle, [side, 0]) -// } -// col = up(col, sum) -// col = right(col, i * (side + padding)) -// models[name] = col -// i++ -// } -// return {models} -// } + rotate(angle, origin=[0, 0]) { + this.p = m.point.rotate(this.p, angle, origin) + this.r += angle + return this + } -// const thumbfan = (func) => ({ -// models: { -// inner: func('thumb', 'inner'), -// home: rotate( -// right( -// func('thumb', 'home'), -// side + padding + kc_diff -// ), -// thumb_angle, -// [side + padding / 2, -overhang] -// ), -// outer: rotate( -// right( -// rotate( -// right( -// func('thumb', 'outer'), -// side + padding + kc_diff -// ), -// thumb_angle, -// [side + padding / 2 + kc_diff, -overhang] -// ), -// side + padding + kc_diff -// ), -// thumb_angle, -// [side + padding / 2, -overhang] -// ) -// } -// }) + mirror(x) { + this.x = 2 * x - this.x + this.r = 180 - this.r + return this + } -// const half = (func) => { -// const result = { -// models: { -// matrix: matrix(func), -// thumbfan: move( -// thumbfan(func), -// [ -// 3 * (side + padding) + thumb_start, -// -(side + padding) + staggers_sum -// ] -// ) -// } -// } -// return m.model.rotate(result, half_angle) -// } - - -// TODO temp -let _debug_key = 0 -const _debug = {} -const debug = (xy) => { - _debug[_debug_key++] = { - type: 'circle', - origin: [xy[0], xy[1]], - radius: 1 + clone() { + return new Point( + this.x, + this.y, + this.r, + this.meta + ) } } @@ -109,39 +79,15 @@ const dump = exports.dump = (points, opts={}) => { models[key] = m.model.moveRelative(m.model.rotate({paths}, point.r), point.p) } - // TODO - models['debug'] = {paths: _debug} - const assembly = m.model.originate({ models, units: 'mm' }) + fs.writeFileSync(`${opts.file || 'dump'}.json`, JSON.stringify(points, null, ' ')) fs.writeFileSync(`${opts.file || 'dump'}.dxf`, m.exporter.toDXF(assembly)) } - - - -const Point = exports.Point = class Point { - constructor(x, y, r, col={}, row={}) { - this.x = x - this.y = y - this.r = r - this.col = col - this.row = row - } - - get p() { - return [this.x, this.y] - } - - set p(val) { - [this.x, this.y] = val - } -} - - const push_rotation = (list, angle, origin) => { let candidate = origin for (const r of list) { @@ -153,143 +99,106 @@ const push_rotation = (list, angle, origin) => { }) } - -const matrix = (cols, rows, anchor=new Point(0, 0, 0), reverse=false) => { - - console.log('matrix', cols, rows, anchor, reverse) - +const render_zone = (cols, rows, anchor=new Point(), reverse=false) => { const sign = reverse ? -1 : 1 const points = {} - let x = anchor.x - let stagger_sum = anchor.y - let rotation_sum = anchor.r - const rotations = [{ + const rotations = [] + + // transferring the anchor rotation to "real" rotations + rotations.push({ angle: anchor.r, - origin: [0, 0] - }] + origin: anchor.p + }) + for (const col of cols) { + anchor.y += col.stagger || 0 + const col_anchor = anchor.clone() + // clear potential rotations, as they will get re-applied anyway + // and we don't want to apply them twice... + col_anchor.r = 0 - // TODO rotation origin reversal is not this easy - // and probably the whole reversal idea is not even necessary - // sleep on it, and possibly remove to simplify - - - if (reverse) { - x -= col.padding || 19 - if (col.rotate) { - let ox = 0, oy = 0 - if (col.origin) { - ox = -col.origin[0] - oy = col.origin[1] - } - push_rotation( - rotations, - col.rotate, - m.point.add([ox, oy], [x, stagger_sum]) - ) - rotation_sum += col.rotate - } - } - - stagger_sum += sign * col.stagger || 0 - let y = stagger_sum - - - for (const row of (col.rows || rows || [{name: 'default'}])) { - let point = [x, y] - debug(point) + for (const row of col.rows || rows) { + let point = col_anchor.clone() for (const r of rotations) { - point = m.point.rotate(point, r.angle, r.origin) - debug(point) + point.rotate(r.angle, r.origin) } - points[col.name + '_' + row.name] = new Point( - point[0], - point[1], - rotation_sum + (row.angle || 0), - col, - row + point.r += col.angle || 0 + point.meta = {col, row} + + const key = `${col.name}_${row.name}` + points[key] = point + + col_anchor.y += row.padding || 19 + } + + if (col.rotate) { + push_rotation( + rotations, + col.rotate, + anchor.add(col.origin || [0, 0]).p ) - console.log('new Point', points[col.name + '_' + row.name]) - y += row.padding || 19 } - - - - if (!reverse) { - if (col.rotate) { - push_rotation( - rotations, - -col.rotate, - m.point.add(col.origin || [0, 0], [x, stagger_sum]) - ) - - a = rotations.pop() - debug(a.origin) - rotations.push(a) - - rotation_sum += -col.rotate - } - x += col.padding || 19 - } - - - + anchor.x += sign * (col.padding || 19) } + return points } - - - - - - +const anchor = (raw, points={}) => { + let a = new Point() + if (raw) { + if (raw.ref && points[raw.ref]) { + a = points[raw.ref].clone() + } + if (raw.shift) { + a.x += raw.shift[0] + a.y += raw.shift[1] + } + a.r += raw.angle || 0 + } + return a +} exports.parse = (config) => { - // basic matrix - let matrix_anchor - if (config.anchor) { - let x = 0 - let y = 0 - if (config.anchor.shift) { - x = config.anchor.shift[0] - y = config.anchor.shift[1] + let points = {} + + for (const zone of Object.values(config.zones)) { + points = Object.assign(points, render_zone( + zone.columns || [], + zone.rows || [{name: 'default'}], + anchor(zone.anchor, points), + !!zone.reverse + )) + } + + if (config.angle) { + for (const p of Object.values(points)) { + p.rotate(config.angle) } - const r = -config.anchor.angle || 0 - matrix_anchor = new Point(x, y, r) - } - let points = matrix(config.columns, config.rows, matrix_anchor) - - // thumbfan - let thumb_anchor = new Point(0, 0, 0) - let thumb_anchor_index = -1 - for (const key of config.thumbfan) { - thumb_anchor_index++ - if (key.anchor) { - const ref = points[key.anchor.ref] || thumb_anchor - thumb_anchor.p = m.point.add(ref.p, key.anchor.shift) - thumb_anchor.r += key.anchor.angle || 0 - break - } else continue } - const forward = config.thumbfan.slice(thumb_anchor_index) - const rewind = config.thumbfan.slice(0, thumb_anchor_index) - const thumb_rows = config.thumb_rows || [{name: 'thumb'}] - - points = Object.assign(points, matrix(forward, thumb_rows, thumb_anchor)) - points = Object.assign(points, matrix(rewind, thumb_rows, thumb_anchor, true)) + if (config.mirror) { + let x = anchor(config.mirror, points).x + x += (config.mirror.distance || 0) / 2 + const mirrored_points = {} + for (const [name, p] of Object.entries(points)) { + const mp = p.clone().mirror(x) + mp.meta.mirrored = true + const mname = `mirror_${name}` + mirrored_points[mname] = mp + } + Object.assign(points, mirrored_points) + } + const filtered = {} + for (const [k, p] of Object.entries(points)) { + if (p.meta.col.skip || p.meta.row.skip) continue + filtered[k] = p + } - dump(points) - throw 2 - - - - - return points + return filtered } \ No newline at end of file