@jscad/modeling 2.9.0 → 2.9.3

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 (131) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +4 -4
  3. package/dist/jscad-modeling.min.js +437 -428
  4. package/package.json +3 -2
  5. package/src/colors/colorize.test.js +1 -1
  6. package/src/geometries/geom2/index.d.ts +1 -0
  7. package/src/geometries/geom2/index.js +2 -1
  8. package/src/geometries/geom2/toOutlines.js +66 -52
  9. package/src/geometries/geom2/validate.d.ts +3 -0
  10. package/src/geometries/geom2/validate.js +36 -0
  11. package/src/geometries/geom3/create.js +1 -1
  12. package/src/geometries/geom3/create.test.js +1 -1
  13. package/src/geometries/geom3/fromPoints.js +1 -1
  14. package/src/geometries/geom3/index.d.ts +1 -0
  15. package/src/geometries/geom3/index.js +2 -1
  16. package/src/geometries/geom3/isA.js +1 -1
  17. package/src/geometries/geom3/validate.d.ts +3 -0
  18. package/src/geometries/geom3/validate.js +62 -0
  19. package/src/geometries/path2/index.d.ts +1 -1
  20. package/src/geometries/path2/index.js +2 -2
  21. package/src/geometries/path2/validate.d.ts +3 -0
  22. package/src/geometries/path2/validate.js +41 -0
  23. package/src/geometries/poly2/arePointsInside.js +0 -35
  24. package/src/geometries/poly3/create.js +1 -1
  25. package/src/geometries/poly3/index.d.ts +1 -0
  26. package/src/geometries/poly3/index.js +2 -1
  27. package/src/geometries/poly3/measureArea.test.js +16 -16
  28. package/src/geometries/poly3/measureBoundingSphere.test.js +8 -8
  29. package/src/geometries/poly3/validate.d.ts +4 -0
  30. package/src/geometries/poly3/validate.js +64 -0
  31. package/src/maths/constants.d.ts +1 -0
  32. package/src/maths/constants.js +11 -0
  33. package/src/maths/utils/aboutEqualNormals.js +1 -5
  34. package/src/measurements/measureCenterOfMass.test.js +2 -2
  35. package/src/operations/booleans/intersect.test.js +8 -0
  36. package/src/operations/booleans/intersectGeom3.js +2 -1
  37. package/src/operations/booleans/scission.test.js +4 -4
  38. package/src/operations/booleans/subtract.test.js +8 -0
  39. package/src/operations/booleans/subtractGeom3.js +2 -1
  40. package/src/operations/booleans/to3DWalls.js +1 -1
  41. package/src/operations/booleans/trees/Node.js +10 -16
  42. package/src/operations/booleans/trees/PolygonTreeNode.js +13 -14
  43. package/src/operations/booleans/trees/Tree.js +1 -2
  44. package/src/operations/booleans/trees/splitPolygonByPlane.js +2 -3
  45. package/src/operations/booleans/union.test.js +27 -0
  46. package/src/operations/booleans/unionGeom3.js +2 -1
  47. package/src/operations/expansions/expand.test.js +30 -21
  48. package/src/operations/expansions/expandGeom3.test.js +14 -14
  49. package/src/operations/expansions/expandShell.js +5 -4
  50. package/src/operations/expansions/extrudePolygon.js +7 -7
  51. package/src/operations/expansions/offset.test.js +25 -0
  52. package/src/operations/extrusions/earcut/assignHoles.js +7 -3
  53. package/src/operations/extrusions/earcut/assignHoles.test.js +50 -4
  54. package/src/operations/extrusions/earcut/linkedList.js +1 -1
  55. package/src/operations/extrusions/extrudeFromSlices.test.js +16 -10
  56. package/src/operations/extrusions/extrudeLinear.test.js +15 -9
  57. package/src/operations/extrusions/extrudeRectangular.test.js +15 -8
  58. package/src/operations/extrusions/extrudeRotate.js +5 -1
  59. package/src/operations/extrusions/extrudeRotate.test.js +12 -0
  60. package/src/operations/extrusions/extrudeWalls.js +2 -2
  61. package/src/operations/extrusions/project.js +11 -14
  62. package/src/operations/extrusions/project.test.js +55 -55
  63. package/src/operations/hulls/hull.test.js +24 -1
  64. package/src/operations/hulls/hullChain.test.js +6 -4
  65. package/src/operations/hulls/hullGeom2.js +6 -18
  66. package/src/operations/hulls/hullGeom3.js +5 -18
  67. package/src/operations/hulls/hullPath2.js +4 -14
  68. package/src/operations/hulls/hullPath2.test.js +1 -1
  69. package/src/operations/hulls/hullPoints2.js +43 -92
  70. package/src/operations/hulls/toUniquePoints.js +34 -0
  71. package/src/operations/modifiers/generalize.js +2 -13
  72. package/src/operations/modifiers/generalize.test.js +5 -31
  73. package/src/operations/modifiers/insertTjunctions.js +1 -1
  74. package/src/operations/modifiers/insertTjunctions.test.js +21 -21
  75. package/src/operations/modifiers/mergePolygons.js +11 -14
  76. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.js +1 -1
  77. package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.test.js +5 -5
  78. package/src/operations/{booleans → modifiers}/retessellate.js +2 -9
  79. package/src/operations/{booleans → modifiers}/retessellate.test.js +0 -0
  80. package/src/operations/modifiers/snapPolygons.test.js +12 -12
  81. package/src/operations/modifiers/triangulatePolygons.js +3 -3
  82. package/src/operations/transforms/align.test.js +12 -0
  83. package/src/operations/transforms/center.js +1 -1
  84. package/src/operations/transforms/center.test.js +12 -0
  85. package/src/operations/transforms/mirror.test.js +16 -0
  86. package/src/operations/transforms/rotate.test.js +10 -0
  87. package/src/operations/transforms/scale.test.js +15 -0
  88. package/src/operations/transforms/transform.test.js +5 -0
  89. package/src/operations/transforms/translate.test.js +16 -0
  90. package/src/primitives/arc.test.js +11 -0
  91. package/src/primitives/circle.test.js +15 -9
  92. package/src/primitives/cube.test.js +3 -0
  93. package/src/primitives/cuboid.js +1 -1
  94. package/src/primitives/cuboid.test.js +9 -24
  95. package/src/primitives/cylinder.test.js +7 -4
  96. package/src/primitives/cylinderElliptic.js +14 -7
  97. package/src/primitives/cylinderElliptic.test.js +72 -50
  98. package/src/primitives/ellipse.js +3 -1
  99. package/src/primitives/ellipse.test.js +14 -8
  100. package/src/primitives/ellipsoid.js +8 -6
  101. package/src/primitives/ellipsoid.test.js +84 -80
  102. package/src/primitives/geodesicSphere.test.js +3 -0
  103. package/src/primitives/line.test.js +1 -0
  104. package/src/primitives/polygon.test.js +15 -10
  105. package/src/primitives/polyhedron.js +1 -1
  106. package/src/primitives/polyhedron.test.js +14 -42
  107. package/src/primitives/rectangle.test.js +3 -0
  108. package/src/primitives/roundedCuboid.js +6 -6
  109. package/src/primitives/roundedCuboid.test.js +5 -0
  110. package/src/primitives/roundedCylinder.js +7 -5
  111. package/src/primitives/roundedCylinder.test.js +40 -36
  112. package/src/primitives/roundedRectangle.test.js +5 -0
  113. package/src/primitives/sphere.test.js +52 -73
  114. package/src/primitives/square.test.js +3 -0
  115. package/src/primitives/star.test.js +6 -0
  116. package/src/primitives/torus.test.js +8 -1
  117. package/src/primitives/triangle.js +1 -2
  118. package/src/primitives/triangle.test.js +7 -0
  119. package/src/utils/areAllShapesTheSameType.js +2 -2
  120. package/src/utils/areAllShapesTheSameType.test.js +17 -0
  121. package/src/utils/index.d.ts +1 -0
  122. package/src/utils/index.js +3 -1
  123. package/src/utils/trigonometry.d.ts +2 -0
  124. package/src/utils/trigonometry.js +34 -0
  125. package/src/utils/trigonometry.test.js +25 -0
  126. package/test/helpers/nearlyEqual.js +4 -1
  127. package/src/geometries/path2/eachPoint.d.ts +0 -9
  128. package/src/geometries/path2/eachPoint.js +0 -17
  129. package/src/geometries/path2/eachPoint.test.js +0 -11
  130. package/src/operations/modifiers/edges.js +0 -195
  131. package/src/operations/modifiers/repairTjunctions.js +0 -44
@@ -6,20 +6,20 @@ const expandGeom3 = require('./expandGeom3')
6
6
  test('expandGeom3: expand completes properly, issue 876', async (t) => {
7
7
  setTimeout(() => t.fail(), 1000)
8
8
  const polies = [
9
- poly3.fromPoints([[-19.61, -0.7999999999999986, 11.855], [-19.61, -0.8000000000000015, -11.855], [-19.61, -2.7500000000000018, -11.855], [-19.61, -2.7499999999999982, 11.855]]),
10
- poly3.fromPoints([[-17.32, -2.75, 10], [-17.32, -2.7500000000000013, -10], [-17.32, -0.8000000000000014, -10], [-17.32, -0.7999999999999987, 10]]),
11
- poly3.fromPoints([[-16.863040644206997, -0.8000000000000015, -10.28], [-16.863040644206997, -2.7500000000000018, -10.28], [-14.292644267871385, -2.7500000000000018, -11.855000000000016], [-14.292644267871383, -0.8000000000000015, -11.855000000000018]]),
12
- poly3.fromPoints([[-17.319999999999993, -0.8000000000000015, -9.999999999999996], [-17.319999999999993, -2.7500000000000018, -9.999999999999996], [-16.87560702649131, -2.7500000000000018, -10.272299999999998], [-16.866696319053347, -0.8000000000000015, -10.277759999999997]]),
13
- poly3.fromPoints([[-16.863040644207004, -2.7500000000000013, -10.280000000000001], [-16.863040644207004, -0.8000000000000014, -10.280000000000001], [-16.86669631905335, -0.8000000000000012, -10.27776], [-16.875607026491313, -2.75, -10.272300000000001]]),
14
- poly3.fromPoints([[-14.107140000000015, -0.7999999999999987, 11.85500000000003], [-14.107140000000015, -2.7499999999999982, 11.85500000000003], [-17.319999999999975, -2.7499999999999982, 9.999999999999956], [-17.319999999999975, -0.7999999999999987, 9.999999999999956]]),
15
- poly3.fromPoints([[-17.32, -0.7999999999999988, 9.999999999999993], [-17.32, -0.7999999999999986, 11.855], [-14.107139999999994, -0.7999999999999986, 11.855]]),
16
- poly3.fromPoints([[-17.32, -0.800000000000001, -11.855], [-17.32, -0.8000000000000008, -10.000000000000078], [-14.292644267871482, -0.800000000000001, -11.855]]),
17
- poly3.fromPoints([[-17.32, -0.800000000000001, -11.855], [-19.61, -0.800000000000001, -11.855], [-19.61, -0.7999999999999986, 11.855], [-17.32, -0.7999999999999986, 11.855]]),
18
- poly3.fromPoints([[-17.32, -2.7500000000000013, -10.000000000000076], [-17.32, -2.7500000000000018, -11.855], [-14.292644267871482, -2.7500000000000018, -11.855]]),
19
- poly3.fromPoints([[-17.32, -2.7499999999999982, 11.855], [-17.32, -2.7499999999999987, 9.999999999999996], [-14.107139999999996, -2.7499999999999982, 11.855]]),
20
- poly3.fromPoints([[-17.32, -2.7499999999999982, 11.855], [-19.61, -2.7499999999999982, 11.855], [-19.61, -2.7500000000000018, -11.855], [-17.32, -2.7500000000000018, -11.855]]),
21
- poly3.fromPoints([[-14.107139999999996, -0.7999999999999986, 11.855], [-19.61, -0.7999999999999986, 11.855], [-19.61, -2.7499999999999982, 11.855], [-14.107139999999994, -2.7499999999999982, 11.855]]),
22
- poly3.fromPoints([[-19.61, -0.8000000000000015, -11.855], [-14.292644267871486, -0.8000000000000015, -11.855], [-14.292644267871482, -2.7500000000000018, -11.855], [-19.61, -2.7500000000000018, -11.855]])
9
+ poly3.create([[-19.61, -0.7999999999999986, 11.855], [-19.61, -0.8000000000000015, -11.855], [-19.61, -2.7500000000000018, -11.855], [-19.61, -2.7499999999999982, 11.855]]),
10
+ poly3.create([[-17.32, -2.75, 10], [-17.32, -2.7500000000000013, -10], [-17.32, -0.8000000000000014, -10], [-17.32, -0.7999999999999987, 10]]),
11
+ poly3.create([[-16.863040644206997, -0.8000000000000015, -10.28], [-16.863040644206997, -2.7500000000000018, -10.28], [-14.292644267871385, -2.7500000000000018, -11.855000000000016], [-14.292644267871383, -0.8000000000000015, -11.855000000000018]]),
12
+ poly3.create([[-17.319999999999993, -0.8000000000000015, -9.999999999999996], [-17.319999999999993, -2.7500000000000018, -9.999999999999996], [-16.87560702649131, -2.7500000000000018, -10.272299999999998], [-16.866696319053347, -0.8000000000000015, -10.277759999999997]]),
13
+ poly3.create([[-16.863040644207004, -2.7500000000000013, -10.280000000000001], [-16.863040644207004, -0.8000000000000014, -10.280000000000001], [-16.86669631905335, -0.8000000000000012, -10.27776], [-16.875607026491313, -2.75, -10.272300000000001]]),
14
+ poly3.create([[-14.107140000000015, -0.7999999999999987, 11.85500000000003], [-14.107140000000015, -2.7499999999999982, 11.85500000000003], [-17.319999999999975, -2.7499999999999982, 9.999999999999956], [-17.319999999999975, -0.7999999999999987, 9.999999999999956]]),
15
+ poly3.create([[-17.32, -0.7999999999999988, 9.999999999999993], [-17.32, -0.7999999999999986, 11.855], [-14.107139999999994, -0.7999999999999986, 11.855]]),
16
+ poly3.create([[-17.32, -0.800000000000001, -11.855], [-17.32, -0.8000000000000008, -10.000000000000078], [-14.292644267871482, -0.800000000000001, -11.855]]),
17
+ poly3.create([[-17.32, -0.800000000000001, -11.855], [-19.61, -0.800000000000001, -11.855], [-19.61, -0.7999999999999986, 11.855], [-17.32, -0.7999999999999986, 11.855]]),
18
+ poly3.create([[-17.32, -2.7500000000000013, -10.000000000000076], [-17.32, -2.7500000000000018, -11.855], [-14.292644267871482, -2.7500000000000018, -11.855]]),
19
+ poly3.create([[-17.32, -2.7499999999999982, 11.855], [-17.32, -2.7499999999999987, 9.999999999999996], [-14.107139999999996, -2.7499999999999982, 11.855]]),
20
+ poly3.create([[-17.32, -2.7499999999999982, 11.855], [-19.61, -2.7499999999999982, 11.855], [-19.61, -2.7500000000000018, -11.855], [-17.32, -2.7500000000000018, -11.855]]),
21
+ poly3.create([[-14.107139999999996, -0.7999999999999986, 11.855], [-19.61, -0.7999999999999986, 11.855], [-19.61, -2.7499999999999982, 11.855], [-14.107139999999994, -2.7499999999999982, 11.855]]),
22
+ poly3.create([[-19.61, -0.8000000000000015, -11.855], [-14.292644267871486, -0.8000000000000015, -11.855], [-14.292644267871482, -2.7500000000000018, -11.855], [-19.61, -2.7500000000000018, -11.855]])
23
23
  ]
24
24
 
25
25
  const sub = geom3.create(polies)
@@ -10,7 +10,8 @@ const poly3 = require('../../geometries/poly3')
10
10
 
11
11
  const sphere = require('../../primitives/sphere')
12
12
 
13
- const retessellate = require('../booleans/retessellate')
13
+ const retessellate = require('../modifiers/retessellate')
14
+
14
15
  const unionGeom3Sub = require('../booleans/unionGeom3Sub')
15
16
 
16
17
  const extrudePolygon = require('./extrudePolygon')
@@ -166,7 +167,7 @@ const expandShell = (options, geometry) => {
166
167
  startfacevertices.push(p1)
167
168
  endfacevertices.push(p2)
168
169
  const points = [prevp2, p2, p1, prevp1]
169
- const polygon = poly3.fromPoints(points)
170
+ const polygon = poly3.create(points)
170
171
  polygons.push(polygon)
171
172
  }
172
173
  prevp1 = p1
@@ -174,8 +175,8 @@ const expandShell = (options, geometry) => {
174
175
  }
175
176
  }
176
177
  endfacevertices.reverse()
177
- polygons.push(poly3.fromPoints(startfacevertices))
178
- polygons.push(poly3.fromPoints(endfacevertices))
178
+ polygons.push(poly3.create(startfacevertices))
179
+ polygons.push(poly3.create(endfacevertices))
179
180
 
180
181
  const cylinder = geom3.create(polygons)
181
182
  result = unionGeom3Sub(result, cylinder)
@@ -17,14 +17,14 @@ const extrudePolygon = (offsetvector, polygon1) => {
17
17
  const polygon2 = poly3.transform(mat4.fromTranslation(mat4.create(), offsetvector), polygon1)
18
18
  const numvertices = polygon1.vertices.length
19
19
  for (let i = 0; i < numvertices; i++) {
20
- const sidefacepoints = []
21
20
  const nexti = (i < (numvertices - 1)) ? i + 1 : 0
22
- sidefacepoints.push(polygon1.vertices[i])
23
- sidefacepoints.push(polygon2.vertices[i])
24
- sidefacepoints.push(polygon2.vertices[nexti])
25
- sidefacepoints.push(polygon1.vertices[nexti])
26
- const sidefacepolygon = poly3.fromPoints(sidefacepoints)
27
- newpolygons.push(sidefacepolygon)
21
+ const sideFacePolygon = poly3.create([
22
+ polygon1.vertices[i],
23
+ polygon2.vertices[i],
24
+ polygon2.vertices[nexti],
25
+ polygon1.vertices[nexti]
26
+ ])
27
+ newpolygons.push(sideFacePolygon)
28
28
  }
29
29
  newpolygons.push(poly3.invert(polygon2))
30
30
 
@@ -12,6 +12,7 @@ test('offset: offsetting a straight line produces expected geometry', (t) => {
12
12
  // offset it by 2.
13
13
  let offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2)
14
14
  let offsetPoints = path2.toPoints(offsetLinePath2)
15
+ t.notThrows(() => path2.validate(offsetLinePath2))
15
16
  t.is(offsetPoints.length, 2)
16
17
  let boundingBox = measureBoundingBox(offsetLinePath2)
17
18
  t.true(comparePoints(boundingBox, [[2, 0, 0], [2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
@@ -19,6 +20,7 @@ test('offset: offsetting a straight line produces expected geometry', (t) => {
19
20
  // offset it by -2.
20
21
  offsetLinePath2 = offset({ delta: -2, corners: 'edge', segments: 8 }, linePath2)
21
22
  offsetPoints = path2.toPoints(offsetLinePath2)
23
+ t.notThrows(() => path2.validate(offsetLinePath2))
22
24
  t.is(offsetPoints.length, 2)
23
25
  boundingBox = measureBoundingBox(offsetLinePath2)
24
26
  t.true(comparePoints(boundingBox, [[-2, 0, 0], [-2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
@@ -27,6 +29,7 @@ test('offset: offsetting a straight line produces expected geometry', (t) => {
27
29
  linePath2 = path2.fromPoints({ closed: false }, points.reverse())
28
30
  offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2)
29
31
  offsetPoints = path2.toPoints(offsetLinePath2)
32
+ t.notThrows(() => path2.validate(offsetLinePath2))
30
33
  t.is(offsetPoints.length, 2)
31
34
  boundingBox = measureBoundingBox(offsetLinePath2)
32
35
  t.true(comparePoints(boundingBox, [[-2, 0, 0], [-2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
@@ -39,6 +42,7 @@ test('offset: offsetting a bent line produces expected geometry', (t) => {
39
42
  // offset it by 2.
40
43
  let offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2)
41
44
  let offsetPoints = path2.toPoints(offsetLinePath2)
45
+ t.notThrows(() => path2.validate(offsetLinePath2))
42
46
  t.is(offsetPoints.length, 5)
43
47
  let boundingBox = measureBoundingBox(offsetLinePath2)
44
48
  t.true(comparePoints(boundingBox, [[2, 0, 0], [10, 8, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
@@ -46,6 +50,7 @@ test('offset: offsetting a bent line produces expected geometry', (t) => {
46
50
  // offset it by -2.
47
51
  offsetLinePath2 = offset({ delta: -2, corners: 'edge', segments: 8 }, linePath2)
48
52
  offsetPoints = path2.toPoints(offsetLinePath2)
53
+ t.notThrows(() => path2.validate(offsetLinePath2))
49
54
  t.is(offsetPoints.length, 5)
50
55
  boundingBox = measureBoundingBox(offsetLinePath2)
51
56
  t.true(comparePoints(boundingBox, [[-2, 0, 0], [10, 12, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
@@ -56,6 +61,7 @@ test('offset: offsetting a 2 segment straight line produces expected geometry',
56
61
  const linePath2 = path2.fromPoints({ closed: false }, points)
57
62
  const offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2)
58
63
  const offsetPoints = path2.toPoints(offsetLinePath2)
64
+ t.notThrows(() => path2.validate(offsetLinePath2))
59
65
  t.is(offsetPoints.length, 3)
60
66
  const boundingBox = measureBoundingBox(offsetLinePath2)
61
67
  t.true(comparePoints(boundingBox, [[2, 0, 0], [2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
@@ -71,6 +77,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
71
77
  let pts = path2.toPoints(obs)
72
78
  let exp = [
73
79
  ]
80
+ t.notThrows(() => path2.validate(obs))
74
81
  t.true(comparePoints(pts, exp))
75
82
 
76
83
  // expand +
@@ -82,6 +89,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
82
89
  [5.707106781186548, 0.7071067811865475],
83
90
  [0.7071067811865475, 5.707106781186548]
84
91
  ]
92
+ t.notThrows(() => path2.validate(obs))
85
93
  t.true(comparePoints(pts, exp))
86
94
 
87
95
  obs = offset({ delta: 1, corners: 'chamfer' }, closeline)
@@ -94,6 +102,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
94
102
  [-1, 5],
95
103
  [-1, 6.123233995736766e-17]
96
104
  ]
105
+ t.notThrows(() => path2.validate(obs))
97
106
  t.true(comparePoints(pts, exp))
98
107
 
99
108
  // contract -
@@ -104,6 +113,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
104
113
  [2.5857864376269046, 1],
105
114
  [-0.7071067811865475, 4.292893218813452]
106
115
  ]
116
+ t.notThrows(() => path2.validate(obs))
107
117
  t.true(comparePoints(pts, exp))
108
118
 
109
119
  obs = offset({ delta: -1, corners: 'chamfer' }, closeline)
@@ -113,6 +123,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
113
123
  [2.5857864376269046, 1],
114
124
  [0.9999999999999996, 2.585786437626905]
115
125
  ]
126
+ t.notThrows(() => path2.validate(obs))
116
127
  t.true(comparePoints(pts, exp))
117
128
  })
118
129
 
@@ -132,6 +143,7 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
132
143
  [-1.9999999999999996, 6],
133
144
  [-5, 6]
134
145
  ]
146
+ t.notThrows(() => path2.validate(obs))
135
147
  t.true(comparePoints(pts, exp))
136
148
 
137
149
  obs = offset({ delta: 1, corners: 'edge' }, closeline)
@@ -146,6 +158,7 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
146
158
  [-6, 6],
147
159
  [-6, -6]
148
160
  ]
161
+ t.notThrows(() => path2.validate(obs))
149
162
  t.true(comparePoints(pts, exp))
150
163
 
151
164
  obs = offset({ delta: -0.5, corners: 'edge' }, openline)
@@ -160,6 +173,7 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
160
173
  [-3.5, 4.5],
161
174
  [-5, 4.5]
162
175
  ]
176
+ t.notThrows(() => path2.validate(obs))
163
177
  t.true(comparePoints(pts, exp))
164
178
 
165
179
  obs = offset({ delta: -0.5, corners: 'edge' }, closeline)
@@ -174,6 +188,7 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
174
188
  [-3.5, 4.5],
175
189
  [-4.5, 4.5]
176
190
  ]
191
+ t.notThrows(() => path2.validate(obs))
177
192
  t.true(comparePoints(pts, exp))
178
193
  })
179
194
 
@@ -209,6 +224,7 @@ test('offset (corners: round): offset of a path2 produces expected offset path2'
209
224
  [-3, 6],
210
225
  [-5, 6]
211
226
  ]
227
+ t.notThrows(() => path2.validate(obs))
212
228
  t.true(comparePoints(pts, exp))
213
229
 
214
230
  obs = offset({ delta: 1, corners: 'round', segments: 16 }, closeline)
@@ -247,6 +263,7 @@ test('offset (corners: round): offset of a path2 produces expected offset path2'
247
263
  [-6, 5],
248
264
  [-6, -5]
249
265
  ]
266
+ t.notThrows(() => path2.validate(obs))
250
267
  t.true(comparePoints(pts, exp))
251
268
  })
252
269
 
@@ -289,6 +306,7 @@ test('offset (corners: round): offset of a CW path2 produces expected offset pat
289
306
  [5, -6],
290
307
  [-5, -6]
291
308
  ]
309
+ t.notThrows(() => path2.validate(obs))
292
310
  t.true(comparePoints(pts, exp))
293
311
  })
294
312
 
@@ -301,6 +319,7 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
301
319
  let pts = geom2.toPoints(obs)
302
320
  let exp = [
303
321
  ]
322
+ t.notThrows(() => geom2.validate(obs))
304
323
  t.true(comparePoints(pts, exp))
305
324
 
306
325
  // expand +
@@ -322,6 +341,7 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
322
341
  [-6, 5],
323
342
  [-6, -5]
324
343
  ]
344
+ t.notThrows(() => geom2.validate(obs))
325
345
  t.true(comparePoints(pts, exp))
326
346
 
327
347
  // contract -
@@ -339,6 +359,7 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
339
359
  [-3.5, 4.5],
340
360
  [-4.5, 4.5]
341
361
  ]
362
+ t.notThrows(() => geom2.validate(obs))
342
363
  t.true(comparePoints(pts, exp))
343
364
 
344
365
  // segments 1 - sharp points at corner
@@ -354,6 +375,7 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
354
375
  [-6, 6],
355
376
  [-6, -6]
356
377
  ]
378
+ t.notThrows(() => geom2.validate(obs))
357
379
  t.true(comparePoints(pts, exp))
358
380
 
359
381
  // segments 16 - rounded corners
@@ -377,6 +399,7 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
377
399
  [-3.5, 4.5],
378
400
  [-4.5, 4.5]
379
401
  ]
402
+ t.notThrows(() => geom2.validate(obs))
380
403
  t.true(comparePoints(pts, exp))
381
404
  })
382
405
 
@@ -429,6 +452,7 @@ test('offset (options): offsetting of a complex geom2 produces expected offset g
429
452
  [-4, -13],
430
453
  [-77, -77]
431
454
  ]
455
+ t.notThrows(() => geom2.validate(obs))
432
456
  t.is(pts.length, 20)
433
457
  t.true(comparePoints(pts, exp))
434
458
  })
@@ -473,6 +497,7 @@ test('offset (options): offsetting of round geom2 produces expected offset geom2
473
497
  [6.7105900605102855, -6.710590060510285],
474
498
  [8.767810140100096, -3.6317399864658024]
475
499
  ]
500
+ t.notThrows(() => geom2.validate(obs))
476
501
  t.is(pts.length, 16)
477
502
  t.true(comparePoints(pts, exp))
478
503
  })
@@ -31,6 +31,7 @@ const assignHoles = (geometry) => {
31
31
  solids.push(i)
32
32
  }
33
33
  })
34
+
34
35
  // for each hole, determine what solids it is inside of
35
36
  const children = [] // child holes of solid[i]
36
37
  const parents = [] // parent solids of hole[i]
@@ -47,19 +48,22 @@ const assignHoles = (geometry) => {
47
48
  }
48
49
  })
49
50
  })
51
+
50
52
  // check if holes have multiple parents and choose one with fewest children
51
53
  holes.forEach((h, j) => {
52
54
  // ensure at least one parent exists
53
55
  if (parents[j] && parents[j].length > 1) {
54
- const parent = minIndex(parents[j], (p) => p.length)
56
+ // the solid directly containing this hole
57
+ const directParent = minIndex(parents[j], (p) => children[p].length)
55
58
  parents[j].forEach((p, i) => {
56
- if (i !== parent) {
59
+ if (i !== directParent) {
57
60
  // Remove hole from skip level parents
58
- children[p] = children[p].filter((c) => c !== j)
61
+ children[p] = children[p].filter((c) => c !== h)
59
62
  }
60
63
  })
61
64
  }
62
65
  })
66
+
63
67
  // map indices back to points
64
68
  return children.map((holes, i) => ({
65
69
  solid: outlines[solids[i]],
@@ -1,7 +1,7 @@
1
1
  const test = require('ava')
2
2
 
3
- const { subtract } = require('../../../operations/booleans')
4
- const rectangle = require('../../../primitives/rectangle')
3
+ const { subtract, union } = require('../../../operations/booleans')
4
+ const square = require('../../../primitives/square')
5
5
  const assignHoles = require('./assignHoles')
6
6
 
7
7
  test('slice: assignHoles() should return a polygon hierarchy', (t) => {
@@ -20,9 +20,55 @@ test('slice: assignHoles() should return a polygon hierarchy', (t) => {
20
20
  ]]
21
21
  }]
22
22
  const geometry = subtract(
23
- rectangle({ size: [6, 6] }),
24
- rectangle({ size: [4, 4] })
23
+ square({ size: 6 }),
24
+ square({ size: 4 })
25
25
  )
26
26
  const obs1 = assignHoles(geometry)
27
27
  t.deepEqual(obs1, exp1)
28
28
  })
29
+
30
+ test('slice: assignHoles() should handle nested holes', (t) => {
31
+ const geometry = union(
32
+ subtract(
33
+ square({ size: 6 }),
34
+ square({ size: 4 })
35
+ ),
36
+ subtract(
37
+ square({ size: 10 }),
38
+ square({ size: 8 })
39
+ )
40
+ )
41
+ const obs1 = assignHoles(geometry)
42
+
43
+ const exp1 = [
44
+ {
45
+ solid: [
46
+ [-3.0000006060444444, -3.0000006060444444],
47
+ [3.0000006060444444, -3.0000006060444444],
48
+ [3.0000006060444444, 3.0000006060444444],
49
+ [-3.0000006060444444, 3.0000006060444444]
50
+ ],
51
+ holes: [[
52
+ [-2.0000248485333336, 2.0000248485333336],
53
+ [2.0000248485333336, 2.0000248485333336],
54
+ [2.0000248485333336, -2.0000248485333336],
55
+ [-2.0000248485333336, -2.0000248485333336]
56
+ ]]
57
+ },
58
+ {
59
+ solid: [
60
+ [-5.000025454577778, -5.000025454577778],
61
+ [5.000025454577778, -5.000025454577778],
62
+ [5.000025454577778, 5.000025454577778],
63
+ [-5.000025454577778, 5.000025454577778]
64
+ ],
65
+ holes: [[
66
+ [-3.9999763635555556, 3.9999763635555556],
67
+ [3.9999763635555556, 3.9999763635555556],
68
+ [3.9999763635555556, -3.9999763635555556],
69
+ [-3.9999763635555556, -3.9999763635555556]
70
+ ]]
71
+ }
72
+ ]
73
+ t.deepEqual(obs1, exp1)
74
+ })
@@ -1,7 +1,7 @@
1
1
  const sortLinked = require('./linkedListSort')
2
2
 
3
3
  class Node {
4
- constructor(i, x, y) {
4
+ constructor (i, x, y) {
5
5
  // vertex index in coordinates array
6
6
  this.i = i
7
7
 
@@ -32,10 +32,11 @@ test('extrudeFromSlices (defaults)', (t) => {
32
32
  t.is(pts.length, 12)
33
33
  t.true(comparePolygonsAsPoints(pts, exp))
34
34
 
35
- const poly2 = poly3.fromPoints([[10, 10, 0], [-10, 10, 0], [-10, -10, 0], [10, -10, 0]])
35
+ const poly2 = poly3.create([[10, 10, 0], [-10, 10, 0], [-10, -10, 0], [10, -10, 0]])
36
36
  geometry3 = extrudeFromSlices({ }, poly2)
37
37
  pts = geom3.toPoints(geometry3)
38
38
 
39
+ t.notThrows(() => geom3.validate(geometry3))
39
40
  t.is(pts.length, 12)
40
41
  t.true(comparePolygonsAsPoints(pts, exp))
41
42
  })
@@ -44,7 +45,7 @@ test('extrudeFromSlices (torus)', (t) => {
44
45
  const sqrt3 = Math.sqrt(3) / 2
45
46
  const radius = 10
46
47
 
47
- let hex = poly3.fromPoints([
48
+ let hex = poly3.create([
48
49
  [radius, 0, 0],
49
50
  [radius / 2, radius * sqrt3, 0],
50
51
  [-radius / 2, radius * sqrt3, 0],
@@ -68,6 +69,7 @@ test('extrudeFromSlices (torus)', (t) => {
68
69
  }, hex
69
70
  )
70
71
  const pts = geom3.toPoints(geometry3)
72
+ t.notThrows(() => geom3.validate(geometry3))
71
73
  t.is(pts.length, 96)
72
74
  })
73
75
 
@@ -86,6 +88,8 @@ test('extrudeFromSlices (same shape, changing dimensions)', (t) => {
86
88
  }, base
87
89
  )
88
90
  const pts = geom3.toPoints(geometry3)
91
+ // expected to throw because capEnd is false (non-closed geometry)
92
+ t.throws(() => geom3.validate(geometry3))
89
93
  t.is(pts.length, 26)
90
94
  })
91
95
 
@@ -103,20 +107,21 @@ test('extrudeFromSlices (changing shape, changing dimensions)', (t) => {
103
107
  }, base
104
108
  )
105
109
  const pts = geom3.toPoints(geometry3)
110
+ t.notThrows.skip(() => geom3.validate(geometry3))
106
111
  t.is(pts.length, 304)
107
112
  })
108
113
 
109
114
  test('extrudeFromSlices (holes)', (t) => {
110
115
  const geometry2 = geom2.create(
111
116
  [
112
- [[-10.0, 10.0], [-10.0, -10.0]],
113
- [[-10.0, -10.0], [10.0, -10.0]],
114
- [[10.0, -10.0], [10.0, 10.0]],
115
- [[10.0, 10.0], [-10.0, 10.0]],
116
- [[-5.0, -5.0], [-5.0, 5.0]],
117
- [[5.0, -5.0], [-5.0, -5.0]],
118
- [[5.0, 5.0], [5.0, -5.0]],
119
- [[-5.0, 5.0], [5.0, 5.0]]
117
+ [[-10, 10], [-10, -10]],
118
+ [[-10, -10], [10, -10]],
119
+ [[10, -10], [10, 10]],
120
+ [[10, 10], [-10, 10]],
121
+ [[-5, -5], [-5, 5]],
122
+ [[5, -5], [-5, -5]],
123
+ [[5, 5], [5, -5]],
124
+ [[-5, 5], [5, 5]]
120
125
  ]
121
126
  )
122
127
  const geometry3 = extrudeFromSlices({ }, geometry2)
@@ -155,6 +160,7 @@ test('extrudeFromSlices (holes)', (t) => {
155
160
  [[-5, -5, 0], [5, -5, 0], [-10, -10, 0]],
156
161
  [[-10, -10, 0], [-10, 10, 0], [-5, -5, 0]]
157
162
  ]
163
+ t.notThrows(() => geom3.validate(geometry3))
158
164
  t.is(pts.length, 32)
159
165
  t.true(comparePolygonsAsPoints(pts, exp))
160
166
  })
@@ -25,6 +25,7 @@ test('extrudeLinear (defaults)', (t) => {
25
25
  [[5, 5, 0], [5, -5, 0], [-5, -5, 0]],
26
26
  [[-5, -5, 0], [-5, 5, 0], [5, 5, 0]]
27
27
  ]
28
+ t.notThrows(() => geom3.validate(geometry3))
28
29
  t.is(pts.length, 12)
29
30
  t.true(comparePolygonsAsPoints(pts, exp))
30
31
  })
@@ -48,6 +49,7 @@ test('extrudeLinear (no twist)', (t) => {
48
49
  [[5, 5, 0], [5, -5, 0], [-5, -5, 0]],
49
50
  [[-5, -5, 0], [-5, 5, 0], [5, 5, 0]]
50
51
  ]
52
+ t.notThrows(() => geom3.validate(geometry3))
51
53
  t.is(pts.length, 12)
52
54
  t.true(comparePolygonsAsPoints(pts, exp))
53
55
 
@@ -67,6 +69,7 @@ test('extrudeLinear (no twist)', (t) => {
67
69
  [[5, -5, 0], [5, 5, 0], [-5, 5, 0]],
68
70
  [[-5, 5, 0], [-5, -5, 0], [5, -5, 0]]
69
71
  ]
72
+ t.notThrows(() => geom3.validate(geometry3))
70
73
  t.is(pts.length, 12)
71
74
  t.true(comparePolygonsAsPoints(pts, exp))
72
75
  })
@@ -98,6 +101,7 @@ test('extrudeLinear (twist)', (t) => {
98
101
  [[5, 5, 0], [5, -5, 0], [-5, -5, 0]],
99
102
  [[-5, -5, 0], [-5, 5, 0], [5, 5, 0]]
100
103
  ]
104
+ t.notThrows(() => geom3.validate(geometry3))
101
105
  t.is(pts.length, 12)
102
106
  t.true(comparePolygonsAsPoints(pts, exp))
103
107
 
@@ -138,19 +142,20 @@ test('extrudeLinear (twist)', (t) => {
138
142
 
139
143
  geometry3 = extrudeLinear({ height: 15, twistAngle: Math.PI / 2, twistSteps: 30 }, geometry2)
140
144
  pts = geom3.toPoints(geometry3)
145
+ t.notThrows(() => geom3.validate(geometry3))
141
146
  t.is(pts.length, 244)
142
147
  })
143
148
 
144
149
  test('extrudeLinear (holes)', (t) => {
145
150
  const geometry2 = geom2.create([
146
- [[-5.00000, 5.00000], [-5.00000, -5.00000]],
147
- [[-5.00000, -5.00000], [5.00000, -5.00000]],
148
- [[5.00000, -5.00000], [5.00000, 5.00000]],
149
- [[5.00000, 5.00000], [-5.00000, 5.00000]],
150
- [[-2.00000, -2.00000], [-2.00000, 2.00000]],
151
- [[2.00000, -2.00000], [-2.00000, -2.00000]],
152
- [[2.00000, 2.00000], [2.00000, -2.00000]],
153
- [[-2.00000, 2.00000], [2.00000, 2.00000]]
151
+ [[-5, 5], [-5, -5]],
152
+ [[-5, -5], [5, -5]],
153
+ [[5, -5], [5, 5]],
154
+ [[5, 5], [-5, 5]],
155
+ [[-2, -2], [-2, 2]],
156
+ [[2, -2], [-2, -2]],
157
+ [[2, 2], [2, -2]],
158
+ [[-2, 2], [2, 2]]
154
159
  ])
155
160
  const geometry3 = extrudeLinear({ height: 15 }, geometry2)
156
161
  const pts = geom3.toPoints(geometry3)
@@ -188,6 +193,7 @@ test('extrudeLinear (holes)', (t) => {
188
193
  [[-2, -2, 0], [2, -2, 0], [-5, -5, 0]],
189
194
  [[-5, -5, 0], [-5, 5, 0], [-2, -2, 0]]
190
195
  ]
196
+ t.notThrows(() => geom3.validate(geometry3))
191
197
  t.is(pts.length, 32)
192
198
  t.true(comparePolygonsAsPoints(pts, exp))
193
199
  })
@@ -195,7 +201,7 @@ test('extrudeLinear (holes)', (t) => {
195
201
  test('extrudeLinear (path2)', (t) => {
196
202
  const geometry2 = path2.fromPoints({ closed: true }, [[0, 0], [12, 0], [6, 10]])
197
203
  const geometry3 = extrudeLinear({ height: 15 }, geometry2)
198
- t.true(geom3.isA(geometry3))
204
+ t.notThrows(() => geom3.validate(geometry3))
199
205
  const pts = geom3.toPoints(geometry3)
200
206
  const exp = [
201
207
  [[6, 10, 0], [0, 0, 0], [0, 0, 15]],
@@ -12,10 +12,12 @@ test('extrudeRectangular (defaults)', (t) => {
12
12
 
13
13
  let obs = extrudeRectangular({ }, geometry1)
14
14
  let pts = geom3.toPoints(obs)
15
+ t.notThrows(() => geom3.validate(obs))
15
16
  t.is(pts.length, 44)
16
17
 
17
18
  obs = extrudeRectangular({ }, geometry2)
18
19
  pts = geom3.toPoints(obs)
20
+ t.notThrows(() => geom3.validate(obs))
19
21
  t.is(pts.length, 32)
20
22
  })
21
23
 
@@ -25,10 +27,12 @@ test('extrudeRectangular (chamfer)', (t) => {
25
27
 
26
28
  let obs = extrudeRectangular({ corners: 'chamfer' }, geometry1)
27
29
  let pts = geom3.toPoints(obs)
30
+ t.notThrows(() => geom3.validate(obs))
28
31
  t.is(pts.length, 60)
29
32
 
30
33
  obs = extrudeRectangular({ corners: 'chamfer' }, geometry2)
31
34
  pts = geom3.toPoints(obs)
35
+ t.notThrows(() => geom3.validate(obs))
32
36
  t.is(pts.length, 48)
33
37
  })
34
38
 
@@ -38,26 +42,29 @@ test('extrudeRectangular (segments = 8, round)', (t) => {
38
42
 
39
43
  let obs = extrudeRectangular({ segments: 8, corners: 'round' }, geometry1)
40
44
  let pts = geom3.toPoints(obs)
45
+ t.notThrows(() => geom3.validate(obs))
41
46
  t.is(pts.length, 84)
42
47
 
43
48
  obs = extrudeRectangular({ segments: 8, corners: 'round' }, geometry2)
44
49
  pts = geom3.toPoints(obs)
50
+ t.notThrows(() => geom3.validate(obs))
45
51
  t.is(pts.length, 64)
46
52
  })
47
53
 
48
54
  test('extrudeRectangular (holes)', (t) => {
49
55
  const geometry2 = geom2.create([
50
- [[15.00000, 15.00000], [-15.00000, 15.00000]],
51
- [[-15.00000, 15.00000], [-15.00000, -15.00000]],
52
- [[-15.00000, -15.00000], [15.00000, -15.00000]],
53
- [[15.00000, -15.00000], [15.00000, 15.00000]],
54
- [[-5.00000, 5.00000], [5.00000, 5.00000]],
55
- [[5.00000, 5.00000], [5.00000, -5.00000]],
56
- [[5.00000, -5.00000], [-5.00000, -5.00000]],
57
- [[-5.00000, -5.00000], [-5.00000, 5.00000]]
56
+ [[15, 15], [-15, 15]],
57
+ [[-15, 15], [-15, -15]],
58
+ [[-15, -15], [15, -15]],
59
+ [[15, -15], [15, 15]],
60
+ [[-5, 5], [5, 5]],
61
+ [[5, 5], [5, -5]],
62
+ [[5, -5], [-5, -5]],
63
+ [[-5, -5], [-5, 5]]
58
64
  ])
59
65
 
60
66
  const obs = extrudeRectangular({ size: 2, height: 15, segments: 16, corners: 'round' }, geometry2)
61
67
  const pts = geom3.toPoints(obs)
68
+ t.notThrows(() => geom3.validate(obs))
62
69
  t.is(pts.length, 192)
63
70
  })
@@ -114,7 +114,11 @@ const extrudeRotate = (options, geometry) => {
114
114
 
115
115
  const matrix = mat4.create()
116
116
  const createSlice = (progress, index, base) => {
117
- const Zrotation = rotationPerSlice * index + startAngle
117
+ let Zrotation = rotationPerSlice * index + startAngle
118
+ // fix rounding error when rotating 2 * PI radians
119
+ if (totalRotation === Math.PI * 2 && index === segments) {
120
+ Zrotation = startAngle
121
+ }
118
122
  mat4.multiply(matrix, mat4.fromZRotation(matrix, Zrotation), mat4.fromXRotation(mat4.create(), Math.PI / 2))
119
123
 
120
124
  return slice.transform(matrix, base)