@jscad/modeling 2.12.4 → 2.12.6

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 CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.12.6](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/modeling@2.12.5...@jscad/modeling@2.12.6) (2025-09-20)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **modeling:** corrected handling of pitch vs height in extrudeHelical ([074f05a](https://github.com/jscad/OpenJSCAD.org/commit/074f05aa5432fbdc8277088e9a45f003f2f978c7))
12
+
13
+
14
+
15
+
16
+
17
+ ## [2.12.5](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/modeling@2.12.4...@jscad/modeling@2.12.5) (2024-12-29)
18
+
19
+ **Note:** Version bump only for package @jscad/modeling
20
+
21
+
22
+
23
+
24
+
6
25
  ## [2.12.4](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/modeling@2.12.3...@jscad/modeling@2.12.4) (2024-11-10)
7
26
 
8
27
  **Note:** Version bump only for package @jscad/modeling
package/LICENSE CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  The MIT License (MIT)
3
3
 
4
- Copyright (c) 2017-2021 JSCAD Organization
4
+ Copyright (c) 2017-2024 JSCAD Organization
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
@@ -923,7 +923,7 @@ const pointInTriangle=(n,a,e,r,i,o,t,x)=>(i-t)*(a-x)-(n-t)*(o-x)>=0&&(n-t)*(r-x)
923
923
  const mat4=require("../../maths/mat4"),geom2=require("../../geometries/geom2"),geom3=require("../../geometries/geom3"),poly3=require("../../geometries/poly3"),slice=require("./slice"),repairSlice=require("./slice/repair"),extrudeWalls=require("./extrudeWalls"),defaultCallback=(e,r,l)=>{let t=null;return geom2.isA(l)&&(t=slice.fromSides(geom2.toSides(l))),poly3.isA(l)&&(t=slice.fromPoints(poly3.toPoints(l))),0===e||1===e?slice.transform(mat4.fromTranslation(mat4.create(),[0,0,e]),t):null},extrudeFromSlices=(e,r)=>{const l={numberOfSlices:2,capStart:!0,capEnd:!0,close:!1,repair:!0,callback:defaultCallback},{numberOfSlices:t,capStart:o,capEnd:c,close:s,repair:i,callback:a}=Object.assign({},l,e);if(t<2)throw new Error("numberOfSlices must be 2 or more");i&&(r=repairSlice(r));const n=t-1;let u=null,m=null,f=null,g=[];for(let e=0;e<t;e++){const l=a(e/n,e,r);if(l){if(!slice.isA(l))throw new Error("the callback function must return slice objects");if(0===slice.toEdges(l).length)throw new Error("the callback function must return slices with one or more edges");f&&(g=g.concat(extrudeWalls(f,l))),0===e&&(u=l),e===t-1&&(m=l),f=l}}if(c){const e=slice.toPolygons(m);g=g.concat(e)}if(o){const e=slice.toPolygons(u).map(poly3.invert);g=g.concat(e)}return o||c||s&&!slice.equals(m,u)&&(g=g.concat(extrudeWalls(m,u))),geom3.create(g)};module.exports=extrudeFromSlices;
924
924
 
925
925
  },{"../../geometries/geom2":25,"../../geometries/geom3":41,"../../geometries/poly3":79,"../../maths/mat4":143,"./extrudeWalls":317,"./slice":326,"./slice/repair":328}],309:[function(require,module,exports){
926
- const{TAU:TAU}=require("../../maths/constants"),slice=require("./slice"),mat4=require("../../maths/mat4"),extrudeFromSlices=require("./extrudeFromSlices"),geom2=require("../../geometries/geom2"),extrudeHelical=(e,t)=>{const r={angle:TAU,startAngle:0,pitch:10,endOffset:0,segmentsPerRotation:32},{angle:a,endOffset:s,segmentsPerRotation:o,startAngle:i}=Object.assign({},r,e);let n;n=!e.pitch&&e.height?e.height/(a/TAU):e.pitch?e.pitch:r.pitch;if(o<3)throw new Error("The number of segments per rotation needs to be at least 3.");const m=geom2.toSides(t);if(0===m.length)throw new Error("the given geometry cannot be empty");const c=m.filter(e=>e[0][0]>=0);let l=slice.fromSides(m);0===c.length&&(l=slice.reverse(l));const h=Math.round(o/TAU*Math.abs(a)),g=h>=2?h:2,u=mat4.create();let f;return extrudeFromSlices({numberOfSlices:g+1,callback:(e,t,r)=>{const o=i+a/g*t,m=s/g*t,c=(o-i)/TAU*n;return mat4.multiply(u,mat4.fromTranslation(mat4.create(),[m,0,c*Math.sign(a)]),mat4.fromXRotation(mat4.create(),-TAU/4*Math.sign(a))),f=mat4.create(),mat4.multiply(f,mat4.fromZRotation(mat4.create(),o),u),slice.transform(f,r)}},l)};module.exports=extrudeHelical;
926
+ const{TAU:TAU}=require("../../maths/constants"),mat4=require("../../maths/mat4"),geom2=require("../../geometries/geom2"),extrudeFromSlices=require("./extrudeFromSlices"),slice=require("./slice"),extrudeHelical=(e,t)=>{const r={angle:TAU,startAngle:0,pitch:10,height:0,endOffset:0,segmentsPerRotation:32};let{angle:a,startAngle:s,pitch:o,height:n,endOffset:i,segmentsPerRotation:m}=Object.assign({},r,e);0!=n&&(o=n/(a/TAU));if(m<3)throw new Error("The number of segments per rotation needs to be at least 3.");const l=geom2.toSides(t);if(0===l.length)throw new Error("The given geometry cannot be empty");const c=l.filter(e=>e[0][0]>=0);let g=slice.fromSides(l);0===c.length&&(g=slice.reverse(g));const h=Math.round(m/TAU*Math.abs(a)),u=h>=2?h:2,f=mat4.create(),d=mat4.create();return extrudeFromSlices({numberOfSlices:u+1,callback:(e,t,r)=>{const n=s+a/u*t,m=i/u*t,l=(n-s)/TAU*o;return mat4.multiply(f,mat4.fromTranslation(mat4.create(),[m,0,l*Math.sign(a)]),mat4.fromXRotation(mat4.create(),-TAU/4*Math.sign(a))),mat4.multiply(d,mat4.fromZRotation(mat4.create(),n),f),slice.transform(d,r)}},g)};module.exports=extrudeHelical;
927
927
 
928
928
  },{"../../geometries/geom2":25,"../../maths/constants":94,"../../maths/mat4":143,"./extrudeFromSlices":308,"./slice":326}],310:[function(require,module,exports){
929
929
  const flatten=require("../../utils/flatten"),geom2=require("../../geometries/geom2"),path2=require("../../geometries/path2"),extrudeLinearGeom2=require("./extrudeLinearGeom2"),extrudeLinearPath2=require("./extrudeLinearPath2"),extrudeLinear=(e,...t)=>{const{height:r,twistAngle:i,twistSteps:n,repair:a}=Object.assign({},{height:1,twistAngle:0,twistSteps:1,repair:!0},e);if(0===(t=flatten(t)).length)throw new Error("wrong number of arguments");e={offset:[0,0,r],twistAngle:i,twistSteps:n,repair:a};const s=t.map(t=>path2.isA(t)?extrudeLinearPath2(e,t):geom2.isA(t)?extrudeLinearGeom2(e,t):t);return 1===s.length?s[0]:s};module.exports=extrudeLinear;
@@ -1163,7 +1163,7 @@ const rectangle=require("./rectangle"),{isGTE:isGTE}=require("./commonChecks"),s
1163
1163
  const{TAU:TAU}=require("../maths/constants"),vec2=require("../maths/vec2"),geom2=require("../geometries/geom2"),{isGT:isGT,isGTE:isGTE,isNumberArray:isNumberArray}=require("./commonChecks"),getRadiusRatio=(e,r)=>e>0&&r>1&&r<e/2?Math.cos(Math.PI*r/e)/Math.cos(Math.PI*(r-1)/e):0,getPoints=(e,r,t,s)=>{const o=TAU/e,i=[];for(let a=0;a<e;a++){const e=vec2.fromAngleRadians(vec2.create(),o*a+t);vec2.scale(e,e,r),vec2.add(e,s,e),i.push(e)}return i},star=e=>{let{center:r,vertices:t,outerRadius:s,innerRadius:o,density:i,startAngle:a}=Object.assign({},{center:[0,0],vertices:5,outerRadius:1,innerRadius:0,density:2,startAngle:0},e);if(!isNumberArray(r,2))throw new Error("center must be an array of X and Y values");if(!isGTE(t,2))throw new Error("vertices must be two or more");if(!isGT(s,0))throw new Error("outerRadius must be greater than zero");if(!isGTE(o,0))throw new Error("innerRadius must be greater than zero");if(!isGTE(a,0))throw new Error("startAngle must be greater than zero");if(t=Math.floor(t),i=Math.floor(i),a%=TAU,0===o){if(!isGTE(i,2))throw new Error("density must be two or more");o=s*getRadiusRatio(t,i)}const n=vec2.clone(r),u=getPoints(t,s,a,n),c=getPoints(t,o,a+Math.PI/t,n),h=[];for(let e=0;e<t;e++)h.push(u[e]),h.push(c[e]);return geom2.fromPoints(h)};module.exports=star;
1164
1164
 
1165
1165
  },{"../geometries/geom2":25,"../maths/constants":94,"../maths/vec2":191,"./commonChecks":370}],389:[function(require,module,exports){
1166
- const{TAU:TAU}=require("../maths/constants"),extrudeRotate=require("../operations/extrusions/extrudeRotate"),{rotate:rotate}=require("../operations/transforms/rotate"),{translate:translate}=require("../operations/transforms/translate"),circle=require("./circle"),{isGT:isGT,isGTE:isGTE}=require("./commonChecks"),torus=e=>{const r={innerRadius:1,innerSegments:32,outerRadius:4,outerSegments:32,innerRotation:0,startAngle:0,outerRotation:TAU},{innerRadius:t,innerSegments:o,outerRadius:n,outerSegments:s,innerRotation:i,startAngle:a,outerRotation:u}=Object.assign({},r,e);if(!isGT(t,0))throw new Error("innerRadius must be greater than zero");if(!isGTE(o,3))throw new Error("innerSegments must be three or more");if(!isGT(n,0))throw new Error("outerRadius must be greater than zero");if(!isGTE(s,3))throw new Error("outerSegments must be three or more");if(!isGTE(a,0))throw new Error("startAngle must be positive");if(!isGT(u,0))throw new Error("outerRotation must be greater than zero");if(t>=n)throw new Error("inner circle is two large to rotate about the outer circle");let m=circle({radius:t,segments:o});return 0!==i&&(m=rotate([0,0,i],m)),m=translate([n,0],m),extrudeRotate({startAngle:a,angle:u,segments:s},m)};module.exports=torus;
1166
+ const{TAU:TAU}=require("../maths/constants"),extrudeRotate=require("../operations/extrusions/extrudeRotate"),{rotate:rotate}=require("../operations/transforms/rotate"),{translate:translate}=require("../operations/transforms/translate"),circle=require("./circle"),{isGT:isGT,isGTE:isGTE}=require("./commonChecks"),torus=e=>{const r={innerRadius:1,innerSegments:32,outerRadius:4,outerSegments:32,innerRotation:0,startAngle:0,outerRotation:TAU},{innerRadius:t,innerSegments:o,outerRadius:n,outerSegments:s,innerRotation:i,startAngle:a,outerRotation:u}=Object.assign({},r,e);if(!isGT(t,0))throw new Error("innerRadius must be greater than zero");if(!isGTE(o,3))throw new Error("innerSegments must be three or more");if(!isGT(n,0))throw new Error("outerRadius must be greater than zero");if(!isGTE(s,3))throw new Error("outerSegments must be three or more");if(!isGTE(a,0))throw new Error("startAngle must be positive");if(!isGT(u,0))throw new Error("outerRotation must be greater than zero");if(t>=n)throw new Error("inner circle is too large to rotate about the outer circle");let m=circle({radius:t,segments:o});return 0!==i&&(m=rotate([0,0,i],m)),m=translate([n,0],m),extrudeRotate({startAngle:a,angle:u,segments:s},m)};module.exports=torus;
1167
1167
 
1168
1168
  },{"../maths/constants":94,"../operations/extrusions/extrudeRotate":316,"../operations/transforms/rotate":364,"../operations/transforms/translate":367,"./circle":369,"./commonChecks":370}],390:[function(require,module,exports){
1169
1169
  const{NEPS:NEPS}=require("../maths/constants"),vec2=require("../maths/vec2"),geom2=require("../geometries/geom2"),{isNumberArray:isNumberArray}=require("./commonChecks"),solveAngleFromSSS=(e,r,t)=>Math.acos((e*e+r*r-t*t)/(2*e*r)),solveSideFromSAS=(e,r,t)=>r>NEPS?Math.sqrt(e*e+t*t-2*e*t*Math.cos(r)):Math.sqrt((e-t)*(e-t)+e*t*r*r*(1-r*r/12)),solveAAA=e=>{if(Math.abs(e[0]+e[1]+e[2]-Math.PI)>NEPS)throw new Error("AAA triangles require angles that sum to PI");const r=e[0],t=e[1],s=Math.PI-r-t,a=1/Math.sin(s)*Math.sin(r),n=1/Math.sin(s)*Math.sin(t);return createTriangle(r,t,s,a,n,1)},solveAAS=e=>{const r=e[0],t=e[1],s=Math.PI+NEPS-r-t;if(s<NEPS)throw new Error("AAS triangles require angles that sum to PI");const a=e[2],n=a/Math.sin(r)*Math.sin(t),o=a/Math.sin(r)*Math.sin(s);return createTriangle(r,t,s,a,n,o)},solveASA=e=>{const r=e[0],t=e[2],s=Math.PI+NEPS-r-t;if(s<NEPS)throw new Error("ASA triangles require angles that sum to PI");const a=e[1],n=a/Math.sin(s)*Math.sin(r),o=a/Math.sin(s)*Math.sin(t);return createTriangle(r,t,s,n,o,a)},solveSAS=e=>{const r=e[0],t=e[1],s=e[2],a=solveSideFromSAS(r,t,s),n=solveAngleFromSSS(a,r,s),o=Math.PI-n-t;return createTriangle(n,t,o,s,a,r)},solveSSA=e=>{const r=e[0],t=e[1],s=e[2],a=Math.asin(t*Math.sin(s)/r),n=Math.PI-a-s,o=r/Math.sin(s)*Math.sin(n);return createTriangle(a,n,s,t,o,r)},solveSSS=e=>{const r=e[1],t=e[2],s=e[0];if(r+t<=s||t+s<=r||s+r<=t)throw new Error("SSS triangle is incorrect, as the longest side is longer than the sum of the other sides");const a=solveAngleFromSSS(t,s,r),n=solveAngleFromSSS(s,r,t),o=Math.PI-a-n;return createTriangle(a,n,o,r,t,s)},createTriangle=(e,r,t,s,a,n)=>{const o=vec2.fromValues(0,0),i=vec2.fromValues(n,0),S=vec2.fromValues(s,0);return vec2.add(S,vec2.rotate(S,S,[0,0],Math.PI-r),i),geom2.fromPoints([o,i,S])},triangle=e=>{let{type:r,values:t}=Object.assign({},{type:"SSS",values:[1,1,1]},e);if("string"!=typeof r)throw new Error("triangle type must be a string");if("A"!==(r=r.toUpperCase())[0]&&"S"!==r[0]||"A"!==r[1]&&"S"!==r[1]||"A"!==r[2]&&"S"!==r[2])throw new Error("triangle type must contain three letters; A or S");if(!isNumberArray(t,3))throw new Error("triangle values must contain three values");if(!t.every(e=>e>0))throw new Error("triangle values must be greater than zero");switch(r){case"AAA":return solveAAA(t);case"AAS":return solveAAS(t);case"ASA":return solveASA(t);case"SAS":return solveSAS(t);case"SSA":return solveSSA(t);case"SSS":return solveSSS(t);default:throw new Error("invalid triangle type, try again")}};module.exports=triangle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jscad/modeling",
3
- "version": "2.12.4",
3
+ "version": "2.12.6",
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",
@@ -61,5 +61,5 @@
61
61
  "nyc": "15.1.0",
62
62
  "uglifyify": "5.0.2"
63
63
  },
64
- "gitHead": "ce4978ee40c30803cba05d1c96ba8b6aaf3abc61"
64
+ "gitHead": "11a9a2d9235d804360116f776076c9f3237a3eb0"
65
65
  }
@@ -3,7 +3,6 @@
3
3
  * @module modeling/curves
4
4
  * @example
5
5
  * const { bezier } = require('@jscad/modeling').curves
6
-
7
6
  */
8
7
  module.exports = {
9
8
  bezier: require('./bezier')
@@ -4,8 +4,7 @@
4
4
  * @see Most computations are based upon the glMatrix library (glmatrix.net)
5
5
  * @module modeling/maths
6
6
  * @example
7
- * const { constants, line2, mat4, vec2, vec3 } = require('@jscad/modeling').maths
8
-
7
+ * const { constants, line2, line3, mat4, plane, utils, vec2, vec3, vec4 } = require('@jscad/modeling').maths
9
8
  */
10
9
  module.exports = {
11
10
  constants: require('./constants'),
@@ -17,7 +17,7 @@ const intersectGeom3 = require('./intersectGeom3')
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
  * +-------+
@@ -9,6 +9,8 @@ const scissionGeom3 = require('./scissionGeom3')
9
9
  /**
10
10
  * Scission (divide) the given geometry into the component pieces.
11
11
  *
12
+ * NOTE: Currently only 3D geometries are supported.
13
+ *
12
14
  * @param {...Object} objects - list of geometries
13
15
  * @returns {Array} list of pieces from each geometry
14
16
  * @alias module:modeling/booleans.scission
@@ -17,7 +17,7 @@ const subtractGeom3 = require('./subtractGeom3')
17
17
  * @alias module:modeling/booleans.subtract
18
18
  *
19
19
  * @example
20
- * let myshape = subtract(cuboid({size: [5,5,5]}), cuboid({size: [5,5,5], center: [5,5,5]}))
20
+ * let myshape = subtract(cuboid({size: 5}), cuboid({size: 5, center: [3,3,3]}))
21
21
  *
22
22
  * @example
23
23
  * +-------+ +-------+
@@ -16,7 +16,7 @@ const unionGeom3 = require('./unionGeom3')
16
16
  * @alias module:modeling/booleans.union
17
17
  *
18
18
  * @example
19
- * let myshape = union(cube({size: [5,5,5]}), cube({size: [5,5,5], center: [5,5,5]}))
19
+ * let myshape = union(cube({size: 5}), cube({size: 5, center: [3,3,3]}))
20
20
  *
21
21
  * @example
22
22
  * +-------+ +-------+
@@ -24,7 +24,7 @@ const expandPath2 = require('./expandPath2')
24
24
  * @example
25
25
  * let newarc = expand({delta: 5, corners: 'edge'}, arc({}))
26
26
  * let newsquare = expand({delta: 5, corners: 'chamfer'}, square({size: 30}))
27
- * let newsphere = expand({delta: 2, corners: 'round'}, cuboid({size: [20, 25, 5]}))
27
+ * let newcuboid = expand({delta: 2, corners: 'round'}, cuboid({size: [20, 25, 5]}))
28
28
  */
29
29
  const expand = (options, ...objects) => {
30
30
  objects = flatten(objects)
@@ -1,16 +1,18 @@
1
1
  const { TAU } = require('../../maths/constants')
2
- const slice = require('./slice')
3
2
  const mat4 = require('../../maths/mat4')
4
- const extrudeFromSlices = require('./extrudeFromSlices')
3
+
5
4
  const geom2 = require('../../geometries/geom2')
6
5
 
6
+ const extrudeFromSlices = require('./extrudeFromSlices')
7
+ const slice = require('./slice')
8
+
7
9
  /**
8
10
  * Perform a helical extrude of the geometry, using the given options.
9
11
  *
10
12
  * @param {Object} options - options for extrusion
11
- * @param {Number} [options.angle=TAU] - angle of the extrusion (RADIANS) positive for right-hand rotation, negative for left-hand
13
+ * @param {Number} [options.angle=TAU] - angle of the extrusion (RADIANS); positive for right-hand rotation, negative for left-hand
12
14
  * @param {Number} [options.startAngle=0] - start angle of the extrusion (RADIANS)
13
- * @param {Number} [options.pitch=10] - elevation gain for each turn
15
+ * @param {Number} [options.pitch=10] - elevation gain for each full rotation
14
16
  * @param {Number} [options.height] - total height of the helix path. Ignored if pitch is set.
15
17
  * @param {Number} [options.endOffset=0] - offset the final radius of the extrusion, allowing for tapered helix, and or spiral
16
18
  * @param {Number} [options.segmentsPerRotation=32] - number of segments per full rotation of the extrusion
@@ -19,31 +21,24 @@ const geom2 = require('../../geometries/geom2')
19
21
  * @alias module:modeling/extrusions.extrudeHelical
20
22
  *
21
23
  * @example
22
- * const myshape = extrudeHelical(
23
- * {
24
- * angle: Math.PI * 4,
25
- * pitch: 10,
26
- * segmentsPerRotation: 64
27
- * },
28
- * circle({size: 3, center: [10, 0]})
29
- * )
24
+ * const myshape = circle({size: 3, center: [10, 0]}) // position for extrusion about Z
25
+ * const mycoil = extrudeHelical({angle: TAU * 2, pitch: 10, segmentsPerRotation: 64}, myshape))
30
26
  */
31
27
  const extrudeHelical = (options, geometry) => {
32
28
  const defaults = {
33
29
  angle: TAU,
34
30
  startAngle: 0,
35
31
  pitch: 10,
32
+ height: 0,
36
33
  endOffset: 0,
37
34
  segmentsPerRotation: 32
38
35
  }
39
- const { angle, endOffset, segmentsPerRotation, startAngle } = Object.assign({}, defaults, options)
40
-
41
- let pitch
42
- // ignore height if pitch is set
43
- if (!options.pitch && options.height) {
44
- pitch = options.height / (angle / TAU)
45
- } else {
46
- pitch = options.pitch ? options.pitch : defaults.pitch
36
+ let { angle, startAngle, pitch, height, endOffset, segmentsPerRotation } = Object.assign({}, defaults, options)
37
+
38
+ // calculate pitch from height if available
39
+ if (height != 0) {
40
+ // height / number of full rotations
41
+ pitch = height / (angle / TAU)
47
42
  }
48
43
 
49
44
  // needs at least 3 segments for each revolution
@@ -52,7 +47,7 @@ const extrudeHelical = (options, geometry) => {
52
47
  if (segmentsPerRotation < minNumberOfSegments) { throw new Error('The number of segments per rotation needs to be at least 3.') }
53
48
 
54
49
  const shapeSides = geom2.toSides(geometry)
55
- if (shapeSides.length === 0) throw new Error('the given geometry cannot be empty')
50
+ if (shapeSides.length === 0) throw new Error('The given geometry cannot be empty')
56
51
 
57
52
  // const pointsWithNegativeX = shapeSides.filter((s) => (s[0][0] < 0))
58
53
  const pointsWithPositiveX = shapeSides.filter((s) => (s[0][0] >= 0))
@@ -66,9 +61,11 @@ const extrudeHelical = (options, geometry) => {
66
61
 
67
62
  const calculatedSegments = Math.round(segmentsPerRotation / TAU * Math.abs(angle))
68
63
  const segments = calculatedSegments >= 2 ? calculatedSegments : 2
64
+
69
65
  // define transform matrix variables for performance increase
70
66
  const step1 = mat4.create()
71
- let matrix
67
+ const step2 = mat4.create()
68
+
72
69
  const sliceCallback = (progress, index, base) => {
73
70
  const zRotation = startAngle + angle / segments * index
74
71
  const xOffset = endOffset / segments * index
@@ -90,14 +87,13 @@ const extrudeHelical = (options, geometry) => {
90
87
  mat4.fromXRotation(mat4.create(), -TAU / 4 * Math.sign(angle)) // rotate the slice correctly to not create inside-out polygon
91
88
  )
92
89
 
93
- matrix = mat4.create()
94
90
  mat4.multiply(
95
- matrix,
91
+ step2,
96
92
  // finally rotate around Z axis
97
93
  mat4.fromZRotation(mat4.create(), zRotation),
98
94
  step1
99
95
  )
100
- return slice.transform(matrix, base)
96
+ return slice.transform(step2, base)
101
97
  }
102
98
 
103
99
  return extrudeFromSlices(
@@ -34,7 +34,17 @@ test('extrudeHelical: (pitch) extruding of a circle produces an expected geom3',
34
34
  const geometry2 = circle({ size: 3, center: [10, 0] })
35
35
  for (const index of [...Array(20).keys()]) {
36
36
  // also test negative pitches
37
- const geometry3 = extrudeHelical({ pitch: startPitch + index }, geometry2)
37
+ const geometry3 = extrudeHelical({ pitch: startPitch + index, startAngle: TAU * index }, geometry2)
38
+ t.notThrows(() => geom3.validate(geometry3))
39
+ }
40
+ })
41
+
42
+ test('extrudeHelical: (height) extruding of a circle produces an expected geom3', (t) => {
43
+ const startHeight = -5
44
+ const geometry2 = circle({ size: 3, center: [10, 0] })
45
+ for (const index of [...Array(10).keys()]) {
46
+ // also test negative heights
47
+ const geometry3 = extrudeHelical({ height: startHeight + index, angle: TAU * (index + 1) }, geometry2)
38
48
  t.notThrows(() => geom3.validate(geometry3))
39
49
  }
40
50
  })
@@ -49,12 +59,12 @@ test('extrudeHelical: (endRadiusOffset) extruding of a circle produces an expect
49
59
  }
50
60
  })
51
61
 
52
- test('extrudeHelical: (segments) extruding of a circle produces an expected geom3', (t) => {
62
+ test('extrudeHelical: (segmentsPerRotation) extruding of a circle produces an expected geom3', (t) => {
53
63
  const startSegments = 3
54
64
  const geometry2 = circle({ size: 3, center: [10, 0] })
55
65
  for (const index of [...Array(30).keys()]) {
56
66
  // also test negative pitches
57
- const geometry3 = extrudeHelical({ segments: startSegments + index }, geometry2)
67
+ const geometry3 = extrudeHelical({ segmentsPerRotation: startSegments + index }, geometry2)
58
68
  t.notThrows(() => geom3.validate(geometry3))
59
69
  }
60
70
  })
@@ -3,7 +3,7 @@
3
3
  * In all cases, the function returns the results, and never changes the original shapes.
4
4
  * @module modeling/extrusions
5
5
  * @example
6
- * const { extrudeLinear, extrudeRectangular, extrudeRotate } = require('@jscad/modeling').extrusions
6
+ * const { extrudeHelical, extrudeLinear, extrudeRectangular, extrudeRotate, project, slice } = require('@jscad/modeling').extrusions
7
7
  */
8
8
  module.exports = {
9
9
  extrudeFromSlices: require('./extrudeFromSlices'),
@@ -4,7 +4,7 @@
4
4
  * In all cases, the function returns the results, and never changes the original shapes.
5
5
  * @module modeling/hulls
6
6
  * @example
7
- * const { hull, hullChain } = require('@jscad/modeling').hulls
7
+ * const { hull, hullChain, hullPoints2, hullPoints3 } = require('@jscad/modeling').hulls
8
8
  */
9
9
  module.exports = {
10
10
  hull: require('./hull'),
@@ -3,7 +3,7 @@
3
3
  * In all cases, these functions returns the results, and never changes the original geometry.
4
4
  * @module modeling/modifiers
5
5
  * @example
6
- * const { snap } = require('@jscad/modeling').modifiers
6
+ * const { generalize, snap, retessellate } = require('@jscad/modeling').modifiers
7
7
  */
8
8
  module.exports = {
9
9
  generalize: require('./generalize'),
@@ -35,7 +35,7 @@ const snapGeom3 = (geometry) => {
35
35
  }
36
36
 
37
37
  /**
38
- * Snap the given geometries to the overall precision (epsilon) of the geometry.
38
+ * Snap the given geometries to the precision (calculated epsilon) of the geometry.
39
39
  * @see measurements.measureEpsilon()
40
40
  * @param {...Object} geometries - the geometries to snap
41
41
  * @return {Object|Array} the snapped geometry, or a list of snapped geometries
@@ -17,6 +17,8 @@ const { isGT, isGTE, isNumberArray } = require('./commonChecks')
17
17
  * @param {Boolean} [options.makeTangent=false] - adds line segments at both ends of the arc to ensure that the gradients at the edges are tangent
18
18
  * @returns {path2} new 2D path
19
19
  * @alias module:modeling/primitives.arc
20
+ * @example
21
+ * let myshape = arc({ center: [-1, -1], radius: 2, endAngle: (TAU / 4)})
20
22
  */
21
23
  const arc = (options) => {
22
24
  const defaults = {
@@ -5,6 +5,7 @@ const { isNumberArray } = require('./commonChecks')
5
5
 
6
6
  /**
7
7
  * Construct a polyhedron in three dimensional space from the given set of 3D points and faces.
8
+ *
8
9
  * The faces can define outward or inward facing polygons (orientation).
9
10
  * However, each face must define a counter clockwise rotation of points which follows the right hand rule.
10
11
  * @param {Object} options - options for construction
@@ -18,7 +19,7 @@ const { isNumberArray } = require('./commonChecks')
18
19
  * @example
19
20
  * let mypoints = [ [10, 10, 0], [10, -10, 0], [-10, -10, 0], [-10, 10, 0], [0, 0, 10] ]
20
21
  * let myfaces = [ [0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4], [1, 0, 3], [2, 1, 3] ]
21
- * let myshape = polyhedron({points: mypoint, faces: myfaces, orientation: 'inward'})
22
+ * let myshape = polyhedron({points: mypoints, faces: myfaces, orientation: 'inward'})
22
23
  */
23
24
  const polyhedron = (options) => {
24
25
  const defaults = {
@@ -43,7 +43,7 @@ const torus = (options) => {
43
43
  if (!isGTE(startAngle, 0)) throw new Error('startAngle must be positive')
44
44
  if (!isGT(outerRotation, 0)) throw new Error('outerRotation must be greater than zero')
45
45
 
46
- if (innerRadius >= outerRadius) throw new Error('inner circle is two large to rotate about the outer circle')
46
+ if (innerRadius >= outerRadius) throw new Error('inner circle is too large to rotate about the outer circle')
47
47
 
48
48
  let innerCircle = circle({ radius: innerRadius, segments: innerSegments })
49
49
 
@@ -22,11 +22,8 @@ const vectorParams = require('./vectorParams')
22
22
  *
23
23
  * @example
24
24
  * let vectorCharObject = vectorChar()
25
- * or
26
25
  * let vectorCharObject = vectorChar('A')
27
- * or
28
26
  * let vectorCharObject = vectorChar({ xOffset: 57 }, 'C')
29
- * or
30
27
  * let vectorCharObject = vectorChar({ xOffset: 78, input: '!' })
31
28
  */
32
29
  const vectorChar = (options, char) => {
@@ -35,11 +35,8 @@ const translateLine = (options, line) => {
35
35
  *
36
36
  * @example
37
37
  * let textSegments = vectorText()
38
- * or
39
38
  * let textSegments = vectorText('OpenJSCAD')
40
- * or
41
39
  * let textSegments = vectorText({ yOffset: -50 }, 'OpenJSCAD')
42
- * or
43
40
  * let textSegments = vectorText({ yOffset: -80, input: 'OpenJSCAD' })
44
41
  */
45
42
  const vectorText = (options, text) => {
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Utility functions of various sorts.
2
+ * Utility functions of various sorts, including conversions from different angular measures.
3
3
  * @module modeling/utils
4
4
  * @example
5
- * const { flatten, insertSorted } = require('@jscad/modeling').utils
5
+ * const { areAllShapesTheSameType, degToRad, radiusToSegments, radToDeg } = require('@jscad/modeling').utils
6
6
  */
7
7
  module.exports = {
8
8
  areAllShapesTheSameType: require('./areAllShapesTheSameType'),