Skip to main content

Working along a wire

A wire is more than an outline. You can stand anywhere on it and ask where you are, which way it is heading and how far you have come, and that is how you hang posts on a fence line or thread beads on a string.

Copies that follow a curve​

framesAlongWire spreads count frames evenly by length along a wire. Each frame has an origin on the wire and a normal along it, so a part placed on the frames with transforms.placeOnFrames follows the curve. Model the part around the origin with its forward direction along Z, because placeOnFrames carries the world's Z axis onto each frame's normal.

kind chooses how a frame turns about the curve:

  • rotationMinimizing is carried along from the start without twisting, the way a swept profile rides. It is the safe choice for curves in space.
  • perpendicular stands across the curve and stays level with up.
  • frenet lies in the plane the curve bends in, and has no normal where the curve runs straight.

This chain takes 17 frames and shares them out with lists.getNthItem. The links on the odd frames are turned 90 degrees about Z, so each link threads through its neighbours. They are spaced closely enough to overlap and far enough apart not to touch:

Bitbybit Platform

A chain along a curve in Rete

rete logoRete
Script Source (rete)
{
"id": "rete-v2-json",
"nodes": {
"points": {
"id": "points",
"name": "bitbybit.json.parse",
"customName": "parse",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"text": "[[0, 0, 0], [8, 2, 3], [16, 0, 0], [24, 3, -3], [32, 0, 0]]"
},
"inputs": {},
"position": [
40,
40
]
},
"path": {
"id": "path",
"name": "bitbybit.occt.shapes.wire.interpolatePoints",
"customName": "interpolate points",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"periodic": false,
"tolerance": 1e-7,
"parametrization": "chordLength"
},
"inputs": {
"points": {
"connections": [
{
"node": "points",
"output": "result",
"data": {}
}
]
}
},
"position": [
460,
40
]
},
"frames": {
"id": "frames",
"name": "bitbybit.occt.shapes.wire.framesAlongWire",
"customName": "frames along wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"count": 17,
"skipEndOnClosed": true,
"kind": "rotationMinimizing",
"up": [
0,
1,
0
]
},
"inputs": {
"shape": {
"connections": [
{
"node": "path",
"output": "result",
"data": {}
}
]
}
},
"position": [
880,
40
]
},
"everyOther": {
"id": "everyOther",
"name": "bitbybit.lists.getNthItem",
"customName": "get nth item",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"nth": 2,
"offset": 0,
"clone": true
},
"inputs": {
"list": {
"connections": [
{
"node": "frames",
"output": "result",
"data": {}
}
]
}
},
"position": [
1300,
40
]
},
"theOthers": {
"id": "theOthers",
"name": "bitbybit.lists.getNthItem",
"customName": "get nth item",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"nth": 2,
"offset": 1,
"clone": true
},
"inputs": {
"list": {
"connections": [
{
"node": "frames",
"output": "result",
"data": {}
}
]
}
},
"position": [
1300,
380
]
},
"ring": {
"id": "ring",
"name": "bitbybit.occt.shapes.wire.createEllipseWire",
"customName": "ellipse wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"center": [
0,
0,
0
],
"direction": [
0,
1,
0
],
"radiusMinor": 0.9,
"radiusMajor": 1.6
},
"inputs": {},
"position": [
40,
280
]
},
"link": {
"id": "link",
"name": "bitbybit.occt.operations.pipeWireCylindrical",
"customName": "pipe wire cylindrical",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"radius": 0.22,
"makeSolid": true,
"trihedronEnum": "isConstantNormal",
"forceApproxC1": false,
"bendRadius": 0
},
"inputs": {
"shape": {
"connections": [
{
"node": "ring",
"output": "result",
"data": {}
}
]
}
},
"position": [
460,
518
]
},
"lying": {
"id": "lying",
"name": "bitbybit.occt.transforms.placeOnFrames",
"customName": "place on frames",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
}
},
"inputs": {
"shape": {
"connections": [
{
"node": "link",
"output": "result",
"data": {}
}
]
},
"frames": {
"connections": [
{
"node": "everyOther",
"output": "result",
"data": {}
}
]
}
},
"position": [
1720,
40
]
},
"turned": {
"id": "turned",
"name": "bitbybit.occt.transforms.rotate",
"customName": "rotate",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"axis": [
0,
0,
1
],
"angle": 90
},
"inputs": {
"shape": {
"connections": [
{
"node": "link",
"output": "result",
"data": {}
}
]
}
},
"position": [
880,
426
]
},
"standing": {
"id": "standing",
"name": "bitbybit.occt.transforms.placeOnFrames",
"customName": "place on frames",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
}
},
"inputs": {
"shape": {
"connections": [
{
"node": "turned",
"output": "result",
"data": {}
}
]
},
"frames": {
"connections": [
{
"node": "theOthers",
"output": "result",
"data": {}
}
]
}
},
"position": [
1720,
334
]
},
"steel": {
"id": "steel",
"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": "#5f6773",
"faceColour": "#ff0000",
"vertexColour": "#ff00ff",
"edgeWidth": 2,
"edgeWidthBySize": false,
"vertexSize": 0.03,
"drawEdges": false,
"drawFaces": true,
"drawVertices": false,
"precision": 0.01,
"angularDeflection": 0.5,
"relativeDeflection": false,
"precisionShare": 0,
"drawEdgeIndexes": false,
"edgeIndexHeight": 0.06,
"edgeIndexColour": "#ff00ff",
"drawFaceIndexes": false,
"faceIndexHeight": 0.06,
"faceIndexColour": "#0000ff",
"drawTwoSided": false,
"backFaceColour": "#0000ff",
"backFaceOpacity": 1,
"edgeArrowSize": 0,
"edgeArrowAngle": 15,
"keepMeshData": false,
"allowQualityDecrease": true,
"forceFaceDeflection": false,
"drawIsoCurves": false,
"isoCurvesU": 5,
"isoCurvesV": 5,
"isoCurvesColour": "#808080",
"surfaceAnalysis": "none",
"draftDirection": [
0,
1,
0
]
},
"inputs": {
"faceMaterial": {
"connections": [
{
"node": "steelMaterial",
"output": "result",
"data": {}
}
]
}
},
"position": [
460,
950
]
},
"steelMaterial": {
"id": "steelMaterial",
"name": "bitbybit.draw.createPBRMaterial",
"customName": "create pbr material",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"name": "PBRMaterial",
"baseColor": "#eef0f2",
"metallic": 0.7,
"roughness": 0.3,
"alpha": 1,
"emissiveColor": "#000000",
"emissiveIntensity": 1,
"zOffset": 0,
"zOffsetUnits": 0,
"alphaMode": "opaque",
"alphaCutoff": 0.5,
"doubleSided": false,
"wireframe": false,
"unlit": false
},
"inputs": {},
"position": [
40,
620
]
},
"darker": {
"id": "darker",
"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": "#5f6773",
"faceColour": "#ff0000",
"vertexColour": "#ff00ff",
"edgeWidth": 2,
"edgeWidthBySize": false,
"vertexSize": 0.03,
"drawEdges": false,
"drawFaces": true,
"drawVertices": false,
"precision": 0.01,
"angularDeflection": 0.5,
"relativeDeflection": false,
"precisionShare": 0,
"drawEdgeIndexes": false,
"edgeIndexHeight": 0.06,
"edgeIndexColour": "#ff00ff",
"drawFaceIndexes": false,
"faceIndexHeight": 0.06,
"faceIndexColour": "#0000ff",
"drawTwoSided": false,
"backFaceColour": "#0000ff",
"backFaceOpacity": 1,
"edgeArrowSize": 0,
"edgeArrowAngle": 15,
"keepMeshData": false,
"allowQualityDecrease": true,
"forceFaceDeflection": false,
"drawIsoCurves": false,
"isoCurvesU": 5,
"isoCurvesV": 5,
"isoCurvesColour": "#808080",
"surfaceAnalysis": "none",
"draftDirection": [
0,
1,
0
]
},
"inputs": {
"faceMaterial": {
"connections": [
{
"node": "darkerMaterial",
"output": "result",
"data": {}
}
]
}
},
"position": [
460,
2900
]
},
"darkerMaterial": {
"id": "darkerMaterial",
"name": "bitbybit.draw.createPBRMaterial",
"customName": "create pbr material",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"name": "PBRMaterial",
"baseColor": "#9aa4ae",
"metallic": 0.7,
"roughness": 0.3,
"alpha": 1,
"emissiveColor": "#000000",
"emissiveIntensity": 1,
"zOffset": 0,
"zOffsetUnits": 0,
"alphaMode": "opaque",
"alphaCutoff": 0.5,
"doubleSided": false,
"wireframe": false,
"unlit": false
},
"inputs": {},
"position": [
40,
1650
]
},
"drawLying": {
"id": "drawLying",
"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": "lying",
"output": "result",
"data": {}
}
]
},
"options": {
"connections": [
{
"node": "steel",
"output": "result",
"data": {}
}
]
}
},
"position": [
2140,
40
]
},
"drawStanding": {
"id": "drawStanding",
"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": "standing",
"output": "result",
"data": {}
}
]
},
"options": {
"connections": [
{
"node": "darker",
"output": "result",
"data": {}
}
]
}
},
"position": [
2140,
334
]
},
"view": {
"id": "view",
"name": "bitbybit.babylon.scene.adjustActiveArcRotateCamera",
"customName": "adjust active arc rotate camera",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"position": [
16,
18,
-48
],
"lookAt": [
16,
1,
0
],
"lowerBetaLimit": 1,
"upperBetaLimit": 179,
"angularSensibilityX": 1000,
"angularSensibilityY": 1000,
"maxZ": 1000,
"panningSensibility": 1000,
"wheelPrecision": 3
},
"inputs": {},
"position": [
2140,
628
]
}
},
"modules": {}
}

Points, directions and pieces​

The same questions come in two forms. ...AtLength takes a distance along the wire in model units. ...AtParam takes a place from 0 at the start to 1 at the end of the curve's own parameter, which is close to but not the same as that share of its length. When even spacing matters, use lengths.

To getUse
The length of a wiregetWireLength
A point at a placepointOnWireAtLength, pointOnWireAtParam
Points at regular stepspointsOnWireAtEqualLength, divideWireByEqualDistanceToPoints
Points at a repeating rhythmpointsOnWireAtPatternOfLengths
The heading at a placetangentOnWireAtLength, tangentOnWireAtParam
A frame at a placeframeOnWireAtLength, frameOnWireAtParam
Pieces of the wiresplitWireAtLengths, splitWireAtParams, splitOnPoints

shapes.edge has the matching functions for a single edge, with Edge in the name.