Extend Multi Sides with Preview (Python)

Hi everyone!

I’ve been meaning to create this Python script for Rhino for a long time, and I finally made it happen. It lets you extend surface edges quickly and easily, featuring a live preview so you can see the changes before applying them.

You just select the edges, set the extension length and step, and the surfaces update immediately. The script remembers your last settings to make repeated use smoother.

Hope this tool makes your workflow a bit easier. Give it a try!

"""
Extend Multi Sides for Rhino
Author: leex
Date: 2025-06-01

This script allows you to extend surface edges with live preview and adjustable step.
Select edges, set extension length, preview, and apply.
The script remembers your last used settings, which are saved to your system TEMP folder as ExtendMultiSides_last_value.txt.

Enjoy!
"""



import Rhino
import scriptcontext as sc
import System.Drawing
import os
import tempfile

def get_settings_path():
    tempdir = tempfile.gettempdir()
    return os.path.join(tempdir, "ExtendMultiSides_last_value.txt")

def save_last_value(val):
    path = get_settings_path()
    try:
        with open(path, "w") as f:
            f.write(str(val))
    except:
        pass

def load_last_value(default=2.01):
    path = get_settings_path()
    try:
        if os.path.exists(path):
            with open(path, "r") as f:
                return float(f.read())
    except:
        pass
    return default

def edge_has_only_seam_trims(edge, brep):
    trims = edge.TrimIndices()
    if not trims:
        return False
    for trim_index in trims:
        trim = brep.Trims[trim_index]
        if trim.TrimType != Rhino.Geometry.BrepTrimType.Seam:
            return False
    return True

def is_periodic_edge(edge, brep):
    return edge_has_only_seam_trims(edge, brep)

def get_previews(objs_faces, dist):
    preview_ids = []
    if hasattr(get_previews, "preview_ids"):
        for pid in get_previews.preview_ids:
            sc.doc.Objects.Delete(pid, True)
    for obj_id, data in objs_faces.items():
        brep = data["brep"]
        faces_dict = data["faces"]
        for face_index, isos in faces_dict.items():
            face = brep.Faces[face_index]
            ext_face = face
            for iso in isos:
                ext_face = ext_face.Extend(iso, dist, True) or ext_face
            pid = sc.doc.Objects.AddBrep(ext_face.ToBrep())
            rhobj = sc.doc.Objects.Find(pid)
            if rhobj:
                rhobj.Attributes.ObjectColor = System.Drawing.Color.FromArgb(120, 80, 220, 80)
                rhobj.Attributes.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject
                rhobj.CommitChanges()
            preview_ids.append(pid)
    sc.doc.Views.Redraw()
    get_previews.preview_ids = preview_ids
    return preview_ids

def remove_previews():
    if hasattr(get_previews, "preview_ids"):
        for pid in get_previews.preview_ids:
            sc.doc.Objects.Delete(pid, True)
        get_previews.preview_ids = []

def ExtendMultiSides():
    go = Rhino.Input.Custom.GetObject()
    go.SetCommandPrompt("Select edges to extend")
    go.GeometryFilter = Rhino.DocObjects.ObjectType.EdgeFilter
    go.SubObjectSelect = True
    go.EnablePreSelect(True, True)
    go.GetMultiple(1, 0)
    if go.ObjectCount == 0:
        return

    objs_faces = {}
    for i in range(go.ObjectCount):
        objref = go.Object(i)
        obj_id = objref.ObjectId
        brep = objref.Brep()
        ci = objref.GeometryComponentIndex
        trim = None
        edge = None
        if str(ci.ComponentIndexType) == "BrepEdge":
            edge = brep.Edges[ci.Index]
            trim = edge.Trims[0] if edge.Trims else None
        elif str(ci.ComponentIndexType) == "BrepTrim":
            trim = brep.Trims[ci.Index]
            edge = trim.Edge
        if not trim or not edge:
            continue
        if is_periodic_edge(edge, brep):
            continue
        face = trim.Face
        face_index = face.FaceIndex
        if obj_id not in objs_faces:
            objs_faces[obj_id] = {"brep": brep, "faces": {}}
        if face_index not in objs_faces[obj_id]["faces"]:
            objs_faces[obj_id]["faces"][face_index] = set()
        objs_faces[obj_id]["faces"][face_index].add(trim.IsoStatus)

    dist = load_last_value(default=2.01)
    step = 0.25

    get_previews(objs_faces, dist)
    set_length = True

    done = False
    while not done:
        gn = Rhino.Input.Custom.GetNumber()
        gn.SetCommandPrompt("Enter extension length (Press Esc when done). Step: {:.2f}".format(step))
        less_option = gn.AddOption("Less")
        more_option = gn.AddOption("More")
        step_option = gn.AddOption("SetStep")
        done_option = gn.AddOption("Done")
        gn.SetDefaultNumber(dist)
        gn.SetLowerLimit(0.01, True)
        result = gn.Get()
        if result == Rhino.Input.GetResult.Option:
            idx = gn.Option().Index
            if idx == less_option:
                dist = max(dist - step, 0.01)
                set_length = True
                get_previews(objs_faces, dist)
                continue
            elif idx == more_option:
                dist = dist + step
                set_length = True
                get_previews(objs_faces, dist)
                continue
            elif idx == step_option:
                gn_step = Rhino.Input.Custom.GetNumber()
                gn_step.SetCommandPrompt("Enter new step value")
                gn_step.SetDefaultNumber(step)
                gn_step.SetLowerLimit(0.001, True)
                step_result = gn_step.Get()
                if step_result == Rhino.Input.GetResult.Number:
                    step = gn_step.Number()
                continue
            elif idx == done_option:
                done = True
                break
        elif result == Rhino.Input.GetResult.Number:
            dist = gn.Number()
            set_length = True
            get_previews(objs_faces, dist)
            continue
        else:
            break

    remove_previews()

    if set_length:
        for obj_id, data in objs_faces.items():
            brep = data["brep"]
            faces_dict = data["faces"]
            new_breps = []
            for i, face in enumerate(brep.Faces):
                if i in faces_dict:
                    ext_face = face
                    extending_isos = set(faces_dict[i])
                    for iso in extending_isos:
                        ext_face = ext_face.Extend(iso, dist, True) or ext_face
                    new_breps.append(ext_face.ToBrep())
                else:
                    dupl = face.DuplicateFace(False)
                    if dupl:
                        new_breps.append(dupl)
            joined = Rhino.Geometry.Brep.JoinBreps(new_breps, sc.doc.ModelAbsoluteTolerance)
            sc.doc.Objects.Delete(obj_id, True)
            if joined and len(joined) == 1:
                sc.doc.Objects.AddBrep(joined[0])
            elif joined:
                for jb in joined:
                    sc.doc.Objects.AddBrep(jb)
        sc.doc.Views.Redraw()
        save_last_value(dist)

if __name__ == "__main__":
    ExtendMultiSides()

8 Likes

A video example would be nice.Good and Successful.