How to Loft Between Circles in GH-Python

Hi everyone,
I am trying to create a lofted surface between 4 circles using GH-Python in Grasshopper. However, instead of a single continuous loft between the circles, the script is currently creating individual lofts for each circle, as shown in the first image.
I would like to achieve a result similar to the second image, where all the circles are smoothly connected into a single lofted surface.


Here is my code:

import Rhino
import Rhino.Geometry as rg
import System
import System.Collections.Generic as sg
import ghpythonlib.treehelpers as th  # ✅ Convert Data Tree to Python List

# Convert DataTree to list of point lists
points_list = th.tree_to_list(points_list)
print(f"🔍 Number of point lists: {len(points_list)}")

# Create curves from point lists
curves = []
for i, points in enumerate(points_list):
    real_points = []

    for p in points:
        if isinstance(p, rg.Curve):  
            print(f"✅ List {i} is already a curve - keeping it.")
            curves.append(p)
            break  

        if isinstance(p, rg.Point3d):
            real_points.append(p)
        elif isinstance(p, System.Guid):
            obj = Rhino.RhinoDoc.ActiveDoc.Objects.Find(p)
            if obj and hasattr(obj.Geometry, "Location"):
                real_points.append(obj.Geometry.Location)
            elif isinstance(obj.Geometry, rg.Point):
                real_points.append(obj.Geometry.Location)

    print(f"🔍 List {i}: {len(real_points)} points converted.")

    if len(real_points) < 3:
        print(f"⚠️ Skipping - List {i} is too short for a circle.")
        continue  

    if real_points[0].DistanceTo(real_points[-1]) > 0.01:
        real_points.append(real_points[0])  

    point_list = sg.List[rg.Point3d]()
    for p in real_points:
        point_list.Add(rg.Point3d(p.X, p.Y, p.Z))

    nurbs_curve = rg.PolylineCurve(point_list)
    curves.append(nurbs_curve)

print(f"✅ {len(curves)} curves created.")

if len(curves) < 2:
    raise ValueError(f"❌ Only {len(curves)} curves found. Need at least 2 for Loft!")

# Move curves to different heights
moved_curves = []
for i, curve in enumerate(curves):
    translation = rg.Transform.Translation(0, 0, i * height)
    moved_curve = curve.DuplicateCurve()
    moved_curve.Transform(translation)
    moved_curves.append(moved_curve)

print("✅ All circles moved to their respective heights!")

# Ensure all curves are in a single list
flat_curves = list(moved_curves)
print(f"🔍 Number of curves in flat_curves: {len(flat_curves)}")
for i, curve in enumerate(flat_curves):
    print(f"  - Curve {i}: Type {type(curve).__name__}, Start Point {curve.PointAtStart}, Length {curve.GetLength()}")

# **Perform Loft**
loft = rg.Brep.CreateFromLoft(
    flat_curves,
    rg.Point3d.Unset,
    rg.Point3d.Unset,
    rg.LoftType.Normal,  # Try switching between Tight / Loose / Normal
    False

if loft and len(loft) > 0:
    loft_surface = loft[0]
    print("✅ Loft surface successfully created!")
else:
    loft_surface = None
    print("❌ Loft failed! Check if curves are correct.")

# Output
a = loft_surface  # Lofted surface
b = moved_curves  # Transformed circles

Python - omer livni, eden maman.gh (43.9 KB)

Thanks in advance for any insights! :blush:

A) Please post the relevant file(s).
B) Please format the Python code.

1 Like