New Revision Cloud(s)

Revit:

  • 15+ Years
  • $2,500+ / year
  • Who knows how many developers.

It’s basically just the same two arcs repeating over and over again. It’s hokey.

Me (and Rhino 3D and Grok AI):

  • 2 hours
  • 2 glasses of wine
  • $1,000 (+/-) perpetual license
  • $30/month for ‘Super Grok’ - but I can shut that down whenever I want.

It’s everything I want a revision cloud to be!! Random. Bold (similar to AutoCAD’s calligraphy style). I eventually spent more than two hours (various tweaks + the pline version)… but common!! Given how much revisions Revit drawings need you’d think they’d make the revision clouds easier on the eyes.

Rhino’s built-in revision clouds are pretty okay. The command is begging for some scripting however. And even with a simply Rhino script you can make it much much better. I may end up doing that if this approach proves troublesome.

A native Rhino revision cloud (a command script/alias is your friend!!):

There’s a few catches:

  • It’s mostly AI code and I haven’t completely deciphered it yet. It can likely be streamlined quite a bit.
  • The code will crash if you don’t have my block/layer in your drawing. You may want to adjust the layering to suite you.
  • It might slow down as the drawing file gets bigger due to it iterating through each block multiple times. I’m teaching myself how to work with XForms and had to do the transforms 1 by 1 to make sure they were doing the right thing.

All these issues are completely solvable. In the short term I might just use a combination of a script and a command alias using the existing REVCLOUD command - even that will be much better than Revit. Like a lot of my code, the ideal usage is a delete/replace rather than trying to edit the existing object - the code will group everything to make this easier. It would be nice if the rectangle/pline were chosen via a command line option… and maybe add to that a sticky variable to remember the user’s preference. I also want to eventually convert everything to RhinoCommon.

Revision Cloud Segment.3dm (41.5 KB)

# - ALMOST DONE - REVISION CLOUD PLINE.py (5.3 KB)

# - ALMOST DONE - REVISION CLOUD RECTANGLE.py (3.7 KB)

Here’s the ‘rectangle’ version (to save you the hassle of having to d/l the file to view it). Critique/suggestions & improvements are all welcome as I’m still deciphering a lot of this:

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino
import random
import math

# -------------------------------------------------------------
# Tiny helper: applies only the Y-scale in the block's LOCAL direction
# -------------------------------------------------------------
def apply_local_y_scale(block_id, direction_angle_deg, y_variation):
    """
    block_id           : GUID of the inserted block instance
    direction_angle_deg: angle of the edge (from world X-axis)
    y_variation        : +/- 2.5 units → converted to scale factor
    """
    obj = sc.doc.Objects.FindId(block_id)
    if not obj or not isinstance(obj, Rhino.DocObjects.InstanceObject):
        return False

    # Base point = insertion point
    base_pt = obj.InsertionPoint

    # Local Y direction = rotated 90 degrees from travel direction
    # We want outward bumps → rotate +90° from direction (CCW)
    local_y_angle = direction_angle_deg + 90.0
    rad = math.radians(local_y_angle)
    dir_y = Rhino.Geometry.Vector3d(math.cos(rad), math.sin(rad), 0)

    # Scale factor for Y
    y_scale = (10.0 + y_variation) / 10.0

    # Non-uniform scale: X=1, Y=y_scale, Z=1, anchored at base point
    plane = Rhino.Geometry.Plane(base_pt, dir_y, Rhino.Geometry.Vector3d.ZAxis)
    xform = Rhino.Geometry.Transform.Scale(plane, 1.0, y_scale, 1.0)

    return sc.doc.Objects.Transform(block_id, xform, True)


# -------------------------------------------------------------
# Main function — back to the clean, working version
# -------------------------------------------------------------
def create_custom_revision_cloud():
    pts = rs.GetRectangle()
    if not pts or len(pts) != 4:
        print("Failed to pick rectangle.")
        return

    # Clockwise starting from top-left: TL → TR → BR → BL
    vertices = [pts[3], pts[2], pts[1], pts[0]]

    block_name = "X-RevCloud Segment-1"
    if not rs.IsBlock(block_name):
        print("Block '{}' not found.".format(block_name))
        return

    inserted = []

    for i in range(4):
        start = vertices[i]
        end   = vertices[(i + 1) % 4]

        vec    = rs.VectorCreate(end, start)
        length = rs.Distance(start, end)
        if length < 0.001: continue

        seg_count = max(1, int(length / 10.0 + 0.5))
        spacings = [10 + random.uniform(-5, 5) for _ in range(seg_count)]
        spacings = [s * (length / sum(spacings)) for s in spacings]

        unit_vec = rs.VectorUnitize(vec)
        angle_deg = math.degrees(math.atan2(unit_vec[1], unit_vec[0]))

        cum = 0.0
        for spacing in spacings:
            pos = rs.PointAdd(start, rs.VectorScale(unit_vec, cum))

            uniform_scale = spacing / 10.0
            scale_tuple = (uniform_scale, uniform_scale, uniform_scale)

            blk_id = rs.InsertBlock(
                block_name,
                pos,
                scale=scale_tuple,
                angle_degrees=angle_deg
            )
            if not blk_id:
                cum += spacing
                continue

            # Now apply the independent LOCAL Y-scale
            y_var = random.uniform(-2.5, 2.5)
            apply_local_y_scale(blk_id, angle_deg, y_var)

            inserted.append(blk_id)
            cum += spacing

    # Group
    if inserted:
        if rs.IsLayer("01-Anno-Markups"):
            rs.ObjectLayer(inserted, "01-Anno-Markups")
        elif rs.IsLayer("01-Anno::01-Anno-Markups"):
            rs.ObjectLayer(inserted, "01-Anno::01-Anno-Markups")
        
        rs.AddObjectsToGroup(inserted, rs.AddGroup())
        print("Custom revision cloud complete — {} segments".format(len(inserted)))
    sc.doc.Views.Redraw()

# -------------------------------------------------------------
if __name__ == "__main__":
    create_custom_revision_cloud()