Alignment objects at the same distance from each other


Hi all.
This is the UV Curve of cylinder, which has a 60 mm in length.
In this example, there are three united groups inside the bounding boxes.
These groups need to be distributed at the same distance from each other.

In manual I make calculations as follows:

I use the addition operation, length of each BB group together (10.84 + 13.19 + 20.97 = 45), then the resulting value subtract from the length of UV curve (60 - 45 = 15).

The resulting difference (15) is divided into the number of objects or groups (3) 15/3 = 5
This value (5) will be the same distance between objects.
One of the distances (5) divide into 2. 5/2 = 2.5. The resulting value (2.5) will be the distance from the left and right sides of UV curve.

Can someone help write a script that would make these calculations and move objects automatically?

Thanks in advance.

Don’t forget to calculate the height of the text too, to keep the same aspect ratio, to preserve the design’s look. To cram 19 capital letters into 60mm of length, I’m guessing each must be 2-3mm high, for the result to be legible (from close up).

It’s well worth checking nothing that does this already exists. It’s straightforward to produce a simple script. The padding calculation itself is only 1 line.

Will the text direction always be aligned with the x-axis?
Are the text objects always going to neatly start from the left edge?
Is the user going to select the text objects and containing box, or can the script work on all the text boxes and containers it finds?

It all depends how you’d like to run it too, e.g. from Rhinoscript or Grasshopper.

Hi James -
This is a small part of the jewelry ring, just the area of ​​the words.
As a rule, I make UV Crv. And I use flat elements in the group or separate curves.
It can be letters, markup for precious stones or seamless ornament.
Usually, the UV Crv is located the long side along the X axis.
I’m not interested in vertical distances, it is always handmade.

In the script I would like any objects that are grouped to be perceived as a single object.
There is no difference for me Rhinoscript or Python. But wouldn’t want from Grasshopper.

There should be the same distance on the left and right sides. Exactly half of the calculated length.

Does it help?

Maybe this pseudo code helps (not guaranteed error free):

#pseudo code:
words = [<objects>]
total_length = <float>
word_lengths = [<float>]

words_length =0
for wl in word_lengths:
    words_length += wl

remainder = total_length-words_length
am = len(words)
increment = remainder / am
pos_x = increment/2
for i, word in enumerate(words):
    if i = am-1:
        break
    word.xpos  = pos_x
    pox_x += increment
    pos_x += word_lengths[i]

Hi Gijs, and thank you for your help.
I try and try, but I can’t understand what I’m doing)
I’m getting multiple errors.



import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc

def AlignObjs():
    frame=rs.GetObjects("Select frame")
    objects=rs.GetObjects("Select objects to align")
    
    
    total_length = frame
    words_length = objects
    
    words_length =0
    for wl in words_length:
        words_length += wl

    remainder = total_length-words_length
    am = len(words)
    increment = remainder / am
    pos_x = increment/2
    for i, word in enumerate(words):
       if i == am-1:
           break
    word.xpos = pos_x
    pox_x += increment
    pos_x += word_lengths[i]
AlignObjs()






I didn’t try your code, but to check for equality you need to use == (2 characters).

1 equal sign (=) is the assignment operator
2 equal signs (==) is the equality operator

-Kevin

1 Like

Thank you Kevin.
Next error now.
How to solve it?

Backspace so the beginning of line 25 matches the vertical line shown on lines 24 & 26 (need to do same on following lines).

-Kevin

1 Like

(post deleted by author)

I think this does what you’re looking for.

import rhinoscriptsyntax as rs

def AlignObjs():
    frame = rs.GetObjects("Select frame")
    frame_bbox = rs.BoundingBox(frame)
    frame_left = frame_bbox[0][0]
    frame_width = abs(frame_bbox[0][0] - frame_bbox[1][0])
    
    objects = rs.GetObjects("Select objects to align")
    bboxes = [rs.BoundingBox(obj) for obj in objects]
    widths = [abs(bbox[0][0] - bbox[1][0]) for bbox in bboxes]
    x_positions = [bbox[0][0] for bbox in bboxes]

    width_sum = sum(widths)
    gap = (frame_width - width_sum) / len(objects)
    current_x = frame_left + (gap/2)
    
    for i, obj in enumerate(objects):
        transform = [current_x - x_positions[i] ,0 ,0]
        rs.MoveObject(obj, transform)
        current_x += widths[i] + gap

AlignObjs()

space_objects.py (752 Bytes)

Items are placed from left to right following their selection order.

-Kevin

Hi Kevin
This is exactly what I’m looking for.
Thank you very much for your support!

Is there a way not to move the elements inside the groups?
The first thing that come to mind is convert groups to blocks and run script.

Is there a way not to violate the order of elements, sort from left to right and align in order?
If there are many elements, it will be very difficult to chose for each individually.

Using blocks, It may not be the best idea.
But I did not find another way to freeze the groups.
It works. I got this code from Mitch. https://discourse.mcneel.com/t/group-to-block
It remains to align in order.

import rhinoscriptsyntax as rs

def onegroup_filt(rhino_object, geometry, component_index):
    return rhino_object.GroupCount==1
    
    
def ConvertGroupsToBlocks():
    msg="Select group of objects to convert to block"
    objs=rs.GetObjects(msg,group=True,preselect=True,custom_filter=onegroup_filt)
    if not objs: return
    group_set=set()
    for obj in objs:
        obj_g_names=rs.ObjectGroups(obj)
        for g_name in obj_g_names:
            group_set.add(g_name)
    all_obj_groups=list(group_set)
    
    #all_obj_groups should now contain unique list of groups
    for group in all_obj_groups:
        g_objs=rs.ObjectsByGroup(group)
        origin=rs.coerce3dpoint([0,0,0])
        block=rs.AddBlock(g_objs,origin,group,delete_input=True)
        rs.InsertBlock(block,origin)
    
ConvertGroupsToBlocks()



def AlignObjs():
    frame = rs.GetObjects("Select frame")
    frame_bbox = rs.BoundingBox(frame)
    frame_left = frame_bbox[0][0]
    frame_width = abs(frame_bbox[0][0] - frame_bbox[1][0])

    objects = rs.GetObjects("Select objects to align")
    bboxes = [rs.BoundingBox(obj) for obj in objects]
    widths = [abs(bbox[0][0] - bbox[1][0]) for bbox in bboxes]
    x_positions = [bbox[0][0] for bbox in bboxes]

    width_sum = sum(widths)
    gap = (frame_width - width_sum) / len(objects)
    current_x = frame_left + (gap/2)
    
    for i, obj in enumerate(objects):
        transform = [current_x - x_positions[i] ,0 ,0]
        rs.MoveObject(obj, transform)
        current_x += widths[i] + gap
        rs.UnselectAllObjects()

AlignObjs()

Hi Mitch! @Helvetosaur
I’ve returned to this script, but I still haven’t been able to complete it on my own.
Would you be able to help if you have some spare time?

There are several groups of objects arranged horizontally in order, and there’s a frame within which they are placed.
Currently, when the script runs, and the groups are selected simultaneously, if equal spacing is found and a new distribution is applied, the groups end up changing their original order.
Could you help assign an ID to each group from left to right, for example, relative to the coordinate axis? The group closest to zero should get ID1, the next one ID2, and so on. Then use these IDs to distribute the groups in order along the given frame.
As for the frame itself, it should be sorted from groups along the longest distance or in a larger area, assigned a separate ID, and used as the path along which the group distribution will take place.

import rhinoscriptsyntax as rs

def onegroup_filt(rhino_object, geometry, component_index):
    return rhino_object.GroupCount==1
    
    
def ConvertGroupsToBlocks():
    msg="Select group of objects to convert to block"
    objs=rs.GetObjects(msg,group=True,preselect=True,custom_filter=onegroup_filt)
    if not objs: return
    group_set=set()
    for obj in objs:
        obj_g_names=rs.ObjectGroups(obj)
        for g_name in obj_g_names:
            group_set.add(g_name)
    all_obj_groups=list(group_set)
    
    #all_obj_groups should now contain unique list of groups
    for group in all_obj_groups:
        g_objs=rs.ObjectsByGroup(group)
        origin=rs.coerce3dpoint([0,0,0])
        block=rs.AddBlock(g_objs,origin,group,delete_input=True)
        rs.InsertBlock(block,origin)
    
ConvertGroupsToBlocks()



def AlignObjs():
 

    objects = rs.GetObjects("Select objects to align")
    bboxes = [rs.BoundingBox(obj) for obj in objects]
    
    frame = rs.GetObjects("Select frame")
    frame_bbox = rs.BoundingBox(frame)
    frame_left = frame_bbox[0][0]
    frame_width = abs(frame_bbox[0][0] - frame_bbox[1][0])
    
    
    widths = [abs(bbox[0][0] - bbox[1][0]) for bbox in bboxes]
    x_positions = [bbox[0][0] for bbox in bboxes]

    width_sum = sum(widths)
    gap = (frame_width - width_sum) / len(objects)
    current_x = frame_left + (gap/2)
    
    for i, obj in enumerate(objects):
        transform = [current_x - x_positions[i] ,0 ,0]
        rs.MoveObject(obj, transform)
        current_x += widths[i] + gap
        rs.UnselectAllObjects()

AlignObjs()

Here’s the new version of the script.

import scriptcontext as sc
import rhinoscriptsyntax as rs
import Rhino


def AlignObjs():
   
    #SelectOneByOne
    
    msg = "Select cures to sort"
    obj_ids = rs.GetObjects(msg, 4096)
    if not obj_ids: return
    
    rh_objs = [rs.coercerhinoobject(obj_id, True, True) for obj_id in obj_ids]
    
    def SortFunction(rh_obj):
        bbox = rh_obj.Geometry.GetBoundingBox(True)
        return (-bbox.Center.Y, bbox.Center.X)
    
    rh_objs.sort(key=SortFunction)
    
    for i, rh_obj in enumerate(rh_objs):
        bbox = rh_obj.Geometry.GetBoundingBox(True)
       
        
    for i, rh_obj in enumerate(rh_objs):
        bbox = rh_obj.Geometry.GetBoundingBox(True)
        rs.SelectObject(rh_obj)
        rs.Sleep(500)



    #AlignObjs
    objects = rs.GetObjects("Select objects to align", preselect=True)
    bboxes = [rs.BoundingBox(obj) for obj in objects]
    
    frame = rs.GetObjects("Select frame")
    frame_bbox = rs.BoundingBox(frame)
    frame_left = frame_bbox[0][0]
    frame_width = abs(frame_bbox[0][0] - frame_bbox[1][0])
    
    
    widths = [abs(bbox[0][0] - bbox[1][0]) for bbox in bboxes]
    x_positions = [bbox[0][0] for bbox in bboxes]

    width_sum = sum(widths)
    gap = (frame_width - width_sum) / len(objects)
    current_x = frame_left + (gap/2)
    
    for i, obj in enumerate(objects):
        transform = [current_x - x_positions[i] ,0 ,0]
        BlockAligng=rs.MoveObject(obj, transform)
        current_x += widths[i] + gap
        rs.SelectObjects(BlockAligng)

        
        
        
    #BlocksToGroups
    ids = rs.GetObjects("Select block instances to convert", 4096, preselect=True)
    if not ids: return
    

    for id in ids:
        rs.UnselectAllObjects()
        rs.SelectObject(id)
        rs.Command("_ExplodeBlock")
        rs.Command("_Group")
        
    rs.UnselectAllObjects()
    
AlignObjs()
import scriptcontext as sc
import rhinoscriptsyntax as rs
import Rhino


def AlignObjs():

    objects = rs.GetObjects("Select objects to align", preselect=True)
    bboxes = [rs.BoundingBox(obj) for obj in objects]
    
    frame = rs.GetObjects("Select frame")
    frame_bbox = rs.BoundingBox(frame)
    frame_left = frame_bbox[0][0]
    frame_width = abs(frame_bbox[0][0] - frame_bbox[1][0])
    
    
    widths = [abs(bbox[0][0] - bbox[1][0]) for bbox in bboxes]
    x_positions = [bbox[0][0] for bbox in bboxes]

    width_sum = sum(widths)
    gap = (frame_width - width_sum) / len(objects)
    current_x = frame_left + (gap/2)
    
    for i, obj in enumerate(objects):
        transform = [current_x - x_positions[i] ,0 ,0]
        BlockAligng=rs.MoveObject(obj, transform)
        current_x += widths[i] + gap
        rs.SelectObjects(BlockAligng)

        
        
        
    #BlocksToGroups
    ids = rs.GetObjects("Select block instances to convert", 4096, preselect=True)
    if not ids: return
    

    for id in ids:
        rs.UnselectAllObjects()
        rs.SelectObject(id)
        rs.Command("_ExplodeBlock")
        rs.Command("_Group")
        
    rs.UnselectAllObjects()
    
AlignObjs()

The selection works fine – it goes from left to right, one by one.
But when it comes to alignment, things get messed up, like all the objects are being selected at once.

If I select them manually with the mouse one by one, the alignment works just right.

Any idea what might be going wrong here?

Align_Objs.3dm (122.1 KB)

Hi @leex,

Maybe I’m misunderstanding but are you simply trying to space these objects equally along a curve from the groups/text’s bounding rectangle center (B)?

Alternatively, are you trying to ensure that the space between the groups is what is equal? (A)

A)

This can definitely be solved in Python as it’s just evaluating bounding rectangles and/or text length compared to a curve length or domain. Seems like it’s getting further from your initial concept but I may be misunderstanding the intent of the result

A quick GH mockup below for purpose of asking the questions above…

B)

Graph Space:

Model Space:

20250421_Space_Text_Groups_Equally_Within_Frame_Response_01a.gh (29.0 KB)

Hi Michael-
Most of the work has been resolved in Python, but there’s still an issue with object distribution when selecting them with a single mouse click. The objects change positions and fall out of order — for example, the first one might end up in the third spot, and the fourth could move to the first. I’m trying to figure out a solution, but nothing has come to mind yet.

At the moment, I don’t have access to your Grasshopper definition — I’ll be able to take a look tomorrow.

Thank you very much for your help!

Hi Michael-
I was a bit shy to ask because I wanted to fix it myself, but unfortunately, I still haven’t figured out why your definition isn’t working for me.
When I open the file, I get an error in the “Deconstruct” component: Object reference not set to an instance of an object.
If I replace the “Deconstruct” component with a new one, I get an error right after in the “Curve” componen: Object reference not set to an instance of an object.

Hi @leex ,

Sorry about that, it’s been a LONG time since I’ve actually opened up that component that is attempting to create data trees from groups in the Rhino Model. I actually don’t think I created that but forget who did. I’ll try and take a look

Stupid question but, do you still get the error if you have at least 1 group in your Rhino Model that has a name?

Likely I just need to handle null values in that component and had some oversight on that.

EDIT:

Actually its possible something is broken here… looks like it’s casting oddly or not handling Curves because I can get an error like so:


@kike do see you see this casting failure on your end with the Query Model Objects component output connected to Curve nodes when no Curves are present in the Rhino Model?