Extrude from an open and non-planar Polysurface

Hi guys!
I wrote this macro for Extrusion.

I almost completely automated the process. Except for _Move
What can be added in order for the “extrusion cap” to automatically moving to the “extrusion sides”?

_Select
_Pause
-Properties _Object _Color _Object 100,170,230 _Name ExtrudeCap _EnterEnd
_Pause
_DupBorder
_Pause
_ExtrudeCrv _Pause _Solid=Yes _DeleteInput=Yes
_Pause
_SelLast
-Properties _Object _Color _Object 100,170,230 _Name ExtrudeSides _EnterEnd
_SelNone
-SelName ExtrudeCap
_Copy _Pause _InPlace
_Sellast
_Move
_Pause
_Pause
_Pause
_SelColor
_Join
-Properties _Object _Color _Layer _Name ExtrudePolySrf _EnterEnd
_SelNone

Hi Leex,

This is almost good, it has a few glitches:
1 - It ditches the color the input polysurface had already
2 - It ditches the name the polysurface had already
3 - It also changes color/name to other things around

See here:

4 - It does not give you a pause to input a direction option if you want other than Cplane Normal.:

I’m bringing all thsi up because it would be great to have a well working one of this. I also have a macro for this but it only gets me half-way there. It still I find it unbelievable that ExtrudeSRF does not have an option to extrude as a single solid, @pascal, could you lobby to have an _extrudejoined option here in ExtrudeSrf? Right now in V6 the command it doing it wrong in two ways:

  • it gives you a false preview of walls between surface seams that it actually does not build.
  • it ditches the base polysurface of extrusion.

Thanks!

G

Hi Gustavo - Gumball extrusion seems to get it right on a polysurface, so it should be possible in the command as well.
https://mcneel.myjetbrains.com/youtrack/issue/RH-38909

thanks,

-Pascal

I’m mad at gumball, it scales things wrong so I’m boycotting it.

Hi Gustavo,

If the macro works correctly to the end, then in the current scene around should be formed only one color of the current layer . And only one name (ExtrudePolySrf)
I do not think that before the start of the macro in your scene there will be objects with the names ExtrudeCap and color 100,170,230
Although everything is possible :wink:

Here is a macro that pauses to select the direction of extrusion

_Select _Pause -Properties _Object _Color _Object 100,170,230 _Name ExtrudeCap _EnterEnd _Pause _DupBorder _Pause _ExtrudeCrv _Pause _Solid=Yes _DeleteInput=Yes _Pause _Pause _Pause _Pause _SelLast -Properties _Object _Color _Object 100,170,230 _Name ExtrudeSides _EnterEnd _SelNone -SelName ExtrudeCap _Copy _Pause _InPlace _Sellast _Move _Pause _Pause _Pause _SelColor _Join -Properties _Object _Color _Layer _Name ExtrudePolySrf _EnterEnd _SelNone

Hi Pascal,
Is it possible to “undo” all commands in macros at once?

Hi Leex - no - when it gets this complex probably scripting becomes worth the effort.

-Pascal

I slightly improved the macro.
To accurately _ExtrudeCrv and _Move the “ExtrudeCap”, need to enable _Osnap.

_Select _Pause -Properties _Object _Color _Object 100,170,230 _Name ExtrudeCap _EnterEnd _Pause _DupBorder -Osnap _Endpoint=On _Near=On Midpoint=On _Perpendicular=On _Enter _Enter _Pause _ExtrudeCrv _Pause _Solid=Yes _DeleteInput=Yes _Pause _Pause _Pause _Pause _SelLast -Properties _Object _Color _Object 100,170,230 _Name ExtrudeSides _EnterEnd _SelNone -SelName ExtrudeCap _Copy _Pause _InPlace _Sellast _Move _Pause _Pause _Pause _SelColor _Join -Properties _Object _Color _Layer _Name ExtrudePolySrf _EnterEnd _SelNone

Took me almost a decade after that thread, but I finally got around to writing this script. Maybe it’ll be useful to someone — just finally found the time to do it.

_-RunPythonScript (
"""
ExtrudePolySurface

Author: leex
Date: 15.11.2025
Rhino: 8

Description:
Creates extruded solid bodies from selected surfaces and polysurfaces

Features:
- Extrude multiple objects at once
- Add top and bottom caps
- Preserve names, colors, and layers of the original objects
- Interactive selection of extrusion direction
- Works with surfaces, polysurfaces, planar and freeform geometry
"""

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino
import time

def get_bbox_center(obj_id):
    robj = sc.doc.Objects.Find(obj_id)
    if not robj: return None
    geo = robj.Geometry
    bb = geo.GetBoundingBox(True)
    return bb.Center

def to_vec_tuple(v):
    return (v.X, v.Y, v.Z)

def is_planar_surface(obj_id):
    surface = rs.coercesurface(obj_id)
    if not surface: return False
    return surface.IsPlanar()

def get_plane_normal(obj_id):
    surface = rs.coercesurface(obj_id)
    if not surface: return None
    if surface.IsPlanar():
        success, plane = surface.TryGetPlane()
        if success:
            return plane.ZAxis
    return None

def main():
    objs = rs.GetObjects("Select surface(s)/polysurface(s)", rs.filter.surface | rs.filter.polysurface, preselect=True)
    if not objs:
        print("Nothing selected.")
        return

    original_colors = {obj: rs.ObjectColor(obj) for obj in objs}
    original_layers = {obj: rs.ObjectLayer(obj) for obj in objs}
    temp_objs = []

    planar_objects = [obj for obj in objs if is_planar_surface(obj)]
    has_planar = len(planar_objects) > 0
    only_planar = len(planar_objects) == len(objs)
    planar_normal = get_plane_normal(planar_objects[0]) if has_planar else None

    prev_redraw = sc.doc.Views.RedrawEnabled
    sc.doc.Views.RedrawEnabled = False

    try:
        rs.UnselectAllObjects()
        rs.SelectObjects(objs)
        pre_all = set(rs.AllObjects() or [])

        rs.Command("_-DupBorder")
        time.sleep(0.05)

        post_dup = set(rs.AllObjects() or [])
        dup_curves = [g for g in post_dup - pre_all if rs.IsCurve(g)]
        if not dup_curves:
            print("No border curves created.")
            return
        temp_objs.extend(dup_curves)

        curve_centers = [get_bbox_center(c) for c in dup_curves if get_bbox_center(c)]
        if not curve_centers:
            print("Could not calculate curve centers.")
            return

        avg_curve_center = Rhino.Geometry.Point3d(
            sum([c.X for c in curve_centers])/len(curve_centers),
            sum([c.Y for c in curve_centers])/len(curve_centers),
            sum([c.Z for c in curve_centers])/len(curve_centers)
        )

        sc.doc.Views.RedrawEnabled = True
        rs.UnselectAllObjects()
        rs.SelectObjects(dup_curves)

        if only_planar and planar_normal:
            print("Using normal for extrusion...")
            cmd = "! _ExtrudeCrv _Solid=No _Direction {} {} {}".format(planar_normal.X, planar_normal.Y, planar_normal.Z)
            rs.Command(cmd)
        else:
            print("Specify ExtrudeCrv direction now (number or click)...")
            rs.Command("_-ExtrudeCrv _Solid=No")

        sc.doc.Views.RedrawEnabled = False

        post_extrude = set(rs.AllObjects() or [])
        new_extrusions = [g for g in post_extrude - pre_all if sc.doc.Objects.Find(g) and (rs.IsBrep(g) or rs.IsSurface(g))]
        if not new_extrusions:
            print("Extrusion failed.")
            return
        temp_objs.extend(new_extrusions)

        for e in new_extrusions:
            if rs.IsObject(e):
                rs.ObjectColor(e, original_colors[objs[0]])
                rs.ObjectLayer(e, original_layers[objs[0]])

        centers_after = [get_bbox_center(g) for g in new_extrusions if get_bbox_center(g)]
        if not centers_after:
            print("Could not calculate extrusion centers.")
            return

        avg_extr_center = Rhino.Geometry.Point3d(
            sum([c.X for c in centers_after])/len(centers_after),
            sum([c.Y for c in centers_after])/len(centers_after),
            sum([c.Z for c in centers_after])/len(centers_after)
        )
        vector = avg_extr_center - avg_curve_center
        full_vector = vector * 2
        vec_tuple = to_vec_tuple(full_vector)

        top_copies = rs.CopyObjects(objs)
        if top_copies:
            rs.MoveObjects(top_copies, vec_tuple)
            temp_objs.extend(top_copies)
            for t in top_copies:
                if rs.IsObject(t):
                    rs.ObjectColor(t, original_colors[objs[0]])
                    rs.ObjectLayer(t, original_layers[objs[0]])
        else:
            top_copies = []

        if only_planar:
            join_list = new_extrusions + top_copies
        else:
            join_list = new_extrusions + list(objs) + top_copies

        valid_join_list = [obj for obj in join_list if rs.IsObject(obj)]
        rs.UnselectAllObjects()
        if len(valid_join_list) >= 2:
            rs.SelectObjects(valid_join_list)
            join_result = rs.Command("_-Join")
            if join_result:
                print("Successfully joined all surfaces into a solid!")
            else:
                print("Join failed, surfaces created but not unified.")
        else:
            print("Not enough objects to join. Found: {}".format(len(valid_join_list)))

        for c in dup_curves:
            if rs.IsObject(c):
                rs.DeleteObject(c)
                if c in temp_objs:
                    temp_objs.remove(c)
        print("Done. Solid with top and bottom surfaces, colors/layer preserved.")

    finally:
        for obj in temp_objs:
            if rs.IsObject(obj):
                try: rs.DeleteObject(obj)
                except: pass
        sc.doc.Views.RedrawEnabled = prev_redraw
        sc.doc.Views.Redraw()
        rs.UnselectAllObjects()

main()
)