Array between with dynamic preview

The script below allows you to make an array of curves or poly(surfaces) linearly. Where ArrayLinear will make an array where you set the distance between items, this script will distribute the arrayed objects over the distance you pick. Made this mainly for learning purposes, but might be useful for some. Should work on Windows and Mac.

"""
This script allows you array curves, surfaces and polysurfaces, from object
boundingbox center to a picked point, where the new objects will get distributed
between center and picked point. You will see a dynamic preview and can
change the amount while the preview updates.

***********************************
* script written by Gijs de Zwart *
* www.studiogijs.nl               *
* April, 2016                     *
***********************************

"""
import Rhino
import rhinoscriptsyntax as rs
from System.Drawing import Color
import scriptcontext as sc

def ArrayBetween():

    def OnDynamicDraw(sender, e):
        pts=[]
        count=optInt.CurrentValue
        line = Rhino.Geometry.Line(center, e.CurrentPoint)
        curve=line.ToNurbsCurve()
        params=curve.DivideByCount(count-1,True)
        for param in params:
            pts.append(line.PointAt(param))
        vec = e.CurrentPoint - center
        length = vec.Length
        dist=length/(count-1)
        vec.Unitize()

        for i in range(1,count):
            translate = vec * i * dist
            xf = Rhino.Geometry.Transform.Translation(translate)
            newobj=obj.Duplicate()
            newobj.Transform(xf)
            if obj.ObjectType==Rhino.DocObjects.ObjectType.Curve:
                e.Display.DrawCurve(newobj, Color.LightCyan, 2)
            if obj.ObjectType==Rhino.DocObjects.ObjectType.Brep:
                e.Display.DrawBrepWires(newobj, Color.LightCyan)
        e.Display.DrawLine(line, Color.Blue, 2)
        

    def getPoint():
        while True:
            result = gp.Get()
            if result == Rhino.Input.GetResult.Point:
                count=optInt.CurrentValue
                line = Rhino.Geometry.Line(center, gp.Point())
                curve=line.ToNurbsCurve()
                params=curve.DivideByCount(count-1,True)
                pts=[]
                for param in params:
                    pts.append(line.PointAt(param))
                vec = gp.Point() - center
                length = vec.Length
                dist=length/(count-1)
                vec.Unitize()

                for i in range(1,count):
                    translate = vec * i * dist
                    xf = Rhino.Geometry.Transform.Translation(translate)
                    newobj=obj.Duplicate()
                    newobj.Transform(xf)
                    if obj.ObjectType==Rhino.DocObjects.ObjectType.Curve:
                        sc.doc.Objects.AddCurve(newobj)
                    if obj.ObjectType==Rhino.DocObjects.ObjectType.Brep:
                        sc.doc.Objects.AddBrep(newobj)
                sc.doc.Views.Redraw()
            elif result == Rhino.Input.GetResult.Option:
                #go back to point selection mode
                getPoint()
            break


    docobj = rs.GetObject("select object to array", 28)
    if rs.IsCurve(docobj):
        obj=rs.coercecurve(docobj)
    if rs.IsBrep(docobj):
        obj=rs.coercebrep(docobj)
    if not obj: return

    plane=Rhino.Geometry.Plane.WorldXY
    bb=obj.GetBoundingBox(plane)
    if bb==None:
        print "can't calculate boundingbox"
        return
    center = bb.Center
    minimum = bb.Min
    center[2]=minimum[2]
    gp=Rhino.Input.Custom.GetPoint()
    gp.SetCommandPrompt("point to array to / distance")
    optInt=Rhino.Input.Custom.OptionInteger(3,3,999)
    gp.AddOptionInteger("Count",optInt)
    gp.SetBasePoint(center, False)
    gp.DynamicDraw += OnDynamicDraw
    getPoint()
if( __name__ == "__main__" ):
    ArrayBetween()

6 Likes

thank you!

Thanks you for sharing this script
Vittorio

Nice !

Thanks.

glad you like it,

attached is an updated version: now you can change basepoint

Still assumes working in world xy plane:

"""
This script allows you array curves, surfaces and polysurfaces, from object
boundingbox center or corners to a picked point, where the new objects will get
distributed between center or corner and picked point. You will see a dynamic preview
and can change the amount while the preview updates.

***********************************
* script written by Gijs de Zwart *
* www.studiogijs.nl               *
* April, 2016                     *
***********************************

"""
import Rhino
import rhinoscriptsyntax as rs
from System.Drawing import Color
import scriptcontext as sc

def ArrayBetween():

    def OnDynamicDraw(sender, e):
        i=optBasePoint[0]
        pts=[]
        count=optInt.CurrentValue
        if i==0:#center
            basept.X = center.X
            basept.Y = center.Y
        if i==1:#lowerleft
            basept.X = center.X-width/2
            basept.Y = center.Y-depth/2
        if i==2:#upperleft
            basept.X = center.X-width/2
            basept.Y = center.Y+depth/2
        if i==3:#lowerright
            basept.X = center.X+width/2
            basept.Y = center.Y-depth/2
        if i==4:#upperright
            basept.X = center.X+width/2
            basept.Y = center.Y+depth/2
        vec = e.CurrentPoint - basept
        
        
        gp.SetBasePoint(basept, False)
        line = Rhino.Geometry.Line(basept, e.CurrentPoint)
        curve=line.ToNurbsCurve()
        params=curve.DivideByCount(count-1,True)
        for param in params:
            pts.append(line.PointAt(param))
        
        length = vec.Length
        dist=length/(count-1)
        vec.Unitize()

        for i in range(1,count):
            translate = vec * i * dist
            xf = Rhino.Geometry.Transform.Translation(translate)
            newobj=obj.Duplicate()
            newobj.Transform(xf)
            if obj.ObjectType==Rhino.DocObjects.ObjectType.Curve:
                e.Display.DrawCurve(newobj, Color.LightCyan, 2)
            if obj.ObjectType==Rhino.DocObjects.ObjectType.Brep:
                e.Display.DrawBrepWires(newobj, Color.LightCyan)
        e.Display.DrawLine(line, Color.Blue, 2)
        

    def getPoint():
        while True:
            result = gp.Get()
            if result == Rhino.Input.GetResult.Point:
                count=optInt.CurrentValue
                line = Rhino.Geometry.Line(center, gp.Point())
                curve=line.ToNurbsCurve()
                params=curve.DivideByCount(count-1,True)
                pts=[]
                for param in params:
                    pts.append(line.PointAt(param))
                vec = gp.Point() - basept
                length = vec.Length
                dist=length/(count-1)
                vec.Unitize()

                for i in range(1,count):
                    translate = vec * i * dist
                    xf = Rhino.Geometry.Transform.Translation(translate)
                    newobj=obj.Duplicate()
                    newobj.Transform(xf)
                    if obj.ObjectType==Rhino.DocObjects.ObjectType.Curve:
                        sc.doc.Objects.AddCurve(newobj)
                    if obj.ObjectType==Rhino.DocObjects.ObjectType.Brep:
                        sc.doc.Objects.AddBrep(newobj)
                sc.doc.Views.Redraw()
            elif result == Rhino.Input.GetResult.Option:
                optionindex = gp.Option().CurrentListOptionIndex
                optBasePoint[0]=optionindex
                
                getPoint()
            break

    
    docobj = rs.GetObject("select object to array", 28)
    if rs.IsCurve(docobj):
        obj=rs.coercecurve(docobj)
    if rs.IsBrep(docobj):
        obj=rs.coercebrep(docobj)
    if not obj: return

    plane=Rhino.Geometry.Plane.WorldXY
    bb=obj.GetBoundingBox(plane)
    if bb==None:
        print "can't calculate boundingbox"
        return
    center = bb.Center
    
    basept = bb.Center
    
    minimum = bb.Min
    maximum = bb.Max
    
    center.Z=minimum.Z
    width = maximum.X-minimum.X
    depth = maximum.Y-minimum.Y
    
    
    gp=Rhino.Input.Custom.GetPoint()
    gp.SetCommandPrompt("point to array to / distance")
    optInt=Rhino.Input.Custom.OptionInteger(3,3,999)
    gp.AddOptionInteger("Count",optInt)
    basepoints=["center", "lower_left","upper_left","lower_right","upper_right"]
    gp.AddOptionList("basepoint", basepoints, 0)
    optBasePoint=[0]#equal to index 0 of basepoints option
       
    gp.DynamicDraw += OnDynamicDraw
    getPoint()
if( __name__ == "__main__" ):
    ArrayBetween()
2 Likes

Nice work Gijs!

Just a reminder that the Rhino WIP has a new Distribute command.

thanks,

didn’t know about distribute. nice addition :slight_smile:

1 Like

How do I import this scrpit into Rhino?
Is there any possibilitie that when you array objects, it number remains dynamic?
You can change it after?

easiest way is to make a button for it (you can use the attached icon) and make a reference to the script on your harddrive. For example, if you save your scripts in c:\scripts\ Then you can place the following in the button:
noecho -RunPythonScript "c:\scripts\arraybetween.py"

array_between

With this simple script, no. I could improve it though, but it’s not high on my priority list. I made the script long time ago to learn working with display conduits.

So since Rhino still doesn’t have this option in its array toolbar, I had to go searching on this forum.

Weirdly, “distribute” asks me to select three objects, but I just want to array one, so has the command changed in the last 6 years?

Thank you for making the script, it works great! But why did you choose a few hard-coded basepoints instead of just letting the user pick their own start and end points?

I think what Dale was referring to at the time was the fact that you can ArrayLinear your objects. Move the last one in the correct place and then distribute the other objects equally in between the first and the last. That’s how I usually solve this problem.
Distribute doesn’t create new geometry, it just spaces objects an equal distance apart.

That’s funny. So after 6 years, something this simple still requires a user made script? Well, at least users can do scripts to fill gaps in functionality. :laughing:

That was 6 years ago, I don’t have a good explanation other than:

Can you post an example where a different basepoint would get you the result you want?

1 Like

Well, sure but consider this instead: The built-in ArrayLinear asks you for a “first reference point” and a “second reference point” and interactively draws a preview while you are selecting the second one. That’s actually great UX and a good target to make it intuitive for new script users (but I understand that it was outside of the scope of the excercise). :slight_smile:

Hi @Gijs I was using this script (very helpful) but I couldn’t array a Block. I went into the script to try to change the filter=28 (which I couldn’t find in the filter list) to =0 with no luck. Also tried to have it accept preselect=True, but again no luck.

Is there an updated version of this floating around anywhere, or would you have a minute to reply with an idea of how to make those changes?

Thanks as always,

Alan

@Alan_Farkas there is a whole lot more that needs to be done to make this work for block instances. Setting the filter to 4124 (4096+28) is only the first step.

A lot of the code in that script will not work with block instances.

1 Like

Understood, thanks. One quick followup:

What is 28, and where would I find that in the python API?

Thx

the info is in rs.GetObjects():

GetObjects(message=None, filter=0, group=True, preselect=False, select=False, objects=None, minimum_count=1, maximum_count=0, custom_filter=None) |      Prompts user to pick or select one or more objects.
 |          Parameters:
 |            message (str, optional): a prompt or message.
 |            filter (number, optional): The type(s) of geometry (points, curves, surfaces, meshes,...)
 |                that can be selected. Object types can be added together to filter
 |                several different kinds of geometry. use the filter class to get values
 |                    Value         Description
 |                    0             All objects (default)
 |                    1             Point
 |                    2             Point cloud
 |                    4             Curve
 |                    8             Surface or single-face brep
 |                    16            Polysurface or multiple-face
 |                    32            Mesh
 |                    256           Light
 |                    512           Annotation
 |                    4096          Instance or block reference
 |                    8192          Text dot object
 |                    16384         Grip object
 |                    32768         Detail
 |                    65536         Hatch
 |                    131072        Morph control
 |                    262144        SubD
 |                    134217728     Cage
 |                    268435456     Phantom
 |                    536870912     Clipping plane
 |                    1073741824    Extrusion

and these numbers can be added up. 28 = 4 + 8 + 16
Those were the only object types I handled in the script.

Ohh, I didn’t know you could add them up :nerd_face:

Thx