OCCT Assemblies: Manage Professional CAD Assets
What Are Assemblies?
If you've ever worked with CAD software like SolidWorks, Fusion 360, or FreeCAD, you've likely encountered assemblies. An assembly is simply a collection of individual parts that come together to form a complete product. Think of a bicycle - it's made up of wheels, a frame, handlebars, pedals, and countless other components. Each part is designed separately, but they all fit together in a structured way to create the final product.
In the CAD world, assemblies are essential because real-world products are rarely made from a single piece. Instead, engineers and designers create individual components and then assemble them digitally to verify that everything fits correctly, moves as expected, and can be manufactured efficiently.
Why Are STEP Files Important?
STEP (Standard for the Exchange of Product Data) is the most widely used file format for sharing 3D CAD models between different software applications. When you export an assembly from professional CAD software, it's often saved as a STEP file because:
- Universal compatibility – Almost every CAD program can read and write STEP files
- Preserves geometry – The exact shapes, dimensions, and relationships between parts are maintained
- Industry standard – It's the go-to format for manufacturing, engineering, and product design workflows
However, STEP files are designed for precision CAD work, not for web visualization. That's where Bitbybit comes in - we help you bridge the gap between engineering-grade CAD data and interactive 3D experiences in the browser.
What You'll Learn in This Section
Throughout these tutorials, you'll discover how to work with OCCT assemblies in Bitbybit. We'll cover:
- Loading and previewing – Import STEP files and display them in your 3D scene
- Format conversion – Transform CAD data into web-friendly formats like glTF
- Parsing structure – Extract individual parts, names, colors, and hierarchy information
- Creating assemblies – Build your own multi-part models from scratch
- Editing and transforming – Modify positions, rotations, and properties of assembly components
Whether you're building a product configurator, an interactive documentation viewer, or a design review tool, understanding assemblies is fundamental to working with professional CAD data.
Your First Assembly Preview
Let's start with something practical! The example below demonstrates the core workflow for loading a STEP file and displaying it in the browser. Click the play button to see a real CAD assembly - a soil sensor device - rendered in 3D.
How the script works:
- Fetch the file – We download a compressed STEP file (
.stpZ) from a URL using thefetchFilefunction - Convert to glTF – The STEP data is processed by OCCT and converted to glTF/GLB format, which is optimized for real-time 3D rendering. The
meshPrecisionparameter controls the quality of the mesh - lower values mean finer detail but larger file sizes - Load into scene – The converted GLB is loaded into a BabylonJS scene where you can rotate, zoom, and explore the model
- Scene setup – We configure lighting, camera position, and a gradient background to make the model look polished
This simple pattern - fetch, convert, display - is the foundation for everything else you'll learn about assemblies. Once you understand this workflow, you can build upon it to create sophisticated CAD viewers and interactive applications.
- Rete
- Blockly
- TypeScript
{
"id": "rete-v2-json",
"nodes": {
"e0e94797745b9499": {
"id": "e0e94797745b9499",
"name": "bitbybit.asset.fetchFile",
"customName": "fetch file",
"async": true,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"url": "https://learn.bitbybit.dev/files/3d/Soil-Sensor.stpZ"
},
"inputs": {},
"position": [
447.0390625,
364.58203125
]
},
"948b052a506ccf73": {
"id": "948b052a506ccf73",
"name": "bitbybit.occt.io.convertStepToGltf",
"customName": "convert step to gltf",
"async": true,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"meshPrecision": 0.05
},
"inputs": {
"stepData": {
"connections": [
{
"node": "e0e94797745b9499",
"output": "result",
"data": {}
}
]
}
},
"position": [
840.12109375,
360.81640625
]
},
"1718e41af5f83534": {
"id": "1718e41af5f83534",
"name": "bitbybit.babylon.io.loadGlbFromArrayBuffer",
"customName": "load glb from array buffer",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"fileName": "model.glb",
"hidden": false
},
"inputs": {
"glbData": {
"connections": [
{
"node": "948b052a506ccf73",
"output": "result",
"data": {}
}
]
}
},
"position": [
1227.234375,
358.921875
]
},
"824be0f956f86e07": {
"id": "824be0f956f86e07",
"name": "bitbybit.babylon.scene.drawDirectionalLight",
"customName": "draw directional light",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"direction": [
-100,
-100,
-100
],
"intensity": 3,
"diffuse": "#ffffff",
"specular": "#ffffff",
"shadowGeneratorMapSize": 6000,
"enableShadows": true,
"shadowDarkness": 0,
"shadowUsePercentageCloserFiltering": true,
"transparencyShadow": false,
"shadowContactHardeningLightSizeUVRatio": 0.2,
"shadowBias": 0.01,
"shadowNormalBias": 0.02,
"shadowMaxZ": 100,
"shadowMinZ": 0,
"shadowRefreshRate": 6
},
"inputs": {
"direction": {
"connections": [
{
"node": "1cf530bc3b05cd75",
"output": "result",
"data": {}
}
]
}
},
"position": [
220.90978154060178,
1141.4474910149524
]
},
"d737b03aa186cfde": {
"id": "d737b03aa186cfde",
"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": {
"position": {
"connections": [
{
"node": "09ff71828df19896",
"output": "result",
"data": {}
}
]
},
"lookAt": {
"connections": [
{
"node": "a2753a0a68a97354",
"output": "result",
"data": {}
}
]
}
},
"position": [
1140.4592129965072,
1279.8313039785085
]
},
"09ff71828df19896": {
"id": "09ff71828df19896",
"name": "bitbybit.point.pointXYZ",
"customName": "point xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 70,
"y": 50,
"z": 70
},
"inputs": {},
"position": [
714.6847345665905,
1143.2908329012987
]
},
"a2753a0a68a97354": {
"id": "a2753a0a68a97354",
"name": "bitbybit.point.pointXYZ",
"customName": "point xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": -5,
"z": 0
},
"inputs": {},
"position": [
720.7524355897118,
1488.399577959778
]
},
"1cf530bc3b05cd75": {
"id": "1cf530bc3b05cd75",
"name": "bitbybit.point.pointXYZ",
"customName": "point xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 40,
"y": -40,
"z": -40
},
"inputs": {},
"position": [
-142.37939449872056,
1144.5669505658402
]
},
"f0d4e9ea3cd24449": {
"id": "f0d4e9ea3cd24449",
"name": "bitbybit.babylon.scene.twoColorLinearGradientBackground",
"customName": "two color linear gradient background",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"colorFrom": "#1a1c1f",
"colorTo": "#93aacd",
"direction": "to top",
"stopFrom": 0,
"stopTo": 100
},
"inputs": {},
"position": [
326.1284717805745,
2031.466101347526
]
}
}
}
<xml xmlns="https://developers.google.com/blockly/xml"><variables><variable id="CJ8jts$|1GSa9wLi${3p">bg</variable></variables><block type="bitbybit.babylon.io.loadGlbFromArrayBufferNoReturn" id="m~D11C?C7$o+WVK%GmT=" x="-562" y="-36"><value name="GlbData"><block type="bitbybit.occt.io.convertStepToGltf" id="NE+P)~vB8O=XNCqx}|y]"><value name="StepData"><block type="bitbybit.asset.fetchFile" id="QY-.J(=)[DN`EczQPPV4"><value name="Url"><block type="text" id="^Rs$#bEY:|(6$L@Qy}uO"><field name="TEXT">https://learn.bitbybit.dev/files/3d/Soil-Sensor.stpZ</field></block></value></block></value><value name="MeshPrecision"><block type="math_number" id="u)fq4#$/QZO]k=5BeAB}"><field name="NUM">0.05</field></block></value></block></value><value name="FileName"><block type="text" id="Tg#ur:.lbrvYi`cY#6BG"><field name="TEXT">model.glb</field></block></value><value name="Hidden"><block type="logic_boolean" id="!tg{X@T.L_i]b_fOp9Yq"><field name="BOOL">FALSE</field></block></value><next><block type="bitbybit.babylon.scene.drawDirectionalLightNoReturn" id="|jBHVg]r)`aJLY-Vlznr"><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="Mk)=K1UI=7?WMix]7[cO"><value name="X"><block type="math_number" id="]4!}D]Zt~k#OJ.;p,^Oc"><field name="NUM">40</field></block></value><value name="Y"><block type="math_number" id="suyWb%}A=tF@_.i!@gB2"><field name="NUM">-40</field></block></value><value name="Z"><block type="math_number" id=")2@}W1iuKwR?%Esf|KtV"><field name="NUM">-40</field></block></value></block></value><value name="Intensity"><block type="math_number" id="HA(5CX20^NlbH03y*F+|"><field name="NUM">3</field></block></value><value name="Diffuse"><block type="colour_picker" id="wQ@~Zdb1l9bu{lwSt`F+"><field name="COLOUR">#ffffff</field></block></value><value name="Specular"><block type="colour_picker" id="B1I=eSEDk#ix::ff*=t6"><field name="COLOUR">#ffffff</field></block></value><value name="ShadowGeneratorMapSize"><block type="math_number" id="%gp-TV#X%BVM2^UYoP|0"><field name="NUM">3000</field></block></value><value name="EnableShadows"><block type="logic_boolean" id="gNM|EpNCL~_1;qK[MQ8w"><field name="BOOL">TRUE</field></block></value><value name="ShadowDarkness"><block type="math_number" id="YuJ4Jmy~2f;[r`0zM0-8"><field name="NUM">0</field></block></value><value name="ShadowUsePercentageCloserFiltering"><block type="logic_boolean" id=",iXFJ}9o00trv43}M.1X"><field name="BOOL">TRUE</field></block></value><value name="TransparencyShadow"><block type="logic_boolean" id="=IBhyw!8c}o|Qx}h#6t;"><field name="BOOL">FALSE</field></block></value><value name="ShadowContactHardeningLightSizeUVRatio"><block type="math_number" id="L1|mxTKr+[,kE84??ZwT"><field name="NUM">0.2</field></block></value><value name="ShadowBias"><block type="math_number" id="CP1qQ}5e_kx1}gP]qF@J"><field name="NUM">0.01</field></block></value><value name="ShadowNormalBias"><block type="math_number" id="ah,g=Nlsd88h[#!tx|VT"><field name="NUM">0.02</field></block></value><value name="ShadowMaxZ"><block type="math_number" id="lgRLLp@%j61r-s$LgPJz"><field name="NUM">100</field></block></value><value name="ShadowMinZ"><block type="math_number" id="rH$53k)?SMo95vBHXrMg"><field name="NUM">0</field></block></value><value name="ShadowRefreshRate"><block type="math_number" id="^:j4g@ljv^9NxK5A1I@,"><field name="NUM">6</field></block></value><next><block type="bitbybit.babylon.scene.adjustActiveArcRotateCamera" id="dOgX`oEWgp3c;]n.CC^?"><value name="Position"><block type="bitbybit.point.pointXYZ" id="H.y[YX!+F*Ky`0nE:XHC"><value name="X"><block type="math_number" id="wHh3N2G[GAec9.Cmqeg8"><field name="NUM">70</field></block></value><value name="Y"><block type="math_number" id="F=F6o#J%c_08`x/gR6R^"><field name="NUM">50</field></block></value><value name="Z"><block type="math_number" id="dw))8JR=Z/[CEP^#p4dn"><field name="NUM">70</field></block></value></block></value><value name="LookAt"><block type="bitbybit.point.pointXYZ" id="{]HVTa^zC-M:E~jK}EFB"><value name="X"><block type="math_number" id="+lZc{VUZG78=V-Y)vLt`"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id=";.CUU|N.DLxLjdW]0tD3"><field name="NUM">-5</field></block></value><value name="Z"><block type="math_number" id=";;Y]L8{cG=.yeFm?/x7Q"><field name="NUM">0</field></block></value></block></value><value name="LowerBetaLimit"><block type="math_number" id="LYGpk}{V:`8_@g}`Vl`9"><field name="NUM">1</field></block></value><value name="UpperBetaLimit"><block type="math_number" id="7Cp#tP$b@q7~7b;zb-Er"><field name="NUM">179</field></block></value><value name="AngularSensibilityX"><block type="math_number" id=")7KEpQ{~LiSM-}-rHIf:"><field name="NUM">1000</field></block></value><value name="AngularSensibilityY"><block type="math_number" id="Gm{7VHx6|x+PTV:jQxRi"><field name="NUM">1000</field></block></value><value name="MaxZ"><block type="math_number" id="G{H#Vyq]?v[R0r(@o}_Y"><field name="NUM">1000</field></block></value><value name="PanningSensibility"><block type="math_number" id="Px#9sP;7JMO?TJ[ygwT6"><field name="NUM">1000</field></block></value><value name="WheelPrecision"><block type="math_number" id="$;l5wppP}t;e3qsB`^v?"><field name="NUM">3</field></block></value><next><block type="variables_set" id="^|#Q:Xj6BGJ]M*bTVvq,"><field name="VAR" id="CJ8jts$|1GSa9wLi${3p">bg</field><value name="VALUE"><block type="bitbybit.babylon.scene.twoColorLinearGradientBackground" id="i^WyDBJy*1{r8m6[9,d:"><value name="ColorFrom"><block type="colour_picker" id="JYXmjR{G{bW$1AnDS2gM"><field name="COLOUR">#1a1c1f</field></block></value><value name="ColorTo"><block type="colour_picker" id="CCO.8cEVz^I5-je^8S];"><field name="COLOUR">#93aacd</field></block></value><value name="Direction"><block type="bitbybit.babylon.enums.gradientDirectionEnum" id="6LKkz}QE0/B0=hx`gNsO"><field name="bitbybit.babylon.enums.gradientDirectionEnum">'to top'</field></block></value><value name="StopFrom"><block type="math_number" id="mORyJnt_$FudMRrZ`2!I"><field name="NUM">0</field></block></value><value name="StopTo"><block type="math_number" id="$a7V_u^xNti0N?F`Adei"><field name="NUM">100</field></block></value></block></value></block></next></block></next></block></next></block></xml>
// Import required DTOs for scene setup and file operations
const { SceneTwoColorLinearGradientDto, DirectionalLightDto, CameraConfigurationDto } = Bit.Inputs.BabylonScene;
const { ConvertStepToGltfDto } = Bit.Inputs.OCCT;
const { gradientDirectionEnum } = Bit.Inputs.Base;
// Import type definitions for type safety
type Point3 = Bit.Inputs.Base.Point3;
type Vector3 = Bit.Inputs.Base.Vector3;
// Get access to modules
const { scene, io } = bitbybit.babylon;
const { asset } = bitbybit;
const start = async () => {
// Setup two-color gradient background
const backgroundOpt = new SceneTwoColorLinearGradientDto();
backgroundOpt.colorFrom = "#1a1c1f";
backgroundOpt.colorTo = "#93aacd";
backgroundOpt.direction = gradientDirectionEnum.toTop;
backgroundOpt.stopFrom = 0;
backgroundOpt.stopTo = 100;
scene.twoColorLinearGradientBackground(backgroundOpt);
// Setup directional light with shadows
const dirLightOpt = new DirectionalLightDto();
dirLightOpt.direction = [40, -40, -40] as Vector3;
dirLightOpt.intensity = 3;
dirLightOpt.diffuse = "#ffffff";
dirLightOpt.specular = "#ffffff";
dirLightOpt.shadowGeneratorMapSize = 6000;
dirLightOpt.enableShadows = true;
dirLightOpt.shadowDarkness = 0;
dirLightOpt.shadowUsePercentageCloserFiltering = true;
dirLightOpt.shadowContactHardeningLightSizeUVRatio = 0.2;
dirLightOpt.shadowBias = 0.01;
dirLightOpt.shadowNormalBias = 0.02;
dirLightOpt.shadowMaxZ = 100;
dirLightOpt.shadowMinZ = 0;
dirLightOpt.shadowRefreshRate = 6;
scene.drawDirectionalLight(dirLightOpt);
// Adjust camera position and settings
const cameraOpt = new CameraConfigurationDto();
cameraOpt.position = [70, 50, 70] as Point3;
cameraOpt.lookAt = [0, -5, 0] as Point3;
cameraOpt.lowerBetaLimit = 1;
cameraOpt.upperBetaLimit = 179;
cameraOpt.angularSensibilityX = 1000;
cameraOpt.angularSensibilityY = 1000;
cameraOpt.maxZ = 1000;
cameraOpt.panningSensibility = 1000;
cameraOpt.wheelPrecision = 3;
scene.adjustActiveArcRotateCamera(cameraOpt);
// Fetch the STEP file from URL
const stepData = await asset.fetchFile({
url: "https://learn.bitbybit.dev/files/3d/Soil-Sensor.stpZ"
});
// Convert STEP to GLTF format
const convertOpt = new ConvertStepToGltfDto();
convertOpt.stepData = stepData;
convertOpt.meshPrecision = 0.05;
const glbData = await bitbybit.occt.io.convertStepToGltf(convertOpt);
// Load the GLB model into the scene
await io.loadGlbFromArrayBuffer({
glbData: glbData,
fileName: "model.glb",
hidden: false
});
}
// Execute the main function
start();
About the Model
The soil sensor assembly used in this example comes from FarmBot - an open-source CNC farming robot project. FarmBot is unique in the hardware world because they release all their CAD files under the CC0 Public Domain Dedication, meaning you can use, modify, and share these files freely without any restrictions.
This makes FarmBot's models perfect for learning and experimentation. Feel free to download the file and use it in your own projects!
📥 Download: Soil-Sensor.stpZ



