Creating Shapes with Rotated Extrusion
Rotated extrusion is a powerful 3D modeling technique used to create spiral-like or helical shapes. It works by taking a 2D profile (an open or closed wire) and sweeping it along a vertical axis while simultaneously rotating the profile around that same axis. This is distinct from a simple revolution (which creates axially symmetric objects) as it involves both linear extrusion and rotation occurring concurrently over the length of the extrusion. This method can be used for generating objects like springs, threads, spiral staircases, or decorative twisted elements.
How Rotated Extrusion Works
The process of creating a shape through rotated extrusion involves several key elements:
- The Profile Shape (Wire): This is the initial 2D wire (which can be open, like a line or an arc, or closed, like a rectangle or a circle) that will be extruded and rotated. The geometry of this profile dictates the cross-section of the resulting spiral or helical 3D object.
- The Extrusion Height (or Length): This scalar value specifies the distance the profile travels along the [0, 1, 0] vector.
- The Total Angle of Rotation (Twist): This defines how much the profile rotates around the [0, 1, 0] axis as it travels the full Extrusion Height. For example, a 360-degree angle means the profile completes one full twist.
- Make Solid Option: If the input profile is a closed wire, setting the
makeSolidoption totrue(as seen in the Rete example'srotatedExtrudenode and TypeScript'srotatedExtrudeOpt.makeSolid = true) will attempt to create a volumetric, solid helical shape. If the wire is open, or ifmakeSolidisfalse, the result will typically be a helical shell or ribbon-like surface.
In the examples below, we will demonstrate how to create a simple solid 3D helical shape by taking a 2D rectangular wire, defining an extrusion height, a rotation angle, and performing a rotated extrusion.
- Rete
- Blockly
- TypeScript
{
"id": "rete-v2-json",
"nodes": {
"b81c269e01ad99bf": {
"id": "b81c269e01ad99bf",
"name": "bitbybit.occt.operations.rotatedExtrude",
"customName": "rotated extrude",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"height": 4,
"angle": 360,
"makeSolid": true
},
"inputs": {
"shape": {
"connections": [
{
"node": "3b59b158c5f9e707",
"output": "result",
"data": {}
}
]
}
},
"position": [
1000.85546875,
374.42578125
]
},
"3b59b158c5f9e707": {
"id": "3b59b158c5f9e707",
"name": "bitbybit.occt.shapes.wire.createRectangleWire",
"customName": "rectangle wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"width": 1,
"length": 3,
"center": [
0,
0,
0
],
"direction": [
0,
1,
0
]
},
"inputs": {},
"position": [
590.111628276491,
367.01635289835633
]
}
}
}
<xml xmlns="https://developers.google.com/blockly/xml"><block type="bitbybit.draw.drawAnyAsyncNoReturn" id="BWkO[mpA[k%*`WcS0D-I" x="430" y="269"><value name="Entity"><block type="bitbybit.occt.operations.rotatedExtrude" id="b;GP0xWv4@@%b1+Jjy)*"><value name="Shape"><block type="bitbybit.occt.shapes.wire.createRectangleWire" id="l}c#+NcBzKg2|7~[Wtz2"><value name="Width"><block type="math_number" id="K,;T)c:|m*NX`p/KSL^b"><field name="NUM">1</field></block></value><value name="Length"><block type="math_number" id="kNkAB)J,hXVgVOZREDfF"><field name="NUM">3</field></block></value><value name="Center"><block type="bitbybit.point.pointXYZ" id="gGsDV%R7pEqVUV)]Ak=!"><value name="X"><block type="math_number" id="9pVY4OAE0=K)Jow-RlN`"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="x)/u`9kUBOlNiT:`!/m#"><field name="NUM">0</field></block></value><value name="Z"><block type="math_number" id="rE[P-M97-1wJY.rX8uu2"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="LUEU*S0,;rY]ou)ec|i~"><value name="X"><block type="math_number" id="T+]$#i1C1Xf0qK=},Y}$"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="n@zY(8TAWfo_]gPsc9^7"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="rbwM(a%i8}!~aI!/vgyt"><field name="NUM">0</field></block></value></block></value></block></value><value name="Height"><block type="math_number" id="wLOt8HP9jS_pJ_7[y`aG"><field name="NUM">4</field></block></value><value name="Angle"><block type="math_number" id="idq9Z3%l9Wu!XT1_q|aT"><field name="NUM">360</field></block></value><value name="MakeSolid"><block type="logic_boolean" id="-BD@,[8}Z?2AU*q@~AR7"><field name="BOOL">TRUE</field></block></value></block></value></block></xml>
const start = async () => {
const recOpt = new Bit.Inputs.OCCT.RectangleDto(1, 3);
const rectangle = await bitbybit.occt.shapes.wire.createRectangleWire(recOpt);
const rotatedExtrudeOpt = new Bit.Inputs.OCCT.RotationExtrudeDto(rectangle, 4);
const rotatedExtrude = await bitbybit.occt.operations.rotatedExtrude(rotatedExtrudeOpt)
bitbybit.draw.drawAnyAsync({ entity: rotatedExtrude });
}
start();
Profiles Further From The Center
When the profile is further from the center it will form a helix like shape. This can be quite powerful and create beautiful looking aesthetics. Check these scripts below.
- Rete
- Blockly
- TypeScript
{
"id": "rete-v2-json",
"nodes": {
"2a178330a72730f1": {
"id": "2a178330a72730f1",
"name": "bitbybit.occt.operations.rotatedExtrude",
"customName": "rotated extrude",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"height": 30,
"angle": 270,
"makeSolid": true
},
"inputs": {
"shape": {
"connections": [
{
"node": "9f969594b0cf6f40",
"output": "result",
"data": {}
}
]
}
},
"position": [
1048.917561518481,
99.26741917649946
]
},
"9f969594b0cf6f40": {
"id": "9f969594b0cf6f40",
"name": "bitbybit.occt.shapes.wire.createNGonWire",
"customName": "ngon wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"center": [
0,
0,
0
],
"direction": [
0,
1,
0
],
"nrCorners": 10,
"radius": 1.5
},
"inputs": {
"center": {
"connections": [
{
"node": "3a3daa50670b0170",
"output": "result",
"data": {}
}
]
}
},
"position": [
681.4368917567588,
98.75263851654029
]
},
"b9686ed54db82b31": {
"id": "b9686ed54db82b31",
"name": "bitbybit.occt.shapes.wire.createEllipseWire",
"customName": "ellipse wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"center": [
0,
0,
0
],
"direction": [
0,
1,
0
],
"radiusMinor": 6,
"radiusMajor": 10
},
"inputs": {},
"position": [
-405.4059203757722,
100.09558335851492
]
},
"50ac968a2267e242": {
"id": "50ac968a2267e242",
"name": "bitbybit.occt.shapes.wire.divideWireByEqualDistanceToPoints",
"customName": "divide wire by equal distance to points",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"nrOfDivisions": 12,
"removeStartPoint": false,
"removeEndPoint": true
},
"inputs": {
"shape": {
"connections": [
{
"node": "b9686ed54db82b31",
"output": "result",
"data": {}
}
]
}
},
"position": [
-43.11135880446045,
99.30732643142413
]
},
"3a3daa50670b0170": {
"id": "3a3daa50670b0170",
"name": "bitbybit.lists.flatten",
"customName": "flatten",
"data": {
"nrLevels": 1
},
"inputs": {
"list": {
"connections": [
{
"node": "50ac968a2267e242",
"output": "result",
"data": {}
}
]
}
},
"position": [
317.43152070664985,
140.10402442188126
]
},
"cf557032f1c85ae6": {
"id": "cf557032f1c85ae6",
"name": "bitbybit.occt.shapes.compound.makeCompound",
"customName": "make compound",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
}
},
"inputs": {
"shapes": {
"connections": [
{
"node": "41273b74cff6013f",
"output": "list",
"data": {}
}
]
}
},
"position": [
1787.5233904608274,
100.3102983195994
]
},
"41273b74cff6013f": {
"id": "41273b74cff6013f",
"name": "bitbybit.lists.createList",
"customName": "create list",
"data": {},
"inputs": {
"listElements": {
"connections": [
{
"node": "2a178330a72730f1",
"output": "result",
"data": {}
}
]
}
},
"position": [
1417.6402186250577,
140.08080278569008
]
},
"f5a25c4870f08e58": {
"id": "f5a25c4870f08e58",
"name": "bitbybit.babylon.scene.adjustActiveArcRotateCamera",
"customName": "adjust active arc rotate camera",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"position": [
10,
10,
10
],
"lookAt": [
0,
0,
0
],
"lowerBetaLimit": 1,
"upperBetaLimit": 179,
"angularSensibilityX": 1000,
"angularSensibilityY": 1000,
"maxZ": 1000,
"panningSensibility": 1000,
"wheelPrecision": 3
},
"inputs": {
"lookAt": {
"connections": [
{
"node": "b598b77e334923e8",
"output": "result",
"data": {}
}
]
},
"position": {
"connections": [
{
"node": "02f6cb1875fcb3b0",
"output": "result",
"data": {}
}
]
}
},
"position": [
-401.10121599018737,
503.67305401118836
]
},
"b598b77e334923e8": {
"id": "b598b77e334923e8",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": 15,
"z": 0
},
"inputs": {},
"position": [
-982.1348053948486,
687.8467720151841
]
},
"02f6cb1875fcb3b0": {
"id": "02f6cb1875fcb3b0",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 30,
"y": 50,
"z": 30
},
"inputs": {},
"position": [
-976.7459224056403,
354.88193439985156
]
}
}
}
<xml xmlns="https://developers.google.com/blockly/xml"><variables><variable id="[ePn3YDgUxp(6jvgtc^%">pointPromises</variable><variable id="N~r?iLtkz{1P+j9_F_.;">points</variable><variable id="#lFFf=PrmJ(s52I9+mXd">ngonRotationPromises</variable><variable id="pOreDzs^|:kJeR;cd8`Y">i</variable></variables><block type="bitbybit.babylon.scene.adjustActiveArcRotateCamera" id="w:k+)AYYK:7xupkSxBW=" x="-479" y="-1129"><value name="Position"><block type="bitbybit.point.pointXYZ" id="SV4qVE0F]isda?4Q6}o6"><value name="X"><block type="math_number" id="(Us@:mLfm^[cXgFG2D8U"><field name="NUM">30</field></block></value><value name="Y"><block type="math_number" id="/28/pJyIa9a,K@XK1^z6"><field name="NUM">50</field></block></value><value name="Z"><block type="math_number" id="}jsS/Dz[K9SA1f_Ix6gb"><field name="NUM">30</field></block></value></block></value><value name="LookAt"><block type="bitbybit.point.pointXYZ" id="-SBUvba}xnGUj4tffFmd"><value name="X"><block type="math_number" id="qmOT=-|o`W7!@*SAd|-r"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="XH~E~fYdv1iE+_vE;Z_n"><field name="NUM">15</field></block></value><value name="Z"><block type="math_number" id="RX{ISy65E578PqhO]0M%"><field name="NUM">0</field></block></value></block></value><value name="LowerBetaLimit"><block type="math_number" id="A/{s2591jmwz$K_HG|+0"><field name="NUM">1</field></block></value><value name="UpperBetaLimit"><block type="math_number" id="u3lnSAKji4cG6qBs|sJI"><field name="NUM">179</field></block></value><value name="AngularSensibilityX"><block type="math_number" id="OG%9ZN$X*t{a@?da5OtK"><field name="NUM">1000</field></block></value><value name="AngularSensibilityY"><block type="math_number" id="71Uv^2J*OX;-DBO,7~C]"><field name="NUM">1000</field></block></value><value name="MaxZ"><block type="math_number" id="Vmd}b*+~pz`PoHGBK6F;"><field name="NUM">1000</field></block></value><value name="PanningSensibility"><block type="math_number" id="lxo?L}-F;X8b@nA]7s|X"><field name="NUM">1000</field></block></value><value name="WheelPrecision"><block type="math_number" id="cV/Irf{r87PRUrbJ@d{M"><field name="NUM">3</field></block></value><next><block type="base_time_async_context" id="ryHl/lk5O)ODdw~2I_k0"><statement name="Then"><block type="variables_set" id="[l7M%G:b8xM4{.0{p#+N"><field name="VAR" id="[ePn3YDgUxp(6jvgtc^%">pointPromises</field><value name="VALUE"><block type="bitbybit.occt.shapes.wire.divideWireByEqualDistanceToPoints" id="PTZ{d{gl.C!E+*~4HeyR"><value name="Shape"><block type="bitbybit.occt.shapes.wire.createEllipseWire" id="?,4hJG`CZ0.Nn@B}|`I2"><value name="Center"><block type="bitbybit.point.pointXYZ" id="KmIv[taPzaHtwrEzE1a:"><value name="X"><block type="math_number" id="Rc(K25~.z`Ma%VG98~PC"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="i=P(|[lU:^Sri[AWi@7l"><field name="NUM">0</field></block></value><value name="Z"><block type="math_number" id="XX:H+=G3tJV{3LE]_P$,"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="UZiXO.s}izjnRsjL8Iiu"><value name="X"><block type="math_number" id="qKmnAkM*[p(cC.sen6fc"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="@--qKD$I@iJDxxJn,!F`"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="qGz;FKy(B9]$HKfNH:SS"><field name="NUM">0</field></block></value></block></value><value name="RadiusMinor"><block type="math_number" id="Tv]3=ZfI0PRB*25MZc=B"><field name="NUM">6</field></block></value><value name="RadiusMajor"><block type="math_number" id="SJEc4XLAkC5b.y^4d-$7"><field name="NUM">10</field></block></value></block></value><value name="NrOfDivisions"><block type="math_number" id="9TqK,:AnC}~]@;ZG.HRd"><field name="NUM">12</field></block></value><value name="RemoveStartPoint"><block type="logic_boolean" id="#cck;-I8RoX^5^+/f={C"><field name="BOOL">FALSE</field></block></value><value name="RemoveEndPoint"><block type="logic_boolean" id="CJX5/t(1Y4-:^+!NdUJ$"><field name="BOOL">TRUE</field></block></value></block></value><next><block type="variables_set" id=";m:LL@/VAt.a)9PTa%az"><field name="VAR" id="N~r?iLtkz{1P+j9_F_.;">points</field><value name="VALUE"><block type="base_time_await_return" id="OGt!axl1ws5RM+.sZraK"><value name="Promise"><block type="variables_get" id="U=JZvKMXo)e8;olhg*K1"><field name="VAR" id="[ePn3YDgUxp(6jvgtc^%">pointPromises</field></block></value></block></value><next><block type="variables_set" id="}[7q~y,oqJ*aK@^g@NQ{"><field name="VAR" id="#lFFf=PrmJ(s52I9+mXd">ngonRotationPromises</field><value name="VALUE"><block type="lists_create_with" id="=6BHT(FV4ed?#ZEPXl@M"><mutation items="0"></mutation></block></value><next><block type="controls_forEach" id="^?20|h8$S!9gs]v/,@s^"><field name="VAR" id="pOreDzs^|:kJeR;cd8`Y">i</field><value name="LIST"><block type="variables_get" id="o%E:.[pzRdT3R/K4Q|7|"><field name="VAR" id="N~r?iLtkz{1P+j9_F_.;">points</field></block></value><statement name="DO"><block type="lists_setIndex" id="E?TqU!17aap[_[:4*kRj"><mutation at="false"></mutation><field name="MODE">INSERT</field><field name="WHERE">LAST</field><value name="LIST"><block type="variables_get" id="8#3em6r-[f^z3ix(nY$("><field name="VAR" id="#lFFf=PrmJ(s52I9+mXd">ngonRotationPromises</field></block></value><value name="TO"><block type="bitbybit.occt.operations.rotatedExtrude" id="0P+~ra4,Ry@-TN*R6)rQ"><value name="Shape"><block type="bitbybit.occt.shapes.wire.createNGonWire" id="4w{my{d6AZ.H]^cXWhY."><value name="Center"><block type="variables_get" id="ZH.,3QAkx2qn6RCAQEpt"><field name="VAR" id="pOreDzs^|:kJeR;cd8`Y">i</field></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="TL3VH)/z@%B,Z~[C{-WJ"><value name="X"><block type="math_number" id="e`:3LBeA1}CLSd15Yh32"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="[HF5IDPI0#`Mn67qSlcu"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="it4uu8m;jjKHJxay0xIw"><field name="NUM">0</field></block></value></block></value><value name="NrCorners"><block type="math_number" id="]n}aT`LRVRpDgU|~0=d["><field name="NUM">6</field></block></value><value name="Radius"><block type="math_number" id=".zd*[SI0Z6YB4~uU!~zh"><field name="NUM">1.5</field></block></value></block></value><value name="Height"><block type="math_number" id="WL=?.u9yjld3Pu1D5aYI"><field name="NUM">30</field></block></value><value name="Angle"><block type="math_number" id="Yt?MPBji^tKG^D`}4TM$"><field name="NUM">270</field></block></value><value name="MakeSolid"><block type="logic_boolean" id="XA.YN_p{H$yxlt9!1D4D"><field name="BOOL">TRUE</field></block></value></block></value></block></statement><next><block type="bitbybit.draw.drawAnyAsyncNoReturn" id="3hhfb)7}BFQ]ksLj.mp@"><value name="Entity"><block type="bitbybit.occt.shapes.compound.makeCompound" id=",+@`LA0-x`Pv5CAMF*(]"><value name="Shapes"><block type="variables_get" id="fSZ1o(qB:0%04c!(h6=~"><field name="VAR" id="#lFFf=PrmJ(s52I9+mXd">ngonRotationPromises</field></block></value></block></value></block></next></block></next></block></next></block></next></block></statement></block></next></block></xml>
// These destructured imports are optional, but convenient later on - option DTO's are classes
const { EllipseDto, RotationExtrudeDto, DivideDto, NGonWireDto, CompoundShapesDto } = Bit.Inputs.OCCT;
const { CameraConfigurationDto } = Bit.Inputs.BabylonScene;
// These are parts of the bitbybit API that will be used in the script
const { wire, compound } = bitbybit.occt.shapes;
const { operations } = bitbybit.occt;
const { scene } = bitbybit.babylon;
// Types need to be destructured one by one
type TopoDSWirePointer = Bit.Inputs.OCCT.TopoDSWirePointer;
// Async definition of the start function where we can await on asynchronous CAD algorithms running inside the web workers
const start = async () => {
// Adjust the default camera position to face the object
const cameraOptions = new CameraConfigurationDto();
cameraOptions.position = [30, 50, 50];
cameraOptions.lookAt = [0, 15, 0];
scene.adjustActiveArcRotateCamera(cameraOptions);
// This ellipse will be used to derive origins for ngons on the ground plane
const ellipseOptions = new EllipseDto();
ellipseOptions.radiusMinor = 6;
ellipseOptions.radiusMajor = 10;
const ellipse = await wire.createEllipseWire(ellipseOptions);
// We divide the wire into 12 segments and return the points
const divideOptions = new DivideDto<TopoDSWirePointer>(ellipse);
divideOptions.removeEndPoint = true;
divideOptions.nrOfDivisions = 12;
const points = await wire.divideWireByEqualDistanceToPoints(divideOptions);
// Create ngons on these points
const ngonOptions = new NGonWireDto();
ngonOptions.radius = 1.5;
const ngonPromises = points.map(point => {
ngonOptions.center = point;
return wire.createNGonWire(ngonOptions);
});
const ngons = await Promise.all(ngonPromises);
// Form rotated extrusions on all of the points
const rotatedExtrudeOptions = new RotationExtrudeDto<TopoDSWirePointer>();
rotatedExtrudeOptions.angle = 270;
rotatedExtrudeOptions.height = 30;
const rotatedExtrusionPromises = ngons.map(ngon => {
rotatedExtrudeOptions.shape = ngon;
return operations.rotatedExtrude(rotatedExtrudeOptions);
});
const rotatedExtrusions = await Promise.all(rotatedExtrusionPromises);
// Compounding multiple shapes will generally deliver much better rendering performance
// as it will form a single mesh for all of the geometries involved in compound
const compoundOptions = new CompoundShapesDto(rotatedExtrusions);
const ngonCompound = await compound.makeCompound(compoundOptions);
// As a last step we draw the ngon with default occt settings (defualt because we omit specifying options property)
bitbybit.draw.drawAnyAsync({ entity: ngonCompound });
}
// Let's not forget to execute the start function, otherwise nothing will happen.
start();
Current Limitations
Currently algorithm is intended to work with flat profiles on the ground plane (normal [0, 1, 0]).
Conclusion
Rotated extrusion is a versatile tool for creating complex helical and spiral geometries from simple 2D wire profiles. By carefully controlling the profile shape, extrusion height, and total rotation angle, you can generate a wide array of interesting and functional 3D models.
Experiment with different 2D wire profiles (open lines, arcs, closed circles, polygons, custom splines), adjust the extrusion height, and vary the rotation angle (try values greater than 360 for multiple twists!). Observe how the makeSolid parameter behaves with open versus closed wires. Happy modeling!



