Skip to main content

OCCT Fillets: Applying to 3D Wires (Non-Planar)

OCCT category icon with a stylized logo representation

Introduction to Filleting 3D Wires​

Applying fillets to 3D wires (wires whose constituent edges do not all lie on a single plane) is a process that creates smooth, rounded transitions at the corners where two or more edges meet. Similar to 2D wires, fillets on 3D wires can be applied:

  • Globally: With a uniform radius to all eligible corners of the wire.
  • Specifically: With potentially different radii to selected corners.

A 3D wire (e.g., a star shape extruded slightly and then its top edges filleted) showing rounded corners in three-dimensional space. Fillets on a 3D wire.

How Fillets are Applied to a 3D Wire​

In Bitbybit, you will typically use a command like bitbybit.occt.fillets.fillet3DWire (or its equivalent in visual editors) to perform this operation. This command generally takes the following inputs:

  • Shape (Wire): The 3D wire to be filleted.
  • Radius or Radius List:
    • A single radius value to apply uniformly to all corners.
    • A list of radii if you intend to apply different fillet sizes to specific corners.
  • Corner Indexes (Optional): A list of indexes specifying which corners to fillet. This is used in conjunction with a radius list. If applying a uniform radius to all corners, this might be left empty.
  • Direction: A vector indicating a direction along which an internal extrusion operation is performed (see "Inner Workings" below).

If you provide a list of radii, you must also provide a corresponding list of corner indexes. If you use a single radius value and leave the radius list and indexes empty, that single radius will be applied to all eligible corners of the 3D wire.

Inner Workings of the 3D Wire Fillet Algorithm​

The filleting of a 3D wire in OCCT, as implemented in Bitbybit, often involves a clever behind-the-scenes process:

  1. A temporary shell is constructed by extruding the input 3D wire along the provided direction vector.
  2. Standard 3D filleting operations are then performed on specific edges of this temporary shell. These edges correspond to the corners of the original 3D wire that were targeted for filleting.
  3. After the filleting on the shell is complete, the newly filleted edges (which now form rounded paths) are extracted and reconstructed back into a new 3D wire.

This is why the direction parameter is crucial for the fillet3DWire algorithm to function correctly. The length of this direction vector can also influence the intermediate extrusion and thus, sometimes, the success or quality of the 3D fillet.

Filleting Specific Corners on a 3D Wire with Variable Radii​

The following examples demonstrate how to fillet a 3D wire by targeting specific corner indexes and applying a list of corresponding radii. The two lists (corner indexes and radii) must be of the same length. It's not necessary to round all corners; you can select specific ones.

In these examples, we first construct a 3D star-shaped wire. Then, we apply different fillet radii to its inner and outer corners, showcasing the ability to create complex filleting patterns.

Bitbybit Platform

Fillet Specific Corners of a 3D Wire in Rete

rete logoRete
Script Source (rete)
{
"id": "rete-v2-json",
"nodes": {
"236000e6be6ccf2b": {
"id": "236000e6be6ccf2b",
"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": 7,
"outerRadius": 10,
"innerRadius": 4,
"offsetOuterEdges": 4,
"half": false
},
"inputs": {
"numRays": {
"connections": [
{
"node": "f3b639af0a8a6f50",
"output": "result",
"data": {}
}
]
}
},
"position": [
-11.315676390459487,
280.7958914765325
]
},
"7287627509e8630e": {
"id": "7287627509e8630e",
"name": "bitbybit.occt.fillets.fillet3DWire",
"customName": "fillet 3d wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.1,
"direction": [
0,
1,
0
]
},
"inputs": {
"shape": {
"connections": [
{
"node": "236000e6be6ccf2b",
"output": "result",
"data": {}
}
]
},
"radiusList": {
"connections": [
{
"node": "a7e05f6656a0212f",
"output": "list",
"data": {}
}
]
},
"indexes": {
"connections": [
{
"node": "70cc1486aae9f9d9",
"output": "result",
"data": {}
}
]
}
},
"position": [
1577.5711831165675,
296.1831250873849
]
},
"f3b639af0a8a6f50": {
"id": "f3b639af0a8a6f50",
"name": "bitbybit.math.numberSlider",
"customName": "number slider",
"data": {
"options": {
"min": 5,
"max": 10,
"step": 1,
"width": 350
},
"number": 7
},
"inputs": {},
"position": [
-1052.3470210619662,
333.63761446923337
]
},
"70cc1486aae9f9d9": {
"id": "70cc1486aae9f9d9",
"name": "bitbybit.vector.range",
"customName": "range",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"max": 1
},
"inputs": {
"max": {
"connections": [
{
"node": "c1feeb47a95c86e6",
"output": "result",
"data": {}
}
]
}
},
"position": [
830.5434465316521,
586.020483457695
]
},
"c1feeb47a95c86e6": {
"id": "c1feeb47a95c86e6",
"name": "bitbybit.math.twoNrOperation",
"customName": "two nr operation",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"first": 1,
"second": 2,
"operation": "multiply"
},
"inputs": {
"first": {
"connections": [
{
"node": "f3b639af0a8a6f50",
"output": "result",
"data": {}
}
]
}
},
"position": [
-18.310614644244822,
866.0500018140378
]
},
"9f48e3e48cfbde1b": {
"id": "9f48e3e48cfbde1b",
"name": "bitbybit.math.numberSlider",
"customName": "number slider",
"data": {
"options": {
"min": 0.5,
"max": 2,
"step": 0.1,
"width": 350
},
"number": 1.7
},
"inputs": {},
"position": [
-1050.1077294976728,
-16.32890598511689
]
},
"b349fa782bf4a508": {
"id": "b349fa782bf4a508",
"name": "bitbybit.math.numberSlider",
"customName": "number slider",
"data": {
"options": {
"min": 0.5,
"max": 1,
"step": 0.1,
"width": 350
},
"number": 0.6
},
"inputs": {},
"position": [
-1054.0906529622296,
152.4521832719633
]
},
"ada8a94e9ae868d1": {
"id": "ada8a94e9ae868d1",
"name": "bitbybit.lists.createList",
"customName": "create list",
"data": {},
"inputs": {
"listElements": {
"connections": [
{
"node": "9f48e3e48cfbde1b",
"output": "result",
"data": {}
},
{
"node": "b349fa782bf4a508",
"output": "result",
"data": {}
}
]
}
},
"position": [
-113.72439356073457,
-167.01602134486205
]
},
"5ef5af7f0aafbcff": {
"id": "5ef5af7f0aafbcff",
"name": "bitbybit.lists.repeat",
"customName": "repeat",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"times": 10
},
"inputs": {
"item": {
"connections": [
{
"node": "ada8a94e9ae868d1",
"output": "list",
"data": {}
}
]
},
"times": {
"connections": [
{
"node": "f3b639af0a8a6f50",
"output": "result",
"data": {}
}
]
}
},
"position": [
307.58835103278113,
-101.21360658941975
]
},
"6401897a43288e43": {
"id": "6401897a43288e43",
"name": "bitbybit.lists.flatten",
"customName": "flatten",
"data": {
"nrLevels": 2
},
"inputs": {
"list": {
"connections": [
{
"node": "5ef5af7f0aafbcff",
"output": "result",
"data": {}
}
]
}
},
"position": [
718.7790781989961,
-125.32777378822661
]
},
"a7e05f6656a0212f": {
"id": "a7e05f6656a0212f",
"name": "bitbybit.lists.createList",
"customName": "create list",
"data": {},
"inputs": {
"listElements": {
"connections": [
{
"node": "6401897a43288e43",
"output": "result",
"data": {}
}
]
}
},
"position": [
1119.8671943851466,
-119.98346408038245
]
},
"e916c48b1203c51f": {
"id": "e916c48b1203c51f",
"name": "bitbybit.occt.transforms.translate",
"customName": "translate",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"translation": [
0,
0,
0
]
},
"inputs": {
"shape": {
"connections": [
{
"node": "7287627509e8630e",
"output": "result",
"data": {}
}
]
},
"translation": {
"connections": [
{
"node": "a4789489fb26b903",
"output": "result",
"data": {}
}
]
}
},
"position": [
2055.461339742562,
439.8635001386789
]
},
"a4789489fb26b903": {
"id": "a4789489fb26b903",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": 5,
"z": 0
},
"inputs": {},
"position": [
1573.6676810146807,
817.2653192641134
]
},
"50186ce95b274d19": {
"id": "50186ce95b274d19",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": 6,
"z": 0
},
"inputs": {},
"position": [
2052.6444596081433,
723.0890796115883
]
},
"724598de3a71b4fd": {
"id": "724598de3a71b4fd",
"name": "bitbybit.occt.transforms.translate",
"customName": "translate",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"translation": [
0,
0,
0
]
},
"inputs": {
"shape": {
"connections": [
{
"node": "e916c48b1203c51f",
"output": "result",
"data": {}
}
]
},
"translation": {
"connections": [
{
"node": "50186ce95b274d19",
"output": "result",
"data": {}
}
]
}
},
"position": [
2652.08911051333,
582.1960376964696
]
},
"bf2f337edb238436": {
"id": "bf2f337edb238436",
"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": false
},
"inputs": {
"shape": {
"connections": [
{
"node": "724598de3a71b4fd",
"output": "result",
"data": {}
}
]
}
},
"position": [
3139.2415194215505,
503.6284458343018
]
},
"cee96b09ceb73b4b": {
"id": "cee96b09ceb73b4b",
"name": "bitbybit.occt.operations.makeThickSolidSimple",
"customName": "make thick solid simple",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"offset": -1
},
"inputs": {
"shape": {
"connections": [
{
"node": "bf2f337edb238436",
"output": "result",
"data": {}
}
]
}
},
"position": [
3528.153271640637,
503.28011127738876
]
},
"b2b7ec3b80b0df50": {
"id": "b2b7ec3b80b0df50",
"name": "bitbybit.occt.fillets.filletEdges",
"customName": "fillet edges",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.3
},
"inputs": {
"shape": {
"connections": [
{
"node": "cee96b09ceb73b4b",
"output": "result",
"data": {}
}
]
}
},
"position": [
3937.7873364263783,
500.45923439202704
]
},
"8bd8ed9fd890e5cb": {
"id": "8bd8ed9fd890e5cb",
"name": "bitbybit.babylon.scene.enableSkybox",
"customName": "enable skybox",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"skybox": "clearSky",
"size": 1000,
"blur": 0.1,
"environmentIntensity": 0.7
},
"inputs": {},
"position": [
-872.9310695464733,
1325.8217652474093
]
},
"60bc0d7e14b90dec": {
"id": "60bc0d7e14b90dec",
"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": "b2b7ec3b80b0df50",
"output": "result",
"data": {}
}
]
},
"options": {
"connections": [
{
"node": "9ddd3ef16739bcaa",
"output": "result",
"data": {}
}
]
}
},
"position": [
4590.697492451062,
813.5693461000375
]
},
"9ddd3ef16739bcaa": {
"id": "9ddd3ef16739bcaa",
"name": "bitbybit.draw.optionsOcctShape",
"customName": "options occt shape",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"faceOpacity": 1,
"edgeOpacity": 1,
"edgeColour": "#000000",
"faceColour": "#0040ff",
"vertexColour": "#ff00ff",
"edgeWidth": 2,
"vertexSize": 0.03,
"drawEdges": true,
"drawFaces": true,
"drawVertices": false,
"precision": 0.005,
"drawEdgeIndexes": false,
"edgeIndexHeight": 0.06,
"edgeIndexColour": "#ff00ff",
"drawFaceIndexes": false,
"faceIndexHeight": 0.06,
"faceIndexColour": "#0000ff"
},
"inputs": {},
"position": [
3983.486163294121,
1202.4958710764429
]
},
"9be05dbe14e2af43": {
"id": "9be05dbe14e2af43",
"name": "bitbybit.draw.drawGridMesh",
"customName": "draw grid mesh",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"width": 400,
"height": 400,
"subdivisions": 10,
"majorUnitFrequency": 10,
"minorUnitVisibility": 0.45,
"gridRatio": 0.5,
"opacity": 0.5,
"backFaceCulling": false,
"mainColor": "#ffffff",
"secondaryColor": "#ffffff"
},
"inputs": {},
"position": [
-862.2779510804795,
1721.8456582103267
]
},
"d63192372194865c": {
"id": "d63192372194865c",
"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": "236000e6be6ccf2b",
"output": "result",
"data": {}
},
{
"node": "e916c48b1203c51f",
"output": "result",
"data": {}
}
]
},
"options": {
"connections": [
{
"node": "7458f3c6c195b724",
"output": "result",
"data": {}
}
]
}
},
"position": [
4093.3009234789824,
-419.951086411402
]
},
"7458f3c6c195b724": {
"id": "7458f3c6c195b724",
"name": "bitbybit.draw.optionsOcctShapeSimple",
"customName": "options occt shape simple",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"precision": 0.01,
"drawFaces": false,
"faceColour": "#ff0000",
"drawEdges": true,
"edgeColour": "#ffffff",
"edgeWidth": 15
},
"inputs": {},
"position": [
3603.530645172864,
-11.344274431578413
]
}
}
}

Visual Results: The images below show the filleted 3D star wire as rendered in the respective editors after running the scripts:

TypeScript editor showing the 3D star wire with filleted corners. TypeScript code in the live editor with finished geometries

Blockly editor showing the 3D star wire with filleted corners. Blockly code in the live editor with finished geometries

Rete editor showing the 3D star wire with filleted corners. Rete code in the live editor with finished geometries

How to Know Which Index Applies to Which Corner?​

  • Counted from 0: corner i is where edge i of the wire meets the next edge, and a closed wire's last corner is where its last edge meets its first. A closed star with 7 rays has 14 corners, numbered 0 to 13, which is why the examples above take vector.range with a maximum of nrRays * 2.
  • Finding a corner: list the wire's edges in order with bitbybit.occt.shapes.edge.getEdgesAlongWire, take edge i with bitbybit.lists.getItem, and draw its end point (bitbybit.occt.shapes.edge.endPointOnEdge) to see where corner i is.