Star Ornament
🎄 Nothing says festive season quite like a beautiful star ornament. In this beginner-friendly tutorial, you'll learn how to create a classic 3D printable star that can grace the top of your tree or hang as a festive decoration.
This tutorial introduces fundamental CAD concepts: creating polygon wires, applying fillets for smooth edges, and extruding 2D shapes into 3D solids. These techniques form the foundation for countless holiday decorations and beyond!
The Star Shape
Our star begins as a simple 2D wire created using the createStarWire function. This generates a classic five-pointed star shape that we'll then fillet (round the corners) and extrude into a 3D solid. The fillet operation gives our star a softer, more elegant look while also making it safer to handle and easier to 3D print.
- Rete
- Blockly
- TypeScript
{
"id": "rete-v2-json",
"nodes": {
"a1b2c3d4e5f6a7b8": {
"id": "a1b2c3d4e5f6a7b8",
"name": "bitbybit.occt.shapes.wire.createStarWire",
"customName": "star wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"center": [
0,
0,
0
],
"direction": [
0,
1,
0
],
"numRays": 5,
"outerRadius": 5,
"innerRadius": 2,
"offsetOuterEdges": 0,
"half": false
},
"inputs": {},
"position": [
200,
300
]
},
"b2c3d4e5f6a7b8c9": {
"id": "b2c3d4e5f6a7b8c9",
"name": "bitbybit.occt.fillets.fillet2d",
"customName": "fillet 2d",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.3
},
"inputs": {
"shape": {
"connections": [
{
"node": "a1b2c3d4e5f6a7b8",
"output": "result",
"data": {}
}
]
}
},
"position": [
600,
300
]
},
"c3d4e5f6a7b8c9d0": {
"id": "c3d4e5f6a7b8c9d0",
"name": "bitbybit.occt.shapes.face.createFaceFromWire",
"customName": "face from wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"planar": true
},
"inputs": {
"shape": {
"connections": [
{
"node": "b2c3d4e5f6a7b8c9",
"output": "result",
"data": {}
}
]
}
},
"position": [
1000,
300
]
},
"d4e5f6a7b8c9d0e1": {
"id": "d4e5f6a7b8c9d0e1",
"name": "bitbybit.occt.operations.extrude",
"customName": "extrude",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"direction": [
0,
1,
0
]
},
"inputs": {
"shape": {
"connections": [
{
"node": "c3d4e5f6a7b8c9d0",
"output": "result",
"data": {}
}
]
}
},
"position": [
1400,
300
]
},
"e5f6a7b8c9d0e1f2": {
"id": "e5f6a7b8c9d0e1f2",
"name": "bitbybit.occt.fillets.filletEdges",
"customName": "fillet edges",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.15
},
"inputs": {
"shape": {
"connections": [
{
"node": "d4e5f6a7b8c9d0e1",
"output": "result",
"data": {}
}
]
}
},
"position": [
1800,
300
]
},
"a932fe6ce769acfa": {
"id": "a932fe6ce769acfa",
"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": "e5f6a7b8c9d0e1f2",
"output": "result",
"data": {}
}
]
},
"options": {
"connections": [
{
"node": "da2acd0d47017c24",
"output": "result",
"data": {}
}
]
}
},
"position": [
2473.1987035849716,
300.3324882556685
]
},
"da2acd0d47017c24": {
"id": "da2acd0d47017c24",
"name": "bitbybit.draw.optionsOcctShapeMaterial",
"customName": "options occt shape material",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"precision": 0.001,
"drawEdges": true,
"edgeColour": "#000000",
"edgeWidth": 1
},
"inputs": {
"faceMaterial": {
"connections": [
{
"node": "afe922cb471989f2",
"output": "result",
"data": {}
}
]
}
},
"position": [
2004.6249617003418,
806.4137688290969
]
},
"afe922cb471989f2": {
"id": "afe922cb471989f2",
"name": "bitbybit.babylon.material.pbrMetallicRoughness.create",
"customName": "pbr metallic roughness",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"name": "Custom Material",
"baseColor": "#ffffff",
"emissiveColor": "#00ffcc",
"metallic": 0.8,
"roughness": 0.99,
"alpha": 1,
"backFaceCulling": false,
"zOffset": 2
},
"inputs": {},
"position": [
1613.2900739427882,
845.1250503184503
]
}
}
}
<xml xmlns="https://developers.google.com/blockly/xml"><block type="bitbybit.draw.drawAnyAsyncNoReturn" id="main_draw" x="-200" y="-150"><value name="Entity"><block type="bitbybit.occt.fillets.filletEdges" id="fillet_edges"><value name="Shape"><block type="bitbybit.occt.operations.extrude" id="extrude_star"><value name="Shape"><block type="bitbybit.occt.shapes.face.createFaceFromWire" id="face_from_wire"><value name="Shape"><block type="bitbybit.occt.fillets.fillet2d" id="fillet_2d"><value name="Shape"><block type="bitbybit.occt.shapes.wire.createStarWire" id="star_wire"><value name="NumRays"><block type="math_number" id="num_rays"><field name="NUM">5</field></block></value><value name="OuterRadius"><block type="math_number" id="outer_radius"><field name="NUM">5</field></block></value><value name="InnerRadius"><block type="math_number" id="inner_radius"><field name="NUM">2</field></block></value><value name="Center"><block type="bitbybit.point.pointXYZ" id="center"><value name="X"><block type="math_number" id="cx"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="cy"><field name="NUM">0</field></block></value><value name="Z"><block type="math_number" id="cz"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="direction"><value name="X"><block type="math_number" id="dx"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="dy"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="dz"><field name="NUM">0</field></block></value></block></value><value name="Half"><block type="logic_boolean" id="half"><field name="BOOL">FALSE</field></block></value></block></value><value name="Radius"><block type="math_number" id="fillet_2d_radius"><field name="NUM">0.3</field></block></value></block></value><value name="Planar"><block type="logic_boolean" id="planar"><field name="BOOL">TRUE</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="extrude_dir"><value name="X"><block type="math_number" id="ex"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="ey"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="ez"><field name="NUM">0</field></block></value></block></value></block></value><value name="Radius"><block type="math_number" id="fillet_3d_radius"><field name="NUM">0.15</field></block></value></block></value><value name="Options"><block type="bitbybit.draw.optionsOcctShapeMaterial" id="draw_options"><value name="Precision"><block type="math_number" id="precision"><field name="NUM">0.001</field></block></value><value name="DrawEdges"><block type="logic_boolean" id="draw_edges"><field name="BOOL">TRUE</field></block></value><value name="EdgeColour"><block type="colour_picker" id="edge_colour"><field name="COLOUR">#000000</field></block></value><value name="EdgeWidth"><block type="math_number" id="edge_width"><field name="NUM">1</field></block></value><value name="FaceMaterial"><block type="bitbybit.babylon.material.pbrMetallicRoughness.create" id="pbr_material"><value name="Name"><block type="text" id="mat_name"><field name="TEXT">Star Material</field></block></value><value name="BaseColor"><block type="colour_picker" id="base_color"><field name="COLOUR">#ffffff</field></block></value><value name="EmissiveColor"><block type="colour_picker" id="emissive_color"><field name="COLOUR">#00ffcc</field></block></value><value name="Metallic"><block type="math_number" id="metallic"><field name="NUM">0.8</field></block></value><value name="Roughness"><block type="math_number" id="roughness"><field name="NUM">0.99</field></block></value><value name="Alpha"><block type="math_number" id="alpha"><field name="NUM">1</field></block></value><value name="BackFaceCulling"><block type="logic_boolean" id="backface"><field name="BOOL">FALSE</field></block></value><value name="ZOffset"><block type="math_number" id="zoffset"><field name="NUM">2</field></block></value></block></value></block></value></block></xml>
const { StarDto, FilletDto, FaceFromWireDto, ExtrudeDto } = Bit.Inputs.OCCT;
const { PBRMetallicRoughnessDto } = Bit.Inputs.BabylonMaterial;
type TopoDSWirePointer = Bit.Inputs.OCCT.TopoDSWirePointer;
type TopoDSFacePointer = Bit.Inputs.OCCT.TopoDSFacePointer;
type TopoDSSolidPointer = Bit.Inputs.OCCT.TopoDSSolidPointer;
const { wire, face } = bitbybit.occt.shapes;
const { fillets, operations } = bitbybit.occt;
const start = async () => {
// Create PBR material with emissive glow
const materialOptions = new PBRMetallicRoughnessDto();
materialOptions.name = "Star Material";
materialOptions.baseColor = "#ffffff";
materialOptions.emissiveColor = "#00ffcc";
materialOptions.metallic = 0.8;
materialOptions.roughness = 0.99;
materialOptions.alpha = 1;
materialOptions.backFaceCulling = false;
materialOptions.zOffset = 2;
const material = bitbybit.babylon.material.pbrMetallicRoughness.create(materialOptions);
// Create the star wire with 5 points
const starOptions = new StarDto();
starOptions.numRays = 5;
starOptions.outerRadius = 5;
starOptions.innerRadius = 2;
starOptions.center = [0, 0, 0];
starOptions.direction = [0, 1, 0];
starOptions.half = false;
const starWire = await wire.createStarWire(starOptions);
// Apply 2D fillet to round the star's corners
const fillet2dOptions = new FilletDto<TopoDSWirePointer>();
fillet2dOptions.shape = starWire;
fillet2dOptions.radius = 0.3;
const filletedWire = await fillets.fillet2d(fillet2dOptions);
// Create a face from the filleted wire
const faceOptions = new FaceFromWireDto<TopoDSWirePointer>();
faceOptions.shape = filletedWire;
faceOptions.planar = true;
const starFace = await face.createFaceFromWire(faceOptions);
// Extrude the face to create a 3D solid
const extrudeOptions = new ExtrudeDto<TopoDSFacePointer>();
extrudeOptions.shape = starFace;
extrudeOptions.direction = [0, 1, 0];
const starSolid = await operations.extrude(extrudeOptions);
// Apply 3D fillet to smooth all edges
const fillet3dOptions = new FilletDto<TopoDSSolidPointer>();
fillet3dOptions.shape = starSolid;
fillet3dOptions.radius = 0.15;
const finalStar = await fillets.filletEdges(fillet3dOptions);
// Draw with custom material and edge styling
const drawOptions = new Bit.Inputs.Draw.DrawOcctShapeOptions();
drawOptions.precision = 0.001;
drawOptions.drawEdges = true;
drawOptions.edgeColour = "#000000";
drawOptions.edgeWidth = 1;
drawOptions.faceMaterial = material;
bitbybit.draw.drawAnyAsync({
entity: finalStar,
options: drawOptions
});
}
start();
Understanding the Code
Star Wire Creation
The createStarWire function generates our star shape with these key parameters:
- numRays: The number of star points (5 for a classic star)
- outerRadius: Distance from center to the tips
- innerRadius: Distance from center to the inner valleys
- direction: The normal vector (facing up in Y direction)
Filleting for Polish
We apply two rounds of filleting:
- 2D Fillet: Rounds the corners of the wire before extrusion, creating smooth transitions between the star's points
- 3D Fillet: Applied after extrusion to round all edges, making the ornament pleasant to touch and easier to print
Extrusion
The extrusion transforms our 2D face into a 3D solid. The direction vector [0, 1, 0] creates a 1-unit thick star. Adjust this for thicker or thinner ornaments!
Adding a Hole
To hang your star ornament on a tree, we need to add a hole at the top. We'll use a boolean difference operation to cut a cylinder through the top point of the star. This is a fundamental CAD technique that allows you to subtract one shape from another.
The cylinder is positioned at the top of the star (at the outer radius on the Z-axis) and oriented along the Y-axis to create a clean hole through the thickness of the ornament.
- Rete
- Blockly
- TypeScript
{
"id": "rete-v2-json",
"nodes": {
"a1b2c3d4e5f6a7b8": {
"id": "a1b2c3d4e5f6a7b8",
"name": "bitbybit.occt.shapes.wire.createStarWire",
"customName": "star wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"center": [
0,
0,
0
],
"direction": [
0,
1,
0
],
"numRays": 5,
"outerRadius": 5,
"innerRadius": 2,
"offsetOuterEdges": 0,
"half": false
},
"inputs": {
"outerRadius": {
"connections": [
{
"node": "19d651a0af7f2e63",
"output": "result",
"data": {}
}
]
},
"innerRadius": {
"connections": [
{
"node": "6776cf8d29fe5fd4",
"output": "result",
"data": {}
}
]
}
},
"position": [
200,
300
]
},
"b2c3d4e5f6a7b8c9": {
"id": "b2c3d4e5f6a7b8c9",
"name": "bitbybit.occt.fillets.fillet2d",
"customName": "fillet 2d",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.3
},
"inputs": {
"shape": {
"connections": [
{
"node": "a1b2c3d4e5f6a7b8",
"output": "result",
"data": {}
}
]
}
},
"position": [
600,
300
]
},
"c3d4e5f6a7b8c9d0": {
"id": "c3d4e5f6a7b8c9d0",
"name": "bitbybit.occt.shapes.face.createFaceFromWire",
"customName": "face from wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"planar": true
},
"inputs": {
"shape": {
"connections": [
{
"node": "b2c3d4e5f6a7b8c9",
"output": "result",
"data": {}
}
]
}
},
"position": [
1000,
300
]
},
"d4e5f6a7b8c9d0e1": {
"id": "d4e5f6a7b8c9d0e1",
"name": "bitbybit.occt.operations.extrude",
"customName": "extrude",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"direction": [
0,
1,
0
]
},
"inputs": {
"shape": {
"connections": [
{
"node": "c3d4e5f6a7b8c9d0",
"output": "result",
"data": {}
}
]
}
},
"position": [
1400,
300
]
},
"cylinder1": {
"id": "cylinder1",
"name": "bitbybit.occt.shapes.solid.createCylinder",
"customName": "cylinder for hole",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.2,
"height": 2,
"center": [
0,
0,
0
],
"direction": [
0,
1,
0
],
"angle": 360,
"originOnCenter": false
},
"inputs": {
"center": {
"connections": [
{
"node": "7979ffd005e581aa",
"output": "result",
"data": {}
}
]
}
},
"position": [
1003.3384125703533,
-164.570359716235
]
},
"difference1": {
"id": "difference1",
"name": "bitbybit.occt.booleans.difference",
"customName": "cut hole",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"keepEdges": false
},
"inputs": {
"shapes": {
"connections": [
{
"node": "4eb728eefee546d6",
"output": "list",
"data": {}
}
]
},
"shape": {
"connections": [
{
"node": "d4e5f6a7b8c9d0e1",
"output": "result",
"data": {}
}
]
}
},
"position": [
1906.9976302985747,
42.43067709905108
]
},
"a932fe6ce769acfa": {
"id": "a932fe6ce769acfa",
"name": "bitbybit.draw.drawAnyAsync",
"customName": "draw any async",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
}
},
"inputs": {
"options": {
"connections": [
{
"node": "da2acd0d47017c24",
"output": "result",
"data": {}
}
]
},
"entity": {
"connections": [
{
"node": "65a615921768d8f2",
"output": "result",
"data": {}
}
]
}
},
"position": [
2774.350559505484,
373.984817304014
]
},
"da2acd0d47017c24": {
"id": "da2acd0d47017c24",
"name": "bitbybit.draw.optionsOcctShapeMaterial",
"customName": "options occt shape material",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"precision": 0.001,
"drawEdges": true,
"edgeColour": "#000000",
"edgeWidth": 1
},
"inputs": {
"faceMaterial": {
"connections": [
{
"node": "afe922cb471989f2",
"output": "result",
"data": {}
}
]
}
},
"position": [
2297.0262463648737,
467.78209772556175
]
},
"afe922cb471989f2": {
"id": "afe922cb471989f2",
"name": "bitbybit.babylon.material.pbrMetallicRoughness.create",
"customName": "pbr metallic roughness",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"name": "Custom Material",
"baseColor": "#ffffff",
"emissiveColor": "#00ffcc",
"metallic": 0.8,
"roughness": 0.99,
"alpha": 1,
"backFaceCulling": false,
"zOffset": 2
},
"inputs": {},
"position": [
1888.9653155293959,
509.6748934617672
]
},
"4eb728eefee546d6": {
"id": "4eb728eefee546d6",
"name": "bitbybit.lists.createList",
"customName": "create list",
"data": {},
"inputs": {
"listElements": {
"connections": [
{
"node": "cylinder1",
"output": "result",
"data": {}
}
]
}
},
"position": [
1386.3322074430675,
-128.37722709859008
]
},
"19d651a0af7f2e63": {
"id": "19d651a0af7f2e63",
"name": "bitbybit.math.numberSlider",
"customName": "number slider",
"data": {
"number": 5.1
},
"inputs": {},
"position": [
-1013.0248874665731,
301.2566859224661
]
},
"6776cf8d29fe5fd4": {
"id": "6776cf8d29fe5fd4",
"name": "bitbybit.math.divide",
"customName": "divide",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"first": 1,
"second": 3
},
"inputs": {
"first": {
"connections": [
{
"node": "19d651a0af7f2e63",
"output": "result",
"data": {}
}
]
}
},
"position": [
-375.6561358564627,
561.2867534889871
]
},
"7979ffd005e581aa": {
"id": "7979ffd005e581aa",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": 0,
"z": 0
},
"inputs": {
"x": {
"connections": [
{
"node": "679ea6925ec1e71e",
"output": "result",
"data": {}
}
]
}
},
"position": [
617.285893936524,
-86.01988260431061
]
},
"679ea6925ec1e71e": {
"id": "679ea6925ec1e71e",
"name": "bitbybit.math.divide",
"customName": "divide",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"first": 1,
"second": 2
},
"inputs": {
"first": {
"connections": [
{
"node": "19d651a0af7f2e63",
"output": "result",
"data": {}
}
]
}
},
"position": [
200.28435820031467,
-80.92053393533374
]
},
"65a615921768d8f2": {
"id": "65a615921768d8f2",
"name": "bitbybit.occt.fillets.filletEdges",
"customName": "fillet edges",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.15
},
"inputs": {
"shape": {
"connections": [
{
"node": "difference1",
"output": "result",
"data": {}
}
]
}
},
"position": [
2302.1515129203385,
43.623665791764665
]
}
}
}
<xml xmlns="https://developers.google.com/blockly/xml"><variables><variable id="outerRadius">outerRadius</variable></variables><block type="variables_set" id="set_radius" x="-600" y="-150"><field name="VAR" id="outerRadius">outerRadius</field><value name="VALUE"><block type="math_number" id="radius_val"><field name="NUM">5.1</field></block></value><next><block type="bitbybit.draw.drawAnyAsyncNoReturn" id="main_draw"><value name="Entity"><block type="bitbybit.occt.fillets.filletEdges" id="fillet_edges"><value name="Shape"><block type="bitbybit.occt.booleans.difference" id="difference"><value name="Shape"><block type="bitbybit.occt.operations.extrude" id="extrude_star"><value name="Shape"><block type="bitbybit.occt.shapes.face.createFaceFromWire" id="face_from_wire"><value name="Shape"><block type="bitbybit.occt.fillets.fillet2d" id="fillet_2d"><value name="Shape"><block type="bitbybit.occt.shapes.wire.createStarWire" id="star_wire"><value name="Center"><block type="bitbybit.point.pointXYZ" id="center"><value name="X"><block type="math_number" id="cx"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="cy"><field name="NUM">0</field></block></value><value name="Z"><block type="math_number" id="cz"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="direction"><value name="X"><block type="math_number" id="dx"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="dy"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="dz"><field name="NUM">0</field></block></value></block></value><value name="NumRays"><block type="math_number" id="num_rays"><field name="NUM">5</field></block></value><value name="OuterRadius"><block type="variables_get" id="get_outer"><field name="VAR" id="outerRadius">outerRadius</field></block></value><value name="InnerRadius"><block type="math_arithmetic" id="inner_calc"><field name="OP">DIVIDE</field><value name="A"><block type="variables_get" id="get_outer2"><field name="VAR" id="outerRadius">outerRadius</field></block></value><value name="B"><block type="math_number" id="divisor"><field name="NUM">3</field></block></value></block></value><value name="Half"><block type="logic_boolean" id="half"><field name="BOOL">FALSE</field></block></value></block></value><value name="Radius"><block type="math_number" id="fillet_2d_radius"><field name="NUM">0.3</field></block></value></block></value><value name="Planar"><block type="logic_boolean" id="planar"><field name="BOOL">TRUE</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="extrude_dir"><value name="X"><block type="math_number" id="ex"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="ey"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="ez"><field name="NUM">0</field></block></value></block></value></block></value><value name="Shapes"><block type="lists_create_with" id="shapes_list"><mutation items="1"></mutation><value name="ADD0"><block type="bitbybit.occt.shapes.solid.createCylinder" id="cylinder"><value name="Center"><block type="bitbybit.point.pointXYZ" id="cyl_center"><value name="X"><block type="math_arithmetic" id="cyl_x"><field name="OP">DIVIDE</field><value name="A"><block type="variables_get" id="get_outer3"><field name="VAR" id="outerRadius">outerRadius</field></block></value><value name="B"><block type="math_number" id="div2"><field name="NUM">2</field></block></value></block></value><value name="Y"><block type="math_number" id="cyl_y"><field name="NUM">0</field></block></value><value name="Z"><block type="math_number" id="cyl_z"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="cyl_dir"><value name="X"><block type="math_number" id="cyl_dx"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="cyl_dy"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="cyl_dz"><field name="NUM">0</field></block></value></block></value><value name="Radius"><block type="math_number" id="cyl_radius"><field name="NUM">0.2</field></block></value><value name="Height"><block type="math_number" id="cyl_height"><field name="NUM">2</field></block></value></block></value></block></value></block></value><value name="Radius"><block type="math_number" id="fillet_3d_radius"><field name="NUM">0.15</field></block></value></block></value><value name="Options"><block type="bitbybit.draw.optionsOcctShapeMaterial" id="draw_options"><value name="Precision"><block type="math_number" id="precision"><field name="NUM">0.001</field></block></value><value name="FaceMaterial"><block type="bitbybit.babylon.material.pbrMetallicRoughness.create" id="pbr_material"><value name="Name"><block type="text" id="mat_name"><field name="TEXT">Star Material</field></block></value><value name="BaseColor"><block type="colour_picker" id="base_color"><field name="COLOUR">#ffffff</field></block></value><value name="EmissiveColor"><block type="colour_picker" id="emissive_color"><field name="COLOUR">#00ffcc</field></block></value><value name="Metallic"><block type="math_number" id="metallic"><field name="NUM">0.8</field></block></value><value name="Roughness"><block type="math_number" id="roughness"><field name="NUM">0.99</field></block></value><value name="Alpha"><block type="math_number" id="alpha"><field name="NUM">1</field></block></value><value name="BackFaceCulling"><block type="logic_boolean" id="backface"><field name="BOOL">FALSE</field></block></value><value name="ZOffset"><block type="math_number" id="zoffset"><field name="NUM">2</field></block></value></block></value><value name="DrawEdges"><block type="logic_boolean" id="draw_edges"><field name="BOOL">TRUE</field></block></value><value name="EdgeColour"><block type="colour_picker" id="edge_colour"><field name="COLOUR">#000000</field></block></value><value name="EdgeWidth"><block type="math_number" id="edge_width"><field name="NUM">1</field></block></value></block></value></block></next></block></xml>
const { StarDto, FilletDto, FaceFromWireDto, ExtrudeDto, CylinderDto, DifferenceDto } = Bit.Inputs.OCCT;
const { PBRMetallicRoughnessDto } = Bit.Inputs.BabylonMaterial;
type TopoDSWirePointer = Bit.Inputs.OCCT.TopoDSWirePointer;
type TopoDSFacePointer = Bit.Inputs.OCCT.TopoDSFacePointer;
type TopoDSShapePointer = Bit.Inputs.OCCT.TopoDSShapePointer;
const { wire, face, solid } = bitbybit.occt.shapes;
const { fillets, operations, booleans } = bitbybit.occt;
const start = async () => {
// Define outer radius as a variable (like the slider in Rete)
const outerRadius = 5.1;
const innerRadius = outerRadius / 3;
// Create PBR material with emissive glow
const materialOptions = new PBRMetallicRoughnessDto();
materialOptions.name = "Star Material";
materialOptions.baseColor = "#ffffff";
materialOptions.emissiveColor = "#00ffcc";
materialOptions.metallic = 0.8;
materialOptions.roughness = 0.99;
materialOptions.alpha = 1;
materialOptions.backFaceCulling = false;
materialOptions.zOffset = 2;
const material = bitbybit.babylon.material.pbrMetallicRoughness.create(materialOptions);
// Create the star wire with 5 points
const starOptions = new StarDto();
starOptions.numRays = 5;
starOptions.outerRadius = outerRadius;
starOptions.innerRadius = innerRadius;
starOptions.center = [0, 0, 0];
starOptions.direction = [0, 1, 0];
starOptions.half = false;
const starWire = await wire.createStarWire(starOptions);
// Apply 2D fillet to round the star's corners
const fillet2dOptions = new FilletDto<TopoDSWirePointer>();
fillet2dOptions.shape = starWire;
fillet2dOptions.radius = 0.3;
const filletedWire = await fillets.fillet2d(fillet2dOptions);
// Create a face from the filleted wire
const faceOptions = new FaceFromWireDto<TopoDSWirePointer>();
faceOptions.shape = filletedWire;
faceOptions.planar = true;
const starFace = await face.createFaceFromWire(faceOptions);
// Extrude the face to create a 3D solid
const extrudeOptions = new ExtrudeDto<TopoDSFacePointer>();
extrudeOptions.shape = starFace;
extrudeOptions.direction = [0, 1, 0];
const starSolid = await operations.extrude(extrudeOptions);
// Create cylinder for the hanging hole
const cylinderOptions = new CylinderDto();
cylinderOptions.center = [outerRadius / 2, 0, 0]; // Position based on outer radius
cylinderOptions.direction = [0, 1, 0];
cylinderOptions.radius = 0.2;
cylinderOptions.height = 2;
const holeCylinder = await solid.createCylinder(cylinderOptions);
// Boolean difference to cut the hole (before filleting edges)
const diffOptions = new DifferenceDto<TopoDSShapePointer>();
diffOptions.shape = starSolid;
diffOptions.shapes = [holeCylinder];
diffOptions.keepEdges = false;
const starWithHole = await booleans.difference(diffOptions);
// Apply 3D fillet to smooth all edges (after boolean)
const fillet3dOptions = new FilletDto<TopoDSShapePointer>();
fillet3dOptions.shape = starWithHole;
fillet3dOptions.radius = 0.15;
const finalStar = await fillets.filletEdges(fillet3dOptions);
// Draw with custom material and edge styling
const drawOptions = new Bit.Inputs.Draw.DrawOcctShapeOptions();
drawOptions.precision = 0.001;
drawOptions.drawEdges = true;
drawOptions.edgeColour = "#000000";
drawOptions.edgeWidth = 1;
drawOptions.faceMaterial = material;
bitbybit.draw.drawAnyAsync({
entity: finalStar,
options: drawOptions
});
}
start();
Understanding the Hanging Hole
The hanging hole is created using these key concepts:
-
Cylinder Creation: We create a cylinder using
createCylinderwith:- center:
[0, -0.5, 4.2]- positioned at the top star point (Z = 4.2 is slightly inside the outer radius of 5) - radius: 0.4 - small enough to leave material around the hole
- height: 2 - extends through the full thickness of the star
- center:
-
Boolean Difference: The
differenceoperation subtracts the cylinder from the star, creating a clean hole. This is one of the most powerful CAD operations, allowing you to:- Create holes and cutouts
- Carve complex shapes
- Remove material precisely
Exporting to STL for 3D Printing
Now that we have our star ornament ready, let's export it as an STL file for 3D printing! The saveShapeStl function converts our OCCT shape into a binary STL file that can be opened in any slicer software.
- Rete
- Blockly
- TypeScript
{
"id": "rete-v2-json",
"nodes": {
"a1b2c3d4e5f6a7b8": {
"id": "a1b2c3d4e5f6a7b8",
"name": "bitbybit.occt.shapes.wire.createStarWire",
"customName": "star wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"center": [
0,
0,
0
],
"direction": [
0,
1,
0
],
"numRays": 5,
"outerRadius": 5,
"innerRadius": 2,
"offsetOuterEdges": 0,
"half": false
},
"inputs": {
"outerRadius": {
"connections": [
{
"node": "fec2dafcd515af38",
"output": "result",
"data": {}
}
]
},
"innerRadius": {
"connections": [
{
"node": "6776cf8d29fe5fd4",
"output": "result",
"data": {}
}
]
}
},
"position": [
200,
300
]
},
"b2c3d4e5f6a7b8c9": {
"id": "b2c3d4e5f6a7b8c9",
"name": "bitbybit.occt.fillets.fillet2d",
"customName": "fillet 2d",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.3
},
"inputs": {
"shape": {
"connections": [
{
"node": "a1b2c3d4e5f6a7b8",
"output": "result",
"data": {}
}
]
}
},
"position": [
600,
300
]
},
"c3d4e5f6a7b8c9d0": {
"id": "c3d4e5f6a7b8c9d0",
"name": "bitbybit.occt.shapes.face.createFaceFromWire",
"customName": "face from wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"planar": true
},
"inputs": {
"shape": {
"connections": [
{
"node": "b2c3d4e5f6a7b8c9",
"output": "result",
"data": {}
}
]
}
},
"position": [
1000,
300
]
},
"d4e5f6a7b8c9d0e1": {
"id": "d4e5f6a7b8c9d0e1",
"name": "bitbybit.occt.operations.extrude",
"customName": "extrude",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"direction": [
0,
1,
0
]
},
"inputs": {
"shape": {
"connections": [
{
"node": "c3d4e5f6a7b8c9d0",
"output": "result",
"data": {}
}
]
}
},
"position": [
1400,
300
]
},
"cylinder1": {
"id": "cylinder1",
"name": "bitbybit.occt.shapes.solid.createCylinder",
"customName": "cylinder for hole",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.2,
"height": 2,
"center": [
0,
0,
0
],
"direction": [
0,
1,
0
],
"angle": 360,
"originOnCenter": false
},
"inputs": {
"center": {
"connections": [
{
"node": "7979ffd005e581aa",
"output": "result",
"data": {}
}
]
}
},
"position": [
1003.3384125703533,
-164.570359716235
]
},
"difference1": {
"id": "difference1",
"name": "bitbybit.occt.booleans.difference",
"customName": "cut hole",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"keepEdges": false
},
"inputs": {
"shapes": {
"connections": [
{
"node": "4db2839f06602c56",
"output": "list",
"data": {}
}
]
},
"shape": {
"connections": [
{
"node": "d4e5f6a7b8c9d0e1",
"output": "result",
"data": {}
}
]
}
},
"position": [
1906.9976302985747,
42.43067709905108
]
},
"a932fe6ce769acfa": {
"id": "a932fe6ce769acfa",
"name": "bitbybit.draw.drawAnyAsync",
"customName": "draw any async",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
}
},
"inputs": {
"options": {
"connections": [
{
"node": "da2acd0d47017c24",
"output": "result",
"data": {}
}
]
},
"entity": {
"connections": [
{
"node": "65a615921768d8f2",
"output": "result",
"data": {}
}
]
}
},
"position": [
2774.350559505484,
373.984817304014
]
},
"da2acd0d47017c24": {
"id": "da2acd0d47017c24",
"name": "bitbybit.draw.optionsOcctShapeMaterial",
"customName": "options occt shape material",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"precision": 0.001,
"drawEdges": true,
"edgeColour": "#000000",
"edgeWidth": 1
},
"inputs": {
"faceMaterial": {
"connections": [
{
"node": "afe922cb471989f2",
"output": "result",
"data": {}
}
]
}
},
"position": [
2297.0262463648737,
467.78209772556175
]
},
"afe922cb471989f2": {
"id": "afe922cb471989f2",
"name": "bitbybit.babylon.material.pbrMetallicRoughness.create",
"customName": "pbr metallic roughness",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"name": "Custom Material",
"baseColor": "#ffffff",
"emissiveColor": "#00ffcc",
"metallic": 0.8,
"roughness": 0.99,
"alpha": 1,
"backFaceCulling": false,
"zOffset": 2
},
"inputs": {},
"position": [
1888.9653155293959,
509.6748934617672
]
},
"4db2839f06602c56": {
"id": "4db2839f06602c56",
"name": "bitbybit.lists.createList",
"customName": "create list",
"data": {},
"inputs": {
"listElements": {
"connections": [
{
"node": "cylinder1",
"output": "result",
"data": {}
}
]
}
},
"position": [
1386.3322074430675,
-128.37722709859008
]
},
"fec2dafcd515af38": {
"id": "fec2dafcd515af38",
"name": "bitbybit.math.numberSlider",
"customName": "number slider",
"data": {
"number": 5.1
},
"inputs": {},
"position": [
-1013.0248874665731,
301.2566859224661
]
},
"6776cf8d29fe5fd4": {
"id": "6776cf8d29fe5fd4",
"name": "bitbybit.math.divide",
"customName": "divide",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"first": 1,
"second": 3
},
"inputs": {
"first": {
"connections": [
{
"node": "fec2dafcd515af38",
"output": "result",
"data": {}
}
]
}
},
"position": [
-375.6561358564627,
561.2867534889871
]
},
"7979ffd005e581aa": {
"id": "7979ffd005e581aa",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": 0,
"z": 0
},
"inputs": {
"x": {
"connections": [
{
"node": "679ea6925ec1e71e",
"output": "result",
"data": {}
}
]
}
},
"position": [
617.285893936524,
-86.01988260431061
]
},
"679ea6925ec1e71e": {
"id": "679ea6925ec1e71e",
"name": "bitbybit.math.divide",
"customName": "divide",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"first": 1,
"second": 2
},
"inputs": {
"first": {
"connections": [
{
"node": "fec2dafcd515af38",
"output": "result",
"data": {}
}
]
}
},
"position": [
200.28435820031467,
-80.92053393533374
]
},
"65a615921768d8f2": {
"id": "65a615921768d8f2",
"name": "bitbybit.occt.fillets.filletEdges",
"customName": "fillet edges",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.15
},
"inputs": {
"shape": {
"connections": [
{
"node": "difference1",
"output": "result",
"data": {}
}
]
}
},
"position": [
2302.1515129203385,
43.623665791764665
]
},
"f61c10222fe8b6cd": {
"id": "f61c10222fe8b6cd",
"name": "bitbybit.occt.io.saveShapeStl",
"customName": "save shape stl",
"async": true,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"fileName": "star.stl",
"precision": 0.001,
"adjustYtoZ": false,
"tryDownload": true,
"binary": true
},
"inputs": {
"shape": {
"connections": [
{
"node": "65a615921768d8f2",
"output": "result",
"data": {}
}
]
}
},
"position": [
2788.424323239864,
-185.299280890669
]
}
}
}
<xml xmlns="https://developers.google.com/blockly/xml"><variables><variable id="outerRadius">outerRadius</variable><variable id="finalStar">finalStar</variable></variables><block type="variables_set" id="set_radius" x="-600" y="-150"><field name="VAR" id="outerRadius">outerRadius</field><value name="VALUE"><block type="math_number" id="radius_val"><field name="NUM">5.1</field></block></value><next><block type="variables_set" id="set_star"><field name="VAR" id="finalStar">finalStar</field><value name="VALUE"><block type="bitbybit.occt.fillets.filletEdges" id="fillet_edges"><value name="Shape"><block type="bitbybit.occt.booleans.difference" id="difference"><value name="Shape"><block type="bitbybit.occt.operations.extrude" id="extrude_star"><value name="Shape"><block type="bitbybit.occt.shapes.face.createFaceFromWire" id="face_from_wire"><value name="Shape"><block type="bitbybit.occt.fillets.fillet2d" id="fillet_2d"><value name="Shape"><block type="bitbybit.occt.shapes.wire.createStarWire" id="star_wire"><value name="Center"><block type="bitbybit.point.pointXYZ" id="center"><value name="X"><block type="math_number" id="cx"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="cy"><field name="NUM">0</field></block></value><value name="Z"><block type="math_number" id="cz"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="direction"><value name="X"><block type="math_number" id="dx"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="dy"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="dz"><field name="NUM">0</field></block></value></block></value><value name="NumRays"><block type="math_number" id="num_rays"><field name="NUM">5</field></block></value><value name="OuterRadius"><block type="variables_get" id="get_outer"><field name="VAR" id="outerRadius">outerRadius</field></block></value><value name="InnerRadius"><block type="math_arithmetic" id="inner_calc"><field name="OP">DIVIDE</field><value name="A"><block type="variables_get" id="get_outer2"><field name="VAR" id="outerRadius">outerRadius</field></block></value><value name="B"><block type="math_number" id="divisor"><field name="NUM">3</field></block></value></block></value><value name="Half"><block type="logic_boolean" id="half"><field name="BOOL">FALSE</field></block></value></block></value><value name="Radius"><block type="math_number" id="fillet_2d_radius"><field name="NUM">0.3</field></block></value></block></value><value name="Planar"><block type="logic_boolean" id="planar"><field name="BOOL">TRUE</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="extrude_dir"><value name="X"><block type="math_number" id="ex"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="ey"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="ez"><field name="NUM">0</field></block></value></block></value></block></value><value name="Shapes"><block type="lists_create_with" id="shapes_list"><mutation items="1"></mutation><value name="ADD0"><block type="bitbybit.occt.shapes.solid.createCylinder" id="cylinder"><value name="Center"><block type="bitbybit.point.pointXYZ" id="cyl_center"><value name="X"><block type="math_arithmetic" id="cyl_x"><field name="OP">DIVIDE</field><value name="A"><block type="variables_get" id="get_outer3"><field name="VAR" id="outerRadius">outerRadius</field></block></value><value name="B"><block type="math_number" id="div2"><field name="NUM">2</field></block></value></block></value><value name="Y"><block type="math_number" id="cyl_y"><field name="NUM">0</field></block></value><value name="Z"><block type="math_number" id="cyl_z"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ" id="cyl_dir"><value name="X"><block type="math_number" id="cyl_dx"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number" id="cyl_dy"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number" id="cyl_dz"><field name="NUM">0</field></block></value></block></value><value name="Radius"><block type="math_number" id="cyl_radius"><field name="NUM">0.2</field></block></value><value name="Height"><block type="math_number" id="cyl_height"><field name="NUM">2</field></block></value></block></value></block></value></block></value><value name="Radius"><block type="math_number" id="fillet_3d_radius"><field name="NUM">0.15</field></block></value></block></value><next><block type="bitbybit.draw.drawAnyAsyncNoReturn" id="main_draw"><value name="Entity"><block type="variables_get" id="get_star"><field name="VAR" id="finalStar">finalStar</field></block></value><value name="Options"><block type="bitbybit.draw.optionsOcctShapeMaterial" id="draw_options"><value name="Precision"><block type="math_number" id="precision"><field name="NUM">0.001</field></block></value><value name="FaceMaterial"><block type="bitbybit.babylon.material.pbrMetallicRoughness.create" id="pbr_material"><value name="Name"><block type="text" id="mat_name"><field name="TEXT">Star Material</field></block></value><value name="BaseColor"><block type="colour_picker" id="base_color"><field name="COLOUR">#ffffff</field></block></value><value name="EmissiveColor"><block type="colour_picker" id="emissive_color"><field name="COLOUR">#00ffcc</field></block></value><value name="Metallic"><block type="math_number" id="metallic"><field name="NUM">0.8</field></block></value><value name="Roughness"><block type="math_number" id="roughness"><field name="NUM">0.99</field></block></value><value name="Alpha"><block type="math_number" id="alpha"><field name="NUM">1</field></block></value><value name="BackFaceCulling"><block type="logic_boolean" id="backface"><field name="BOOL">FALSE</field></block></value><value name="ZOffset"><block type="math_number" id="zoffset"><field name="NUM">2</field></block></value></block></value><value name="DrawEdges"><block type="logic_boolean" id="draw_edges"><field name="BOOL">TRUE</field></block></value><value name="EdgeColour"><block type="colour_picker" id="edge_colour"><field name="COLOUR">#000000</field></block></value><value name="EdgeWidth"><block type="math_number" id="edge_width"><field name="NUM">1</field></block></value></block></value><next><block type="bitbybit.occt.io.saveShapeStl" id="save_stl"><value name="Shape"><block type="variables_get" id="get_star2"><field name="VAR" id="finalStar">finalStar</field></block></value><value name="FileName"><block type="text" id="filename"><field name="TEXT">star.stl</field></block></value><value name="Precision"><block type="math_number" id="stl_precision"><field name="NUM">0.001</field></block></value><value name="AdjustYtoZ"><block type="logic_boolean" id="adjust_yz"><field name="BOOL">FALSE</field></block></value><value name="TryDownload"><block type="logic_boolean" id="try_download"><field name="BOOL">TRUE</field></block></value></block></next></block></next></block></next></block></xml>
const { StarDto, FilletDto, FaceFromWireDto, ExtrudeDto, CylinderDto, DifferenceDto } = Bit.Inputs.OCCT;
const { PBRMetallicRoughnessDto } = Bit.Inputs.BabylonMaterial;
const { SaveStlDto } = Bit.Inputs.OCCT;
type TopoDSWirePointer = Bit.Inputs.OCCT.TopoDSWirePointer;
type TopoDSFacePointer = Bit.Inputs.OCCT.TopoDSFacePointer;
type TopoDSShapePointer = Bit.Inputs.OCCT.TopoDSShapePointer;
const { wire, face, solid } = bitbybit.occt.shapes;
const { fillets, operations, booleans, io } = bitbybit.occt;
const start = async () => {
// Define outer radius as a variable (like the slider in Rete)
const outerRadius = 5.1;
const innerRadius = outerRadius / 3;
// Create PBR material with emissive glow
const materialOptions = new PBRMetallicRoughnessDto();
materialOptions.name = "Star Material";
materialOptions.baseColor = "#ffffff";
materialOptions.emissiveColor = "#00ffcc";
materialOptions.metallic = 0.8;
materialOptions.roughness = 0.99;
materialOptions.alpha = 1;
materialOptions.backFaceCulling = false;
materialOptions.zOffset = 2;
const material = bitbybit.babylon.material.pbrMetallicRoughness.create(materialOptions);
// Create the star wire with 5 points
const starOptions = new StarDto();
starOptions.numRays = 5;
starOptions.outerRadius = outerRadius;
starOptions.innerRadius = innerRadius;
starOptions.center = [0, 0, 0];
starOptions.direction = [0, 1, 0];
starOptions.half = false;
const starWire = await wire.createStarWire(starOptions);
// Apply 2D fillet to round the star's corners
const fillet2dOptions = new FilletDto<TopoDSWirePointer>();
fillet2dOptions.shape = starWire;
fillet2dOptions.radius = 0.3;
const filletedWire = await fillets.fillet2d(fillet2dOptions);
// Create a face from the filleted wire
const faceOptions = new FaceFromWireDto<TopoDSWirePointer>();
faceOptions.shape = filletedWire;
faceOptions.planar = true;
const starFace = await face.createFaceFromWire(faceOptions);
// Extrude the face to create a 3D solid
const extrudeOptions = new ExtrudeDto<TopoDSFacePointer>();
extrudeOptions.shape = starFace;
extrudeOptions.direction = [0, 1, 0];
const starSolid = await operations.extrude(extrudeOptions);
// Create cylinder for the hanging hole
const cylinderOptions = new CylinderDto();
cylinderOptions.center = [outerRadius / 2, 0, 0]; // Position based on outer radius
cylinderOptions.direction = [0, 1, 0];
cylinderOptions.radius = 0.2;
cylinderOptions.height = 2;
const holeCylinder = await solid.createCylinder(cylinderOptions);
// Boolean difference to cut the hole (before filleting edges)
const diffOptions = new DifferenceDto<TopoDSShapePointer>();
diffOptions.shape = starSolid;
diffOptions.shapes = [holeCylinder];
diffOptions.keepEdges = false;
const starWithHole = await booleans.difference(diffOptions);
// Apply 3D fillet to smooth all edges (after boolean)
const fillet3dOptions = new FilletDto<TopoDSShapePointer>();
fillet3dOptions.shape = starWithHole;
fillet3dOptions.radius = 0.15;
const finalStar = await fillets.filletEdges(fillet3dOptions);
// Draw with custom material and edge styling
const drawOptions = new Bit.Inputs.Draw.DrawOcctShapeOptions();
drawOptions.precision = 0.001;
drawOptions.drawEdges = true;
drawOptions.edgeColour = "#000000";
drawOptions.edgeWidth = 1;
drawOptions.faceMaterial = material;
bitbybit.draw.drawAnyAsync({
entity: finalStar,
options: drawOptions
});
// Export to STL for 3D printing
const stlOptions = new SaveStlDto<TopoDSShapePointer>();
stlOptions.shape = finalStar;
stlOptions.fileName = "star.stl";
stlOptions.precision = 0.001;
stlOptions.adjustYtoZ = false;
stlOptions.tryDownload = true;
stlOptions.binary = true;
await io.saveShapeStl(stlOptions);
}
start();
Understanding STL Export
The saveShapeStl function exports your 3D model with these options:
- fileName: The name of the downloaded file (e.g.,
star.stl) - precision: Controls mesh quality (0.001 is high quality)
- adjustYtoZ: Set to
falseto keep the Y-up orientation, ortruefor Z-up (some 3D printers prefer this) - tryDownload: Automatically triggers the file download
- binary: Uses binary STL format (smaller file size than ASCII)
Making It Your Own 🎁
Try these festive variations:
- Six-pointed Star: Change
numRaysto 6 for a Star of David style - Chubby Star: Increase
innerRadiuscloser toouterRadius - Tree Topper: Increase the thickness for a substantial tree-top star
3D Printing Tips
Your star ornament is print-ready! For best results:
- Layer Height: 0.2mm works great
- Infill: 15-20% is sufficient for lightweight ornaments
- Material: PLA in gold, silver, or festive red looks stunning
- Support: Usually not needed when printed flat
Happy holidays and happy modeling! 🌟



