I was going to write up the general approach I would take but once this is an interesting problem and I couldn't help but write some code. The coding went smoothly so I went ahead and finished it. Here's the results of what the program below creates. The code currently looks for all sketches in the root component that have "Connect" as part of the name. For example I had a sketch named "Sketch 1 (Connect)" and "Sketch 2 (Connect)". It passes this list of sketches to a function that then builds all of the connectors. The different inputs are hard coded in the program (length, outer diameter, hole diameter, and the maximum distance two points can be away from each other to be considered the same. And since you're 3D printing them I thought why not add fillets between the cylinders in the connector, so there's also a variable for the fillet radius. Each connector is a separate component.

There are probably some simpler ways to approach this but this is my quick solution. Hopefully you'll be able to figure out what I'm doing but I know some of it's not obvious and I got lazy with my commenting because I was in a hurry and just wanted to see if I could get it to work but I don't have any more time I can spend on it.
import adsk.core, adsk.fusion, adsk.cam, traceback
import math
_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)
connectorDia = 1
legLength = 2
holeDia = 0.6
holeDepth = 1
filletRad = 0.1
tolerance = 0.05
class ConnectionPoint(object):
pointIndexCount = 0
def __init__(self, coordinate):
self.coordinate = coordinate
self.pointIndex = self.pointIndexCount
ConnectionPoint.pointIndexCount += 1
self.connectedPoints = []
def addConnection(self, connectedPointIndex):
self.connectedPoints.append(connectedPointIndex)
def calculateConnections(sketches, tolerance):
try:
connections = []
sketch = adsk.fusion.Sketch.cast(None)
for sketch in sketches:
# Iterate through the lines in each sketch.
line = adsk.fusion.SketchLine.cast(None)
for line in sketch.sketchCurves.sketchLines:
startPnt = line.startSketchPoint.geometry
endPnt = line.endSketchPoint.geometry
# Check to see if the start point is already in the point list.
foundPoint = False
for currentConnection in connections:
if startPnt.distanceTo(currentConnection.coordinate) <= tolerance:
foundPoint = True
# Check if the end point is in the list.
foundOtherPoint = False
for otherConnection in connections:
if endPnt.distanceTo(otherConnection.coordinate) <= tolerance:
foundOtherPoint = True
currentConnection.addConnection(otherConnection.pointIndex)
if not foundOtherPoint:
# Create a new connection point.
connectPoint = ConnectionPoint(endPnt)
connections.append(connectPoint)
currentConnection.addConnection(connectPoint.pointIndex)
elif endPnt.distanceTo(currentConnection.coordinate) <= tolerance:
foundPoint = True
# Check if the end point is in the list.
foundOtherPoint = False
for otherConnection in connections:
if startPnt.distanceTo(otherConnection.coordinate) <= tolerance:
foundOtherPoint = True
currentConnection.addConnection(otherConnection.pointIndex)
if not foundOtherPoint:
# Create a new connection point.
connectPoint = ConnectionPoint(startPnt)
connections.append(connectPoint)
currentConnection.addConnection(connectPoint.pointIndex)
if not foundPoint:
# The points weren't found at all so add both of them as new connections.
connectOne = ConnectionPoint(startPnt)
connections.append(connectOne)
connectTwo = ConnectionPoint(endPnt)
connectTwo.addConnection(connectOne.pointIndex)
connections.append(connectTwo)
connectOne.addConnection(connectTwo.pointIndex)
return connections
except:
if _ui:
_ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
def drawConnector(root2, connections, index, name):
try:
root = adsk.fusion.Component.cast(root2)
occ = root.occurrences.addNewComponent(adsk.core.Matrix3D.create())
comp = occ.component
comp.name = name
planeInput = comp.constructionPlanes.createInput()
originPnt = adsk.core.Point3D.cast(connections[index].coordinate)
first = True
for connectedIndex in connections[index].connectedPoints:
directionPnt = connections[connectedIndex].coordinate
planeInput.setByPlane(adsk.core.Plane.create(originPnt, originPnt.vectorTo(directionPnt)))
constPlane = comp.constructionPlanes.add(planeInput)
sk = comp.sketches.add(constPlane)
sk.sketchCurves.sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0,0,0), connectorDia/2.0)
if first:
extInput = comp.features.extrudeFeatures.createInput(sk.profiles.item(0), adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
first = False
else:
extInput = comp.features.extrudeFeatures.createInput(sk.profiles.item(0), adsk.fusion.FeatureOperations.JoinFeatureOperation)
extInput.setDistanceExtent(False, adsk.core.ValueInput.createByReal(legLength))
comp.features.extrudeFeatures.add(extInput)
holeOrigin = originPnt.copy()
offsetDir = holeOrigin.vectorTo(directionPnt)
offsetDir.normalize()
offsetDir.scaleBy(legLength - holeDepth)
holeOrigin.translateBy(offsetDir)
planeInput.setByPlane(adsk.core.Plane.create(holeOrigin, offsetDir))
holeConstPlane = comp.constructionPlanes.add(planeInput)
sk = comp.sketches.add(holeConstPlane)
sk.sketchCurves.sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0,0,0), holeDia/2.0)
extInput = comp.features.extrudeFeatures.createInput(sk.profiles.item(0), adsk.fusion.FeatureOperations.CutFeatureOperation)
extInput.setDistanceExtent(False, adsk.core.ValueInput.createByReal(holeDepth))
extrude = comp.features.extrudeFeatures.add(extInput)
sk = comp.sketches.add(constPlane)
midLine = sk.sketchCurves.sketchLines.addByTwoPoints(adsk.core.Point3D.create(0, connectorDia/2, 0), adsk.core.Point3D.create(0, -connectorDia/2, 0))
arc = sk.sketchCurves.sketchArcs.addByThreePoints(midLine.startSketchPoint, adsk.core.Point3D.create(connectorDia/2, 0, 0), midLine.endSketchPoint)
revInput = comp.features.revolveFeatures.createInput(sk.profiles.item(0), midLine, adsk.fusion.FeatureOperations.JoinFeatureOperation)
revInput.setAngleExtent(False, adsk.core.ValueInput.createByReal(math.pi*2))
try:
rev = comp.features.revolveFeatures.add(revInput)
except:
pass
body = extrude.bodies.item(0)
filletInput = adsk.fusion.FilletFeatureInput.cast(comp.features.filletFeatures.createInput())
filletEdges = adsk.core.ObjectCollection.create()
edge = adsk.fusion.BRepEdge.cast(None)
for edge in body.edges:
if (edge.faces.item(0).geometry.objectType == adsk.core.Cylinder.classType() and
edge.faces.item(1).geometry.objectType == adsk.core.Cylinder.classType()):
filletEdges.add(edge)
filletInput.addConstantRadiusEdgeSet(filletEdges, adsk.core.ValueInput.createByReal(filletRad), False)
comp.features.filletFeatures.add(filletInput)
except:
if _ui:
_ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
def run(context):
try:
global _app, _ui
_app = adsk.core.Application.get()
_ui = _app.userInterface
des = adsk.fusion.Design.cast(_app.activeProduct)
if des.designType != adsk.fusion.DesignTypes.DirectDesignType:
_ui.messageBox('The design must be a direct edit design. use the context menu of the top node in the browser and choose "Do not capture Design History".')
return False
root = des.rootComponent
sketches = []
for sk in root.sketches:
if '(Connect)' in sk.name:
sketches.append(sk)
if len(sketches) == 0:
_ui.messageBox('No sketches with "(Connect)" were found.')
return False
connections = calculateConnections(sketches, tolerance)
for i in range(0, len(connections)):
name = "Connector " + str(i)
drawConnector(root, connections, i, name)
des.activateRootComponent()
except:
if _ui:
_ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))