ScriptSpot

Forum topic · Scripts Wanted

Distance Align Then Distribute

By JokerMartini · 2010-12-07

Description

I would like a script that does two things with the option of doing both or each individually.

It is just like the Distribute tools for layers in Photoshop.

Example: You have a scene with a handful of objects that you want to distribute.

http://www.scriptspot.com/files/distance.png

Option 1: The script takes the selected objects and without moving/adjusting the end objects (which would be the two objects furthest from each other) the script would align them all. Think of it as if you made a straight line between the 2 end objects and then moved each object in between to match that line.

http://www.scriptspot.com/files/distancealign.png

Option 2: Would take all the objects except for the 2 end objects and then evenly distribute those objects along the virtual line that would be drawn between the 2 end objects. Which would result in all the objects being evenly space apart based on there pivot or bounding box.

http://www.scriptspot.com/files/distancedistribute.png

Thanks
JokerMartini

Attachments
Comments (15)

Distribute script

pankovea · 2024-11-29

I have been looking for a working tool for distributing objects in space for a long time. As a result, I am developing my own.
At the moment, it can distribute not only objects, but also vertices in splines and meshes. He selects the first and last objects himself and sorts them by position in space (not by selection order). That is, everything works with one button without unnecessary windows. Highlighted - clicked the button on the panel and that's it. Or you can add keyboard shortcuts.

Download from the Github

subscribe for updates.

Attachments

Distribute different objects - how I can do it???

Monaco Felice · 2017-03-05

Please explain how to use this script, evenly distribute the objects that have different sizes? The script does equal intervals between the centers of the objects. But how to make equal intervals between the objects???

dub73 · 2012-07-09

nice script, but when I have objects which are not at 0,0,0 and I check "Distribute" and then "align in X", the scripts move the objects back to the x axis? the objects doesn't stay at their position (the two guide objects at the sides)?

Attachments

JokerMartini · 2010-12-13

Great work Garp. This is a very useful script you've created here.

I've noticed that when you use the option of the first and second end objects and hit the buttons that the end objects tend to move? Why is that?

Garps

JokerMartini · 2010-12-10

Hey Garp, Great script. I took what you wrote and threw it into a UI as well as tried to go ahead and add the option of choosing a start or end object if the user wants to.

It is a bit finiky. I figured I would post it in here and seek some help.
This is a great script.
I just added variables objA and objB for the option of a start and end obj. I'd like to add checkboxes which would allow users to choose X Y or Z


(
local distrib = true
local cnt = selection.count
local obj1, obj2
local max_dist = 0
local objA = undefined
local objB = undefined

fn AutoLineup =
(
if cnt > 2 do
(
for i = 1 to cnt - 1 do for j = i + 1 to cnt do
(
local dist = distance selection[i] selection[j]
if dist > max_dist do
(
if objA ==undefined then
(
obj1 = selection[i]
)
else
(
obj1 = objA
)
if objB ==undefined then
(
obj2 = selection[j]
)
else
(
obj1 = objB
)
max_dist = dist
)
)

local dum = dummy pos:obj1.pos dir:(obj2.pos - obj1.pos)

in coordsys dum
(
local arr = selection as array
fn order o1 o2 = o1.pos.z - o2.pos.z
qsort arr order
local dz = obj2.pos.z / (cnt - 1)

for i = 2 to cnt - 1 do
(
obj = arr[i]
obj.pos.x = obj.pos.y = 0
if distrib do obj.pos.z = (i - 1) * dz
)
)

delete dum
)
)

rollout rlAlignDistribute "Lineup"
(
button btnClearStart "X" width:20 height:20 pos:[2,2]
button btnClearEnd "X" width:20 height:20 pos:[2,24]
pickbutton pkbtnPickStart "Start Object" width:126 height:20 pos:[22,2]
pickbutton pkbtnPickEnd "End Object" width:126 height:20 pos:[22,24]
button btnAutoLineUp "Auto Lineup" width:146 height:24 pos:[2,46]

on btnAutoLineUp pressed do
(
AutoLineup()
)

on pkbtnPickStart picked obj do
(
if obj != undefined then
(
pkbtnPickStart.text = obj.name
objA = obj
)
)

on pkbtnPickEnd picked obj do
(
if obj != undefined then
(
pkbtnPickEnd.text = obj.name
objB = obj
)
)

on btnClearStart pressed do
(
pkbtnPickStart.text = "Start Object"
objA = undefined
)

on btnClearEnd pressed do
(
pkbtnPickEnd.text = "End Object"
objB = undefined
)

)
createDialog rlAlignDistribute 150 72
)

Great

JokerMartini · 2010-12-09

That is great Garp.
I'm going to try and make it work for all axis. X Y and Z for aligning.

Garp · 2010-12-08

This one seems to work.
(could use a rollout though)

(
    local distrib = true

    local cnt = selection.count
    local obj1, obj2
    local max_dist = 0

    if cnt > 2 do
    (
        for i = 1 to cnt - 1 do for j = i + 1 to cnt do
        (
            local dist = distance selection[i] selection[j]
            if dist > max_dist do
            (
                obj1 = selection[i]
                obj2 = selection[j]
                max_dist = dist
            )
        )

        local dum = dummy pos:obj1.pos dir:(obj2.pos - obj1.pos)

        in coordsys dum
        (
            local arr = selection as array
            fn order o1 o2 = o1.pos.z - o2.pos.z
            qsort arr order
            local dz = obj2.pos.z / (cnt - 1)

            for i = 2 to cnt - 1 do
            (
                obj = arr[i]
                obj.pos.x = obj.pos.y = 0
                if distrib do obj.pos.z = (i - 1) * dz
            )
        )

        delete dum
    )
)

Solution

JokerMartini · 2010-12-07

All i need to do really to make this work from here is figure out a way to find the two objects furthest away from each other and use those as the start and end objects for the function.
That is it then.

John Martini

Swordslayer · 2010-12-08

One way to get the objects furthest away from each other is to use parts of the previous scripts (you can merge them later, I don't have much time right now so I leave that part to you), like:

fn getFurthestNodes objs =
(
	struct geom_item (obj1, obj2, dist)
	
	fn compareDist obj1 obj2 =
		obj1.dist - obj2.dist
	
	local already_checked = #()
	distances_arr = for o in objs collect
	(
			local item_arr = for item in objs
				where findItem already_checked item == 0 AND item != o collect
					geom_item obj1:o obj2:item dist:(distance o.pos item.pos)

			with redraw off
				qsort item_arr compareDist					
			
			append already_checked o
			if item_arr.count == 0 then continue else item_arr[item_arr.count]
	)
	with redraw off qsort distances_arr compareDist	
	local result = if distances_arr.count != 0 do
		distances_arr[distances_arr.count]
	if result != undefined do
		select #(result.obj1,result.obj2)
)

Again, to use it just evaluate it and then you can pass it whatever you like, e.g.

getFurthestNodes selection

It selects the resulting nodes for you this time ;)

This is what I was looking for.

JokerMartini · 2010-12-08

you'll have to make two objects as your start and end and the run the script on the selected objects. It is not a streamline thing just yet but I wanted to show you what i wrote as where i was going with this.

Make sure your start object is at 0,0,0


(
local EndObj = $end
local StartObj = $start
local userSel = $Sphere003
local averageVal = [0,0,0]
local userSel = selection as array

fn averagePos startVal endVal =
(
userSel = getCurrentSelection()
averageVal += (startVal.position + endVal.position) ; averageVal /= (userSel.count+1)
)

if userSel.count > 0 do
(
Val = averagePos StartObj EndObj
for i = 1 to selection.count do
(
Obj = selection[i]
print Val
Obj.position = [(Val.x*i),(Val.y*i),(Val.z*i)]
)

completeRedraw()
)
)

Swordslayer · 2010-12-08

This is exactly the same as the alignAndDistribute2 function if passed the type:#evenly argument with the exception that here you have to specify one object extra (and have one object at [0,0,0] coordinates) and as you don't sort the objects, they're aligned not based on their positions but rather their positions in the selection array (i.e. you get random results depending on the way you select them. As for finding out both the "border" nodes, in some sitations it could work, but those would be quite special. You have to pick at least one object as the starting one - that's what I did. I'm not sure if you tried the function, be sure to do it and then we can discuss what you think is missing/not working as you imagined.

Swordslayer · 2010-12-07

Here you go:

fn alignAndDistribute obj_arr distr_line type:#closest =
(
	obj_nr = obj_arr.count

	case type of
	(
		#closest:
		(
			fn pointLineProj pA pB pC =
			(
				local vAB = pB - pA
				local vAC = pC - pA
				local d = dot (normalize vAB) (normalize vAC)
				(pA + (vAB * (d * (length vAC/length vAB))))
			)
			
			for objs in obj_arr do
			(
				new_pos = (pointLineProj (getKnotPoint distr_line 1 1) (getKnotPoint distr_line 1 2) objs.pos)
				objs.pos = new_pos
			)
		)
		#evenly:
		(
			local start_pos = getKnotPoint distr_line 1 1
			local point_dist = (getKnotPoint distr_line 1 2 - getKnotPoint distr_line 1 1)/(obj_nr + 1)
			struct geom_item (obj, dist)

			fn compareDist obj1 obj2 =
				obj1.dist - obj2.dist

			dist_arr = for g in obj_arr collect geom_item obj:g dist:(distance g.pos start_pos)

			with redraw off
       			qsort dist_arr compareDist

			for objs in dist_arr do
			(
				start_pos += point_dist
				objs.obj.pos = start_pos
			)
		)
	)
)

Pass it some objects, a line (to keep it simple, it has to have 2 knots and 1 spline to work as expected) and distribution type. When object will be "out of the range" of the line (i.e. somewhere else than the other objects), the sorting may give unexpected results, but for the situation on your pictures, it will work. PointLineProj function implementation comes from prettyPixel.

Example usage: alignAndDistribute (selection as array) my_line type:#evenly

Swordslayer · 2010-12-07

Well, looking at it again, looks like I should learn to read first :) Anyway, if you want to use it for object collection without a line, use this version instead:

fn alignAndDistribute2 obj_arr start_node type:#closest =
(
	obj_nr = obj_arr.count
	local start_pos = start_node.pos, end_pos, point_dist
	struct geom_item (obj, dist)
	 
	fn compareDist obj1 obj2 =
		obj1.dist - obj2.dist
	 
	dist_arr = for g in obj_arr collect geom_item obj:g dist:(distance g.pos start_pos)
 
	with redraw off
		qsort dist_arr compareDist

	end_pos = dist_arr[dist_arr.count].obj.pos
	point_dist = (end_pos - start_pos)/obj_nr
	
	deleteItem dist_arr dist_arr.count

	case type of
	(
		#closest:
		(
			fn pointLineProj pA pB pC =
			(
				local vAB = pB - pA
				local vAC = pC - pA
				local d = dot (normalize vAB) (normalize vAC)
				(pA + (vAB * (d * (length vAC/length vAB))))
			)
 
			for objs in dist_arr do
			(
				new_pos = (pointLineProj start_pos end_pos objs.obj.pos)
				objs.obj.pos = new_pos
			)
		)
		#evenly:
		(
			for objs in dist_arr do
			(
				start_pos += point_dist
				objs.obj.pos = start_pos
			)
		)
	)
)

Pass it some border object (it's hard to find one programatically in many situations when some coordinates you'd like to test against are the same and so on) and the rest of the objects to be aligned. Now it should be roughly what you wanted in the beginning.