@jscad/modeling 2.12.5 → 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,17 @@
|
|
|
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
|
+
|
|
6
17
|
## [2.12.5](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/modeling@2.12.4...@jscad/modeling@2.12.5) (2024-12-29)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @jscad/modeling
|
|
@@ -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"),
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscad/modeling",
|
|
3
|
-
"version": "2.12.
|
|
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": "
|
|
64
|
+
"gitHead": "11a9a2d9235d804360116f776076c9f3237a3eb0"
|
|
65
65
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
const { TAU } = require('../../maths/constants')
|
|
2
|
-
const slice = require('./slice')
|
|
3
2
|
const mat4 = require('../../maths/mat4')
|
|
4
|
-
|
|
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
|
|
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
|
|
@@ -20,24 +22,23 @@ const geom2 = require('../../geometries/geom2')
|
|
|
20
22
|
*
|
|
21
23
|
* @example
|
|
22
24
|
* const myshape = circle({size: 3, center: [10, 0]}) // position for extrusion about Z
|
|
23
|
-
* const mycoil = extrudeHelical({angle: TAU*2, pitch: 10, segmentsPerRotation: 64}, myshape))
|
|
25
|
+
* const mycoil = extrudeHelical({angle: TAU * 2, pitch: 10, segmentsPerRotation: 64}, myshape))
|
|
24
26
|
*/
|
|
25
27
|
const extrudeHelical = (options, geometry) => {
|
|
26
28
|
const defaults = {
|
|
27
29
|
angle: TAU,
|
|
28
30
|
startAngle: 0,
|
|
29
31
|
pitch: 10,
|
|
32
|
+
height: 0,
|
|
30
33
|
endOffset: 0,
|
|
31
34
|
segmentsPerRotation: 32
|
|
32
35
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
pitch =
|
|
39
|
-
} else {
|
|
40
|
-
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)
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
// needs at least 3 segments for each revolution
|
|
@@ -46,7 +47,7 @@ const extrudeHelical = (options, geometry) => {
|
|
|
46
47
|
if (segmentsPerRotation < minNumberOfSegments) { throw new Error('The number of segments per rotation needs to be at least 3.') }
|
|
47
48
|
|
|
48
49
|
const shapeSides = geom2.toSides(geometry)
|
|
49
|
-
if (shapeSides.length === 0) throw new Error('
|
|
50
|
+
if (shapeSides.length === 0) throw new Error('The given geometry cannot be empty')
|
|
50
51
|
|
|
51
52
|
// const pointsWithNegativeX = shapeSides.filter((s) => (s[0][0] < 0))
|
|
52
53
|
const pointsWithPositiveX = shapeSides.filter((s) => (s[0][0] >= 0))
|
|
@@ -60,9 +61,11 @@ const extrudeHelical = (options, geometry) => {
|
|
|
60
61
|
|
|
61
62
|
const calculatedSegments = Math.round(segmentsPerRotation / TAU * Math.abs(angle))
|
|
62
63
|
const segments = calculatedSegments >= 2 ? calculatedSegments : 2
|
|
64
|
+
|
|
63
65
|
// define transform matrix variables for performance increase
|
|
64
66
|
const step1 = mat4.create()
|
|
65
|
-
|
|
67
|
+
const step2 = mat4.create()
|
|
68
|
+
|
|
66
69
|
const sliceCallback = (progress, index, base) => {
|
|
67
70
|
const zRotation = startAngle + angle / segments * index
|
|
68
71
|
const xOffset = endOffset / segments * index
|
|
@@ -84,14 +87,13 @@ const extrudeHelical = (options, geometry) => {
|
|
|
84
87
|
mat4.fromXRotation(mat4.create(), -TAU / 4 * Math.sign(angle)) // rotate the slice correctly to not create inside-out polygon
|
|
85
88
|
)
|
|
86
89
|
|
|
87
|
-
matrix = mat4.create()
|
|
88
90
|
mat4.multiply(
|
|
89
|
-
|
|
91
|
+
step2,
|
|
90
92
|
// finally rotate around Z axis
|
|
91
93
|
mat4.fromZRotation(mat4.create(), zRotation),
|
|
92
94
|
step1
|
|
93
95
|
)
|
|
94
|
-
return slice.transform(
|
|
96
|
+
return slice.transform(step2, base)
|
|
95
97
|
}
|
|
96
98
|
|
|
97
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: (
|
|
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({
|
|
67
|
+
const geometry3 = extrudeHelical({ segmentsPerRotation: startSegments + index }, geometry2)
|
|
58
68
|
t.notThrows(() => geom3.validate(geometry3))
|
|
59
69
|
}
|
|
60
70
|
})
|