I think the problem I’m trying to solve requires recursion, or a more clever use of callbacks than my brute force attempt. The test code works if you assign values via the transform type in, but it’s really something that needs to work in real-time with the transform gizmo in the viewport to be most effective. My brute force use of callbacks is just too slow to fully work in the viewport–it becomes unreliable.

The goal is a rig that keeps the delta z height between any adjacent object, or the edge, below 1 unit. So the perimeter objects can be +/- 1 in height, and no object can be more than +/- 1 unit from another. So that, if you grab a middle object and move it up past 1, the adjacent 8 objects will move upwards to maintain the 1 unit delta.

Test code is posted below, I’d appreciate any hints or tips as to how to make this more efficient to work in realtime. If you set the line REALTIMEMODE = true, then it will continuously update, not only on redraw, and that’s when it will fall apart.

```
z_min = 0
z_max = 10
delta_z_limit = 1.0
z_pos = (z_max - z_min) * 0.5
x_grid_count = 9
y_grid_count = 21
spacing_x = 5.0
spacing_y = 5.0
REALTIMEMODE = false
function dist_from_edge input_cell axis_count = (
midpoint = axis_count * .5 +.5
dist = (midpoint - (abs (input_cell - midpoint) as integer)) as integer
)
function calc_max_delta x y = (
-- calculate the maximum height this cell can be based on the dist from the edge
max_x_delta = (dist_from_edge x x_grid_count) * delta_z_limit
max_y_delta = (dist_from_edge y y_grid_count) * delta_z_limit
-- use the lowest value
max_delta = (z_max - z_min) * 0.5
if max_x_delta < max_delta then max_delta = max_x_delta
if max_y_delta < max_delta then max_delta = max_y_delta
max_delta
)
function get_xy_from_objname objname = (
y_grid = (substring objname 7 2) as integer
x_grid = (substring objname 10 2) as integer
#(x_grid,y_grid)
)
function get_adjacent_objs obj = (
-- find all the objects within 1 grid cell
xy_array = get_xy_from_objname obj.name
x_grid = xy_array[1]
y_grid = xy_array[2]
selnames = #()
for s in selection do appendifunique selnames s.name
adjacent_list = #()
for adj_y = y_grid - 1 to y_grid + 1 do(
if adj_y > 0 and adj_y <= y_grid_count do(
for adj_x = x_grid - 1 to x_grid + 1 do(
if adj_x > 0 and adj_x <= x_grid_count do(
x_string = (formattedPrint adj_x format:"02d")
y_string = (formattedPrint adj_y format:"02d")
adj_name = "Dummy_" + y_string + "_" + x_string
if not (adj_x == x_grid and adj_y == y_grid) do( -- don't add self
if (finditem selnames adj_name) == 0 then( -- don't add selected
append adjacent_list adj_name
)
)
)
)
)
)
for c in adjacent_list collect getnodebyname c
)
function conform_adj_objs obj = (
-- make sure any adjacent object is within delta_z_limit of the z position
adj = get_adjacent_objs obj
zpos = obj.pos.z
for a in adj do(
if a.pos.z < (zpos - delta_z_limit) then(
a.pos.z = zpos - delta_z_limit
)
else if a.pos.z > (zpos + delta_z_limit) then(
a.pos.z = zpos + delta_z_limit
)
)
)
function limit_z objects_in = (
-- set the max height of any grid cell to be no more than delta_z_limit * rows in from perimeter edge
-- such that the slope of any adjacent object is no more than delta_z_limit
for obj in objects_in do(
xy_array = get_xy_from_objname obj.name
max_delta = calc_max_delta xy_array[1] xy_array[2]
if not REALTIMEMODE then obj.wirecolor = [0,255,0]
half = (z_max - z_min) * 0.5
cell_z_max = half + max_delta
cell_z_min = half - max_delta
if obj.pos.z > cell_z_max do(
obj.pos.z = cell_z_max
)
if obj.pos.z < cell_z_min do(
obj.pos.z = cell_z_min
)
if not REALTIMEMODE then(
if obj.pos.z >= cell_z_max then obj.wirecolor = [255,64,64] -- upper limit, set to red
else if obj.pos.z <= cell_z_min then obj.wirecolor = [64,64,255] -- lower limit, set to blue
)
-- now make sure the adjacent objects conform to the slope of delta_z_limit for the new height
conform_adj_objs obj
)
--redrawviews()
)
function create_helper_array = (
for y = 1 to y_grid_count do(
for x = 1 to x_grid_count do(
x_string = (formattedPrint x format:"02d")
y_string = (formattedPrint y format:"02d")
point_name = "Dummy_" + y_string + "_" + x_string
new_point = point name:point_name
new_point.box = true
new_point.axistripod = false
new_point.cross = false
new_point.wirecolor = [0,255,0]
new_point.size = spacing_x * .2
new_point.pos = [(x - 1) * spacing_x,(y - 1) * -spacing_y, z_pos ]
setTransformLockFlags new_point #{1,2,4,5,6,7,8,9} -- only allow z transform, lock everything else
if REALTIMEMODE then when transform new_point changes selection do limit_z selection -- REALTIME DOES NOT WORK WELL
else when transform new_point changes handleAt: #redrawViews selection do limit_z selection
)
)
)
delete $*
clearlistener()
create_helper_array()
```