Polylines in Bitbybit
View Full Source & Details on GitHub
bitbybit.polyline works with polylines: chains of straight segments through a list of points. Lengths are in model units.
What a polyline is
A polyline is an object { points: [[x1,y1,z1], [x2,y2,z2], ...], isClosed }. When isClosed is true, a last segment joins the last point back to the first, so the polyline outlines a polygon. The first point need not be repeated at the end.
create builds one from a list of points and isClosed (default false).
Reading and measuring
getPointsgives the list of points andcountPointscounts them.lengthadds up the distances between neighbouring points. The closing segment of a closed polyline is not counted, so to measure the full loop of a polygon, repeat its first point at the end.polygonAreameasures the area the polyline encloses, read as a closed outline whetherisClosedis set or not. The result is never negative.polygonNormalgives the unit normal of that outline, pointing the way the outline turns by the right-hand rule: a square traced counter-clockwise in the XY plane gives[0, 0, 1]. It holds for outlines that are not convex or not quite flat, and an outline with no area givesundefined.
Reversing and transforming
reversereverses the order of the points, so the polyline runs the other way. It reverses the point list of the polyline you pass in, and the polyline it gives has noisClosed; set it again withcreatewhen it matters.transformPolylineapplies a transformation matrix, or a list of them in order, such asbitbybit.babylon.transformsbuilds, to every point. The result is a new polyline, also withoutisClosed.
Splitting into segments
polylineToSegmentsgives one segment[start, end]per pair of neighbouring points, andpolylineToLinesgives the same as line objects{ start, end }.- For a closed polyline both add the segment from the last point back to the first, unless the two points already coincide.
Where polylines cross
polylineSelfIntersectionfinds the points where a polyline crosses itself. Neighbouring segments, which always share a point, are not tested against each other.twoPolylineIntersectionfinds the points where two polylines cross, testing every segment of one against every segment of the other.
Both use tolerance (default 1e-5): crossings closer together than it are reported once.
Joining loose segments
sortSegmentsIntoPolylines takes segments in any order and joins those whose ends meet within tolerance into polylines. A chain that comes back to where it started becomes a closed polyline, without its first point repeated at the end. A segment that touches nothing becomes a polyline of its own. bitbybit.mesh uses it to turn the segments where two meshes cut each other into curves.
Fillets at corners
maxFilletsHalfLinegives the largest fillet radius for every corner, each limited to the nearer half of its two segments so the fillets never overlap. A closed polyline also gets the corners at its first and last points.safestFilletRadiusgives the smallest of those: one radius that fits every corner. A polyline with fewer than three points, or any corner that allows no fillet, gives 0.
The same calculations for loose points are in bitbybit.point.
Here the corners of an OpenCascade star are read into a closed polyline. safestFilletRadius finds the largest radius every corner allows, and the star is rounded with it:
- Rete
- Blockly
- TypeScript
{
"id": "rete-v2-json",
"nodes": {
"star": {
"id": "star",
"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": 6,
"innerRadius": 3,
"offsetOuterEdges": 0,
"half": false
},
"inputs": {},
"position": [
40,
40
]
},
"corners": {
"id": "corners",
"name": "bitbybit.occt.shapes.wire.divideWireByEqualDistanceToPoints",
"customName": "divide wire by equal distance to points",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"nrOfDivisions": 14,
"removeStartPoint": false,
"removeEndPoint": true
},
"inputs": {
"shape": {
"connections": [
{
"node": "star",
"output": "result",
"data": {}
}
]
}
},
"position": [
460,
40
]
},
"outline": {
"id": "outline",
"name": "bitbybit.polyline.create",
"customName": "polyline",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"isClosed": true
},
"inputs": {
"points": {
"connections": [
{
"node": "corners",
"output": "result",
"data": {}
}
]
}
},
"position": [
880,
40
]
},
"radius": {
"id": "radius",
"name": "bitbybit.polyline.safestFilletRadius",
"customName": "safest fillet radius",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"tolerance": 0.00001
},
"inputs": {
"polyline": {
"connections": [
{
"node": "outline",
"output": "result",
"data": {}
}
]
}
},
"position": [
1300,
40
]
},
"rounded": {
"id": "rounded",
"name": "bitbybit.occt.fillets.fillet2d",
"customName": "fillet 2d",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.1
},
"inputs": {
"shape": {
"connections": [
{
"node": "star",
"output": "result",
"data": {}
}
]
},
"radius": {
"connections": [
{
"node": "radius",
"output": "result",
"data": {}
}
]
}
},
"position": [
1720,
40
]
}
},
"modules": {}
}
<xml xmlns="https://developers.google.com/blockly/xml"><variables><variable id="star">star</variable><variable id="outline">outline</variable></variables><block type="variables_set" x="40" y="40"><field name="VAR" id="star">star</field><value name="VALUE"><block type="bitbybit.occt.shapes.wire.createStarWire"><value name="Center"><block type="bitbybit.point.pointXYZ"><value name="X"><block type="math_number"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number"><field name="NUM">0</field></block></value><value name="Z"><block type="math_number"><field name="NUM">0</field></block></value></block></value><value name="Direction"><block type="bitbybit.vector.vectorXYZ"><value name="X"><block type="math_number"><field name="NUM">0</field></block></value><value name="Y"><block type="math_number"><field name="NUM">1</field></block></value><value name="Z"><block type="math_number"><field name="NUM">0</field></block></value></block></value><value name="NumRays"><block type="math_number"><field name="NUM">7</field></block></value><value name="OuterRadius"><block type="math_number"><field name="NUM">6</field></block></value><value name="InnerRadius"><block type="math_number"><field name="NUM">3</field></block></value><value name="OffsetOuterEdges"><block type="math_number"><field name="NUM">0</field></block></value><value name="Half"><block type="logic_boolean"><field name="BOOL">FALSE</field></block></value></block></value><next><block type="variables_set"><field name="VAR" id="outline">outline</field><value name="VALUE"><block type="bitbybit.polyline.create"><value name="Points"><block type="base_time_await_return"><value name="Promise"><block type="bitbybit.occt.shapes.wire.divideWireByEqualDistanceToPoints"><value name="Shape"><block type="variables_get"><field name="VAR" id="star">star</field></block></value><value name="NrOfDivisions"><block type="math_number"><field name="NUM">14</field></block></value><value name="RemoveStartPoint"><block type="logic_boolean"><field name="BOOL">FALSE</field></block></value><value name="RemoveEndPoint"><block type="logic_boolean"><field name="BOOL">TRUE</field></block></value></block></value></block></value><value name="IsClosed"><block type="logic_boolean"><field name="BOOL">TRUE</field></block></value></block></value><next><block type="bitbybit.draw.drawAnyAsyncNoReturn"><value name="Entity"><block type="variables_get"><field name="VAR" id="outline">outline</field></block></value><next><block type="bitbybit.draw.drawAnyAsyncNoReturn"><value name="Entity"><block type="bitbybit.occt.fillets.fillet2d"><value name="Shape"><block type="variables_get"><field name="VAR" id="star">star</field></block></value><value name="Radius"><block type="bitbybit.polyline.safestFilletRadius"><value name="Polyline"><block type="variables_get"><field name="VAR" id="outline">outline</field></block></value><value name="Tolerance"><block type="math_number"><field name="NUM">0.00001</field></block></value></block></value></block></value></block></next></block></next></block></next></block></xml>
const star = await bitbybit.occt.shapes.wire.createStarWire({ center: [0, 0, 0], direction: [0, 1, 0], numRays: 7, outerRadius: 6, innerRadius: 3, offsetOuterEdges: 0, half: false });
const corners = await bitbybit.occt.shapes.wire.divideWireByEqualDistanceToPoints({ shape: star, nrOfDivisions: 14, removeStartPoint: false, removeEndPoint: true });
const outline = bitbybit.polyline.create({ points: corners, isClosed: true });
const radius = bitbybit.polyline.safestFilletRadius({ polyline: outline, tolerance: 1e-5 });
const rounded = await bitbybit.occt.fillets.fillet2d({ shape: star, radius });
await bitbybit.draw.drawAnyAsync({ entity: outline });
await bitbybit.draw.drawAnyAsync({ entity: rounded });
Every function
| Function | What it does |
|---|---|
countPoints | Counts the points of the polyline. |
create | Builds a polyline object from points, open or closed. |
getPoints | Reads the list of points out of the polyline. |
length | Measures the polyline by adding up the straight distances between neighboring points. |
maxFilletsHalfLine | Finds the largest fillet for every corner of the polyline, each limited to the nearer half of its segments so the fillets never overlap. |
polygonArea | Measures the area a polyline encloses, read as a closed outline in its own plane. |
polygonNormal | Finds the unit normal of the plane a polyline encloses, read as a closed outline. |
polylineSelfIntersection | Finds the points where the polyline crosses itself. |
polylineToLines | Splits the polyline into line objects, one per segment, each with a start and an end point. |
polylineToSegments | Splits the polyline into segments, each a pair of points. |
reverse | Reverses the order of the points, so the polyline runs the other way. |
safestFilletRadius | Finds one fillet radius that fits every corner of the polyline: the smallest of the per-corner maximums under the half-segment rule. |
sortSegmentsIntoPolylines | Joins loose segments into polylines by matching up ends that meet within the tolerance. |
transformPolyline | Applies a transformation matrix, or a list of them in order, to every point of the polyline. |
twoPolylineIntersection | Finds the points where two polylines cross each other, testing every segment of one against every segment of the other. |



