@jscad/modeling
Advanced tools
Comparing version 2.0.0-alpha.5 to 2.0.0-alpha.6
@@ -6,2 +6,14 @@ # Change Log | ||
# [2.0.0-alpha.6](https://github.com/jscad/openjscad.org/compare/@jscad/modeling@2.0.0-alpha.5...@jscad/modeling@2.0.0-alpha.6) (2020-10-11) | ||
### Bug Fixes | ||
* **all:** V2 : several fixes for modeling ([#705](https://github.com/jscad/openjscad.org/issues/705)) ([62017a4](https://github.com/jscad/openjscad.org/commit/62017a41214169d6e000f1e0c11aaefdd68e1097)) | ||
* **core:** Remove connectors from public api ([#703](https://github.com/jscad/openjscad.org/issues/703)) ([a3bf8a4](https://github.com/jscad/openjscad.org/commit/a3bf8a42e7ccf2204351da4a4acff55c2d6acad6)) | ||
# [2.0.0-alpha.5](https://github.com/jscad/openjscad.org/compare/@jscad/modeling@2.0.0-alpha.4...@jscad/modeling@2.0.0-alpha.5) (2020-09-29) | ||
@@ -8,0 +20,0 @@ |
{ | ||
"name": "@jscad/modeling", | ||
"version": "2.0.0-alpha.5", | ||
"version": "2.0.0-alpha.6", | ||
"description": "Constructive Solid Geometry (CSG) Library", | ||
@@ -8,2 +8,3 @@ "repository": "https://github.com/jscad/openjscad.org", | ||
"scripts": { | ||
"build": "browserify src/index.js -o dist/modeling.js -g uglifyify --standalone modeling", | ||
"coverage": "nyc --all --reporter=html --reporter=text npm test", | ||
@@ -58,5 +59,7 @@ "test": "ava 'src/**/*.test.js' --verbose --timeout 2m", | ||
"ava": "3.10.0", | ||
"conventional-changelog-cli": "^1.3.4" | ||
"browserify": "16.5.1", | ||
"nyc": "15.1.0", | ||
"uglifyify": "5.0.2" | ||
}, | ||
"gitHead": "0d21a54b5e3435867420e4d8c96a23893db28ef2" | ||
"gitHead": "a08ba28184627770c74c3dbfa25fb181b8459b0c" | ||
} |
@@ -48,3 +48,3 @@ /** | ||
let firstPointType = null | ||
points.forEach(point => { | ||
points.forEach((point) => { | ||
let pType = '' | ||
@@ -54,3 +54,3 @@ if (Number.isFinite(point)) { | ||
} else if (Array.isArray(point)) { | ||
point.forEach(val => { | ||
point.forEach((val) => { | ||
if (!Number.isFinite(val)) throw new Error('Bezier point values must all be numbers.') | ||
@@ -57,0 +57,0 @@ }) |
@@ -26,3 +26,3 @@ /** | ||
const singleDimensionPoints = [] | ||
for (var j = 0; j < bezier.points.length; j++) { | ||
for (let j = 0; j < bezier.points.length; j++) { | ||
singleDimensionPoints.push(bezier.points[j][i]) | ||
@@ -29,0 +29,0 @@ } |
@@ -18,12 +18,16 @@ const vec2 = require('../../maths/vec2') | ||
} | ||
if (points.length < 3) { | ||
let length = points.length | ||
if (length < 3) { | ||
throw new Error('the given points must define a closed geometry with three or more points') | ||
} | ||
// adjust length if the given points are closed by the same point | ||
if (vec2.equals(points[0], points[length - 1])) --length | ||
const sides = [] | ||
let prevpoint = points[points.length - 1] | ||
points.forEach((point) => { | ||
let prevpoint = points[length - 1] | ||
for (let i = 0; i < length; i++) { | ||
const point = points[i] | ||
sides.push([vec2.fromArray(prevpoint), vec2.fromArray(point)]) | ||
prevpoint = point | ||
}) | ||
} | ||
return create(sides) | ||
@@ -30,0 +34,0 @@ } |
@@ -12,2 +12,5 @@ const test = require('ava') | ||
t.deepEqual(fromPoints(points), expected) | ||
const points2 = [[0, 0], [1, 0], [0, 1], [0, 0]] | ||
t.deepEqual(fromPoints(points2), expected) | ||
}) | ||
@@ -14,0 +17,0 @@ |
const test = require('ava') | ||
const { fromCompactBinary, toCompactBinary, create, fromPoints, equals } = require('./index') | ||
const { fromCompactBinary, toCompactBinary, create, fromPoints } = require('./index') | ||
@@ -5,0 +5,0 @@ test('toCompactBinary: converts geom3 (default)', (t) => { |
@@ -10,6 +10,4 @@ /** | ||
const measureArea = (polygon) => { | ||
return area(polygon.vertices) | ||
} | ||
const measureArea = (polygon) => area(polygon.vertices) | ||
module.exports = measureArea |
module.exports = { | ||
colors: require('./colors'), | ||
connectors: require('./connectors'), | ||
curves: require('./curves'), | ||
@@ -5,0 +4,0 @@ geometries: require('./geometries'), |
@@ -21,3 +21,3 @@ const test = require('ava') | ||
const int4 = intersectPointOfLines(line4, line3) | ||
// FIXME BUG?? t.true(compareVectors(int4, [Infinity, -Infinity])) | ||
t.true(compareVectors(int4, [Infinity, -Infinity])) | ||
@@ -24,0 +24,0 @@ // intersecting lines |
@@ -7,4 +7,2 @@ const test = require('ava') | ||
const { compareVectors } = require('../../../test/helpers/index') | ||
test('mat4: isMirroring() should determine correctlly', (t) => { | ||
@@ -11,0 +9,0 @@ let matrix = identity() |
@@ -5,4 +5,2 @@ const test = require('ava') | ||
const { compareVectors } = require('../../../test/helpers/index') | ||
test('vec4: dot() should return proper values', (t) => { | ||
@@ -9,0 +7,0 @@ const vecA = fromValues(1, 2, 3, 4) |
@@ -1,3 +0,1 @@ | ||
const vec3 = require('../../maths/vec3') | ||
const geom3 = require('../../geometries/geom3') | ||
@@ -4,0 +2,0 @@ const poly3 = require('../../geometries/poly3') |
@@ -36,3 +36,3 @@ const test = require('ava') | ||
test('offset: offsetting a bent line produces expected geometry', (t) => { | ||
const points = [[0, 0], [0, 10], [10, 10]] | ||
const points = [[0, 0], [0, 5], [0, 10], [5, 10], [10, 10]] | ||
const linePath2 = path2.fromPoints({ closed: false }, points) | ||
@@ -43,3 +43,3 @@ | ||
let offsetPoints = path2.toPoints(offsetLinePath2) | ||
t.is(offsetPoints.length, 3) | ||
t.is(offsetPoints.length, 5) | ||
let boundingBox = measureBoundingBox(offsetLinePath2) | ||
@@ -51,3 +51,3 @@ t.true(comparePoints(boundingBox, [[2, 0, 0], [10, 8, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox)) | ||
offsetPoints = path2.toPoints(offsetLinePath2) | ||
t.is(offsetPoints.length, 5) // obtuse angles produce 3 points, even in edge offsets | ||
t.is(offsetPoints.length, 7) | ||
boundingBox = measureBoundingBox(offsetLinePath2) | ||
@@ -57,12 +57,11 @@ t.true(comparePoints(boundingBox, [[-2, 0, 0], [10, 12, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox)) | ||
// TODO This test fails, since two parallel lines that connect are not considered to intersect, causing it to try and create impossible geometry. | ||
// 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.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: 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.is(offsetPoints.length, 3) | ||
const boundingBox = measureBoundingBox(offsetLinePath2) | ||
t.true(comparePoints(boundingBox, [[2, 0, 0], [2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox)) | ||
}) | ||
@@ -69,0 +68,0 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path2', (t) => { |
@@ -100,6 +100,13 @@ const { EPS } = require('../../maths/constants') | ||
const ip = line2.intersectPointOfLines(line0, line1) | ||
const p0 = corner.s0[1] | ||
let i = newPoints.findIndex((point) => vec2.equals(p0, point)) | ||
i = (i + 1) % newPoints.length | ||
newPoints.splice(i, 0, ip) | ||
if (Number.isFinite(ip[0]) && Number.isFinite(ip[1])) { | ||
const p0 = corner.s0[1] | ||
let i = newPoints.findIndex((point) => vec2.equals(p0, point)) | ||
i = (i + 1) % newPoints.length | ||
newPoints.splice(i, 0, ip) | ||
} else { | ||
// paralell segments, drop one | ||
const p0 = corner.s1[0] | ||
const i = newPoints.findIndex((point) => vec2.equals(p0, point)) | ||
newPoints.splice(i, 1) | ||
} | ||
}) | ||
@@ -124,18 +131,25 @@ } | ||
// generate the segments | ||
cornersegments = Math.floor(segments * (Math.abs(rotation) / (2 * Math.PI))) | ||
const step = rotation / cornersegments | ||
const start = vec2.angle(vec2.subtract(corner.s0[1], corner.c)) | ||
const cornerpoints = [] | ||
for (let i = 1; i < cornersegments; i++) { | ||
const radians = start + (step * i) | ||
const point = vec2.add(corner.c, vec2.scale(delta, vec2.fromAngleRadians(radians))) | ||
cornerpoints.push(point) | ||
if (rotation !== 0.0) { | ||
// generate the segments | ||
cornersegments = Math.floor(segments * (Math.abs(rotation) / (2 * Math.PI))) | ||
const step = rotation / cornersegments | ||
const start = vec2.angle(vec2.subtract(corner.s0[1], corner.c)) | ||
const cornerpoints = [] | ||
for (let i = 1; i < cornersegments; i++) { | ||
const radians = start + (step * i) | ||
const point = vec2.add(corner.c, vec2.scale(delta, vec2.fromAngleRadians(radians))) | ||
cornerpoints.push(point) | ||
} | ||
if (cornerpoints.length > 0) { | ||
const p0 = corner.s0[1] | ||
let i = newPoints.findIndex((point) => vec2.equals(p0, point)) | ||
i = (i + 1) % newPoints.length | ||
newPoints.splice(i, 0, ...cornerpoints) | ||
} | ||
} else { | ||
// paralell segments, drop one | ||
const p0 = corner.s1[0] | ||
const i = newPoints.findIndex((point) => vec2.equals(p0, point)) | ||
newPoints.splice(i, 1) | ||
} | ||
if (cornerpoints.length > 0) { | ||
const p0 = corner.s0[1] | ||
let i = newPoints.findIndex((point) => vec2.equals(p0, point)) | ||
i = (i + 1) % newPoints.length | ||
newPoints.splice(i, 0, ...cornerpoints) | ||
} | ||
}) | ||
@@ -142,0 +156,0 @@ } |
@@ -9,7 +9,7 @@ const flatten = require('../../utils/flatten') | ||
options.modes = padArrayToLength(options.modes, 'none', 3) | ||
if (options.modes.filter(mode => ['center', 'max', 'min', 'none'].includes(mode)).length !== 3) throw new Error('align(): all modes must be one of "center", "max" or "min"') | ||
if (options.modes.filter((mode) => ['center', 'max', 'min', 'none'].includes(mode)).length !== 3) throw new Error('align(): all modes must be one of "center", "max" or "min"') | ||
if (!Array.isArray(options.alignTo) || options.alignTo.length > 3) throw new Error('align(): alignTo must be an array of length <= 3') | ||
options.alignTo = padArrayToLength(options.alignTo, 0, 3) | ||
if (options.alignTo.filter(alignVal => (Number.isFinite(alignVal) || alignVal == null)).length !== 3) throw new Error('align(): all alignTo values must be a number, or null.') | ||
if (options.alignTo.filter((alignVal) => (Number.isFinite(alignVal) || alignVal == null)).length !== 3) throw new Error('align(): all alignTo values must be a number, or null.') | ||
@@ -78,3 +78,3 @@ if (typeof options.grouped !== 'boolean') throw new Error('align(): grouped must be a boolean value.') | ||
if (alignTo.filter(val => val == null).length) { | ||
if (alignTo.filter((val) => val == null).length) { | ||
const bounds = measureAggregateBoundingBox(geometries) | ||
@@ -86,5 +86,3 @@ alignTo = populateAlignToFromBounds(alignTo, modes, bounds) | ||
} else { | ||
geometries = geometries.map(geometry => { | ||
return alignGeometries(geometry, modes, alignTo) | ||
}) | ||
geometries = geometries.map((geometry) => alignGeometries(geometry, modes, alignTo)) | ||
} | ||
@@ -91,0 +89,0 @@ return geometries.length === 1 ? geometries[0] : geometries |
@@ -64,6 +64,24 @@ const flatten = require('../../utils/flatten') | ||
/** | ||
* Center the given geometries about the X axis. | ||
* @param {...Object} geometries - the geometries to center | ||
* @return {Object|Array} the centered geometry, or a list of centered geometry | ||
* @alias module:modeling/transforms.centerX | ||
*/ | ||
const centerX = (...objects) => center({ axes: [true, false, false] }, objects) | ||
/** | ||
* Center the given geometries about the Y axis. | ||
* @param {...Object} geometries - the geometries to center | ||
* @return {Object|Array} the centered geometry, or a list of centered geometry | ||
* @alias module:modeling/transforms.centerY | ||
*/ | ||
const centerY = (...objects) => center({ axes: [false, true, false] }, objects) | ||
/** | ||
* Center the given geometries about the Z axis. | ||
* @param {...Object} geometries - the geometries to center | ||
* @return {Object|Array} the centered geometry, or a list of centered geometry | ||
* @alias module:modeling/transforms.centerZ | ||
*/ | ||
const centerZ = (...objects) => center({ axes: [false, false, true] }, objects) | ||
@@ -70,0 +88,0 @@ |
@@ -11,2 +11,4 @@ const ellipse = require('./ellipse') | ||
* @param {Number} [options.radius=1] - radius of circle | ||
* @param {Number} [options.startAngle=0] - start angle of circle, in radians | ||
* @param {Number} [options.endAngle=(Math.PI * 2)] - end angle of circle, in radians | ||
* @param {Number} [options.segments=32] - number of segments to create per full rotation | ||
@@ -22,5 +24,7 @@ * @returns {geom2} new 2D geometry | ||
radius: 1, | ||
startAngle: 0, | ||
endAngle: (Math.PI * 2), | ||
segments: 32 | ||
} | ||
let { center, radius, segments } = Object.assign({}, defaults, options) | ||
let { center, radius, startAngle, endAngle, segments } = Object.assign({}, defaults, options) | ||
@@ -31,5 +35,5 @@ if (!isGT(radius, 0)) throw new Error('radius must be greater than zero') | ||
return ellipse({ center, radius, segments }) | ||
return ellipse({ center, radius, startAngle, endAngle, segments }) | ||
} | ||
module.exports = circle |
@@ -83,2 +83,40 @@ const test = require('ava') | ||
// test startAngle | ||
geometry = circle({ radius: 3.5, startAngle: Math.PI / 2, segments: 16 }) | ||
pts = geom2.toPoints(geometry) | ||
exp = [ | ||
[2.143131898507868e-16, 3.5], | ||
[-1.339392013277814, 3.2335783637895035], | ||
[-2.474873734152916, 2.4748737341529163], | ||
[-3.2335783637895035, 1.3393920132778145], | ||
[-3.5, 4.286263797015736e-16], | ||
[-3.233578363789504, -1.3393920132778139], | ||
[-2.474873734152917, -2.474873734152916], | ||
[-1.339392013277816, -3.233578363789503], | ||
[-6.429395695523604e-16, -3.5], | ||
[1.339392013277815, -3.233578363789503], | ||
[2.474873734152916, -2.474873734152917], | ||
[3.233578363789503, -1.3393920132778163], | ||
[3.5, -8.572527594031472e-16], | ||
[0, 0] | ||
] | ||
t.deepEqual(pts.length, 14) | ||
t.true(comparePoints(pts, exp)) | ||
// test endAngle | ||
geometry = circle({ radius: 3.5, endAngle: Math.PI / 2, segments: 16 }) | ||
pts = geom2.toPoints(geometry) | ||
exp = [ | ||
[3.5, 0], | ||
[3.2335783637895035, 1.3393920132778143], | ||
[2.4748737341529163, 2.474873734152916], | ||
[1.3393920132778145, 3.2335783637895035], | ||
[2.143131898507868e-16, 3.5], | ||
[0, 0] | ||
] | ||
t.deepEqual(pts.length, 6) | ||
t.true(comparePoints(pts, exp)) | ||
// test segments | ||
@@ -85,0 +123,0 @@ geometry = circle({ radius: 3.5, segments: 5 }) |
@@ -66,3 +66,3 @@ const { EPS } = require('../maths/constants') | ||
(2 * minradius * minradius)) | ||
if (rotation < minangle) throw new Error('startAngle and endAngle to not define a significant rotation') | ||
if (rotation < minangle) throw new Error('startAngle and endAngle do not define a significant rotation') | ||
@@ -69,0 +69,0 @@ const slices = Math.floor(segments * (rotation / (Math.PI * 2))) |
@@ -0,1 +1,3 @@ | ||
const { EPS } = require('../maths/constants') | ||
const vec2 = require('../maths/vec2') | ||
@@ -13,2 +15,4 @@ | ||
* @param {Array} [options.radius=[1,1]] - radius of ellipse, along X and Y | ||
* @param {Number} [options.startAngle=0] - start angle of ellipse, in radians | ||
* @param {Number} [options.endAngle=(Math.PI * 2)] - end angle of ellipse, in radians | ||
* @param {Number} [options.segments=32] - number of segments to create per full rotation | ||
@@ -24,5 +28,7 @@ * @returns {geom2} new 2D geometry | ||
radius: [1, 1], | ||
startAngle: 0, | ||
endAngle: (Math.PI * 2), | ||
segments: 32 | ||
} | ||
const { center, radius, segments } = Object.assign({}, defaults, options) | ||
let { center, radius, startAngle, endAngle, segments } = Object.assign({}, defaults, options) | ||
@@ -32,13 +38,36 @@ if (!isNumberArray(center, 2)) throw new Error('center must be an array of X and Y values') | ||
if (!radius.every((n) => n > 0)) throw new Error('radius values must be greater than zero') | ||
if (!isGTE(startAngle, 0)) throw new Error('startAngle must be positive') | ||
if (!isGTE(endAngle, 0)) throw new Error('endAngle must be positive') | ||
if (!isGTE(segments, 3)) throw new Error('segments must be three or more') | ||
startAngle = startAngle % (Math.PI * 2) | ||
endAngle = endAngle % (Math.PI * 2) | ||
let rotation = (Math.PI * 2) | ||
if (startAngle < endAngle) { | ||
rotation = endAngle - startAngle | ||
} | ||
if (startAngle > endAngle) { | ||
rotation = endAngle + ((Math.PI * 2) - startAngle) | ||
} | ||
const minradius = Math.min(radius[0], radius[1]) | ||
const minangle = Math.acos(((minradius * minradius) + (minradius * minradius) - (EPS * EPS)) / | ||
(2 * minradius * minradius)) | ||
if (rotation < minangle) throw new Error('startAngle and endAngle do not define a significant rotation') | ||
segments = Math.floor(segments * (rotation / (Math.PI * 2))) | ||
const centerv = vec2.fromArray(center) | ||
const step = 2 * Math.PI / segments // radians | ||
const step = rotation / segments // radians per segment | ||
const points = [] | ||
segments = (rotation < Math.PI * 2) ? segments + 1 : segments | ||
for (let i = 0; i < segments; i++) { | ||
const point = vec2.fromValues(radius[0] * Math.cos(step * i), radius[1] * Math.sin(step * i)) | ||
const angle = (step * i) + startAngle | ||
const point = vec2.fromValues(radius[0] * Math.cos(angle), radius[1] * Math.sin(angle)) | ||
vec2.add(point, centerv, point) | ||
points.push(point) | ||
} | ||
if (rotation < Math.PI * 2) points.push(centerv) | ||
return geom2.fromPoints(points) | ||
@@ -45,0 +74,0 @@ } |
@@ -83,2 +83,40 @@ const test = require('ava') | ||
// test startAngle | ||
geometry = ellipse({ radius: [3, 5], startAngle: Math.PI / 2, segments: 16 }) | ||
obs = geom2.toPoints(geometry) | ||
exp = [ | ||
[1.8369701987210297e-16, 5], | ||
[-1.1480502970952693, 4.619397662556434], | ||
[-2.1213203435596424, 3.5355339059327378], | ||
[-2.77163859753386, 1.9134171618254494], | ||
[-3, 6.123233995736766e-16], | ||
[-2.7716385975338604, -1.9134171618254483], | ||
[-2.121320343559643, -3.5355339059327373], | ||
[-1.148050297095271, -4.619397662556432], | ||
[-5.51091059616309e-16, -5], | ||
[1.14805029709527, -4.619397662556433], | ||
[2.121320343559642, -3.5355339059327386], | ||
[2.7716385975338595, -1.913417161825452], | ||
[3, -1.2246467991473533e-15], | ||
[0, 0] | ||
] | ||
t.deepEqual(obs.length, 14) | ||
t.true(comparePoints(obs, exp)) | ||
// test endAngle | ||
geometry = ellipse({ radius: [3, 5], endAngle: Math.PI / 2, segments: 16 }) | ||
obs = geom2.toPoints(geometry) | ||
exp = [ | ||
[3, 0], | ||
[2.77163859753386, 1.913417161825449], | ||
[2.121320343559643, 3.5355339059327373], | ||
[1.1480502970952695, 4.619397662556434], | ||
[1.8369701987210297e-16, 5], | ||
[0, 0] | ||
] | ||
t.deepEqual(obs.length, 6) | ||
t.true(comparePoints(obs, exp)) | ||
// test segments | ||
@@ -85,0 +123,0 @@ geometry = ellipse({ segments: 72 }) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1051473
25226
4
635