├── README.md └── Cycloidal.py /README.md: -------------------------------------------------------------------------------- 1 | # Cycloidal-Drive 2 | 3D Printed Gearbox 3 | 4 | Cyclodial Gearbox with Tarot 4108 BLDC and CUI AMT102 capacitive encoder... 5 | 6 | All printing files available 7 | https://www.prusaprinters.org/prints/53681-cycloidal-drive-3d-printed-gearbox-401 8 | 9 | Assembly instructions 10 | https://youtu.be/QrpcD1v9rp8 11 | 12 | Based on a design of Dongil Choi..make sure to check his builds over at 13 | https://hackaday.io/dongilc 14 | 15 | Mounting instructions for the encoder 16 | https://youtu.be/NNBdfpqhKBI 17 | -------------------------------------------------------------------------------- /Cycloidal.py: -------------------------------------------------------------------------------- 1 | import adsk.core 2 | import adsk.fusion 3 | import traceback 4 | import math 5 | 6 | # All units cm 7 | 8 | # Rotor extrude thickness 9 | rotorThickness = 0.5 10 | # Rotor nominal diameter 11 | rotorDiameter = 5.1 12 | # Rotor bearing hole diameter 13 | rotorBearingHoleDiameter = 2.406 14 | # Spacing between opposing rotors 15 | rotorSpacing = 0.1 16 | 17 | # Camshaft cam diameter 18 | camDiameter = 0.4 19 | 20 | # Camshaft face diameter 21 | camshaftDiameter = 5.96 22 | 23 | # Camshaft reinforcement screw diameter 24 | camshaftScrewHoleDiameter = 0.3 25 | 26 | # Ring gear extrude thickness 27 | ringGearThickness = rotorThickness * 2 + rotorSpacing 28 | # Ring gear outer diameter 29 | ringGearOuterDiameter = rotorDiameter + 0.5 30 | # Ring gear margin around nominal rotor diameter 31 | ringGearMargin = 0.02 32 | # Number of pins in ring gear 33 | ringGearPins = 40 34 | # Number of lobes on cycloidal rotor 35 | rotorLobes = ringGearPins - 1 36 | # Ring gear pin radius (ideally circumference / n_pins / 4) 37 | ringGearPinRadius = rotorDiameter * math.pi / ringGearPins / 4 38 | # Eccentric offset 39 | eccentricOffset = 0.5 * ringGearPinRadius 40 | 41 | # Rotor output hole diameter 42 | outputHoleDiameter = 0.708 43 | # Rotor output hole count 44 | outputHoleCount = 8 45 | # Rotor output hole circle diameter 46 | outputCircleDiameter = (rotorDiameter + rotorBearingHoleDiameter) / 2 - ringGearPinRadius * 1.5 47 | 48 | # Output pin diameter 49 | outputPinDiameter = outputHoleDiameter - ringGearPinRadius; 50 | # Output pin reinforcement screw diameter 51 | outputPinScrewHoleDiameter = 0.3 52 | # Output body thickness 53 | outputPlateThickness = 0.3 54 | 55 | # Calculated 56 | rotorRadius = rotorDiameter / 2 57 | # Maximum allowed distance between spline points 58 | maxDist = 0.25 * ringGearPinRadius 59 | minDist = 0.5 * maxDist # Minimum allowed distance between spline points 60 | 61 | def getPoint(theta, rMajor, rMinor, e, n): 62 | psi = math.atan2(math.sin((1 - n) * theta), ((rMajor / (e * n)) - math.cos((1 - n) * theta))) 63 | x = (rMajor * math.cos(theta)) - (rMinor * math.cos(theta + psi)) - (e * math.cos(n * theta)) 64 | y = (-rMajor * math.sin(theta)) + (rMinor * math.sin(theta + psi)) + (e * math.sin(n * theta)) 65 | return (x, y) 66 | 67 | def distance(xa, ya, xb, yb): 68 | return math.hypot((xa - xb), (ya - yb)) 69 | 70 | def combineFeatures_add(rootComp: adsk.fusion.Design.rootComponent, targetBody, toolBody): 71 | combineFeatures = rootComp.features.combineFeatures 72 | tools = adsk.core.ObjectCollection.create() 73 | tools.add(toolBody) 74 | input: adsk.fusion.CombineFeatureInput = combineFeatures.createInput(targetBody, tools) 75 | input.isNewComponent = False 76 | input.isKeepToolBodies = False 77 | input.operation = adsk.fusion.FeatureOperations.JoinFeatureOperation 78 | combineFeature = combineFeatures.add(input) 79 | 80 | def rotor(design, root, invert, zOffset): 81 | newEccentricOffset = eccentricOffset 82 | offsetAngle = 0 83 | if invert: 84 | newEccentricOffset *= -1 85 | offsetAngle = math.pi / rotorLobes 86 | 87 | rotorOcc = root.occurrences.addNewComponent(adsk.core.Matrix3D.create()) 88 | rotor = rotorOcc.component 89 | rotor.name = 'Rotor' 90 | 91 | planes = rotor.constructionPlanes 92 | planeInput = planes.createInput() 93 | offsetValue = adsk.core.ValueInput.createByReal(zOffset) 94 | planeInput.setByOffset(root.xYConstructionPlane, offsetValue) 95 | constructionPlane = planes.add(planeInput) 96 | 97 | sk = rotor.sketches.add(constructionPlane) 98 | points = adsk.core.ObjectCollection.create() 99 | 100 | (xs, ys) = getPoint(0, rotorRadius, ringGearPinRadius, eccentricOffset, ringGearPins) 101 | points.add(adsk.core.Point3D.create(xs, ys, 0)) 102 | 103 | et = 2 * math.pi / rotorLobes 104 | (xe, ye) = getPoint(et, rotorRadius, ringGearPinRadius, eccentricOffset, ringGearPins) 105 | x = xs 106 | y = ys 107 | dist = 0 108 | ct = 0 109 | dt = math.pi / ringGearPins 110 | numPoints = 0 111 | 112 | while ((distance(x, y, xe, ye) > maxDist or ct < et / 2) and ct < et): 113 | (xt, yt) = getPoint(ct + dt, rotorRadius, ringGearPinRadius, eccentricOffset, ringGearPins) 114 | dist = distance(x, y, xt, yt) 115 | 116 | ddt = dt / 2 117 | lastTooBig = False 118 | lastTooSmall = False 119 | 120 | while (dist > maxDist or dist < minDist): 121 | if (dist > maxDist): 122 | if (lastTooSmall): 123 | ddt /= 2 124 | 125 | lastTooSmall = False 126 | lastTooBig = True 127 | 128 | if (ddt > dt / 2): 129 | ddt = dt / 2 130 | 131 | dt -= ddt 132 | 133 | elif (dist < minDist): 134 | if (lastTooBig): 135 | ddt /= 2 136 | 137 | lastTooSmall = True 138 | lastTooBig = False 139 | dt += ddt 140 | 141 | (xt, yt) = getPoint(ct + dt, rotorRadius, ringGearPinRadius, eccentricOffset, ringGearPins) 142 | dist = distance(x, y, xt, yt) 143 | 144 | x = xt 145 | y = yt 146 | points.add(adsk.core.Point3D.create(x, y, 0)) 147 | numPoints += 1 148 | ct += dt 149 | 150 | points.add(adsk.core.Point3D.create(xe, ye, 0)) 151 | curve = sk.sketchCurves.sketchFittedSplines.add(points) 152 | 153 | lines = sk.sketchCurves.sketchLines 154 | line1 = lines.addByTwoPoints(adsk.core.Point3D.create(0, 0, 0), curve.startSketchPoint) 155 | line2 = lines.addByTwoPoints(line1.startSketchPoint, curve.endSketchPoint) 156 | 157 | # Extrude 158 | prof = sk.profiles.item(0) 159 | dist = adsk.core.ValueInput.createByReal(rotorThickness) 160 | extrudes = rotor.features.extrudeFeatures 161 | extrude = extrudes.addSimple(prof, dist, adsk.fusion.FeatureOperations.NewBodyFeatureOperation) 162 | 163 | # Create component 164 | body1 = extrude.bodies.item(0) 165 | body1.name = "Rotor" 166 | inputEntities = adsk.core.ObjectCollection.create() 167 | inputEntities.add(body1) 168 | 169 | # Circular pattern 170 | zAxis = rotor.zConstructionAxis 171 | circularFeats = rotor.features.circularPatternFeatures 172 | circularFeatInput = circularFeats.createInput(inputEntities, zAxis) 173 | circularFeatInput.quantity = adsk.core.ValueInput.createByReal(rotorLobes) 174 | circularFeatInput.totalAngle = adsk.core.ValueInput.createByString('360 deg') 175 | circularFeatInput.isSymmetric = True 176 | circularFeat = circularFeats.add(circularFeatInput) 177 | 178 | # Combine pattern features 179 | ToolBodies = adsk.core.ObjectCollection.create() 180 | for b in circularFeat.bodies: 181 | ToolBodies.add(b) 182 | try: 183 | for b in ToolBodies: 184 | combineFeatures_add(rotor, body1, b) 185 | except: 186 | pass 187 | 188 | # Center bearing hole 189 | sk = rotor.sketches.add(constructionPlane) 190 | sketchCircles = sk.sketchCurves.sketchCircles 191 | sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0, 0, 0), rotorBearingHoleDiameter / 2) 192 | 193 | prof = sk.profiles.item(0) 194 | dist = adsk.core.ValueInput.createByReal(rotorThickness) 195 | extrudes = rotor.features.extrudeFeatures 196 | extrude = extrudes.addSimple(prof, dist, adsk.fusion.FeatureOperations.CutFeatureOperation) 197 | 198 | # Output holes 199 | sk = rotor.sketches.add(constructionPlane) 200 | sketchCircles = sk.sketchCurves.sketchCircles 201 | sketchCircles.addByCenterRadius(adsk.core.Point3D.create(math.cos(-offsetAngle + math.pi / 2) * outputCircleDiameter / 2, math.sin(-offsetAngle + math.pi / 2) * outputCircleDiameter / 2, 0),outputHoleDiameter / 2) 202 | 203 | prof = sk.profiles.item(0) 204 | dist = adsk.core.ValueInput.createByReal(rotorThickness) 205 | extrudes = rotor.features.extrudeFeatures 206 | extrude = extrudes.addSimple(prof, dist, adsk.fusion.FeatureOperations.CutFeatureOperation) 207 | 208 | inputEntities = adsk.core.ObjectCollection.create() 209 | inputEntities.add(extrude) 210 | 211 | # Circular pattern 212 | circularFeats = rotor.features.circularPatternFeatures 213 | circularFeatInput = circularFeats.createInput(inputEntities, zAxis) 214 | circularFeatInput.quantity = adsk.core.ValueInput.createByReal(outputHoleCount) 215 | circularFeatInput.totalAngle = adsk.core.ValueInput.createByString('360 deg') 216 | circularFeatInput.isSymmetric = True 217 | circularFeat = circularFeats.add(circularFeatInput) 218 | 219 | # Offset the rotor to make the ring gear concentric with origin 220 | transform = rotorOcc.transform 221 | transform.setToRotation(offsetAngle, adsk.core.Vector3D.create(0, 0, 1), adsk.core.Point3D.create(0, 0, 0)) 222 | transform.translation = adsk.core.Vector3D.create(newEccentricOffset, 0, 0) 223 | rotorOcc.transform = transform 224 | design.snapshots.add() 225 | 226 | def run(context): 227 | ui = None 228 | 229 | try: 230 | app = adsk.core.Application.get() 231 | ui = app.userInterface 232 | des = adsk.fusion.Design.cast(app.activeProduct) 233 | root = des.rootComponent 234 | 235 | ui.messageBox('Ratio: ' + str((ringGearPins - rotorLobes) / rotorLobes)) 236 | 237 | # Section: rotor 238 | rotor(des, root, False, 0) 239 | rotor(des, root, True, rotorThickness + rotorSpacing) 240 | 241 | # Section: camshaft 242 | camshaftOcc = root.occurrences.addNewComponent(adsk.core.Matrix3D.create()) 243 | camshaft = camshaftOcc.component 244 | camshaft.name = 'Camshaft' 245 | 246 | # Cam 247 | sk = camshaft.sketches.add(root.xYConstructionPlane) 248 | sketchCircles = sk.sketchCurves.sketchCircles 249 | sketchCircles.addByCenterRadius(adsk.core.Point3D.create(eccentricOffset, 0, 0), camDiameter / 2) 250 | sketchCircles.addByCenterRadius(adsk.core.Point3D.create(eccentricOffset, 0, 0), camshaftScrewHoleDiameter / 2) 251 | 252 | prof = sk.profiles.item(0) 253 | dist = adsk.core.ValueInput.createByReal(rotorThickness) 254 | extrudes = camshaft.features.extrudeFeatures 255 | extrude = extrudes.addSimple(prof, dist, adsk.fusion.FeatureOperations.NewBodyFeatureOperation) 256 | 257 | # Section: output assembly 258 | outputOcc = root.occurrences.addNewComponent(adsk.core.Matrix3D.create()) 259 | output = outputOcc.component 260 | output.name = 'Output' 261 | 262 | # Output pins 263 | sk = output.sketches.add(root.xYConstructionPlane) 264 | sketchCircles = sk.sketchCurves.sketchCircles 265 | sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0, outputCircleDiameter / 2, 0), outputPinDiameter / 2) 266 | 267 | prof = sk.profiles.item(0) 268 | dist = adsk.core.ValueInput.createByReal(ringGearThickness) 269 | extrudes = output.features.extrudeFeatures 270 | extrude = extrudes.addSimple(prof, dist, adsk.fusion.FeatureOperations.NewBodyFeatureOperation) 271 | 272 | inputEntities = adsk.core.ObjectCollection.create() 273 | inputEntities.add(extrude) 274 | 275 | # Circular pattern 276 | zAxis = output.zConstructionAxis 277 | circularFeats = output.features.circularPatternFeatures 278 | circularFeatInput = circularFeats.createInput(inputEntities, zAxis) 279 | circularFeatInput.quantity = adsk.core.ValueInput.createByReal(outputHoleCount) 280 | circularFeatInput.totalAngle = adsk.core.ValueInput.createByString('360 deg') 281 | circularFeatInput.isSymmetric = True 282 | circularFeat = circularFeats.add(circularFeatInput) 283 | 284 | # Output body 285 | sk = output.sketches.add(root.xYConstructionPlane) 286 | sketchCircles = sk.sketchCurves.sketchCircles 287 | sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0, 0, 0), outputCircleDiameter / 2 + outputPinDiameter) 288 | 289 | prof = sk.profiles.item(0) 290 | dist = adsk.core.ValueInput.createByReal(-outputPlateThickness) 291 | extrudes = output.features.extrudeFeatures 292 | extrude = extrudes.addSimple(prof, dist, adsk.fusion.FeatureOperations.JoinFeatureOperation) 293 | 294 | # Screw holes 295 | sk = output.sketches.add(root.xYConstructionPlane) 296 | sketchCircles = sk.sketchCurves.sketchCircles 297 | sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0, outputCircleDiameter / 2, 0), outputPinScrewHoleDiameter / 2) 298 | 299 | prof = sk.profiles.item(0) 300 | extrudeInput = extrudes.createInput(prof, adsk.fusion.FeatureOperations.CutFeatureOperation) 301 | extentAll = adsk.fusion.ThroughAllExtentDefinition.create() 302 | deg0 = adsk.core.ValueInput.createByString('0 deg') 303 | extrudeInput.setTwoSidesExtent(extentAll, extentAll, deg0, deg0) 304 | extrudes = output.features.extrudeFeatures 305 | extrude = extrudes.add(extrudeInput) 306 | 307 | inputEntities = adsk.core.ObjectCollection.create() 308 | inputEntities.add(extrude) 309 | 310 | # Circular pattern 311 | zAxis = output.zConstructionAxis 312 | circularFeats = output.features.circularPatternFeatures 313 | circularFeatInput = circularFeats.createInput(inputEntities, zAxis) 314 | circularFeatInput.quantity = adsk.core.ValueInput.createByReal(outputHoleCount) 315 | circularFeatInput.totalAngle = adsk.core.ValueInput.createByString('360 deg') 316 | circularFeatInput.isSymmetric = True 317 | circularFeat = circularFeats.add(circularFeatInput) 318 | 319 | # Section: ring gear 320 | ringGearOcc = root.occurrences.addNewComponent(adsk.core.Matrix3D.create()) 321 | ringGear = ringGearOcc.component 322 | ringGear.name = 'Ring Gear' 323 | 324 | # Pins 325 | sk = ringGear.sketches.add(root.xYConstructionPlane) 326 | sketchCircles = sk.sketchCurves.sketchCircles 327 | centerPoint = adsk.core.Point3D.create(rotorRadius, 0, 0) 328 | sketchCircles.addByCenterRadius(centerPoint, ringGearPinRadius) 329 | 330 | prof = sk.profiles.item(0) 331 | dist = adsk.core.ValueInput.createByReal(ringGearThickness) 332 | extrudes = ringGear.features.extrudeFeatures 333 | extrude = extrudes.addSimple(prof, dist, adsk.fusion.FeatureOperations.NewBodyFeatureOperation) 334 | 335 | pin = extrude.bodies.item(0) 336 | pin.name = "Pin" 337 | inputEntities = adsk.core.ObjectCollection.create() 338 | inputEntities.add(pin) 339 | 340 | # Circular pattern 341 | zAxis = ringGear.zConstructionAxis 342 | circularFeats = ringGear.features.circularPatternFeatures 343 | circularFeatInput = circularFeats.createInput(inputEntities, zAxis) 344 | circularFeatInput.quantity = adsk.core.ValueInput.createByReal(ringGearPins) 345 | circularFeatInput.totalAngle = adsk.core.ValueInput.createByString('360 deg') 346 | circularFeatInput.isSymmetric = True 347 | circularFeat = circularFeats.add(circularFeatInput) 348 | 349 | # Housing 350 | sk = ringGear.sketches.add(root.xYConstructionPlane) 351 | sketchCircles = sk.sketchCurves.sketchCircles 352 | sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0, 0, 0), rotorRadius + ringGearMargin) 353 | sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0, 0, 0), ringGearOuterDiameter / 2) 354 | 355 | prof = sk.profiles.item(1) 356 | dist = adsk.core.ValueInput.createByReal(ringGearThickness) 357 | extrudes = ringGear.features.extrudeFeatures 358 | extrude = extrudes.addSimple(prof, dist, adsk.fusion.FeatureOperations.JoinFeatureOperation) 359 | 360 | # Fillets - conditional fillet on edges with length matching gear thickness 361 | fillets = ringGear.features.filletFeatures 362 | 363 | edgeCollection1 = adsk.core.ObjectCollection.create() 364 | faces = ringGear.bRepBodies.item(0).faces 365 | for face in faces: 366 | for edge in face.edges: 367 | if abs(edge.length - ringGearThickness) < 0.005: 368 | edgeCollection1.add(edge) 369 | 370 | radius1 = adsk.core.ValueInput.createByReal(ringGearPinRadius) 371 | input1 = fillets.createInput() 372 | input1.addConstantRadiusEdgeSet(edgeCollection1, radius1, True) 373 | input1.isG2 = False 374 | input1.isRollingBallCorner = True 375 | fillet1 = fillets.add(input1) 376 | 377 | return 378 | 379 | except: 380 | if ui: 381 | ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) 382 | --------------------------------------------------------------------------------