@jscad/modeling 3.0.0-alpha.0 → 3.0.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE +1 -1
- package/dist/jscad-modeling.es.js +2 -2
- package/dist/jscad-modeling.min.js +2 -2
- package/package.json +2 -2
- package/rollup.config.js +1 -1
- package/src/colors/colorize.js +1 -5
- package/src/colors/colorize.test.js +8 -8
- package/src/geometries/geom2/transform.js +9 -1
- package/src/geometries/geom2/transform.test.js +57 -0
- package/src/geometries/geom3/fromPointsConvex.d.ts +4 -0
- package/src/geometries/geom3/fromPointsConvex.js +25 -0
- package/src/geometries/geom3/fromPointsConvex.test.js +32 -0
- package/src/geometries/geom3/index.d.ts +1 -0
- package/src/geometries/geom3/index.js +1 -0
- package/src/geometries/index.js +3 -4
- package/src/geometries/poly3/type.d.ts +1 -1
- package/src/geometries/slice/validate.js +1 -2
- package/src/maths/index.js +1 -0
- package/src/maths/mat4/isOnlyTransformScale.js +1 -1
- package/src/measurements/measureAggregateArea.js +0 -1
- package/src/measurements/measureAggregateBoundingBox.js +0 -1
- package/src/measurements/measureAggregateEpsilon.js +0 -1
- package/src/measurements/measureAggregateVolume.js +0 -1
- package/src/measurements/measureArea.js +0 -1
- package/src/measurements/measureBoundingBox.js +0 -1
- package/src/measurements/measureEpsilon.js +0 -1
- package/src/measurements/measureVolume.js +0 -1
- package/src/operations/booleans/index.d.ts +1 -0
- package/src/operations/booleans/intersect.js +5 -5
- package/src/operations/booleans/intersect.test.js +6 -7
- package/src/operations/booleans/intersectGeom2.js +2 -6
- package/src/operations/booleans/intersectGeom2.test.js +25 -1
- package/src/operations/booleans/intersectGeom3.js +2 -6
- package/src/operations/booleans/intersectGeom3.test.js +5 -1
- package/src/operations/booleans/mayOverlap.js +0 -1
- package/src/operations/booleans/scission.d.ts +5 -0
- package/src/operations/booleans/scission.js +3 -5
- package/src/operations/booleans/scission.test.js +6 -0
- package/src/operations/booleans/subtract.js +5 -5
- package/src/operations/booleans/subtract.test.js +6 -7
- package/src/operations/booleans/subtractGeom2.js +2 -6
- package/src/operations/booleans/subtractGeom2.test.js +25 -1
- package/src/operations/booleans/subtractGeom3.js +2 -6
- package/src/operations/booleans/subtractGeom3.test.js +5 -1
- package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +33 -0
- package/src/operations/booleans/union.js +5 -5
- package/src/operations/booleans/union.test.js +6 -7
- package/src/operations/booleans/unionGeom2.js +2 -6
- package/src/operations/booleans/unionGeom2.test.js +25 -1
- package/src/operations/booleans/unionGeom3.js +2 -6
- package/src/operations/booleans/unionGeom3.test.js +6 -1
- package/src/operations/extrusions/extrudeFromSlices.test.js +8 -1
- package/src/operations/extrusions/extrudeHelical.js +2 -8
- package/src/operations/extrusions/extrudeLinear.js +1 -5
- package/src/operations/extrusions/extrudeLinear.test.js +7 -1
- package/src/operations/extrusions/extrudeRotate.js +3 -2
- package/src/operations/extrusions/extrudeRotate.test.js +13 -1
- package/src/operations/extrusions/project.js +1 -5
- package/src/operations/hulls/hull.js +6 -5
- package/src/operations/hulls/hull.test.js +56 -3
- package/src/operations/hulls/hullChain.js +11 -6
- package/src/operations/hulls/hullChain.test.js +12 -2
- package/src/operations/hulls/hullGeom2.js +5 -6
- package/src/operations/hulls/hullGeom3.js +9 -18
- package/src/operations/hulls/hullPath2.js +6 -7
- package/src/operations/hulls/hullPath2.test.js +1 -1
- package/src/operations/hulls/hullPoints2.d.ts +3 -0
- package/src/operations/hulls/hullPoints2.js +4 -2
- package/src/operations/hulls/hullPoints3.d.ts +4 -0
- package/src/operations/hulls/hullPoints3.js +21 -0
- package/src/operations/hulls/index.d.ts +2 -0
- package/src/operations/hulls/index.js +3 -1
- package/src/operations/modifiers/generalize.js +2 -6
- package/src/operations/modifiers/index.js +1 -1
- package/src/operations/modifiers/snap.js +2 -6
- package/src/operations/offsets/offset.js +1 -5
- package/src/operations/offsets/offsetFromPoints.test.js +0 -1
- package/src/operations/offsets/offsetGeom2.test.js +1 -0
- package/src/operations/offsets/offsetGeom3.js +0 -2
- package/src/operations/offsets/offsetGeom3.test.js +9 -1
- package/src/operations/offsets/offsetPath2.js +3 -3
- package/src/operations/transforms/align.js +8 -7
- package/src/operations/transforms/align.test.js +2 -2
- package/src/operations/transforms/center.js +6 -9
- package/src/operations/transforms/center.test.js +19 -1
- package/src/operations/transforms/mirror.js +5 -8
- package/src/operations/transforms/mirror.test.js +7 -7
- package/src/operations/transforms/rotate.js +5 -8
- package/src/operations/transforms/scale.js +5 -8
- package/src/operations/transforms/transform.js +2 -5
- package/src/operations/transforms/translate.js +5 -8
- package/src/primitives/arc.js +2 -0
- package/src/primitives/arc.test.js +11 -11
- package/src/primitives/circle.test.js +18 -8
- package/src/primitives/cube.test.js +10 -0
- package/src/primitives/cuboid.test.js +10 -0
- package/src/primitives/cylinder.test.js +12 -0
- package/src/primitives/cylinderElliptic.test.js +21 -1
- package/src/primitives/ellipse.test.js +18 -8
- package/src/primitives/ellipsoid.test.js +12 -0
- package/src/primitives/geodesicSphere.test.js +8 -0
- package/src/primitives/line.test.js +1 -1
- package/src/primitives/polygon.d.ts +1 -0
- package/src/primitives/polygon.js +13 -4
- package/src/primitives/polygon.test.js +15 -0
- package/src/primitives/polyhedron.js +1 -0
- package/src/primitives/polyhedron.test.js +8 -2
- package/src/primitives/rectangle.test.js +9 -3
- package/src/primitives/roundedCuboid.js +1 -1
- package/src/primitives/roundedCuboid.test.js +20 -4
- package/src/primitives/roundedCylinder.js +1 -1
- package/src/primitives/roundedCylinder.test.js +20 -0
- package/src/primitives/roundedRectangle.js +1 -1
- package/src/primitives/roundedRectangle.test.js +15 -6
- package/src/primitives/sphere.test.js +12 -0
- package/src/primitives/square.test.js +10 -4
- package/src/primitives/star.test.js +14 -6
- package/src/primitives/torus.js +1 -1
- package/src/primitives/torus.test.js +11 -1
- package/src/primitives/triangle.test.js +17 -9
- package/src/utils/coalesce.d.ts +3 -0
- package/src/utils/coalesce.js +20 -0
- package/src/utils/index.js +2 -2
- package/src/maths/mat4/leftMultiplyVec2.d.ts +0 -4
- package/src/maths/mat4/leftMultiplyVec2.js +0 -26
- package/src/maths/mat4/leftMultiplyVec3.d.ts +0 -4
- package/src/maths/mat4/leftMultiplyVec3.js +0 -27
- package/src/maths/mat4/mirror.d.ts +0 -4
- package/src/maths/mat4/mirror.js +0 -32
- package/src/maths/mat4/rightMultiplyVec2.d.ts +0 -4
- package/src/maths/mat4/rightMultiplyVec2.js +0 -27
- package/src/maths/mat4/rightMultiplyVec3.d.ts +0 -4
- package/src/maths/mat4/rightMultiplyVec3.js +0 -28
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { areAllShapesTheSameType } from '../../utils/areAllShapesTheSameType.js'
|
|
2
|
-
import {
|
|
2
|
+
import { coalesce } from '../../utils/coalesce.js'
|
|
3
3
|
|
|
4
4
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
5
5
|
import * as geom3 from '../../geometries/geom3/index.js'
|
|
@@ -13,11 +13,11 @@ import { subtractGeom3 } from './subtractGeom3.js'
|
|
|
13
13
|
* The given geometries should be of the same type, either geom2 or geom3.
|
|
14
14
|
*
|
|
15
15
|
* @param {...Object} geometries - list of geometries
|
|
16
|
-
* @returns {Geom2|
|
|
16
|
+
* @returns {Geom2|Geom3} a new geometry
|
|
17
17
|
* @alias module:modeling/booleans.subtract
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
|
-
* let myshape = subtract(cuboid({size:
|
|
20
|
+
* let myshape = subtract(cuboid({size: 5}), cuboid({size: 5, center: [3,3,3]}))
|
|
21
21
|
*
|
|
22
22
|
* @example
|
|
23
23
|
* +-------+ +-------+
|
|
@@ -30,9 +30,9 @@ import { subtractGeom3 } from './subtractGeom3.js'
|
|
|
30
30
|
* +-------+
|
|
31
31
|
*/
|
|
32
32
|
export const subtract = (...geometries) => {
|
|
33
|
-
geometries =
|
|
34
|
-
if (geometries.length === 0) throw new Error('subtract wrong number of arguments')
|
|
33
|
+
geometries = coalesce(geometries)
|
|
35
34
|
|
|
35
|
+
if (geometries.length === 0) return undefined
|
|
36
36
|
if (!areAllShapesTheSameType(geometries)) {
|
|
37
37
|
throw new Error('subtract arguments must be the same geometry type')
|
|
38
38
|
}
|
|
@@ -4,11 +4,11 @@ import { geom2, geom3 } from '../../geometries/index.js'
|
|
|
4
4
|
|
|
5
5
|
import { subtract } from './index.js'
|
|
6
6
|
|
|
7
|
-
test('subtract
|
|
8
|
-
|
|
9
|
-
t.
|
|
10
|
-
t.
|
|
11
|
-
t.
|
|
7
|
+
test('subtract empty arguments', (t) => {
|
|
8
|
+
t.is(subtract(), undefined)
|
|
9
|
+
t.is(subtract([]), undefined)
|
|
10
|
+
t.is(subtract([[], []]), undefined)
|
|
11
|
+
t.is(subtract(null, null), undefined)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
test('subtract error different geometry types', (t) => {
|
|
@@ -20,6 +20,5 @@ test('subtract error non-geometries', (t) => {
|
|
|
20
20
|
const message = 'subtract unsupported geometry type'
|
|
21
21
|
t.throws(() => subtract([1, 2, 3], [4, 5, 6]), { message })
|
|
22
22
|
t.throws(() => subtract([], [123]), { message })
|
|
23
|
-
t.throws(() => subtract(
|
|
24
|
-
t.throws(() => subtract(null, null), { message })
|
|
23
|
+
t.throws(() => subtract('one', 'two'), { message })
|
|
25
24
|
})
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import { DIFFERENCE } from './martinez/operation.js'
|
|
4
2
|
import { boolean } from './martinez/index.js'
|
|
5
3
|
|
|
6
4
|
/*
|
|
7
5
|
* Return a new 2D geometry representing space in the first geometry but
|
|
8
6
|
* not in the subsequent geometries. None of the given geometries are modified.
|
|
9
|
-
* @param {
|
|
7
|
+
* @param {Geom2[]} geometries - a flat list of 2D geometries
|
|
10
8
|
* @returns {Geom2} new 2D geometry
|
|
11
9
|
*/
|
|
12
|
-
export const subtractGeom2 = (
|
|
13
|
-
geometries = flatten(geometries)
|
|
14
|
-
|
|
10
|
+
export const subtractGeom2 = (geometries) => {
|
|
15
11
|
let newGeometry = geometries.shift()
|
|
16
12
|
geometries.forEach((geometry) => {
|
|
17
13
|
newGeometry = boolean(newGeometry, geometry, DIFFERENCE)
|
|
@@ -6,7 +6,7 @@ import { geom2 } from '../../geometries/index.js'
|
|
|
6
6
|
|
|
7
7
|
import { measureArea } from '../../measurements/index.js'
|
|
8
8
|
|
|
9
|
-
import { circle, rectangle } from '../../primitives/index.js'
|
|
9
|
+
import { circle, rectangle, square } from '../../primitives/index.js'
|
|
10
10
|
|
|
11
11
|
import { center } from '../transforms/index.js'
|
|
12
12
|
|
|
@@ -75,3 +75,27 @@ test('subtract: subtract of one or more geom2 objects produces expected geometry
|
|
|
75
75
|
t.is(obs.length, 0)
|
|
76
76
|
t.deepEqual(obs, exp)
|
|
77
77
|
})
|
|
78
|
+
|
|
79
|
+
test('subtract with undefined/null values', (t) => {
|
|
80
|
+
const square1 = square({ size: 8 })
|
|
81
|
+
const square2 = square({ size: 6 })
|
|
82
|
+
const square3 = square({ size: 4 })
|
|
83
|
+
const geometries = [square1, undefined, square2, null, square3]
|
|
84
|
+
|
|
85
|
+
const obs = subtract(...geometries)
|
|
86
|
+
const pts = geom2.toPoints(obs)
|
|
87
|
+
t.notThrows(() => geom2.validate(obs))
|
|
88
|
+
t.is(pts.length, 8)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('subtract of nested arrays', (t) => {
|
|
92
|
+
const square1 = square({ size: 8 })
|
|
93
|
+
const square2 = square({ size: 6 })
|
|
94
|
+
const square3 = square({ size: 4 })
|
|
95
|
+
const geometries = [square1, [square2, [square3]]]
|
|
96
|
+
|
|
97
|
+
const obs = subtract(...geometries)
|
|
98
|
+
const pts = geom2.toPoints(obs)
|
|
99
|
+
t.notThrows(() => geom2.validate(obs))
|
|
100
|
+
t.is(pts.length, 8)
|
|
101
|
+
})
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import { retessellate } from '../modifiers/retessellate.js'
|
|
4
2
|
|
|
5
3
|
import { subtractGeom3Sub } from './subtractGeom3Sub.js'
|
|
@@ -7,12 +5,10 @@ import { subtractGeom3Sub } from './subtractGeom3Sub.js'
|
|
|
7
5
|
/*
|
|
8
6
|
* Return a new 3D geometry representing space in this geometry but not in the given geometries.
|
|
9
7
|
* Neither this geometry nor the given geometries are modified.
|
|
10
|
-
* @param {
|
|
8
|
+
* @param {Geom3[]} geometries - a flat list of 3D geometries
|
|
11
9
|
* @returns {Geom3} new 3D geometry
|
|
12
10
|
*/
|
|
13
|
-
export const subtractGeom3 = (
|
|
14
|
-
geometries = flatten(geometries)
|
|
15
|
-
|
|
11
|
+
export const subtractGeom3 = (geometries) => {
|
|
16
12
|
let newGeometry = geometries.shift()
|
|
17
13
|
geometries.forEach((geometry) => {
|
|
18
14
|
newGeometry = subtractGeom3Sub(newGeometry, geometry)
|
|
@@ -4,7 +4,7 @@ import { comparePolygonsAsPoints } from '../../../test/helpers/index.js'
|
|
|
4
4
|
|
|
5
5
|
import { geom3 } from '../../geometries/index.js'
|
|
6
6
|
|
|
7
|
-
import { measureVolume } from '../../measurements/index.js'
|
|
7
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
8
8
|
|
|
9
9
|
import { sphere, cuboid } from '../../primitives/index.js'
|
|
10
10
|
|
|
@@ -69,6 +69,7 @@ test('subtract: subtract of one or more geom3 objects produces expected geometry
|
|
|
69
69
|
[[8.65956056235493e-17, 8.659560562354935e-17, 2], [1.4142135623730951, 3.4638242249419736e-16, 1.414213562373095], [0.9999999999999998, 1.0000000000000002, 1.414213562373095]]
|
|
70
70
|
]
|
|
71
71
|
t.notThrows.skip(() => geom3.validate(result1))
|
|
72
|
+
t.is(measureArea(result1), 44.053756306589825)
|
|
72
73
|
t.is(measureVolume(result1), 25.751611331979678)
|
|
73
74
|
t.is(obs.length, 32)
|
|
74
75
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -79,6 +80,7 @@ test('subtract: subtract of one or more geom3 objects produces expected geometry
|
|
|
79
80
|
const result2 = subtract(geometry1, geometry2)
|
|
80
81
|
obs = geom3.toPoints(result2)
|
|
81
82
|
t.notThrows.skip(() => geom3.validate(result2))
|
|
83
|
+
t.is(measureArea(result2), 44.053756306589825)
|
|
82
84
|
t.is(measureVolume(result2), 25.751611331979678)
|
|
83
85
|
t.is(obs.length, 32)
|
|
84
86
|
|
|
@@ -102,6 +104,7 @@ test('subtract: subtract of one or more geom3 objects produces expected geometry
|
|
|
102
104
|
[[12, 9, 8], [12, 8, 8], [9, 8, 8], [9, 9, 8]]
|
|
103
105
|
]
|
|
104
106
|
t.notThrows.skip(() => geom3.validate(result3))
|
|
107
|
+
t.is(measureArea(result3), 96)
|
|
105
108
|
t.is(measureVolume(result3), 63)
|
|
106
109
|
t.is(obs.length, 12)
|
|
107
110
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -110,6 +113,7 @@ test('subtract: subtract of one or more geom3 objects produces expected geometry
|
|
|
110
113
|
const result4 = subtract(geometry1, geometry3)
|
|
111
114
|
obs = geom3.toPoints(result4)
|
|
112
115
|
t.notThrows(() => geom3.validate(result4))
|
|
116
|
+
t.is(measureArea(result4), 0)
|
|
113
117
|
t.is(measureVolume(result4), 0)
|
|
114
118
|
t.is(obs.length, 0)
|
|
115
119
|
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Poly3 } from '../../../geometries/types';
|
|
2
|
+
import { Plane } from '../../../maths/types';
|
|
3
|
+
|
|
4
|
+
enum ResType
|
|
5
|
+
{
|
|
6
|
+
coplanar_front = 0,
|
|
7
|
+
coplanar_back = 1,
|
|
8
|
+
front = 2,
|
|
9
|
+
back = 3,
|
|
10
|
+
spanning = 4,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
interface SplitRes
|
|
15
|
+
{
|
|
16
|
+
type: ResType,
|
|
17
|
+
front: Poly3,
|
|
18
|
+
back: Poly3;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Returns object:
|
|
22
|
+
// .type:
|
|
23
|
+
// 0: coplanar-front
|
|
24
|
+
// 1: coplanar-back
|
|
25
|
+
// 2: front
|
|
26
|
+
// 3: back
|
|
27
|
+
// 4: spanning
|
|
28
|
+
// In case the polygon is spanning, returns:
|
|
29
|
+
// .front: a Polygon3 of the front part
|
|
30
|
+
// .back: a Polygon3 of the back part
|
|
31
|
+
declare function splitPolygonByPlane(plane: Plane, polygon: Poly3): SplitRes;
|
|
32
|
+
|
|
33
|
+
export default splitPolygonByPlane;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { areAllShapesTheSameType } from '../../utils/areAllShapesTheSameType.js'
|
|
2
|
-
import {
|
|
2
|
+
import { coalesce } from '../../utils/coalesce.js'
|
|
3
3
|
|
|
4
4
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
5
5
|
import * as geom3 from '../../geometries/geom3/index.js'
|
|
@@ -12,11 +12,11 @@ import { unionGeom3 } from './unionGeom3.js'
|
|
|
12
12
|
* The given geometries should be of the same type, either geom2 or geom3.
|
|
13
13
|
*
|
|
14
14
|
* @param {...Object} geometries - list of geometries
|
|
15
|
-
* @returns {Geom2|
|
|
15
|
+
* @returns {Geom2|Geom3} a new geometry
|
|
16
16
|
* @alias module:modeling/booleans.union
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
|
-
* let myshape = union(cube({size:
|
|
19
|
+
* let myshape = union(cube({size: 5}), cube({size: 5, center: [3,3,3]}))
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
22
22
|
* +-------+ +-------+
|
|
@@ -29,9 +29,9 @@ import { unionGeom3 } from './unionGeom3.js'
|
|
|
29
29
|
* +-------+ +-------+
|
|
30
30
|
*/
|
|
31
31
|
export const union = (...geometries) => {
|
|
32
|
-
geometries =
|
|
33
|
-
if (geometries.length === 0) throw new Error('union wrong number of arguments')
|
|
32
|
+
geometries = coalesce(geometries)
|
|
34
33
|
|
|
34
|
+
if (geometries.length === 0) return undefined
|
|
35
35
|
if (!areAllShapesTheSameType(geometries)) {
|
|
36
36
|
throw new Error('union arguments must be the same geometry type')
|
|
37
37
|
}
|
|
@@ -4,11 +4,11 @@ import { geom2, geom3 } from '../../geometries/index.js'
|
|
|
4
4
|
|
|
5
5
|
import { union } from './index.js'
|
|
6
6
|
|
|
7
|
-
test('union
|
|
8
|
-
|
|
9
|
-
t.
|
|
10
|
-
t.
|
|
11
|
-
t.
|
|
7
|
+
test('union empty arguments', (t) => {
|
|
8
|
+
t.is(union(), undefined)
|
|
9
|
+
t.is(union([]), undefined)
|
|
10
|
+
t.is(union([[], []]), undefined)
|
|
11
|
+
t.is(union(null, null), undefined)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
test('union error different geometry types', (t) => {
|
|
@@ -20,6 +20,5 @@ test('union error non-geometries', (t) => {
|
|
|
20
20
|
const message = 'union unsupported geometry type'
|
|
21
21
|
t.throws(() => union([1, 2, 3], [4, 5, 6]), { message })
|
|
22
22
|
t.throws(() => union([], [123]), { message })
|
|
23
|
-
t.throws(() => union(
|
|
24
|
-
t.throws(() => union(null, null), { message })
|
|
23
|
+
t.throws(() => union('one', 'two'), { message })
|
|
25
24
|
})
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import { UNION } from './martinez/operation.js'
|
|
4
2
|
import { boolean } from './martinez/index.js'
|
|
5
3
|
|
|
6
4
|
/*
|
|
7
5
|
* Return a new 2D geometry representing the total space in the given 2D geometries.
|
|
8
|
-
* @param {
|
|
6
|
+
* @param {Geom2[]} geometries - a flat list of 2D geometries to union
|
|
9
7
|
* @returns {Geom2} new 2D geometry
|
|
10
8
|
*/
|
|
11
|
-
export const unionGeom2 = (
|
|
12
|
-
geometries = flatten(geometries)
|
|
13
|
-
|
|
9
|
+
export const unionGeom2 = (geometries) => {
|
|
14
10
|
let newGeometry = geometries.shift()
|
|
15
11
|
geometries.forEach((geometry) => {
|
|
16
12
|
newGeometry = boolean(newGeometry, geometry, UNION)
|
|
@@ -6,7 +6,7 @@ import { geom2 } from '../../geometries/index.js'
|
|
|
6
6
|
|
|
7
7
|
import { measureArea } from '../../measurements/index.js'
|
|
8
8
|
|
|
9
|
-
import { circle, rectangle } from '../../primitives/index.js'
|
|
9
|
+
import { circle, rectangle, square } from '../../primitives/index.js'
|
|
10
10
|
|
|
11
11
|
import { center, translate } from '../transforms/index.js'
|
|
12
12
|
|
|
@@ -209,3 +209,27 @@ test('union of geom2 with colinear edge (martinez issue #155)', (t) => {
|
|
|
209
209
|
t.is(pts.length, 5)
|
|
210
210
|
t.true(comparePoints(pts, exp))
|
|
211
211
|
})
|
|
212
|
+
|
|
213
|
+
test('union with undefined/null values', (t) => {
|
|
214
|
+
const square1 = square({ size: 8 })
|
|
215
|
+
const square2 = square({ size: 6 })
|
|
216
|
+
const square3 = square({ size: 4 })
|
|
217
|
+
const geometries = [square1, undefined, square2, null, square3]
|
|
218
|
+
|
|
219
|
+
const obs = union(...geometries)
|
|
220
|
+
const pts = geom2.toPoints(obs)
|
|
221
|
+
t.notThrows(() => geom2.validate(obs))
|
|
222
|
+
t.is(pts.length, 4)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('union of nested arrays', (t) => {
|
|
226
|
+
const square1 = square({ size: 8 })
|
|
227
|
+
const square2 = square({ size: 6 })
|
|
228
|
+
const square3 = square({ size: 4 })
|
|
229
|
+
const geometries = [square1, [square2, [square3]]]
|
|
230
|
+
|
|
231
|
+
const obs = union(...geometries)
|
|
232
|
+
const pts = geom2.toPoints(obs)
|
|
233
|
+
t.notThrows(() => geom2.validate(obs))
|
|
234
|
+
t.is(pts.length, 4)
|
|
235
|
+
})
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import { retessellate } from '../modifiers/retessellate.js'
|
|
4
2
|
|
|
5
3
|
import { unionGeom3Sub } from './unionGeom3Sub.js'
|
|
6
4
|
|
|
7
5
|
/*
|
|
8
6
|
* Return a new 3D geometry representing the space in the given 3D geometries.
|
|
9
|
-
* @param {
|
|
7
|
+
* @param {Geom3[]} geometries - a flat list of 3D geometries to union
|
|
10
8
|
* @returns {Geom3} new 3D geometry
|
|
11
9
|
*/
|
|
12
|
-
export const unionGeom3 = (
|
|
13
|
-
geometries = flatten(geometries)
|
|
14
|
-
|
|
10
|
+
export const unionGeom3 = (geometries) => {
|
|
15
11
|
// combine geometries in a way that forms a balanced binary tree pattern
|
|
16
12
|
let i
|
|
17
13
|
for (i = 1; i < geometries.length; i += 2) {
|
|
@@ -4,7 +4,7 @@ import { comparePolygonsAsPoints } from '../../../test/helpers/index.js'
|
|
|
4
4
|
|
|
5
5
|
import { geom3 } from '../../geometries/index.js'
|
|
6
6
|
|
|
7
|
-
import { measureVolume } from '../../measurements/index.js'
|
|
7
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
8
8
|
|
|
9
9
|
import { sphere, cuboid } from '../../primitives/index.js'
|
|
10
10
|
|
|
@@ -69,6 +69,7 @@ test('union of one or more geom3 objects produces expected geometry', (t) => {
|
|
|
69
69
|
[[8.65956056235493e-17, 8.659560562354935e-17, 2], [1.4142135623730951, 3.4638242249419736e-16, 1.414213562373095], [0.9999999999999998, 1.0000000000000002, 1.414213562373095]]
|
|
70
70
|
]
|
|
71
71
|
t.notThrows.skip(() => geom3.validate(result1))
|
|
72
|
+
t.is(measureArea(result1), 44.053756306589825)
|
|
72
73
|
t.is(measureVolume(result1), 25.751611331979678)
|
|
73
74
|
t.is(obs.length, 32)
|
|
74
75
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -79,6 +80,7 @@ test('union of one or more geom3 objects produces expected geometry', (t) => {
|
|
|
79
80
|
const result2 = union(geometry1, geometry2)
|
|
80
81
|
obs = geom3.toPoints(result2)
|
|
81
82
|
t.notThrows.skip(() => geom3.validate(result2))
|
|
83
|
+
t.is(measureArea(result2), 140.05375630658983)
|
|
82
84
|
t.is(measureVolume(result2), 89.75161133197969)
|
|
83
85
|
t.is(obs.length, 38)
|
|
84
86
|
|
|
@@ -108,6 +110,7 @@ test('union of one or more geom3 objects produces expected geometry', (t) => {
|
|
|
108
110
|
[[-9, 8, 9], [-9, -9, 9], [9, -9, 9], [9, 8, 9]]
|
|
109
111
|
]
|
|
110
112
|
t.notThrows.skip(() => geom3.validate(result3))
|
|
113
|
+
t.is(measureArea(result3), 2034)
|
|
111
114
|
t.is(measureVolume(result3), 5895)
|
|
112
115
|
t.is(obs.length, 18)
|
|
113
116
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -124,6 +127,7 @@ test('union of one or more geom3 objects produces expected geometry', (t) => {
|
|
|
124
127
|
[[-9, -9, 9], [9, -9, 9], [9, 9, 9], [-9, 9, 9]]
|
|
125
128
|
]
|
|
126
129
|
t.notThrows(() => geom3.validate(result4))
|
|
130
|
+
t.is(measureArea(result4), 1944)
|
|
127
131
|
t.is(measureVolume(result4), 5832)
|
|
128
132
|
t.is(obs.length, 6)
|
|
129
133
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -136,6 +140,7 @@ test('union of geom3 with rounding issues #137', (t) => {
|
|
|
136
140
|
const result = union(geometry1, geometry2)
|
|
137
141
|
const pts = geom3.toPoints(result)
|
|
138
142
|
t.notThrows(() => geom3.validate(result))
|
|
143
|
+
t.is(measureArea(result), 3240.00014)
|
|
139
144
|
t.is(measureVolume(result), 7779.201144000001)
|
|
140
145
|
t.is(pts.length, 6) // number of polygons in union
|
|
141
146
|
})
|
|
@@ -7,7 +7,7 @@ import { mat4 } from '../../maths/index.js'
|
|
|
7
7
|
|
|
8
8
|
import { geom2, geom3, poly3, slice } from '../../geometries/index.js'
|
|
9
9
|
|
|
10
|
-
import { measureVolume } from '../../measurements/index.js'
|
|
10
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
11
11
|
|
|
12
12
|
import { circle, square } from '../../primitives/index.js'
|
|
13
13
|
|
|
@@ -32,6 +32,8 @@ test('extrudeFromSlices (defaults)', (t) => {
|
|
|
32
32
|
[[-10, -10, 0], [-10, 10, 0], [10, 10, 0]],
|
|
33
33
|
[[10, 10, 0], [10, -10, 0], [-10, -10, 0]]
|
|
34
34
|
]
|
|
35
|
+
t.is(measureArea(geometry3), 880)
|
|
36
|
+
t.is(measureVolume(geometry3), 400.00000000000006)
|
|
35
37
|
t.is(pts.length, 12)
|
|
36
38
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
37
39
|
|
|
@@ -40,6 +42,7 @@ test('extrudeFromSlices (defaults)', (t) => {
|
|
|
40
42
|
pts = geom3.toPoints(geometry3)
|
|
41
43
|
|
|
42
44
|
t.notThrows(() => geom3.validate(geometry3))
|
|
45
|
+
t.is(measureArea(geometry3), 880)
|
|
43
46
|
t.is(measureVolume(geometry3), 400.00000000000006)
|
|
44
47
|
t.is(pts.length, 12)
|
|
45
48
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -74,6 +77,7 @@ test('extrudeFromSlices (torus)', (t) => {
|
|
|
74
77
|
)
|
|
75
78
|
const pts = geom3.toPoints(geometry3)
|
|
76
79
|
t.notThrows(() => geom3.validate(geometry3))
|
|
80
|
+
t.is(measureArea(geometry3), 7070.694617452831)
|
|
77
81
|
t.is(measureVolume(geometry3), 29393.876913398108)
|
|
78
82
|
t.is(pts.length, 96)
|
|
79
83
|
})
|
|
@@ -95,6 +99,7 @@ test('extrudeFromSlices (same shape, changing dimensions)', (t) => {
|
|
|
95
99
|
const pts = geom3.toPoints(geometry3)
|
|
96
100
|
// expected to throw because capEnd is false (non-closed geometry)
|
|
97
101
|
t.throws(() => geom3.validate(geometry3))
|
|
102
|
+
t.is(measureArea(geometry3), 53.70100297794013)
|
|
98
103
|
t.is(measureVolume(geometry3), 8.5)
|
|
99
104
|
t.is(pts.length, 26)
|
|
100
105
|
})
|
|
@@ -114,6 +119,7 @@ test('extrudeFromSlices (changing shape, changing dimensions)', (t) => {
|
|
|
114
119
|
)
|
|
115
120
|
const pts = geom3.toPoints(geometry3)
|
|
116
121
|
t.notThrows.skip(() => geom3.validate(geometry3))
|
|
122
|
+
t.is(measureArea(geometry3), 1965.8643589631802)
|
|
117
123
|
t.is(measureVolume(geometry3), 5260.067107417433)
|
|
118
124
|
t.is(pts.length, 304)
|
|
119
125
|
})
|
|
@@ -160,6 +166,7 @@ test('extrudeFromSlices (holes)', (t) => {
|
|
|
160
166
|
[[-5, -5, 0], [5, -5, 0], [-10, -10, 0]]
|
|
161
167
|
]
|
|
162
168
|
t.notThrows(() => geom3.validate(geometry3))
|
|
169
|
+
t.is(measureArea(geometry3), 720)
|
|
163
170
|
t.is(measureVolume(geometry3), 300)
|
|
164
171
|
t.is(pts.length, 32)
|
|
165
172
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -19,14 +19,8 @@ import { extrudeFromSlices } from './extrudeFromSlices.js'
|
|
|
19
19
|
* @alias module:modeling/extrusions.extrudeHelical
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
22
|
-
* const myshape =
|
|
23
|
-
*
|
|
24
|
-
* angle: Math.PI * 4,
|
|
25
|
-
* pitch: 10,
|
|
26
|
-
* segmentsPerRotation: 64
|
|
27
|
-
* },
|
|
28
|
-
* circle({size: 3, center: [10, 0]})
|
|
29
|
-
* )
|
|
22
|
+
* const myshape = circle({size: 3, center: [10, 0]}) // position for extrusion about Z
|
|
23
|
+
* const mycoil = extrudeHelical({angle: TAU*2, pitch: 10, segmentsPerRotation: 64}, myshape))
|
|
30
24
|
*/
|
|
31
25
|
export const extrudeHelical = (options, geometry) => {
|
|
32
26
|
const defaults = {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
4
2
|
import * as path2 from '../../geometries/path2/index.js'
|
|
5
3
|
|
|
@@ -30,15 +28,13 @@ export const extrudeLinear = (options, ...objects) => {
|
|
|
30
28
|
}
|
|
31
29
|
const { height, twistAngle, twistSteps, repair } = Object.assign({ }, defaults, options)
|
|
32
30
|
|
|
33
|
-
objects = flatten(objects)
|
|
34
|
-
if (objects.length === 0) throw new Error('wrong number of arguments')
|
|
35
|
-
|
|
36
31
|
options = { offset: [0, 0, height], twistAngle, twistSteps, repair }
|
|
37
32
|
|
|
38
33
|
const results = objects.map((object) => {
|
|
39
34
|
if (path2.isA(object)) return extrudeLinearPath2(options, object)
|
|
40
35
|
if (geom2.isA(object)) return extrudeLinearGeom2(options, object)
|
|
41
36
|
// if (geom3.isA(object)) return geom3.extrude(options, object)
|
|
37
|
+
if (Array.isArray(object)) return extrudeLinear(options, ...object)
|
|
42
38
|
return object
|
|
43
39
|
})
|
|
44
40
|
return results.length === 1 ? results[0] : results
|
|
@@ -8,7 +8,7 @@ import { colorize } from '../../colors/index.js'
|
|
|
8
8
|
|
|
9
9
|
import { geom2, geom3, path2 } from '../../geometries/index.js'
|
|
10
10
|
|
|
11
|
-
import { measureVolume } from '../../measurements/index.js'
|
|
11
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
12
12
|
|
|
13
13
|
import { square } from '../../primitives/index.js'
|
|
14
14
|
|
|
@@ -34,6 +34,7 @@ test('extrudeLinear (defaults)', (t) => {
|
|
|
34
34
|
[[5, 5, 0], [5, -5, 0], [-5, -5, 0]]
|
|
35
35
|
]
|
|
36
36
|
t.notThrows(() => geom3.validate(geometry3))
|
|
37
|
+
t.is(measureArea(geometry3), 240)
|
|
37
38
|
t.is(measureVolume(geometry3), 100.00000000000001)
|
|
38
39
|
t.is(pts.length, 12)
|
|
39
40
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -70,6 +71,7 @@ test('extrudeLinear (no twist)', (t) => {
|
|
|
70
71
|
[[5, 5, 0], [5, -5, 0], [-5, -5, 0]]
|
|
71
72
|
]
|
|
72
73
|
t.notThrows(() => geom3.validate(geometry3))
|
|
74
|
+
t.is(measureArea(geometry3), 800)
|
|
73
75
|
t.is(measureVolume(geometry3), 1500)
|
|
74
76
|
t.is(pts.length, 12)
|
|
75
77
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -91,6 +93,7 @@ test('extrudeLinear (no twist)', (t) => {
|
|
|
91
93
|
[[5, -5, 0], [5, 5, 0], [-5, 5, 0]]
|
|
92
94
|
]
|
|
93
95
|
t.notThrows(() => geom3.validate(geometry3))
|
|
96
|
+
t.is(measureArea(geometry3), 800)
|
|
94
97
|
t.is(measureVolume(geometry3), 1500)
|
|
95
98
|
t.is(pts.length, 12)
|
|
96
99
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -124,6 +127,7 @@ test('extrudeLinear (twist)', (t) => {
|
|
|
124
127
|
[[5, 5, 0], [5, -5, 0], [-5, -5, 0]]
|
|
125
128
|
]
|
|
126
129
|
t.notThrows(() => geom3.validate(geometry3))
|
|
130
|
+
t.is(measureArea(geometry3), 805.6920958788816)
|
|
127
131
|
t.is(measureVolume(geometry3), 1707.1067811865476)
|
|
128
132
|
t.is(pts.length, 12)
|
|
129
133
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -166,6 +170,7 @@ test('extrudeLinear (twist)', (t) => {
|
|
|
166
170
|
geometry3 = extrudeLinear({ height: 15, twistAngle: TAU / 2, twistSteps: 30 }, geometry2)
|
|
167
171
|
pts = geom3.toPoints(geometry3)
|
|
168
172
|
t.notThrows(() => geom3.validate(geometry3))
|
|
173
|
+
t.is(measureArea(geometry3), 1091.9932843446968)
|
|
169
174
|
t.is(measureVolume(geometry3), 1444.9967160503095)
|
|
170
175
|
t.is(pts.length, 244)
|
|
171
176
|
})
|
|
@@ -212,6 +217,7 @@ test('extrudeLinear (holes)', (t) => {
|
|
|
212
217
|
[[-2, -2, 0], [2, -2, 0], [-5, -5, 0]]
|
|
213
218
|
]
|
|
214
219
|
t.notThrows(() => geom3.validate(geometry3))
|
|
220
|
+
t.is(measureArea(geometry3), 1008)
|
|
215
221
|
t.is(measureVolume(geometry3), 1260)
|
|
216
222
|
t.is(pts.length, 32)
|
|
217
223
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -4,6 +4,7 @@ import * as mat4 from '../../maths/mat4/index.js'
|
|
|
4
4
|
import { mirrorX } from '../transforms/mirror.js'
|
|
5
5
|
|
|
6
6
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
7
|
+
import * as geom3 from '../../geometries/geom3/index.js'
|
|
7
8
|
import * as slice from '../../geometries/slice/index.js'
|
|
8
9
|
|
|
9
10
|
import { extrudeFromSlices } from './extrudeFromSlices.js'
|
|
@@ -58,7 +59,7 @@ export const extrudeRotate = (options, geometry) => {
|
|
|
58
59
|
|
|
59
60
|
// convert geometry to an array of sides, easier to deal with
|
|
60
61
|
let shapeSides = geom2.toSides(geometry)
|
|
61
|
-
if (shapeSides.length === 0) return
|
|
62
|
+
if (shapeSides.length === 0) return geom3.create()
|
|
62
63
|
let sliceGeometry = geometry
|
|
63
64
|
|
|
64
65
|
// determine if the extrusion can be computed in the first place
|
|
@@ -88,7 +89,7 @@ export const extrudeRotate = (options, geometry) => {
|
|
|
88
89
|
return [point0, point1]
|
|
89
90
|
})
|
|
90
91
|
// recreate the geometry from the (-) capped points
|
|
91
|
-
sliceGeometry = geom2.
|
|
92
|
+
sliceGeometry = geom2.fromSides(shapeSides)
|
|
92
93
|
sliceGeometry = mirrorX(sliceGeometry)
|
|
93
94
|
} else if (pointsWithPositiveX.length >= pointsWithNegativeX.length) {
|
|
94
95
|
shapeSides = shapeSides.map((side) => {
|