UNPKG

@jscad/modeling

Version:

Constructive Solid Geometry (CSG) Library for JSCAD

504 lines (468 loc) 15.5 kB
const test = require('ava') const { geom2, path2 } = require('../../geometries') const { offset } = require('./index') const { comparePoints } = require('../../../test/helpers') const measureBoundingBox = require('../../measurements/measureBoundingBox') test('offset: offsetting a straight line produces expected geometry', (t) => { const points = [[0, 0], [0, 10]] let linePath2 = path2.fromPoints({ closed: false }, points) // offset it by 2. let offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2) let offsetPoints = path2.toPoints(offsetLinePath2) t.notThrows(() => path2.validate(offsetLinePath2)) t.is(offsetPoints.length, 2) let boundingBox = measureBoundingBox(offsetLinePath2) t.true(comparePoints(boundingBox, [[2, 0, 0], [2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox)) // offset it by -2. offsetLinePath2 = offset({ delta: -2, corners: 'edge', segments: 8 }, linePath2) offsetPoints = path2.toPoints(offsetLinePath2) t.notThrows(() => path2.validate(offsetLinePath2)) t.is(offsetPoints.length, 2) boundingBox = measureBoundingBox(offsetLinePath2) t.true(comparePoints(boundingBox, [[-2, 0, 0], [-2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox)) // reverse the points, offset it by 2. linePath2 = path2.fromPoints({ closed: false }, points.reverse()) offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2) offsetPoints = path2.toPoints(offsetLinePath2) t.notThrows(() => path2.validate(offsetLinePath2)) t.is(offsetPoints.length, 2) boundingBox = measureBoundingBox(offsetLinePath2) t.true(comparePoints(boundingBox, [[-2, 0, 0], [-2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox)) }) test('offset: offsetting a bent line produces expected geometry', (t) => { const points = [[0, 0], [0, 5], [0, 10], [5, 10], [10, 10]] const linePath2 = path2.fromPoints({ closed: false }, points) // offset it by 2. let offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2) let offsetPoints = path2.toPoints(offsetLinePath2) t.notThrows(() => path2.validate(offsetLinePath2)) t.is(offsetPoints.length, 5) let boundingBox = measureBoundingBox(offsetLinePath2) t.true(comparePoints(boundingBox, [[2, 0, 0], [10, 8, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox)) // offset it by -2. offsetLinePath2 = offset({ delta: -2, corners: 'edge', segments: 8 }, linePath2) offsetPoints = path2.toPoints(offsetLinePath2) t.notThrows(() => path2.validate(offsetLinePath2)) t.is(offsetPoints.length, 5) boundingBox = measureBoundingBox(offsetLinePath2) t.true(comparePoints(boundingBox, [[-2, 0, 0], [10, 12, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox)) }) test('offset: offsetting a 2 segment straight line produces expected geometry', (t) => { const points = [[0, 0], [0, 5], [0, 10]] const linePath2 = path2.fromPoints({ closed: false }, points) const offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2) const offsetPoints = path2.toPoints(offsetLinePath2) t.notThrows(() => path2.validate(offsetLinePath2)) t.is(offsetPoints.length, 3) const boundingBox = measureBoundingBox(offsetLinePath2) t.true(comparePoints(boundingBox, [[2, 0, 0], [2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox)) }) test('offset (corners: chamfer): offset of a path2 produces expected offset path2', (t) => { const openline = path2.fromPoints({ }, [[0, 0], [5, 0], [0, 5]]) const closeline = path2.fromPoints({ }, [[0, 0], [5, 0], [0, 5], [0, 0]]) // empty path2 const empty = path2.create() let obs = offset({ delta: 1 }, empty) let pts = path2.toPoints(obs) let exp = [ ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) // expand + obs = offset({ delta: 1, corners: 'chamfer' }, openline) pts = path2.toPoints(obs) exp = [ [-6.123233995736766e-17, -1], [5, -1], [5.707106781186548, 0.7071067811865475], [0.7071067811865475, 5.707106781186548] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) obs = offset({ delta: 1, corners: 'chamfer' }, closeline) pts = path2.toPoints(obs) exp = [ [-6.123233995736766e-17, -1], [5, -1], [5.707106781186548, 0.7071067811865475], [0.7071067811865475, 5.707106781186548], [-1, 5], [-1, 6.123233995736766e-17] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) // contract - obs = offset({ delta: -1, corners: 'chamfer' }, openline) pts = path2.toPoints(obs) exp = [ [6.123233995736766e-17, 1], [2.5857864376269046, 1], [-0.7071067811865475, 4.292893218813452] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) obs = offset({ delta: -1, corners: 'chamfer' }, closeline) pts = path2.toPoints(obs) exp = [ [1, 1], [2.5857864376269046, 1], [0.9999999999999996, 2.585786437626905] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) }) test('offset (corners: edge): offset of a path2 produces expected offset path2', (t) => { const openline = path2.fromPoints({ }, [[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5]]) const closeline = path2.fromPoints({ }, [[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5], [-5, -5]]) let obs = offset({ delta: 1, corners: 'edge' }, openline) let pts = path2.toPoints(obs) let exp = [ [-5, -6], [6, -6], [6, 6], [2, 6], [2, 1], [-2, 1], [-1.9999999999999996, 6], [-5, 6] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) obs = offset({ delta: 1, corners: 'edge' }, closeline) pts = path2.toPoints(obs) exp = [ [6, -6], [6, 6], [2, 6], [2, 1], [-2, 1], [-1.9999999999999996, 6], [-6, 6], [-6, -6] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) obs = offset({ delta: -0.5, corners: 'edge' }, openline) pts = path2.toPoints(obs) exp = [ [-5, -4.5], [4.5, -4.5], [4.5, 4.5], [3.5, 4.5], [3.4999999999999996, -0.5], [-3.5, -0.4999999999999996], [-3.5, 4.5], [-5, 4.5] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) obs = offset({ delta: -0.5, corners: 'edge' }, closeline) pts = path2.toPoints(obs) exp = [ [-4.5, -4.5], [4.5, -4.5], [4.5, 4.5], [3.5, 4.5], [3.4999999999999996, -0.5], [-3.5, -0.4999999999999996], [-3.5, 4.5], [-4.5, 4.5] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) }) test('offset (corners: round): offset of a path2 produces expected offset path2', (t) => { const openline = path2.fromPoints({ }, [[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5]]) const closeline = path2.fromPoints({ }, [[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5], [-5, -5]]) let obs = offset({ delta: 1, corners: 'round', segments: 16 }, openline) let pts = path2.toPoints(obs) let exp = [ [-5, -6], [5, -6], [5.38268343236509, -5.923879532511287], [5.707106781186548, -5.707106781186548], [5.923879532511287, -5.38268343236509], [6, -5], [6, 5], [5.923879532511287, 5.38268343236509], [5.707106781186548, 5.707106781186548], [5.38268343236509, 5.923879532511287], [5, 6], [3, 6], [2.6173165676349104, 5.923879532511287], [2.2928932188134525, 5.707106781186548], [2.076120467488713, 5.38268343236509], [2, 5], [2, 1], [-2, 1], [-2, 5], [-2.076120467488713, 5.38268343236509], [-2.2928932188134525, 5.707106781186548], [-2.6173165676349104, 5.923879532511287], [-3, 6], [-5, 6] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) obs = offset({ delta: 1, corners: 'round', segments: 16 }, closeline) pts = path2.toPoints(obs) exp = [ [-5.923879532511287, -5.38268343236509], [-5.707106781186548, -5.707106781186548], [-5.3826834323650905, -5.923879532511286], [-5, -6], [5, -6], [5.38268343236509, -5.923879532511287], [5.707106781186548, -5.707106781186548], [5.923879532511287, -5.38268343236509], [6, -5], [6, 5], [5.923879532511287, 5.38268343236509], [5.707106781186548, 5.707106781186548], [5.38268343236509, 5.923879532511287], [5, 6], [3, 6], [2.6173165676349104, 5.923879532511287], [2.2928932188134525, 5.707106781186548], [2.076120467488713, 5.38268343236509], [2, 5], [2, 1], [-2, 1], [-2, 5], [-2.076120467488713, 5.38268343236509], [-2.2928932188134525, 5.707106781186548], [-2.6173165676349104, 5.923879532511287], [-3, 6], [-5, 6], [-5.38268343236509, 5.923879532511287], [-5.707106781186548, 5.707106781186548], [-5.923879532511287, 5.38268343236509], [-6, 5], [-6, -5] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) }) test('offset (corners: round): offset of a CW path2 produces expected offset path2', (t) => { const closeline = path2.fromPoints({ }, [[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5], [-5, -5]].reverse()) const obs = offset({ delta: 1, corners: 'round', segments: 16 }, closeline) const pts = path2.toPoints(obs) const exp = [ [-5.38268343236509, -5.923879532511287], [-5.707106781186548, -5.707106781186548], [-5.923879532511287, -5.38268343236509], [-6, -5], [-6, 5], [-5.923879532511287, 5.38268343236509], [-5.707106781186548, 5.707106781186548], [-5.38268343236509, 5.923879532511287], [-5, 6], [-3, 6], [-2.6173165676349104, 5.923879532511287], [-2.2928932188134525, 5.707106781186548], [-2.076120467488713, 5.38268343236509], [-2, 5], [-2, 1], [2, 1], [2, 5], [2.076120467488713, 5.38268343236509], [2.2928932188134525, 5.707106781186548], [2.6173165676349104, 5.923879532511287], [3, 6], [5, 6], [5.38268343236509, 5.923879532511287], [5.707106781186548, 5.707106781186548], [5.923879532511287, 5.38268343236509], [6, 5], [6, -5], [5.923879532511287, -5.38268343236509], [5.707106781186548, -5.707106781186548], [5.38268343236509, -5.923879532511287], [5, -6], [-5, -6] ] t.notThrows(() => path2.validate(obs)) t.true(comparePoints(pts, exp)) }) test('offset (options): offsetting of a simple geom2 produces expected offset geom2', (t) => { const geometry = geom2.fromPoints([[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5]]) // empty const empty = geom2.create() let obs = offset({ delta: 1 }, empty) let pts = geom2.toPoints(obs) let exp = [ ] t.notThrows(() => geom2.validate(obs)) t.true(comparePoints(pts, exp)) // expand + obs = offset({ delta: 1, corners: 'round' }, geometry) pts = geom2.toPoints(obs) exp = [ [-5, -6], [5, -6], [6, -5], [6, 5], [5, 6], [3, 6], [2, 5], [2, 1], [-2, 1], [-2, 5], [-3, 6], [-5, 6], [-6, 5], [-6, -5] ] t.notThrows(() => geom2.validate(obs)) t.true(comparePoints(pts, exp)) // contract - obs = offset({ delta: -0.5, corners: 'round' }, geometry) pts = geom2.toPoints(obs) exp = [ [-4.5, -4.5], [4.5, -4.5], [4.5, 4.5], [3.5, 4.5], [3.5, -3.0616171314629196e-17], [3, -0.5], [-3, -0.5], [-3.5, 3.0616171314629196e-17], [-3.5, 4.5], [-4.5, 4.5] ] t.notThrows(() => geom2.validate(obs)) t.true(comparePoints(pts, exp)) // segments 1 - sharp points at corner obs = offset({ delta: 1, corners: 'edge' }, geometry) pts = geom2.toPoints(obs) exp = [ [6, -6], [6, 6], [2, 6], [2, 1], [-2, 1], [-1.9999999999999996, 6], [-6, 6], [-6, -6] ] t.notThrows(() => geom2.validate(obs)) t.true(comparePoints(pts, exp)) // segments 16 - rounded corners obs = offset({ delta: -0.5, corners: 'round', segments: 16 }, geometry) pts = geom2.toPoints(obs) exp = [ [-4.5, -4.5], [4.5, -4.5], [4.5, 4.5], [3.5, 4.5], [3.5, -3.061616997868383e-17], [3.4619397662556435, -0.19134171618254492], [3.353553390593274, -0.3535533905932738], [3.191341716182545, -0.46193976625564337], [3, -0.5], [-3, -0.5], [-3.191341716182545, -0.46193976625564337], [-3.353553390593274, -0.3535533905932738], [-3.4619397662556435, -0.19134171618254495], [-3.5, 3.061616997868383e-17], [-3.5, 4.5], [-4.5, 4.5] ] t.notThrows(() => geom2.validate(obs)) t.true(comparePoints(pts, exp)) }) test('offset (options): offsetting of a complex geom2 produces expected offset geom2', (t) => { const geometry = geom2.create([ [[-75, 75], [-75, -75]], [[-75, -75], [75, -75]], [[75, -75], [75, 75]], [[-40, 75], [-75, 75]], [[75, 75], [40, 75]], [[40, 75], [40, 0]], [[40, 0], [-40, 0]], [[-40, 0], [-40, 75]], [[15, -10], [15, -40]], [[-15, -10], [15, -10]], [[-15, -40], [-15, -10]], [[-8, -40], [-15, -40]], [[15, -40], [8, -40]], [[-8, -25], [-8, -40]], [[8, -25], [-8, -25]], [[8, -40], [8, -25]], [[-2, -15], [-2, -19]], [[-2, -19], [2, -19]], [[2, -19], [2, -15]], [[2, -15], [-2, -15]] ]) // expand + const obs = offset({ delta: 2, corners: 'edge' }, geometry) const pts = geom2.toPoints(obs) const exp = [ [77, -77], [77, 77], [38, 77], [38, 2], [-38, 2], [-37.99999999999999, 77], [-77, 77], [13, -12], [13, -38], [10, -38], [10, -23], [-10, -23], [-10, -38], [-13, -38], [-13, -12], [-4, -21], [3.9999999999999996, -21], [4, -13], [-4, -13], [-77, -77] ] t.notThrows(() => geom2.validate(obs)) t.is(pts.length, 20) t.true(comparePoints(pts, exp)) }) test('offset (options): offsetting of round geom2 produces expected offset geom2', (t) => { const geometry = geom2.fromPoints([ [10.00000, 0.00000], [9.23880, 3.82683], [7.07107, 7.07107], [3.82683, 9.23880], [0.00000, 10.00000], [-3.82683, 9.23880], [-7.07107, 7.07107], [-9.23880, 3.82683], [-10.00000, 0.00000], [-9.23880, -3.82683], [-7.07107, -7.07107], [-3.82683, -9.23880], [-0.00000, -10.00000], [3.82683, -9.23880], [7.07107, -7.07107], [9.23880, -3.82683] ]) const obs = offset({ delta: -0.5, corners: 'round' }, geometry) const pts = geom2.toPoints(obs) const exp = [ [9.490204518135641, 0], [8.767810140100096, 3.6317399864658007], [6.710590060510285, 6.7105900605102855], [3.6317399864658024, 8.767810140100096], [-4.440892098500626e-16, 9.490204518135641], [-3.6317399864658007, 8.767810140100096], [-6.7105900605102855, 6.710590060510285], [-8.767810140100096, 3.6317399864658024], [-9.490204518135641, -4.440892098500626e-16], [-8.767810140100096, -3.6317399864658007], [-6.710590060510285, -6.7105900605102855], [-3.6317399864658024, -8.767810140100096], [4.440892098500626e-16, -9.490204518135641], [3.6317399864658007, -8.767810140100096], [6.7105900605102855, -6.710590060510285], [8.767810140100096, -3.6317399864658024] ] t.notThrows(() => geom2.validate(obs)) t.is(pts.length, 16) t.true(comparePoints(pts, exp)) })