Make a shrinkwrap footing.
Oops just saw the 2.79.. see edit below.
Demonstrate the setup, the red dot foot is shrinkwrapped to grid. The wireframe cube is parented to it.
Similarly to how a tyre rig would be made, can make a 2d mesh footing and projection shrinkwrap to the landscape.
The "block" can then be placed in relation to the shrinkwrapped footing.
In the example below have used a simple circle as the footing and 3 vertices as a parent.
Setting the matrix world of "block" to a copy taken before removing footing places the "blocks". Set remove_block to False to keep the footing.
The script below sets random x, y locations in range -20, 20 to test. GIF became too large to demo,.. can arbitrarily rotate footing in z.
Result of running 20 times with subsurfed upscaled 10x10 grid with texture displace
Object mode. Select grid, run script. Resets the grid to context so can repeat.
import bpy
from mathutils import Matrix
context = bpy.context
from random import uniform # random 20x20 to test
x, y = uniform(-20, 20), uniform(-20, 20)
remove_foot = True
target_surface = context.object
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0))
#make the origin the pottom of cube
cube = context.object
me = cube.data
me.transform(Matrix.Translation((0, 0, 1)))
cube.scale = (4, 1, 1)
bpy.ops.mesh.primitive_circle_add(
location=(x, y, 0),
fill_type='TRIFAN')
foot = context.object
sw = foot.modifiers.new(name="SW", type='SHRINKWRAP')
sw.target = target_surface
sw.wrap_method = 'PROJECT'
sw.use_positive_direction = True
sw.use_negative_direction = True
sw.use_project_z = True
### set the relation to foot
cube.parent = foot
cube.parent_type = 'VERTEX_3'
n = len(foot.data.vertices)
cube.parent_vertices = range(1, n, n // 3)
if remove_foot:
dg = context.evaluated_depsgraph_get()
mw = cube.evaluated_get(dg).matrix_world.copy()
bpy.data.objects.remove(foot)
cube.matrix_world = mw
context.view_layer.objects.active = target_surface
Notes:
Could also look at the vertex coords and normals of the evaluated footing
Here the "block" is located at the middle circle (footing) vert and rotated such that vert normal is z up. For the default circle, vert 0 is the centre, vert 1 is at (0, 1, 0).
### set the relation to foot
dg = context.evaluated_depsgraph_get()
me_inst = foot.evaluated_get(dg).to_mesh(depsgraph=dg)
v = me_inst.vertices[0]
cube.location = foot.matrix_world @ v.co.copy()
q = v.normal.to_track_quat()
cube.rotation_euler = q.to_euler()
if remove_foot:
bpy.data.objects.remove(foot)
context.view_layer.objects.active = target_surface
The angle between the vertex normal and z axis will give the grade, and could be tested so as to not add above a certain slope.
z_axis = Vector((0, 0, 1)
if v.normal.angle(z_axis) > radians(45):
# don't put it there
Another way would be to add vertex groups to the footing and transform the block to it via constraints. eg copy location and a track to constraint targeting different vert groups.
Recommend making a more apt footing shape. Perhaps a poked plane matching bottom block face.
To do for many would use Object.copy()
EDIT 2.79 version of script above.
import bpy
from mathutils import Matrix
context = bpy.context
from random import uniform # random 20x20 to test
x, y = uniform(-20, 20), uniform(-20, 20)
remove_foot = True
scene = context.scene
target_surface = context.object
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0))
#make the origin the pottom of cube
cube = context.object
me = cube.data
me.transform(Matrix.Translation((0, 0, 1)))
cube.scale = (4, 1, 1)
bpy.ops.mesh.primitive_circle_add(
location=(x, y, 0),
fill_type='TRIFAN')
foot = context.object
sw = foot.modifiers.new(name="SW", type='SHRINKWRAP')
sw.target = target_surface
sw.wrap_method = 'PROJECT'
sw.use_positive_direction = True
sw.use_negative_direction = True
sw.use_project_z = True
### set the relation to foot
cube.parent = foot
cube.parent_type = 'VERTEX_3'
n = len(foot.data.vertices)
cube.parent_vertices = range(1, n, n // 3)
if remove_foot:
scene.update()
cube.update_tag(refresh={'OBJECT'})
mw = cube.matrix_world.copy()
bpy.data.objects.remove(foot)
cube.matrix_world = mw
scene.objects.active = target_surface