From 6e520e745a438d10ae84656a476c8f50348d9a56 Mon Sep 17 00:00:00 2001 From: Kim Date: Wed, 19 Jul 2023 15:00:44 -0600 Subject: [PATCH] Add intersect aggregator (#102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added intersect aggregator * Addressed stylistic PR feedback * Set the rotation of the intersected point to the second point’s rotation * Revert "Set the rotation of the intersected point to the second point’s rotation" This reverts commit d210a98f79a8866519fc9695487a9ea235167b8b. * Adjusted aggregate function to not need axis1 and axis2 parameters * Add assert to check for exactly 2 parts * Add unit tests for the aggregate intersect method * Change the assert string to use ${name.length} * Change test group name to align with style guide * Update intersect logic to consider negative Y axis --------- Co-authored-by: Marco Massarelli <60667061+ceoloide@users.noreply.github.com> --- src/anchor.js | 39 +++++++++++++++++++++-- test/unit/anchor.js | 77 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/src/anchor.js b/src/anchor.js index 169ff3c..3d892ee 100644 --- a/src/anchor.js +++ b/src/anchor.js @@ -1,6 +1,7 @@ const u = require('./utils') const a = require('./assert') const Point = require('./point') +const m = require('makerjs') const mirror_ref = exports.mirror = (ref, mirror=true) => { if (mirror) { @@ -28,7 +29,41 @@ const aggregators = { r += part.r } return new Point(x / len, y / len, r / len) - } + }, + intersect: (config, name, parts) => { + // a line is generated from a point by taking their + // (rotated) Y axis. The line is not extended to + // +/- Infinity as that doesn't work with makerjs. + // An arbitrary offset of 1 meter is considered + // sufficient for practical purposes, and the point + // coordinates are used as pivot point for the rotation. + const get_line_from_point = (point, offset=1000) => { + const origin = [point.x, point.y] + const p1 = [point.x, point.y - offset] + const p2 = [point.x, point.y + offset] + + let line = new m.paths.Line(p1, p2) + line = m.path.rotate(line, point.r, origin) + + return line + } + + a.unexpected(config, name, aggregator_common) + a.assert(parts.length==2, `Intersect expects exactly two parts, but it got ${parts.length}!`) + + const line1 = get_line_from_point(parts[0]) + const line2 = get_line_from_point(parts[1]) + const intersection = m.path.intersection(line1, line2) + + a.assert(intersection, `The points under "${name}.parts" do not intersect!`) + + const intersection_point_arr = intersection.intersectionPoints[0] + const intersection_point = new Point( + intersection_point_arr[0], intersection_point_arr[1], 0 + ) + + return intersection_point + }, } const anchor = exports.parse = (raw, name, points={}, start=new Point(), mirror=false) => units => { @@ -56,7 +91,7 @@ const anchor = exports.parse = (raw, name, points={}, start=new Point(), mirror= // // Reference or aggregate handling // - + let point = start.clone() if (raw.ref !== undefined && raw.aggregate !== undefined) { throw new Error(`Fields "ref" and "aggregate" cannot appear together in anchor "${name}"!`) diff --git a/test/unit/anchor.js b/test/unit/anchor.js index d32c981..342d59a 100644 --- a/test/unit/anchor.js +++ b/test/unit/anchor.js @@ -6,6 +6,10 @@ describe('Anchor', function() { const points = { o: new Point(0, 0, 0, {label: 'o'}), + rotated_o: new Point(0, 0, 90, {label: 'rotated_o'}), + o_five: new Point(0, 5, 0, {label: 'o_five'}), + five_o: new Point(5, 0, 0, {label: 'five_o'}), + five: new Point(5, 5, 90, {label: 'five'}), ten: new Point(10, 10, -90, {label: 'ten'}), mirror_ten: new Point(-10, 10, 90, {mirrored: true}) } @@ -72,6 +76,79 @@ describe('Anchor', function() { ref : 'ten' }, 'name', points).should.throw() }) + it('intersect', function() { + // points that intersect on a negative Y axis + check( + parse({ + aggregate: { + parts: ['o','ten'], + method: 'intersect' + } + }, 'name', points)(), + [0,10,0,{}] + ) + + // points that have parallel Y axis, i.e. never intersect + parse({ + aggregate: { + parts: ['o','five_o'], + method: 'intersect' + } + }, 'name', points).should.throw(`The points under "name.aggregate.parts" do not intersect!`) + + // points intersect on their positive Y axis + check( + parse({ + aggregate: { + parts: ['o','five'], + method: 'intersect' + } + }, 'name', points)(), + [0, 5, 0, {}] + ) + + // intersecting points with the same coordinates, but different rotations + check( + parse({ + aggregate: { + parts: ['o','rotated_o'], + method: 'intersect' + } + }, 'name', points)(), + [0, 0, 0, {}] + ) + + // points with overlapping Y axis + parse({ + aggregate: { + parts: ['o','o_five'], + method: 'intersect' + } + }, 'name', points).should.throw(`The points under "name.aggregate.parts" do not intersect!`) + + // more than two parts + parse({ + aggregate: { + parts: ['o', `five`, `ten`], + method: 'intersect' + } + }, 'name', points).should.throw(`Intersect expects exactly two parts, but it got 3!`) + + // only one part + parse({ + aggregate: { + parts: ['o'], + method: 'intersect' + } + }, 'name', points).should.throw(`Intersect expects exactly two parts, but it got 1!`) + + // no parts + parse({ + aggregate: { + method: 'intersect' + } + }, 'name', points).should.throw(`Intersect expects exactly two parts, but it got 0!`) + }) it('shift', function() { // normal shift