Advanced Parametric 3D Model with BabylonJS & Bitbybit
This tutorial explores building a sophisticated, interactive parametric 3D model utilizing Bitbybit's robust integration with the BabylonJS rendering engine. We will construct a configurable "Hex Shell" 3D shape, where its geometry is dynamically controlled by parameters from a lil-gui
interface. The underlying complex CAD operations will be handled by the OpenCascade (OCCT) kernel, accessed via Bitbybit.
You can see what the results of this app look like (rendered in Unreal Engine):
Through this example, you will learn to:
- Set up a BabylonJS scene and rendering engine.
- Initialize Bitbybit with specific geometry kernels (OCCT in this case) for a BabylonJS environment.
- Create intricate parametric geometry using Bitbybit's OCCT API.
- Employ
lil-gui
to generate a user interface for real-time control over model parameters. - Dynamically update the 3D model in the BabylonJS scene as UI parameters change.
- Implement a Level of Detail (LOD) strategy for efficient shape generation during interaction and finalization.
- Integrate functionality to export the 3D model in common formats like STEP, STL, and GLB.
We are providing a higher level explanations of the codebase below, but for working reference always check this live example on StackBlitz, which, as platform evolves could change slightly.
Find the source code on Bitbybit GitHub Examples
Project Structure Overview
A well-organized project structure is key for managing more complex applications. This example typically involves:
index.html
: The entry point for the browser, hosting the canvas and loading our scripts.style.css
: Contains basic CSS for page layout, canvas presentation, and UI elements such as a loading spinner.src/main.ts
: The primary TypeScript file that orchestrates the entire application, from scene setup and Bitbybit initialization to GUI interactions and geometry updates.src/models/
: A directory to define the data structures (TypeScript interfaces/types and initial values) for:model.ts
: Parameters controlling the 3D shape's geometry.kernel-options.ts
: Configuration for enabling Bitbybit geometry kernels.current.ts
: References to current scene objects (meshes, lights, GUI instance).
src/helpers/
: This directory houses utility functions, each with a distinct responsibility:init-babylonjs.ts
: Responsible for setting up the BabylonJSEngine
,Scene
,Camera
, default lighting, and a ground plane.init-kernels.ts
: Manages the initialization of the selected Bitbybit geometry kernels (e.g., OCCT).create-shape.ts
: The core of the geometric logic, containing the functions that use Bitbybit's OCCT API to generate the parametric "Hex Shell" model.create-gui.ts
: Configures thelil-gui
panel, linking its controls to the parameters inmodel.ts
and connecting them to the geometry update functions.downloads.ts
: Implements the logic for exporting the generated 3D model to STEP, STL, and GLB file formats.gui-helper.ts
: Provides simple utility functions for managing the GUI's visual state (e.g., showing/hiding a loading spinner, enabling/disabling GUI controls).
src/workers/
: This directory would contain the individual Web Worker files for each geometry kernel Bitbybit uses (e.g.,occt.worker.ts
).
For a detailed explanation on setting up the Web Worker files (e.g., occt.worker.ts
), which are essential for running geometry kernels in a separate thread, please refer to our BabylonJS Integration Starter Tutorial or a general guide on using workers with Bitbybit. This tutorial assumes that foundation is in place and focuses on the application logic.
1. HTML Setup (index.html
)
The index.html
file provides the basic webpage structure.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bitbybit & BabylonJS Hex Shell Example</title>
</head>
<body>
<a
class="logo"
href="https://bitbybit.dev"
target="_blank"
rel="noopener noreferrer"
>
<img
alt="Logo of Bit by bit developers company"
src="https://bitbybit.dev/assets/logo-gold-small.png"
/>
<div>bitbybit.dev</div>
<br />
<div>support the mission - subscribe</div>
</a>
<canvas id="babylon-canvas"></canvas>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Key elements:
- A
<canvas id="babylon-canvas">
element: This is where BabylonJS will render the 3D scene. - A
<script>
tag: Loads the main application logic fromsrc/main.ts
.
2. Main Application Logic (src/main.ts
)
This file is the central coordinator for our application, bringing together the BabylonJS scene, Bitbybit's functionalities, the GUI, and the dynamic update logic.
import './style.css';
import { BitByBitBase, Inputs } from '@bitbybit-dev/babylonjs';
import { model, type KernelOptions, current } from './models';
import {
initKernels,
initBabylonJS,
createGui,
createShapeLod1,
createShapeLod2,
createDirLightsAndGround,
disableGUI,
enableGUI,
hideSpinner,
showSpinner,
downloadGLB,
downloadSTL,
downloadStep,
} from './helpers';
// Configuration for enabling Bitbybit geometry kernels
const kernelOptions: KernelOptions = {
enableOCCT: true, // This example primarily uses OCCT for its CAD operations
enableJSCAD: false,
enableManifold: false,
};
// Application entry point
start();
async function start() {
// 1. Initialize the BabylonJS Engine and Scene
const { scene, engine } = initBabylonJS();
// 2. Initialize BitByBitBase for BabylonJS
const bitbybit = new BitByBitBase();
// Provide the BabylonJS context to the Bitbybit library.
// This allows Bitbybit to create and manage BabylonJS-specific objects.
bitbybit.context.scene = scene;
bitbybit.context.engine = engine;
// Add default lighting and a ground plane to the scene
createDirLightsAndGround(bitbybit, current);
// Initialize the selected Bitbybit geometry kernels (OCCT in this case)
await initKernels(scene, bitbybit, kernelOptions);
// Variables to store the final OCCT shape and intermediate shapes for cleanup
let finalShape: Inputs.OCCT.TopoDSShapePointer | undefined;
let shapesToClean: Inputs.OCCT.TopoDSShapePointer[] = []; // Crucial for OCCT memory management
// 3. Connect download functions to the model object (for GUI buttons)
model.downloadStep = () => downloadStep(bitbybit, finalShape);
model.downloadGLB = () => downloadGLB(bitbybit);
model.downloadSTL = () => downloadSTL(bitbybit, finalShape);
// 4. Create the GUI panel and link its controls
createGui(current, model, updateShape);
// 5. Setup a simple rotation animation for the generated groups
const rotationSpeed = 0.0005;
const rotateGroup = () => {
if (
model.rotationEnabled &&
current.group1 && // 'current' holds references to BabylonJS Meshes/TransformNodes
current.group2 &&
current.dimensions
) {
current.group1.rotation.y -= rotationSpeed;
current.group2.rotation.y -= rotationSpeed;
current.dimensions.rotation.y -= rotationSpeed;
}
};
// Register the rotation function with BabylonJS's before render observable
scene.onBeforeRenderObservable.add(() => rotateGroup());
// Start BabylonJS's main render loop
engine.runRenderLoop(() => {
scene.render(true, false); // Render the scene on each frame
});
// 6. Generate and draw the initial 3D model (using Level of Detail 1 for faster preview)
finalShape = await createShapeLod1(
bitbybit,
scene,
model, // Contains current parameters from the GUI/defaults
shapesToClean, // Array for OCCT shapes that need explicit cleanup
current // Stores references to created BabylonJS meshes/groups
);
// 7. Define the function to update the shape when GUI parameters change
async function updateShape(finish: boolean) {
disableGUI(); // Temporarily disable GUI during processing
showSpinner(); // Show a loading indicator
// Dispose of previously created BabylonJS Meshes to clear the scene
// The second parameter 'true' ensures child meshes are also disposed.
current.group1?.dispose(false, true);
current.group2?.dispose(false, true);
current.dimensions?.dispose(false, true);
// Note: OCCT shapes are managed and cleaned up within the createShapeLod1/2 functions
// using the 'shapesToClean' array.
if (finish) { // 'finish' is true if the "Finalize" GUI button is pressed
// Generate the shape with higher detail (LOD2)
finalShape = await createShapeLod2(
bitbybit, scene, model, shapesToClean, current
);
} else { // Default update (e.g., when a slider is dragged)
// Generate the shape with lower detail for faster interactive feedback (LOD1)
finalShape = await createShapeLod1(
bitbybit, scene, model, shapesToClean, current
);
}
hideSpinner(); // Hide loading indicator
enableGUI(); // Re-enable GUI
}
}
Explanation of main.ts
:
- Imports: Includes
BitByBitBase
andInputs
from@bitbybit-dev/babylonjs
, along with local models and helper functions. kernelOptions
: Specifies that only the OCCT kernel should be enabled and initialized for this particular application, as it's the one used for the Hex Shell's CAD operations.start()
function (Main Application Flow):- BabylonJS Setup:
initBabylonJS()
initializes the BabylonJSEngine
andScene
. - Bitbybit Initialization: An instance of
BitByBitBase
is created. Crucially,bitbybit.context.scene
andbitbybit.context.engine
are assigned the BabylonJS scene and engine instances. This step is vital for the@bitbybit-dev/babylonjs
integration package to correctly interact with the BabylonJS environment (e.g., for creating materials, adding meshes to the scene). - Scene Elements:
createDirLightsAndGround()
adds basic lighting and a ground plane using Bitbybit's BabylonJS helpers. - Kernel Initialization:
initKernels()
ensures the OCCT worker is started and ready. - Shape Management:
finalShape
will store the primary OCCT geometry.shapesToClean
is an array used to track intermediate OCCT shapes created during geometry generation; these need to be explicitly deleted usingbitbybit.occt.deleteShapes()
to manage memory effectively, especially with complex CAD operations. - Download Functions: The
downloadStep
,downloadGLB
, anddownloadSTL
functions (fromhelpers/downloads.ts
) are attached to themodel
object so they can be easily triggered by GUI buttons. - GUI Creation:
createGui()
sets up thelil-gui
panel, linking its controls to the parameters defined inmodel.ts
. Changes in the GUI will trigger theupdateShape
function. - Animation: A simple
rotateGroup
function is defined and added to BabylonJS'sscene.onBeforeRenderObservable
to animate parts of the model ifmodel.rotationEnabled
is true. - Render Loop:
engine.runRenderLoop()
starts BabylonJS's continuous rendering process. - Initial Shape:
createShapeLod1()
is called to generate and display the initial version of the Hex Shell model using a lower level of detail for quicker startup. updateShape(finish: boolean)
function: This is the core of the dynamic updates.- It's triggered by GUI interactions.
- It disables the GUI and shows a spinner.
- It disposes of previous BabylonJS meshes using
mesh.dispose(false, true)
to clear the old geometry from the scene. - It then calls either
createShapeLod1
(for faster, interactive previews) orcreateShapeLod2
(for a more detailed, finalized version) based on thefinish
flag. - After regeneration, it hides the spinner and re-enables the GUI.
- BabylonJS Setup:
3. Helper Functions (src/helpers/
)
Helper functions promote modularity and code organization.
init-babylonjs.ts
& init-kernels.ts
initBabylonJS()
: This module is responsible for all the initial BabylonJS setup. It:- Gets the
<canvas>
element. - Creates the BabylonJS
Engine
. - Creates the
Scene
and sets basic properties like clear color. - Sets up an
ArcRotateCamera
for user navigation. - Adds a
HemisphericLight
for ambient illumination. - Initializes the render loop via
engine.runRenderLoop()
. - Handles window resize events to keep the rendering correct.
- Gets the
createDirLightsAndGround()
: This helper specifically adds directional lights (important for casting shadows and defining highlights) and a ground mesh (e.g., a cylinder or plane) to the BabylonJS scene. It might use Bitbybit's BabylonJS API helpers for convenience.initKernels()
: This function's role is identical regardless of the rendering engine. It:- Looks at the
kernelOptions
(frommain.ts
). - For each enabled kernel, it creates a new
Worker
instance, pointing to the respective worker script (e.g.,../workers/occt.worker.ts
). - Calls
await bitbybit.init(...)
, passing the BabylonJSscene
and the worker instances. - It then patiently waits for each selected and available kernel to confirm its full initialization by observing their state streams (e.g.,
bitbybit.occtWorkerManager.occWorkerState$
). The function only resolves after all required kernels are ready for use.
- Looks at the
create-shape.ts
(Core OCCT Geometry Logic)
This file is where the detailed CAD work for the "Hex Shell" happens, using Bitbybit's OCCT API. Its structure typically involves:
- LOD Functions:
createShapeLod1
for a faster, simplified version andcreateShapeLod2
for the full-detail version. - OCCT Memory Management: A crucial aspect. Before generating new OCCT geometry, it calls
await bitbybit.occt.deleteShapes({ shapes: shapesToClean })
to free memory from shapes created in the previous update. Any new intermediate OCCT shapes created during the current generation are added to theshapesToClean
array. - Parametric Geometry Creation:
- Uses
model
parameters (e.g.,model.uHex
,model.height
) to drive the dimensions and features. - Employs a sequence of OCCT operations via
bitbybit.occt.*
(e.g.,shapes.wire.createEllipseWire
,transforms.rotate
,operations.loft
,operations.offset
,shapes.face.subdivideToHexagonWires
,shapes.compound.makeCompound
). - The example creates a base lofted surface, then subdivides it into hexagonal wire patterns on several offset surfaces, and finally creates solids by lofting between these patterns.
- Uses
- Dimensioning (Optional but present in example): The code also includes logic to create OCCT 3D dimension entities (lines, angles, labels) using
bitbybit.occt.dimensions.*
functions, which are then compounded with the main shape. - Drawing with BabylonJS Integration:
- After OCCT shapes are generated,
bitbybit.draw.drawAnyAsync({ entity: occtShape, options: drawOptions })
is called. The@bitbybit-dev/babylonjs
version of this function converts the OCCT shape data into BabylonJSMesh
objects. - It creates and applies BabylonJS materials (e.g.,
PBRMetallicRoughnessMaterial
from@babylonjs/core
) to these meshes. - The resulting meshes are often parented to BabylonJS
Mesh
objects (acting as groups, stored incurrent.group1
,current.group2
) for organization and collective transformations (like rotation).
- After OCCT shapes are generated,
The specific sequence of OCCT operations in create-shape.ts
(lofting, offsetting, subdividing, compounding) is tailored to generate the intricate "Hex Shell" design. To understand each step in detail, you would refer to the Bitbybit API documentation for the corresponding OCCT functions.
create-gui.ts
(BabylonJS Context)
This sets up the lil-gui
panel. Its operation is very similar across different rendering engines, but material updates are engine-specific.
// ... (other GUI setup) ...
gui
.addColor(model, 'color1')
.name('Color 1')
.onChange((value: string) => {
// Iterate through meshes in the group and update their material
current.group1?.getChildren().forEach((node) => { // A group node
node.getChildMeshes().forEach(mesh => { // Get actual meshes
if (mesh.material && mesh.material instanceof PBRMetallicRoughnessMaterial) {
const mat = mesh.material as PBRMetallicRoughnessMaterial;
mat.baseColor = Color3.FromHexString(value); // BabylonJS Color3
}
});
});
});
// Similar logic for 'color2' and current.group2
- When a color control in the GUI changes, the callback iterates through the child meshes of the relevant group (
current.group1
orcurrent.group2
). - It accesses the mesh's
material
property. - It updates the material's
baseColor
(assumingPBRMetallicRoughnessMaterial
) using BabylonJS'sColor3.FromHexString()
.
downloads.ts
(BabylonJS Context)
This module handles file exports:
downloadStep()
: Usesbitbybit.occt.io.saveShapeSTEP()
to save thefinalShape
(the OCCT compound object) as a STEP file. This is an OCCT-level operation.downloadSTL()
: Your example usesbitbybit.occt.io.saveShapeStl()
to export thefinalShape
from OCCT directly as an STL. This is efficient for CAD-derived STL.downloadGLB()
: This usesbitbybit.babylon.io.exportGLB()
. This function is specific to the@bitbybit-dev/babylonjs
package and exports the current state of the BabylonJS scene (or selected parts of it) to a GLB file.
gui-helper.ts
These DOM manipulation utilities (disable/enable GUI, show/hide spinner) are generic.
4. Data Models (src/models/
)
kernel-options.ts
: Defines theKernelOptions
interface (same as before).model.ts
: Defines theModel
type for geometric parameters and an initialmodel
object (same structure as before).current.ts
: This is where BabylonJS types become apparent. TheCurrent
type now holds references to BabylonJS objects:
import { DirectionalLight, Mesh } from '@babylonjs/core'; // BabylonJS specific imports
import { GUI } from 'lil-gui';
export type Current = {
group1: Mesh | undefined; // BabylonJS Mesh (can act as a group/parent)
group2: Mesh | undefined; // BabylonJS Mesh
dimensions: Mesh | undefined; // BabylonJS Mesh for dimensions
light1: DirectionalLight | undefined; // BabylonJS Light
ground: Mesh | undefined; // BabylonJS Mesh for the ground
gui: GUI | undefined;
};
export const current: Current = { /* ... initial undefined values ... */ };
5. Styles (style.css
)
The CSS primarily styles the page layout, logo, and the loading spinner. A key selector is for the canvas:
/* ... other styles ... */
#babylon-canvas { /* Targets the canvas ID used in index.html */
width: 100%;
height: 100%;
overflow: hidden;
position: absolute; /* Often used for full-viewport canvas */
outline: none;
}
/* ... spinner styles ... */
Conclusion
This advanced tutorial has demonstrated how to construct a complex, parametric 3D model using Bitbybit's OCCT capabilities and render it interactively with BabylonJS. You've seen how to:
- Structure a project with separate modules for different functionalities.
- Initialize and use Bitbybit within a BabylonJS engine and scene context.
- Implement parametric geometry generation that responds to GUI controls.
- Manage different Levels of Detail (LOD) for performance.
- Handle OCCT memory by cleaning up intermediate shapes.
- Dispose of BabylonJS meshes before redrawing to update the scene.
- Integrate export functionalities for common 3D file formats.
This robust approach allows for the creation of highly interactive and detailed 3D web applications, leveraging the strengths of both Bitbybit's CAD engine and BabylonJS's rendering power.