OK, here's a script that constructs the planes and the Surface Loft in one go. I can't get it to do the final Sweep yet - fighting with the API (https://forums.autodesk.com/t5/fusion-design-validate-document/minimal-working-example-of-creating-a...) - but it's fairly usable as is.

One thing I can't figure out is that Fusion seems to concentrate the wiggliness of the Sweep differently to the guide surface. For instance given this curve:

The body created has all the torsion concentrated in the middle:

So it doesn't really seem to be following the surface properly. But at least for gentle curves, it works relatively sensibly.
Next step, I might look into constructing the lines at the 'correct' angle so that they are in the plane of the splines instantaneous curvature.
I can't attach a .py file, so here's the full script
# Fusion 360 Script: Planes + Sketches + Perpendicular Lines along a 3D spline
#
# Basic Script Command pattern (see Fusion Commands doc):
# https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-3922697A-7BF1-4799-9A5B-C8539DF57051
#
# How to use:
# - UTILITIES > Add-Ins > Scripts and Add-Ins... > Scripts tab > + (add this .py) > Run
# - A single command dialog appears: select spline, set options, click OK.
import adsk.core
import adsk.fusion
import traceback
_app = None
_ui = None
# Keep event handlers in scope (required for Python scripts using commands)
handlers = []
CMD_ID = "SplinePlanesLoftCmd"
def _do_create_planes_and_lines(design, sketch_curve, plane_count, line_len_cm):
"""Creates planes, sketches, and lines along the spline. Returns (created_count, list_of_lines)."""
root_comp = design.rootComponent
path = root_comp.features.createPath(sketch_curve, False)
if plane_count == 1:
proportions = [0.5]
else:
# Always include endpoints: 0, 1/(n-1), ..., 1
proportions = [i / (plane_count - 1) for i in range(plane_count)]
planes = root_comp.constructionPlanes
sketches = root_comp.sketches
created_lines = []
created = 0
for t in proportions:
plane_input = planes.createInput()
plane_input.setByDistanceOnPath(path, adsk.core.ValueInput.createByReal(t))
cplane = planes.add(plane_input)
sk = sketches.add(cplane)
start_pt = adsk.core.Point3D.create(0, 0, 0)
end_pt = adsk.core.Point3D.create(line_len_cm, 0, 0)
line = sk.sketchCurves.sketchLines.addByTwoPoints(start_pt, end_pt)
line.startSketchPoint.isFixed = True
text_pt = adsk.core.Point3D.create(line_len_cm / 2, 0, 0)
length_dim = sk.sketchDimensions.addDistanceDimension(
line.startSketchPoint,
line.endSketchPoint,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
text_pt,
True
)
length_dim.value = line_len_cm
created_lines.append(line)
created += 1
return created, created_lines
def _create_loft_surface(root_comp, sketch_curve, line_list):
"""
Creates a Surface Loft (isSolid=False) using the given lines as sections
and sketch_curve as guide rail.
"""
loft_feats = root_comp.features.loftFeatures
loft_input = loft_feats.createInput(
adsk.fusion.FeatureOperations.NewBodyFeatureOperation
)
# Surface loft: isSolid=False creates a surface body, not a solid
loft_input.isSolid = False
loft_input.isClosed = False
loft_input.startLoftEdgeAlignment = (
adsk.fusion.LoftEdgeAlignments.FreeEdgesLoftEdgeAlignment
)
loft_input.endLoftEdgeAlignment = (
adsk.fusion.LoftEdgeAlignments.FreeEdgesLoftEdgeAlignment
)
sections = loft_input.loftSections
for line in line_list:
sections.add(line)
# Add spline as guide rail (per Fusion API: LoftCenterLineOrRails.addRail)
if hasattr(loft_input, "centerLineOrRails"):
loft_input.centerLineOrRails.addRail(sketch_curve)
return loft_feats.add(loft_input)
# --- Command event handlers (Basic Script Command pattern) ---
class SplinePlanesCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
event_args = adsk.core.CommandCreatedEventArgs.cast(args)
cmd = event_args.command
inputs = cmd.commandInputs
app = adsk.core.Application.get()
design = adsk.fusion.Design.cast(app.activeProduct)
if not design:
return
inputs.addSelectionInput(
"spline",
"Spline",
"Select a 3D spline (sketch curve)"
)
sel_in = inputs.itemById("spline")
sel_in.addSelectionFilter("SketchCurves")
sel_in.setSelectionLimits(1, 1)
inputs.addDistanceValueCommandInput(
"normal_line_len",
"Line length",
adsk.core.ValueInput.createByReal(1)
)
inputs.addIntegerSpinnerCommandInput(
"planes",
"Number of planes",
1, 200, 1, 10
)
on_execute = SplinePlanesCommandExecuteHandler()
cmd.execute.add(on_execute)
handlers.append(on_execute)
class SplinePlanesCommandExecuteHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
event_args = adsk.core.CommandEventArgs.cast(args)
app = adsk.core.Application.get()
ui = app.userInterface
try:
design = adsk.fusion.Design.cast(app.activeProduct)
if not design:
ui.messageBox("No active design.")
adsk.terminate()
return
root_comp = design.rootComponent
cmd_inputs = event_args.command.commandInputs
sel_in = cmd_inputs.itemById("spline")
if sel_in.selectionCount < 1:
ui.messageBox("Please select a spline.")
adsk.terminate()
return
entity = sel_in.selection(0).entity
sketch_curve = adsk.fusion.SketchCurve.cast(entity)
if not sketch_curve:
ui.messageBox("Selection must be a sketch curve (spline).")
adsk.terminate()
return
plane_count = int(cmd_inputs.itemById("planes").value)
# DistanceValueCommandInput.value is in internal units (cm)
line_len_cm = cmd_inputs.itemById("normal_line_len").value
created, line_list = _do_create_planes_and_lines(
design, sketch_curve, plane_count, line_len_cm
)
_create_loft_surface(root_comp, sketch_curve, line_list)
ui.messageBox(
"Done.\nCreated {} planes + sketches + lines and surface loft.".format(created)
)
except Exception:
ui.messageBox("Failed:\n{}".format(traceback.format_exc()))
finally:
adsk.terminate()
def run(context):
global _app, _ui
_app = adsk.core.Application.get()
_ui = _app.userInterface
try:
design = adsk.fusion.Design.cast(_app.activeProduct)
if not design:
_ui.messageBox("No active Fusion design. Please open a design and try again.")
return
cmd_defs = _ui.commandDefinitions
cmd_def = cmd_defs.itemById(CMD_ID)
if cmd_def:
cmd_def.deleteMe()
cmd_def = cmd_defs.addButtonDefinition(
CMD_ID,
"Planes + Loft along spline",
"Create planes, perpendicular lines, and surface loft along a 3D spline"
)
created_handler = SplinePlanesCommandCreatedHandler()
cmd_def.commandCreated.add(created_handler)
handlers.append(created_handler)
cmd_def.execute()
adsk.autoTerminate(False)
except Exception:
if _ui:
_ui.messageBox("Failed:\n{}".format(traceback.format_exc()))
def stop(context):
try:
app = adsk.core.Application.get()
ui = app.userInterface
cmd_def = ui.commandDefinitions.itemById(CMD_ID)
if cmd_def:
cmd_def.deleteMe()
except Exception:
pass