Creating a Simple Loft
A loft operation is a fundamental technique in 3D modeling that allows you to create complex surfaces by defining a series of 2D profiles (cross-sections) in space. The software then generates a smooth transitional surface that connects these profiles. This is incredibly useful for modeling organic shapes, aerodynamic bodies, or any object with a varying cross-section.
In this tutorial, we'll walk through the process of creating a simple lofted shape. We will define three distinct elliptical wires at different positions and then use the loft operation to generate a surface that smoothly interpolates between them. We'll demonstrate how to achieve this using Rete, Blockly, and TypeScript, providing a clear understanding of the underlying logic and its implementation in different environments.
The power of lofting comes from its versatility. You can:
- Use different shapes: Combine circles, rectangles, polygons, or even more complex custom curves.
- Vary the number of profiles: More profiles can lead to more detailed and controlled surfaces.
- Adjust profile orientation and scale: Tilting or resizing profiles can dramatically alter the resulting shape.
- Create solids: By setting the
makeSolidoption totrue(where applicable), you can create volumetric bodies instead of just surfaces. - Nr of wire edges: For loft to work correctly in most situations you must ensure that you loft wires that contain the same amount of edges to form topologically coherent faces.
- Order of wires matter: If you reverse the order of wires in the list your loft faces will point to different direction.
- Wire direction matters: If you reverse directions of wires and their edges, your loft faces will point to different direction.
- Rete
- Blockly
- TypeScript
{
"id": "rete-v2-json",
"nodes": {
"b8a58571ebd30b44": {
"id": "b8a58571ebd30b44",
"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": 4,
"radiusMajor": 8
},
"inputs": {},
"position": [
409.9140625,
135.00390625
]
},
"0999348ac27dbe3a": {
"id": "0999348ac27dbe3a",
"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": 1,
"radiusMajor": 4
},
"inputs": {
"center": {
"connections": [
{
"node": "5b0a2e7a36e52bc6",
"output": "result",
"data": {}
}
]
}
},
"position": [
411.21484375,
550.01953125
]
},
"5b0a2e7a36e52bc6": {
"id": "5b0a2e7a36e52bc6",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": 3,
"z": 0
},
"inputs": {},
"position": [
-13.58203125,
587.8515625
]
},
"58baa94ee15ce87c": {
"id": "58baa94ee15ce87c",
"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": 2,
"radiusMajor": 6
},
"inputs": {
"center": {
"connections": [
{
"node": "31cc8830d0d5b8d6",
"output": "result",
"data": {}
}
]
}
},
"position": [
414.354436433955,
962.7267506423071
]
},
"31cc8830d0d5b8d6": {
"id": "31cc8830d0d5b8d6",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": 7,
"z": 0
},
"inputs": {},
"position": [
-18.337436698711855,
985.1818818123244
]
},
"aa7b869ef39ad66c": {
"id": "aa7b869ef39ad66c",
"name": "bitbybit.occt.operations.loft",
"customName": "loft",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"makeSolid": false
},
"inputs": {
"shapes": {
"connections": [
{
"node": "5f66390fcd956929",
"output": "list",
"data": {}
}
]
}
},
"position": [
1345.6703881224773,
574.9945464709863
]
},
"5f66390fcd956929": {
"id": "5f66390fcd956929",
"name": "bitbybit.lists.createList",
"customName": "create list",
"data": {},
"inputs": {
"listElements": {
"connections": [
{
"node": "b8a58571ebd30b44",
"output": "result",
"data": {}
},
{
"node": "0999348ac27dbe3a",
"output": "result",
"data": {}
},
{
"node": "58baa94ee15ce87c",
"output": "result",
"data": {}
}
]
}
},
"position": [
938.8623690201757,
627.1782400352727
]
},
"71104a241cda7a83": {
"id": "71104a241cda7a83",
"name": "bitbybit.draw.drawAnyAsync",
"customName": "draw any async",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
}
},
"inputs": {
"entity": {
"connections": [
{
"node": "aa7b869ef39ad66c",
"output": "result",
"data": {}
}
]
},
"options": {
"connections": [
{
"node": "2b01c4bcdaa8b222",
"output": "result",
"data": {}
}
]
}
},
"position": [
1832.5904062824504,
751.3332776592763
]
},
"2b01c4bcdaa8b222": {
"id": "2b01c4bcdaa8b222",
"name": "bitbybit.draw.optionsOcctShapeSimple",
"customName": "options occt shape simple",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"precision": 0.001,
"drawFaces": true,
"faceColour": "#8000ff",
"drawEdges": true,
"edgeColour": "#ffffff",
"edgeWidth": 2
},
"inputs": {},
"position": [
1348.8560416190364,
915.1471633350661
]
}
}
}
<xml xmlns="https://developers.google.com/blockly/xml"><variables><variable id="48)EaL12jJ;CM6l87kN^">ellipse1</variable><variable id="p!,ge0u?L^@4@^_yF1-/">ellipse2</variable><variable id="R1!`hI~4J!B-H|6ul{7e">ellipse3</variable><variable id="~6]|}Eu]E9UgWCZ{s_,|">ellipses</variable><variable id="XJ4dpWWO2e_jp;mr|J8D">loft</variable></variables><block type="variables_set" id="Z]RXuxwn/6CiqFn:E`{f" x="-397" y="-446"><field name="VAR" id="48)EaL12jJ;CM6l87kN^">ellipse1</field><value name="VALUE"><block type="bitbybit.occt.shapes.wire.createEllipseWire" id="iIh/DfKZ[cfoa6r!#):!"><value name="Center"><block type="bitbybit.point.pointXYZ" id="X%HKp*Q.ty=Rbu~`1@a%"><value name="X"><block type="math_number" id="n+jQq:wH?M2t97E!DPv#"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="zDLV%cFNOLTD^R@?kble"><field name="NUM">0</field></block></value><value name="Z"><block type="math_number" id="AXtz$21v5w!d:WB(0`OK"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="O1hbHY+z/gsL0J-Sd8kg"><value name="X"><block type="math_number" id="=Rp$D.)C;2^dAFJLa[eX"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="fXGqM_6U7koY]wCO7a|$"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="YsRU*rv,(~W7(b;IGkh7"><field name="NUM">0</field></block></value></block></value><value name="RadiusMinor"><block type="math_number" id="_Fa+`F#q}NadJb.n@G4z"><field name="NUM">4</field></block></value><value name="RadiusMajor"><block type="math_number" id="rz%7^,:YrfbnqKwlTtqs"><field name="NUM">8</field></block></value></block></value><next><block type="variables_set" id="GZ84cD@/bkVoj*Hch}rB"><field name="VAR" id="p!,ge0u?L^@4@^_yF1-/">ellipse2</field><value name="VALUE"><block type="bitbybit.occt.shapes.wire.createEllipseWire" id="kgY;cQdd-b|m[o.*Jo[%"><value name="Center"><block type="bitbybit.point.pointXYZ" id="t$1C2Fms89t1[jCC(;oL"><value name="X"><block type="math_number" id="g1y(x%mGqkM(jn{Qlr|H"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="!XIJ[8-b![)TLE^u7VkT"><field name="NUM">3</field></block></value><value name="Z"><block type="math_number" id="C[ea/4tpa9~Eb?ynXf[V"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="XMcn@cy21@#b80Cns|xA"><value name="X"><block type="math_number" id="nV(cW=wvZds45,1RfouJ"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="-e0WUthCCL9qug#_=BJM"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="6pU7|0I!b=f*KBp$u:v="><field name="NUM">0</field></block></value></block></value><value name="RadiusMinor"><block type="math_number" id="$e9i:VfTo`aCrF^Q6Nb6"><field name="NUM">1</field></block></value><value name="RadiusMajor"><block type="math_number" id="Sh[=u*uYzzZNdAfZ*Xcb"><field name="NUM">4</field></block></value></block></value><next><block type="variables_set" id="*,mjB$NhlsRdXq~$+nCa"><field name="VAR" id="R1!`hI~4J!B-H|6ul{7e">ellipse3</field><value name="VALUE"><block type="bitbybit.occt.shapes.wire.createEllipseWire" id="0H{ZSzg[%*(JnQ@4+0$M"><value name="Center"><block type="bitbybit.point.pointXYZ" id="ui-/ebhLH2qUcXTrC9o="><value name="X"><block type="math_number" id="T|/j~aIJb3}q#Ia!ISI#"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="b;L(|dMj7h0gT_xQhe]d"><field name="NUM">7</field></block></value><value name="Z"><block type="math_number" id="r[|/wh(iscswnuV}V9Oc"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="SK0Km_HaI.lCZNP)R7Do"><value name="X"><block type="math_number" id="S/D3CXTeTD*_}i26O{4x"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="W6C,)z8#j2ZTMePm,wa0"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="M0^:yL#sCkV[Iy*.(7N6"><field name="NUM">0</field></block></value></block></value><value name="RadiusMinor"><block type="math_number" id="89_ly7g=/3^$fd}do@b?"><field name="NUM">2</field></block></value><value name="RadiusMajor"><block type="math_number" id="Wc3+2f7_W%0sos]OJYq|"><field name="NUM">6</field></block></value></block></value><next><block type="variables_set" id=":p.Vpn;$AuaAhp~*)QCX"><field name="VAR" id="~6]|}Eu]E9UgWCZ{s_,|">ellipses</field><value name="VALUE"><block type="lists_create_with" id="Z|q(b#IIH-@cw1Ch?MuM"><mutation items="3"></mutation><value name="ADD0"><block type="variables_get" id="85/PiE$R2@)|rjwjfR8@"><field name="VAR" id="48)EaL12jJ;CM6l87kN^">ellipse1</field></block></value><value name="ADD1"><block type="variables_get" id="ISvgiQeb3^tbkKamkhVX"><field name="VAR" id="p!,ge0u?L^@4@^_yF1-/">ellipse2</field></block></value><value name="ADD2"><block type="variables_get" id="cRm_n~%2,avxAHVC!/x1"><field name="VAR" id="R1!`hI~4J!B-H|6ul{7e">ellipse3</field></block></value></block></value><next><block type="variables_set" id="@b5XbQ;3!bw{_=(ouJmv"><field name="VAR" id="XJ4dpWWO2e_jp;mr|J8D">loft</field><value name="VALUE"><block type="bitbybit.occt.operations.loft" id="5)Buz((kWEzwt|l$-xm$"><value name="Shapes"><block type="variables_get" id="l`dw7*JMGP@wv[KnLKD$"><field name="VAR" id="~6]|}Eu]E9UgWCZ{s_,|">ellipses</field></block></value><value name="MakeSolid"><block type="logic_boolean" id="yrc;yqs)XSbmM^jW_{AN"><field name="BOOL">FALSE</field></block></value></block></value><next><block type="bitbybit.draw.drawAnyAsyncNoReturn" id="o$jl#m1hs_7C`[+#^8m+"><value name="Entity"><block type="variables_get" id="CJLL`3s+l=kF;iHlmGH,"><field name="VAR" id="XJ4dpWWO2e_jp;mr|J8D">loft</field></block></value><value name="Options"><block type="bitbybit.draw.optionsOcctShapeSimple" id="!PL/;^%Tk`O}2$`[1CC,"><value name="Precision"><block type="math_number" id="43~8o:Q6^mI0mCDna_YR"><field name="NUM">0.001</field></block></value><value name="DrawFaces"><block type="logic_boolean" id="%Uu[!B?fTVnJp43e?gm!"><field name="BOOL">TRUE</field></block></value><value name="FaceColour"><block type="colour_picker" id="8/F/6c*4?Cxw(IP=EYY)"><field name="COLOUR">#6600cc</field></block></value><value name="DrawEdges"><block type="logic_boolean" id="H-{V3}cu1KiOK@4Diu(_"><field name="BOOL">TRUE</field></block></value><value name="EdgeColour"><block type="colour_picker" id="Bz7|M(~D5z`M)Ffdsi`A"><field name="COLOUR">#ffffff</field></block></value><value name="EdgeWidth"><block type="math_number" id="$.}%Q]3d?|~%G_hSc;/t"><field name="NUM">2</field></block></value></block></value></block></next></block></next></block></next></block></next></block></next></block></xml>
// Import necessary modules from the bitbybit library.
// 'operations' and 'shapes' are for OpenCascade Technology (OCCT) functionalities like lofting and creating wires.
const { operations, shapes } = bitbybit.occt;
// 'draw' module is used for rendering shapes on the canvas.
const { draw } = bitbybit;
// Import Data Transfer Objects (DTOs) for defining OCCT inputs.
// 'EllipseDto' for creating ellipses, 'LoftDto' for loft operation parameters.
const { EllipseDto, LoftDto } = Bit.Inputs.OCCT;
// Import DTO for drawing options.
const { DrawOcctShapeSimpleOptions } = Bit.Inputs.Draw;
// Define a type alias for OCCT shape pointers for better readability.
type TopoDSShapePointer = Bit.Inputs.OCCT.TopoDSShapePointer;
// Define an asynchronous function 'start' which will contain the main logic.
const start = async () => {
// Create an instance of EllipseDto to define the properties of the first ellipse.
const ellipseOpt = new EllipseDto();
// Set the minor radius of the ellipse.
ellipseOpt.radiusMinor = 4;
// Set the major radius of the ellipse.
ellipseOpt.radiusMajor = 8;
// Create the first elliptical wire. The center defaults to [0,0,0] and direction to [0,1,0] if not specified.
// 'await' is used because shape creation is an asynchronous operation.
const ellipse1 = await shapes.wire.createEllipseWire(ellipseOpt);
// Modify the ellipseOpt for the second ellipse.
// Set the center of the second ellipse.
ellipseOpt.center = [0, 3, 0];
// Set the minor radius for the second ellipse.
ellipseOpt.radiusMinor = 1;
// Set the major radius for the second ellipse.
ellipseOpt.radiusMajor = 4;
// Create the second elliptical wire with the updated options.
const ellipse2 = await shapes.wire.createEllipseWire(ellipseOpt);
// Modify the ellipseOpt for the third ellipse.
// Set the center of the third ellipse.
ellipseOpt.center = [0, 7, 0];
// Set the minor radius for the third ellipse.
ellipseOpt.radiusMinor = 2;
// Set the major radius for the third ellipse.
ellipseOpt.radiusMajor = 6;
// Create the third elliptical wire with the updated options.
const ellipse3 = await shapes.wire.createEllipseWire(ellipseOpt);
// Create an instance of LoftDto to define the parameters for the loft operation.
// The generic type TopoDSShapePointer indicates the type of shapes to be lofted.
const loftOpt = new LoftDto<TopoDSShapePointer>();
// Assign an array of the created ellipse wires to the 'shapes' property of loftOpt.
// These are the profiles that will be connected by the loft.
loftOpt.shapes = [ellipse1, ellipse2, ellipse3];
// Perform the loft operation using the defined options.
// This will create a surface (or shell) connecting the three ellipses.
// 'makeSolid' defaults to false, creating a shell.
const loft = await operations.loft(loftOpt);
// Create an instance of DrawOcctShapeSimpleOptions to define how the lofted shape will be displayed.
const drawOpt = new DrawOcctShapeSimpleOptions();
// Set the precision for drawing the shape. This affects the tessellation quality.
drawOpt.precision = 0.001;
// Set the color of the faces of the lofted shape.
drawOpt.faceColour = "#ff00ff"; // Magenta
// Draw the lofted shape asynchronously using the specified entity and drawing options.
draw.drawAnyAsync({ entity: loft, options: drawOpt });
}
// Call the start function to execute the script.
start();
With these examples, you've learned the basics of creating a lofted surface from a series of wire profiles.
Experiment with these parameters to explore the wide range of forms you can achieve with the loft operation. Happy modeling!



