@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.
Files changed (134) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +1 -1
  3. package/dist/jscad-modeling.es.js +2 -2
  4. package/dist/jscad-modeling.min.js +2 -2
  5. package/package.json +2 -2
  6. package/rollup.config.js +1 -1
  7. package/src/colors/colorize.js +1 -5
  8. package/src/colors/colorize.test.js +8 -8
  9. package/src/geometries/geom2/transform.js +9 -1
  10. package/src/geometries/geom2/transform.test.js +57 -0
  11. package/src/geometries/geom3/fromPointsConvex.d.ts +4 -0
  12. package/src/geometries/geom3/fromPointsConvex.js +25 -0
  13. package/src/geometries/geom3/fromPointsConvex.test.js +32 -0
  14. package/src/geometries/geom3/index.d.ts +1 -0
  15. package/src/geometries/geom3/index.js +1 -0
  16. package/src/geometries/index.js +3 -4
  17. package/src/geometries/poly3/type.d.ts +1 -1
  18. package/src/geometries/slice/validate.js +1 -2
  19. package/src/maths/index.js +1 -0
  20. package/src/maths/mat4/isOnlyTransformScale.js +1 -1
  21. package/src/measurements/measureAggregateArea.js +0 -1
  22. package/src/measurements/measureAggregateBoundingBox.js +0 -1
  23. package/src/measurements/measureAggregateEpsilon.js +0 -1
  24. package/src/measurements/measureAggregateVolume.js +0 -1
  25. package/src/measurements/measureArea.js +0 -1
  26. package/src/measurements/measureBoundingBox.js +0 -1
  27. package/src/measurements/measureEpsilon.js +0 -1
  28. package/src/measurements/measureVolume.js +0 -1
  29. package/src/operations/booleans/index.d.ts +1 -0
  30. package/src/operations/booleans/intersect.js +5 -5
  31. package/src/operations/booleans/intersect.test.js +6 -7
  32. package/src/operations/booleans/intersectGeom2.js +2 -6
  33. package/src/operations/booleans/intersectGeom2.test.js +25 -1
  34. package/src/operations/booleans/intersectGeom3.js +2 -6
  35. package/src/operations/booleans/intersectGeom3.test.js +5 -1
  36. package/src/operations/booleans/mayOverlap.js +0 -1
  37. package/src/operations/booleans/scission.d.ts +5 -0
  38. package/src/operations/booleans/scission.js +3 -5
  39. package/src/operations/booleans/scission.test.js +6 -0
  40. package/src/operations/booleans/subtract.js +5 -5
  41. package/src/operations/booleans/subtract.test.js +6 -7
  42. package/src/operations/booleans/subtractGeom2.js +2 -6
  43. package/src/operations/booleans/subtractGeom2.test.js +25 -1
  44. package/src/operations/booleans/subtractGeom3.js +2 -6
  45. package/src/operations/booleans/subtractGeom3.test.js +5 -1
  46. package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +33 -0
  47. package/src/operations/booleans/union.js +5 -5
  48. package/src/operations/booleans/union.test.js +6 -7
  49. package/src/operations/booleans/unionGeom2.js +2 -6
  50. package/src/operations/booleans/unionGeom2.test.js +25 -1
  51. package/src/operations/booleans/unionGeom3.js +2 -6
  52. package/src/operations/booleans/unionGeom3.test.js +6 -1
  53. package/src/operations/extrusions/extrudeFromSlices.test.js +8 -1
  54. package/src/operations/extrusions/extrudeHelical.js +2 -8
  55. package/src/operations/extrusions/extrudeLinear.js +1 -5
  56. package/src/operations/extrusions/extrudeLinear.test.js +7 -1
  57. package/src/operations/extrusions/extrudeRotate.js +3 -2
  58. package/src/operations/extrusions/extrudeRotate.test.js +13 -1
  59. package/src/operations/extrusions/project.js +1 -5
  60. package/src/operations/hulls/hull.js +6 -5
  61. package/src/operations/hulls/hull.test.js +56 -3
  62. package/src/operations/hulls/hullChain.js +11 -6
  63. package/src/operations/hulls/hullChain.test.js +12 -2
  64. package/src/operations/hulls/hullGeom2.js +5 -6
  65. package/src/operations/hulls/hullGeom3.js +9 -18
  66. package/src/operations/hulls/hullPath2.js +6 -7
  67. package/src/operations/hulls/hullPath2.test.js +1 -1
  68. package/src/operations/hulls/hullPoints2.d.ts +3 -0
  69. package/src/operations/hulls/hullPoints2.js +4 -2
  70. package/src/operations/hulls/hullPoints3.d.ts +4 -0
  71. package/src/operations/hulls/hullPoints3.js +21 -0
  72. package/src/operations/hulls/index.d.ts +2 -0
  73. package/src/operations/hulls/index.js +3 -1
  74. package/src/operations/modifiers/generalize.js +2 -6
  75. package/src/operations/modifiers/index.js +1 -1
  76. package/src/operations/modifiers/snap.js +2 -6
  77. package/src/operations/offsets/offset.js +1 -5
  78. package/src/operations/offsets/offsetFromPoints.test.js +0 -1
  79. package/src/operations/offsets/offsetGeom2.test.js +1 -0
  80. package/src/operations/offsets/offsetGeom3.js +0 -2
  81. package/src/operations/offsets/offsetGeom3.test.js +9 -1
  82. package/src/operations/offsets/offsetPath2.js +3 -3
  83. package/src/operations/transforms/align.js +8 -7
  84. package/src/operations/transforms/align.test.js +2 -2
  85. package/src/operations/transforms/center.js +6 -9
  86. package/src/operations/transforms/center.test.js +19 -1
  87. package/src/operations/transforms/mirror.js +5 -8
  88. package/src/operations/transforms/mirror.test.js +7 -7
  89. package/src/operations/transforms/rotate.js +5 -8
  90. package/src/operations/transforms/scale.js +5 -8
  91. package/src/operations/transforms/transform.js +2 -5
  92. package/src/operations/transforms/translate.js +5 -8
  93. package/src/primitives/arc.js +2 -0
  94. package/src/primitives/arc.test.js +11 -11
  95. package/src/primitives/circle.test.js +18 -8
  96. package/src/primitives/cube.test.js +10 -0
  97. package/src/primitives/cuboid.test.js +10 -0
  98. package/src/primitives/cylinder.test.js +12 -0
  99. package/src/primitives/cylinderElliptic.test.js +21 -1
  100. package/src/primitives/ellipse.test.js +18 -8
  101. package/src/primitives/ellipsoid.test.js +12 -0
  102. package/src/primitives/geodesicSphere.test.js +8 -0
  103. package/src/primitives/line.test.js +1 -1
  104. package/src/primitives/polygon.d.ts +1 -0
  105. package/src/primitives/polygon.js +13 -4
  106. package/src/primitives/polygon.test.js +15 -0
  107. package/src/primitives/polyhedron.js +1 -0
  108. package/src/primitives/polyhedron.test.js +8 -2
  109. package/src/primitives/rectangle.test.js +9 -3
  110. package/src/primitives/roundedCuboid.js +1 -1
  111. package/src/primitives/roundedCuboid.test.js +20 -4
  112. package/src/primitives/roundedCylinder.js +1 -1
  113. package/src/primitives/roundedCylinder.test.js +20 -0
  114. package/src/primitives/roundedRectangle.js +1 -1
  115. package/src/primitives/roundedRectangle.test.js +15 -6
  116. package/src/primitives/sphere.test.js +12 -0
  117. package/src/primitives/square.test.js +10 -4
  118. package/src/primitives/star.test.js +14 -6
  119. package/src/primitives/torus.js +1 -1
  120. package/src/primitives/torus.test.js +11 -1
  121. package/src/primitives/triangle.test.js +17 -9
  122. package/src/utils/coalesce.d.ts +3 -0
  123. package/src/utils/coalesce.js +20 -0
  124. package/src/utils/index.js +2 -2
  125. package/src/maths/mat4/leftMultiplyVec2.d.ts +0 -4
  126. package/src/maths/mat4/leftMultiplyVec2.js +0 -26
  127. package/src/maths/mat4/leftMultiplyVec3.d.ts +0 -4
  128. package/src/maths/mat4/leftMultiplyVec3.js +0 -27
  129. package/src/maths/mat4/mirror.d.ts +0 -4
  130. package/src/maths/mat4/mirror.js +0 -32
  131. package/src/maths/mat4/rightMultiplyVec2.d.ts +0 -4
  132. package/src/maths/mat4/rightMultiplyVec2.js +0 -27
  133. package/src/maths/mat4/rightMultiplyVec3.d.ts +0 -4
  134. package/src/maths/mat4/rightMultiplyVec3.js +0 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jscad/modeling",
3
- "version": "3.0.0-alpha.0",
3
+ "version": "3.0.1-alpha.0",
4
4
  "description": "Constructive Solid Geometry (CSG) Library for JSCAD",
5
5
  "homepage": "https://openjscad.xyz/",
6
6
  "repository": "https://github.com/jscad/OpenJSCAD.org",
@@ -64,5 +64,5 @@
64
64
  "rollup": "^2.79.1",
65
65
  "rollup-plugin-banner": "^0.2.1"
66
66
  },
67
- "gitHead": "3656d36ed9cd738ab884e86aae5a2ce08d52adf7"
67
+ "gitHead": "0660b5c1f1a5faf54d4cfae1cb85bb94182a8d32"
68
68
  }
package/rollup.config.js CHANGED
@@ -16,6 +16,6 @@ export default {
16
16
  ],
17
17
  plugins: [
18
18
  banner('<%= pkg.description %>\n@module <%= pkg.name %>\n@version <%= pkg.version %>\n@license <%= pkg.license %>'),
19
- terser({ compress: { module: true }, mangle: false, format: { comments: 'some'} })
19
+ terser({ compress: { module: true }, mangle: false, format: { comments: 'some' } })
20
20
  ]
21
21
  }
@@ -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 geom3 from '../geometries/geom3/index.js'
5
3
  import * as path2 from '../geometries/path2/index.js'
@@ -47,14 +45,12 @@ export const colorize = (color, ...objects) => {
47
45
  if (color.length < 3) throw new Error('color must contain R, G and B values')
48
46
  if (color.length === 3) color = [color[0], color[1], color[2], 1.0] // add alpha
49
47
 
50
- objects = flatten(objects)
51
- if (objects.length === 0) throw new Error('wrong number of arguments')
52
-
53
48
  const results = objects.map((object) => {
54
49
  if (geom2.isA(object)) return colorGeom2(color, object)
55
50
  if (geom3.isA(object)) return colorGeom3(color, object)
56
51
  if (path2.isA(object)) return colorPath2(color, object)
57
52
  if (poly3.isA(object)) return colorPoly3(color, object)
53
+ if (Array.isArray(object)) return colorize(color, ...object)
58
54
 
59
55
  object.color = color
60
56
  return object
@@ -11,14 +11,12 @@ test('color (rgb on objects)', (t) => {
11
11
  const obs = colorize([1, 0, 0], obj1, obj2)
12
12
  const exp1 = { color: [1, 0, 0, 1] }
13
13
  const exp2 = { id: 'a', color: [1, 0, 0, 1] }
14
+ const exp3 = { id: 'b', color: [1, 0, 0, 1] }
14
15
 
15
- t.is(obs.length, 3)
16
+ t.is(obs.length, 2)
16
17
  t.deepEqual(obs[0], exp1)
17
- t.deepEqual(obs[1], exp2)
18
-
19
- const obs3 = colorize([1, 0, 0], obj1)
20
- const exp3 = { color: [1, 0, 0, 1] }
21
- t.deepEqual(obs3, exp3)
18
+ t.deepEqual(obs[1][0], exp2)
19
+ t.deepEqual(obs[1][1], exp3)
22
20
  })
23
21
 
24
22
  test('color (rgba on objects)', (t) => {
@@ -28,10 +26,12 @@ test('color (rgba on objects)', (t) => {
28
26
  const obs = colorize([1, 1, 0.5, 0.8], obj1, obj2)
29
27
  const exp1 = { color: [1, 1, 0.5, 0.8] }
30
28
  const exp2 = { id: 'a', color: [1, 1, 0.5, 0.8] }
29
+ const exp3 = { id: 'b', color: [1, 1, 0.5, 0.8] }
31
30
 
32
- t.is(obs.length, 3)
31
+ t.is(obs.length, 2)
33
32
  t.deepEqual(obs[0], exp1)
34
- t.deepEqual(obs[1], exp2)
33
+ t.deepEqual(obs[1][0], exp2)
34
+ t.deepEqual(obs[1][1], exp3)
35
35
  })
36
36
 
37
37
  test('color (rgba on geometry)', (t) => {
@@ -1,5 +1,7 @@
1
1
  import * as mat4 from '../../maths/mat4/index.js'
2
2
 
3
+ import { reverse } from './reverse.js'
4
+
3
5
  /**
4
6
  * Transform the given geometry using the given matrix.
5
7
  * This is a lazy transform of the outlines, as this function only adjusts the transforms.
@@ -14,5 +16,11 @@ import * as mat4 from '../../maths/mat4/index.js'
14
16
  */
15
17
  export const transform = (matrix, geometry) => {
16
18
  const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
17
- return Object.assign({}, geometry, { transforms })
19
+ const transformed = Object.assign({}, geometry, { transforms })
20
+ // determine if the transform is mirroring in 2D
21
+ if (matrix[0] * matrix[5] - matrix[4] * matrix[1] < 0) {
22
+ // reverse the order to preserve the orientation
23
+ return reverse(transformed)
24
+ }
25
+ return transformed
18
26
  }
@@ -2,6 +2,12 @@ import test from 'ava'
2
2
 
3
3
  import { mat4 } from '../../maths/index.js'
4
4
 
5
+ import { measureArea } from '../../measurements/index.js'
6
+
7
+ import { mirrorX, mirrorY, mirrorZ } from '../../operations/transforms/index.js'
8
+
9
+ import { square } from '../../primitives/index.js'
10
+
5
11
  import { create, transform, toOutlines, toSides } from './index.js'
6
12
 
7
13
  import { comparePoints, compareVectors } from '../../../test/helpers/index.js'
@@ -50,3 +56,54 @@ test('transform: adjusts the transforms of geom2', (t) => {
50
56
  t.true(comparePoints(another.outlines[0], expected.outlines[0]))
51
57
  t.true(compareVectors(another.transforms, expected.transforms))
52
58
  })
59
+
60
+ test('transform: geom2 mirrorX', (t) => {
61
+ const geometry = square()
62
+ const transformed = mirrorX(geometry)
63
+ t.is(measureArea(geometry), 4)
64
+ // area will be negative unless we reversed the points
65
+ t.is(measureArea(transformed), 4)
66
+ const pts = toOutlines(transformed)[0]
67
+ const exp = [[1, 1], [-1, 1], [-1, -1], [1, -1]]
68
+ t.true(comparePoints(pts, exp))
69
+ t.deepEqual(toSides(transformed), [
70
+ [[1, 1], [-1, 1]],
71
+ [[-1, 1], [-1, -1]],
72
+ [[-1, -1], [1, -1]],
73
+ [[1, -1], [1, 1]]
74
+ ])
75
+ })
76
+
77
+ test('transform: geom2 mirrorY', (t) => {
78
+ const geometry = square()
79
+ const transformed = mirrorY(geometry)
80
+ t.is(measureArea(geometry), 4)
81
+ // area will be negative unless we reversed the points
82
+ t.is(measureArea(transformed), 4)
83
+ const pts = toOutlines(transformed)[0]
84
+ const exp = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
85
+ t.true(comparePoints(pts, exp))
86
+ t.deepEqual(toSides(transformed), [
87
+ [[-1, -1], [1, -1]],
88
+ [[1, -1], [1, 1]],
89
+ [[1, 1], [-1, 1]],
90
+ [[-1, 1], [-1, -1]]
91
+ ])
92
+ })
93
+
94
+ test('transform: geom2 mirrorZ', (t) => {
95
+ const geometry = square()
96
+ const transformed = mirrorZ(geometry)
97
+ t.is(measureArea(geometry), 4)
98
+ // area will be negative unless we DIDN'T reverse the points
99
+ t.is(measureArea(transformed), 4)
100
+ const pts = toOutlines(transformed)[0]
101
+ const exp = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
102
+ t.true(comparePoints(pts, exp))
103
+ t.deepEqual(toSides(transformed), [
104
+ [[-1, -1], [1, -1]],
105
+ [[1, -1], [1, 1]],
106
+ [[1, 1], [-1, 1]],
107
+ [[-1, 1], [-1, -1]]
108
+ ])
109
+ })
@@ -0,0 +1,4 @@
1
+ import type { Geom3 } from './type.d.ts'
2
+ import type { Vec3 } from '../../maths/vec3/type.d.ts'
3
+
4
+ export function fromPointsConvex(points: Array<Array<Vec3>>): Geom3
@@ -0,0 +1,25 @@
1
+ import { runner } from '../../operations/hulls/quickhull/index.js'
2
+ import { create } from './create.js'
3
+ import * as poly3 from '../poly3/index.js'
4
+
5
+ /**
6
+ * Construct a new convex 3D geometry from a list of unique points.
7
+ *
8
+ * @param {Array} uniquePoints - list of points to construct convex 3D geometry
9
+ * @returns {geom3} a new geometry
10
+ * @alias module:modeling/geometries/geom3.fromPointsConvex
11
+ */
12
+ export const fromPointsConvex = (uniquePoints) => {
13
+ if (!Array.isArray(uniquePoints)) {
14
+ throw new Error('the given points must be an array')
15
+ }
16
+
17
+ const faces = runner(uniquePoints, { skipTriangulation: true })
18
+
19
+ const polygons = faces.map((face) => {
20
+ const vertices = face.map((index) => uniquePoints[index])
21
+ return poly3.create(vertices)
22
+ })
23
+
24
+ return create(polygons)
25
+ }
@@ -0,0 +1,32 @@
1
+ import test from 'ava'
2
+
3
+ import { fromPointsConvex, validate } from './index.js'
4
+
5
+ test('fromPointsConvex (uniquePoints)', (t) => {
6
+ const out = []
7
+ for (let x = -9; x <= 9; ++x) {
8
+ for (let y = -9; y <= 9; ++y) {
9
+ for (let z = -9; z <= 9; ++z) {
10
+ if (x * x + y * y + z * z <= 96) {
11
+ out.push([x, y, z])
12
+ }
13
+ }
14
+ }
15
+ }
16
+
17
+ const obs = fromPointsConvex(out)
18
+ validate(obs)
19
+ t.is(obs.polygons.length, 170)
20
+ t.true(obs.polygons.every((f) => ([3, 4, 8, 9].indexOf(f.vertices.length) !== -1)))
21
+
22
+ const c = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
23
+ obs.polygons.forEach((f) => c[f.vertices.length]++)
24
+ t.is(c[3], 120)
25
+ t.is(c[4], 24)
26
+ t.is(c[8], 18)
27
+ t.is(c[9], 8)
28
+
29
+ let edges2 = 336 * 2
30
+ obs.polygons.forEach((f) => { edges2 -= f.vertices.length })
31
+ t.is(edges2, 0)
32
+ })
@@ -1,6 +1,7 @@
1
1
  export { clone } from './clone.js'
2
2
  export { create } from './create.js'
3
3
  export { fromPoints } from './fromPoints.js'
4
+ export { fromPointsConvex } from './fromPointsConvex.js'
4
5
  export { fromCompactBinary } from './fromCompactBinary.js'
5
6
  export { invert } from './invert.js'
6
7
  export { isA } from './isA.js'
@@ -17,6 +17,7 @@
17
17
  export { clone } from './clone.js'
18
18
  export { create } from './create.js'
19
19
  export { fromPoints } from './fromPoints.js'
20
+ export { fromPointsConvex } from './fromPointsConvex.js'
20
21
  export { fromCompactBinary } from './fromCompactBinary.js'
21
22
  export { invert } from './invert.js'
22
23
  export { isA } from './isA.js'
@@ -5,14 +5,13 @@
5
5
  * @see {@link geom2} - 2D geometry consisting of 2D outlines
6
6
  * @see {@link geom3} - 3D geometry consisting of polygons
7
7
  * @see {@link path2} - 2D geometry consisting of ordered points
8
- * @see {@link poly2} - 2D polygon consisting of ordered vertices
8
+ * @see {@link poly2} - 2D polygon consisting of ordered points
9
9
  * @see {@link poly3} - 3D polygon consisting of ordered vertices
10
- * @see {@link slice} - 3D geometry consisting of 3D outlines
10
+ * @see {@link slice} - 3D geometry consisting of 3D contours
11
11
  *
12
12
  * @module modeling/geometries
13
13
  * @example
14
- * import { geometries } from '@jscad/modeling'
15
- * const { geom2, geom3, path2, poly2, poly3 } = geometries
14
+ * import { geom2, geom3, path2, poly2, poly3, slice } from '@jscad/modeling'
16
15
  */
17
16
  export * as geom2 from './geom2/index.js'
18
17
  export * as geom3 from './geom3/index.js'
@@ -6,6 +6,6 @@ export interface Poly3 {
6
6
  vertices: Array<Vec3>
7
7
  color?: Color
8
8
 
9
- // used internally for caching:
9
+ // used internally for calculations
10
10
  plane?: Plane
11
11
  }
@@ -28,10 +28,9 @@ export const validate = (object) => {
28
28
  // contours must be coplanar
29
29
  const contourPlane = poly3.plane(poly3.create(contour))
30
30
  if (!plane.equals(slicePlane, contourPlane)) {
31
- throw new Error(`slice contours must be coplanar`)
31
+ throw new Error('slice contours must be coplanar')
32
32
  }
33
33
 
34
-
35
34
  for (let i = 0; i < contour.length; i++) {
36
35
  const vertex = contour[i]
37
36
  // check for infinity, nan
@@ -15,3 +15,4 @@ export * as vec2 from './vec2/index.js'
15
15
  export * as vec3 from './vec3/index.js'
16
16
  export * as vec4 from './vec4/index.js'
17
17
  export { cos, sin } from './utils/trigonometry.js'
18
+ export { area } from './utils/area.js'
@@ -18,7 +18,7 @@ export const isOnlyTransformScale = (matrix) => (
18
18
  )
19
19
 
20
20
  /**
21
- * @param {number} num
21
+ * @param {number} num
22
22
  * @returns {boolean}
23
23
  */
24
24
  const isZero = (num) => Math.abs(num) < Number.EPSILON
@@ -14,7 +14,6 @@ import { measureArea } from './measureArea.js'
14
14
  */
15
15
  export const measureAggregateArea = (...geometries) => {
16
16
  geometries = flatten(geometries)
17
- if (geometries.length === 0) throw new Error('measureAggregateArea: no geometries supplied')
18
17
  const areas = measureArea(geometries)
19
18
  if (geometries.length === 1) {
20
19
  return areas
@@ -15,7 +15,6 @@ import { measureBoundingBox } from './measureBoundingBox.js'
15
15
  */
16
16
  export const measureAggregateBoundingBox = (...geometries) => {
17
17
  geometries = flatten(geometries)
18
- if (geometries.length === 0) throw new Error('measureAggregateBoundingBox: no geometries supplied')
19
18
  const bounds = measureBoundingBox(geometries)
20
19
  if (geometries.length === 1) {
21
20
  return bounds
@@ -18,7 +18,6 @@ import { calculateEpsilonFromBounds } from './calculateEpsilonFromBounds.js'
18
18
  */
19
19
  export const measureAggregateEpsilon = (...geometries) => {
20
20
  geometries = flatten(geometries)
21
- if (geometries.length === 0) throw new Error('measureAggregateEpsilon: no geometries supplied')
22
21
  const bounds = measureAggregateBoundingBox(geometries)
23
22
 
24
23
  let dimensions = 0
@@ -14,7 +14,6 @@ import { measureVolume } from './measureVolume.js'
14
14
  */
15
15
  export const measureAggregateVolume = (...geometries) => {
16
16
  geometries = flatten(geometries)
17
- if (geometries.length === 0) throw new Error('measureAggregateVolume: no geometries supplied')
18
17
  const volumes = measureVolume(geometries)
19
18
  if (geometries.length === 1) {
20
19
  return volumes
@@ -85,7 +85,6 @@ const measureAreaOfSlice = (geometry) => {
85
85
  */
86
86
  export const measureArea = (...geometries) => {
87
87
  geometries = flatten(geometries)
88
- if (geometries.length === 0) throw new Error('wrong number of arguments')
89
88
 
90
89
  const results = geometries.map((geometry) => {
91
90
  if (path2.isA(geometry)) return measureAreaOfPath2(geometry)
@@ -121,7 +121,6 @@ const measureBoundingBoxOfSlice = (geometry) => {
121
121
  */
122
122
  export const measureBoundingBox = (...geometries) => {
123
123
  geometries = flatten(geometries)
124
- if (geometries.length === 0) throw new Error('wrong number of arguments')
125
124
 
126
125
  const results = geometries.map((geometry) => {
127
126
  if (path2.isA(geometry)) return measureCached(geometry, measureBoundingBoxOfPath2)
@@ -20,7 +20,6 @@ import { measureBoundingBox } from './measureBoundingBox.js'
20
20
  */
21
21
  export const measureEpsilon = (...geometries) => {
22
22
  geometries = flatten(geometries)
23
- if (geometries.length === 0) throw new Error('wrong number of arguments')
24
23
 
25
24
  const results = geometries.map((geometry) => {
26
25
  if (path2.isA(geometry)) return calculateEpsilonFromBounds(measureBoundingBox(geometry), 2)
@@ -34,7 +34,6 @@ const measureVolumeOfGeom3 = (geometry) => {
34
34
  */
35
35
  export const measureVolume = (...geometries) => {
36
36
  geometries = flatten(geometries)
37
- if (geometries.length === 0) throw new Error('wrong number of arguments')
38
37
 
39
38
  const results = geometries.map((geometry) => {
40
39
  if (geom3.isA(geometry)) return measureVolumeOfGeom3(geometry)
@@ -1,3 +1,4 @@
1
1
  export { intersect } from './intersect.js'
2
2
  export { subtract } from './subtract.js'
3
3
  export { union } from './union.js'
4
+ export { scission } from './scission.js'
@@ -1,5 +1,5 @@
1
1
  import { areAllShapesTheSameType } from '../../utils/areAllShapesTheSameType.js'
2
- import { flatten } from '../../utils/flatten.js'
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 { intersectGeom3 } from './intersectGeom3.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|geom3} a new geometry
16
+ * @returns {Geom2|Geom3} a new geometry
17
17
  * @alias module:modeling/booleans.intersect
18
18
  *
19
19
  * @example
20
- * let myshape = intersect(cube({size: [5,5,5]}), cube({size: [5,5,5], center: [5,5,5]}))
20
+ * let myshape = intersect(cube({size: 5}), cube({size: 5, center: [3,3,3]}))
21
21
  *
22
22
  * @example
23
23
  * +-------+
@@ -30,9 +30,9 @@ import { intersectGeom3 } from './intersectGeom3.js'
30
30
  * +-------+
31
31
  */
32
32
  export const intersect = (...geometries) => {
33
- geometries = flatten(geometries)
34
- if (geometries.length === 0) throw new Error('intersect 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('intersect 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 { intersect } from './index.js'
6
6
 
7
- test('intersect error wrong number of arguments', (t) => {
8
- const message = 'intersect wrong number of arguments'
9
- t.throws(() => intersect(), { message })
10
- t.throws(() => intersect([]), { message })
11
- t.throws(() => intersect([[], []]), { message })
7
+ test('intersect empty arguments', (t) => {
8
+ t.is(intersect(), undefined)
9
+ t.is(intersect([]), undefined)
10
+ t.is(intersect([[], []]), undefined)
11
+ t.is(intersect(null, null), undefined)
12
12
  })
13
13
 
14
14
  test('intersect error different geometry types', (t) => {
@@ -20,6 +20,5 @@ test('intersect error non-geometries', (t) => {
20
20
  const message = 'intersect unsupported geometry type'
21
21
  t.throws(() => intersect([1, 2, 3], [4, 5, 6]), { message })
22
22
  t.throws(() => intersect([], [123]), { message })
23
- t.throws(() => intersect("one", "two"), { message })
24
- t.throws(() => intersect(null, null), { message })
23
+ t.throws(() => intersect('one', 'two'), { message })
25
24
  })
@@ -1,17 +1,13 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import { INTERSECTION } 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 both the first geometry and
8
6
  * in the subsequent geometries. None of the given geometries are modified.
9
- * @param {...geom2} geometries - list of 2D geometries
7
+ * @param {Geom2[]} geometries - a flat list of 2D geometries
10
8
  * @returns {Geom2} new 2D geometry
11
9
  */
12
- export const intersectGeom2 = (...geometries) => {
13
- geometries = flatten(geometries)
14
-
10
+ export const intersectGeom2 = (geometries) => {
15
11
  let newGeometry = geometries.shift()
16
12
  geometries.forEach((geometry) => {
17
13
  newGeometry = boolean(newGeometry, geometry, INTERSECTION)
@@ -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 { intersect } from './index.js'
12
12
 
@@ -73,3 +73,27 @@ test('intersect: intersect of one or more geom2 objects produces expected geomet
73
73
  t.is(obs.length, 8)
74
74
  t.true(comparePoints(obs, exp))
75
75
  })
76
+
77
+ test('intersect with undefined/null values', (t) => {
78
+ const square1 = square({ size: 8 })
79
+ const square2 = square({ size: 6 })
80
+ const square3 = square({ size: 4 })
81
+ const geometries = [square1, undefined, square2, null, square3]
82
+
83
+ const obs = intersect(...geometries)
84
+ const pts = geom2.toPoints(obs)
85
+ t.notThrows(() => geom2.validate(obs))
86
+ t.is(pts.length, 4)
87
+ })
88
+
89
+ test('intersect of nested arrays', (t) => {
90
+ const square1 = square({ size: 8 })
91
+ const square2 = square({ size: 6 })
92
+ const square3 = square({ size: 4 })
93
+ const geometries = [square1, [square2, [square3]]]
94
+
95
+ const obs = intersect(...geometries)
96
+ const pts = geom2.toPoints(obs)
97
+ t.notThrows(() => geom2.validate(obs))
98
+ t.is(pts.length, 4)
99
+ })
@@ -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 { intersectGeom3Sub } from './intersectGeom3Sub.js'
@@ -7,12 +5,10 @@ import { intersectGeom3Sub } from './intersectGeom3Sub.js'
7
5
  /*
8
6
  * Return a new 3D geometry representing space in both the first geometry and
9
7
  * in the subsequent geometries. None of the given geometries are modified.
10
- * @param {...geom3} geometries - list of 3D geometries
8
+ * @param {Geom3[]} geometries - a flat list of 3D geometries
11
9
  * @returns {Geom3} new 3D geometry
12
10
  */
13
- export const intersectGeom3 = (...geometries) => {
14
- geometries = flatten(geometries)
15
-
11
+ export const intersectGeom3 = (geometries) => {
16
12
  let newGeometry = geometries.shift()
17
13
  geometries.forEach((geometry) => {
18
14
  newGeometry = intersectGeom3Sub(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('intersect: intersect of one or more geom3 objects produces expected geomet
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('intersect: intersect of one or more geom3 objects produces expected geomet
79
80
  const result2 = intersect(geometry1, geometry2)
80
81
  obs = geom3.toPoints(result2)
81
82
  t.notThrows(() => geom3.validate(result2))
83
+ t.is(measureArea(result2), 0)
82
84
  t.is(measureVolume(result2), 0)
83
85
  t.is(obs.length, 0)
84
86
 
@@ -99,6 +101,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
99
101
  ]
100
102
 
101
103
  t.notThrows(() => geom3.validate(result3))
104
+ t.is(measureArea(result3), 6)
102
105
  t.is(measureVolume(result3), 1.0000000000000009)
103
106
  t.is(obs.length, 6)
104
107
  t.true(comparePolygonsAsPoints(obs, exp))
@@ -107,6 +110,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
107
110
  const result4 = intersect(geometry1, geometry3)
108
111
  obs = geom3.toPoints(result4)
109
112
  t.notThrows.skip(() => geom3.validate(result4))
113
+ t.is(measureArea(result4), 44.053756306589825)
110
114
  t.is(measureVolume(result4), 25.751611331979678)
111
115
  t.is(obs.length, 32)
112
116
  })
@@ -2,7 +2,6 @@ import { EPS } from '../../maths/constants.js'
2
2
 
3
3
  import { measureBoundingBox } from '../../measurements/measureBoundingBox.js'
4
4
 
5
-
6
5
  /*
7
6
  * Determine if the given geometries overlap by comparing min and max bounds.
8
7
  * NOTE: This is used in union for performance gains.
@@ -0,0 +1,5 @@
1
+ import type { Geom3 } from '../../geometries/geom3/type.d.ts'
2
+ import type { RecursiveArray } from '../../utils/recursiveArray.d.ts'
3
+
4
+ export function scission(...geometries: RecursiveArray<Geom3>): Geom3
5
+ export function scission(...geometries: RecursiveArray<Geom3>): Array<Geom3>
@@ -1,5 +1,3 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import * as geom3 from '../../geometries/geom3/index.js'
4
2
 
5
3
  import { scissionGeom3 } from './scissionGeom3.js'
@@ -7,6 +5,8 @@ import { scissionGeom3 } from './scissionGeom3.js'
7
5
  /**
8
6
  * Scission (divide) the given geometry into the component pieces.
9
7
  *
8
+ * NOTE: Currently only 3D geometries are supported.
9
+ *
10
10
  * @param {...Object} objects - list of geometries
11
11
  * @returns {Array} list of pieces from each geometry
12
12
  * @alias module:modeling/booleans.scission
@@ -26,13 +26,11 @@ import { scissionGeom3 } from './scissionGeom3.js'
26
26
  * +-------+ +-------+
27
27
  */
28
28
  export const scission = (...objects) => {
29
- objects = flatten(objects)
30
- if (objects.length === 0) throw new Error('wrong number of arguments')
31
-
32
29
  const results = objects.map((object) => {
33
30
  // if (path2.isA(object)) return path2.transform(matrix, object)
34
31
  // if (geom2.isA(object)) return geom2.transform(matrix, object)
35
32
  if (geom3.isA(object)) return scissionGeom3(object)
33
+ if (Array.isArray(object)) return scission(...object)
36
34
  return object
37
35
  })
38
36
  return results.length === 1 ? results[0] : results
@@ -2,6 +2,8 @@ import test from 'ava'
2
2
 
3
3
  import { geom3 } from '../../geometries/index.js'
4
4
 
5
+ import { measureArea, measureVolume } from '../../measurements/index.js'
6
+
5
7
  import { cube, torus } from '../../primitives/index.js'
6
8
 
7
9
  import { scission, union } from './index.js'
@@ -42,6 +44,10 @@ test('scission: scission of complex geom3 produces expected geometry', (t) => {
42
44
  t.is(result1.length, 2)
43
45
  t.notThrows.skip(() => geom3.validate(result1[0]))
44
46
  t.notThrows.skip(() => geom3.validate(result1[1]))
47
+ t.is(measureArea(result1[0]), 7720.0306508548)
48
+ t.is(measureArea(result1[1]), 3860.0153254273987)
49
+ t.is(measureVolume(result1[0]), 18745.166004060953)
50
+ t.is(measureVolume(result1[1]), 9372.583002030477)
45
51
 
46
52
  const rc1 = geom3.toPolygons(result1[0]).length
47
53
  const rc2 = geom3.toPolygons(result1[1]).length