What is blox?
blox is a modern, object-oriented JavaScript CAD library for the web, built on top of the Open CASCADE Technology kernel via opencascade.js and WebAssembly. It provides a high-level, intuitive API for procedural and parametric modeling, enabling the creation, manipulation, and export of 2D and 3D geometry directly in the browser.
Key Features
- Object-Oriented Modeling: Core geometric entities such as
Vertex
,Edge
,Wire
,Face
, andSolid
are represented as classes with rich methods for construction and manipulation. - Geometric Primitives: Easily create and combine 1D, 2D, and 3D primitives (lines, arcs, polygons, circles, cubes, spheres, etc.).
- Sketching API: Build complex planar sketches using a fluent interface, then convert them to faces or wires for further modeling.
- Boolean and Transform Operations: Perform boolean operations (cut, join, intersect) and geometric transformations (translate, rotate, mirror, scale) on shapes.
- Advanced Features: Support for sweeps, lofts, fillets, chamfers, and more.
- Export Capabilities: Export models to industry-standard formats like STL and STEP for 3D printing or CAD workflows.
- WebAssembly Powered: Leverages Open CASCADE compiled to WebAssembly for robust geometry processing in JavaScript environments.
Typical Use Cases
- Generative and parametric design
- Web-based CAD applications
- 3D printing workflows
- Educational tools for geometry and modeling
Getting Started
Using Web Workers
It is highly recommended to use blox inside a Web Worker for two main reasons:
- Performance: CAD operations can be computationally intensive and will most likely block the main thread if run directly in the browser. Running blox in a Web Worker keeps your application responsive by offloading heavy computations.
- Memory Management: Under the hood, blox uses a custom build of opencascade.js, which in turn uses Emscripten to create a WebAssembly version of the Open CASCADE Technology kernel. Emscripten highly recommends explicitly using
.delete()
on each object to avoid memory leaks, but this would lead to a more verbose and harder-to-understand codebase. Instead, I’ve opted to use blox in a Web Worker, which can then be terminated to free up any associated memory.
Visualizing Geometry
To visualize an object created using blox, you can use the built-in triangulate
method. It processes a Solid
, Face
, Wire
, or Edge
and converts it into a structured data format, perfect for creating a custom THREE.BufferGeometry
in three.js.
The triangulate
method allows you to precisely control the quality and density of the generated mesh using the optional linearTolerance
and angularTolerance
parameters. These settings directly impact the balance between visual fidelity, polygon count, and the execution time required to generate the mesh.
In short, lower values produce a more detailed and accurate mesh that is faithful to the original curved surfaces, but this comes at the cost of longer processing times and a higher polygon count.
Here are some suggested presets to get you started:
High Quality (Fine Detail):
linearTolerance: 0.01
,angularTolerance: 0.05
Ideal for final renders or situations where high geometric precision is critical. Be aware that this can significantly increase triangulation time for complex models.Medium Quality (Balanced - Default):
linearTolerance: 0.1
,angularTolerance: 0.1
This is the default setting and offers a good compromise between speed and visual quality. It's suitable for most development and interactive viewing purposes.Low Quality (Coarse Preview):
linearTolerance: 1.0
,angularTolerance: 0.5
Generates a mesh very quickly with a low polygon count. This is perfect for rapid prototyping, performance-critical applications, or when you only need a rough representation of the shape.
The structure of the returned object depends on the type of the input entity
:
- For
Solid
andFace
objects,triangulate
returns a complete mesh data object:- positions: A
Float32Array
of vertex coordinates ([x1, y1, z1, x2, y2, z2, ...]). - normals: A
Float32Array
of normal vectors, corresponding to each vertex. - indexes: A
Uint32Array
of triangle indices that define the faces of the mesh.
- positions: A
- For
Wire
andEdge
objects, it returns an object containing only the vertex data for lines:- positions: A
Float32Array
of vertex coordinates.
- positions: A
Basic Setup Using Vite and three.js
This assumes you're using Vite and know how to setup a basic three.js scene.
// main.js
import * as THREE from "three";
// setup scene, camera, renderer, controls, ...
// instantiate the worker
const worker = new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
});
// wait for the worker to execute the blox code and post back the triangulation data
worker.onmessage = (e) => {
const triangulation = e.data;
// create custom geometry based on triangulation data
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(triangulation.positions, 3));
geometry.setAttribute("normal", new THREE.BufferAttribute(triangulation.normals, 3));
geometry.setIndex(new THREE.BufferAttribute(triangulation.indexes, 1));
// create material and mesh
const material = new THREE.MeshStandardMaterial({color: "#FAB365"});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// terminate the worker
worker.terminate();
};
// worker.js
import {Cube, triangulate} from "blox";
const cube = Cube({size: 5});
const triangulation = triangulate({entity: cube});
self.postMessage(triangulation);
If you want to display Wire
or Edge
objects you can use LineSegments
and LineBasicMaterial
.
// main.js
worker.onmessage = (e) => {
const triangulation = e.data;
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(triangulation.positions, 3));
const material = new THREE.LineBasicMaterial({color: "#FAB365"});
const lineSegments = new THREE.LineSegments(geometry, material);
scene.add(lineSegments);
worker.terminate();
};
Exporting Geometry
blox provides built-in exporters to convert your geometry into industry-standard file formats like STEP and STL. These functions return the raw file content as a binary string. It is then up to you to handle saving this content to a file.
Exporting to STEP
STEP is a high-fidelity, boundary-representation (B-rep) format. It preserves the exact mathematical definition of your geometry, making it ideal for transferring models between different CAD systems without losing precision.
The exportStep
function takes an array of objects and combines them into a single STEP file.
import {Cube, exportStep} from "blox";
const cube = Cube({size: 5});
const stepFileContent = exportStep([cube]);
console.log(stepFileContent);
// use a library of your choosing to create and save the file
Exporting to STL
STL is a mesh-based format that represents geometry as a collection of triangles. It is the most common format for 3D printing and is widely supported in visualization software.
Since STL is a mesh format, the exportStl
function must first triangulate your geometry. You can control the quality and resolution of this triangulation using the same tolerance parameters as the triangulate
method. Lower values produce a finer mesh at the cost of larger file sizes.
import {Cube, exportStl} from "blox";
const cube = Cube({size: 5});
const stlFileContent = exportStl({entities: [cube], linearTolerance: 0.01, angularTolerance: 0.05});
console.log(stlFileContent);
// use a library of your choosing to create and save the file
Exception handling
This library, by design, does not implement a high-level, abstract exception handling system. When an operation fails, the library will not catch the underlying error from opencascade.js. Instead, the raw exception will propagate up, allowing you to handle it directly in your application code.
While this may seem unconventional, this decision was made deliberately for several key reasons:
- The Nature of OpenCascade Exceptions: The underlying OpenCascade Technology (OCCT) C++ kernel, exposed via
opencascade.js
, has a monolithic exception system. Most errors are thrown as a single type,Standard_Failure
, regardless of the cause. This makes it programmatically difficult to distinguish between different kinds of failures. - Unintuitive Error Messages: The error messages originating from the C++ core are often cryptic and deeply tied to OCCT's internal class names and logic (e.g.,
BRep_API: Command not done
). Translating these into a comprehensive set of user-friendly JavaScript errors would be a monumental and brittle task. - Avoiding Unnecessary Complexity: Building a wrapper to catch, interpret, and re-throw these low-level exceptions would add a significant layer of complexity to this library. It would require maintaining a vast and fragile mapping of OCCT errors to new JavaScript error types.