0
$\begingroup$

So I'm doing a script that uses these planes where the origin point needs to be right above the object below it.

From

enter image description here

To this

enter image description here

So the object's position changes to be above the plane/face

A script could do this for multiple objects I looked into this and there is snapping which I found snap to center to work great. It keeps the origin on the place.

However you have to be relatively close for it to snap. I would like it to just snap on the nearest face below it regardless of how far it is.

The ideal thing I'm trying to achieve is basically where you place these planes in orthographic top down view (where Y isn't changed)

enter image description here

Once snapped they go from this:

enter image description here

to this:

enter image description here

Where these origins meet the face below it.

Another option I've tried was using the floor constraint where I would adjust the height so the objects go closest to the object that is the target. This did not work however as i suppose it's based on the bounding box rather than the faces of the object.

If anyone has any idea how I should execute this, that would be great!

$\endgroup$
2
$\begingroup$

This likely needs more testing but should do what you want. This is currently set up for explicitly moving objects on a 'work layer' to the selected faces of a target object in the z direction. Currently if multiple faces are capable of being snapped to the max movement limit should help to limit but be sure to test before saving your file.

import bpy
from bpy.types import Operator
from bpy.types import Panel
from mathutils import Vector
from mathutils import geometry

bl_info = {
    "name": "Blank1",
    "author": "Your Name Here",
    "version": (1, 0),
    "blender": (2, 79, 0),
    "location": "View3D > Tools panel 'T' > Blank1 > Blank2",
    "description": "",
    "warning": "",
    "wiki_url": "",
    "category": "",
    }


def makeprop():
    bpy.types.Scene.my_tolerance = bpy.props.FloatProperty(
        name="bpy.types.Scene.my_tolerance",
        description="",
        default=5.0,
        )
    bpy.types.Scene.my_worklayer = bpy.props.IntProperty(
        name="bpy.types.Scene.my_worklayer",
        description="",
        default=0,
        min=0,
        max=19,
        )


def delprop():
    del bpy.types.Scene.my_tolerance
    del bpy.types.Scene.my_worklayer


def get_index_selected():
    obj = bpy.context.edit_object
    me = obj.data
    myfaces = []
    if obj.type not in ['MESH']:
            return myfaces
    for f in me.polygons:
        if f.select:
            myfaces.append(f.index)
    return myfaces


def GetTargetVert(target, object, tol):
    me = target.data
    bpy.ops.object.editmode_toggle()
    bpy.ops.object.editmode_toggle()
    my_target_faces = get_index_selected()
    for face in my_target_faces:
        x_max = -1000000.0
        x_min = 1000000.0
        y_max = -1000000.0
        y_min = 1000000.0
        face_verts = []
        for v in me.polygons[face].vertices:
            face_verts.append(v)
        bpy.ops.object.editmode_toggle()
        bpy.ops.object.editmode_toggle()
        W_mat = target.matrix_world
        my_line_a = object.location
        my_line_b = object.location + Vector((0, 0, -5))
        vert_index = me.polygons[face].vertices[0]
        my_plane_co = W_mat * me.vertices[vert_index].co
        my_plane_no = W_mat * me.polygons[face].normal
        for v in face_verts:
            l_co = me.vertices[v].co
            loc = W_mat * l_co
            if loc[0] > x_max:
                x_max = loc[0]
            if loc[0] < x_min:
                x_min = loc[0]
            if loc[1] > y_max:
                y_max = loc[1]
            if loc[1] < y_min:
                y_min = loc[1]
        print(
            'x_max %.2f x_min %.2f y_max %.2f y_min %.2f' % (
                x_max, x_min, y_max, y_min))
        print(
            'obj_x %.2f obj_y %.2f' % (object.location[0], object.location[1]))
        if ((x_min < object.location[0] < x_max) and
            (y_min < object.location[1] < y_max)):
                collision = geometry.intersect_line_plane(
                    my_line_a, my_line_b, my_plane_co, my_plane_no)
        else:
            collision = None
#            print('not in x,y range')
        if collision is None:
            print('no collision available between %s Face[%i] and %s'
                  % (target.name, face, object.name))
        elif abs(collision[2]-object.location[2]) < tol:
            print('collision found between %s Face[%i] and %s'
                  % (target.name, face, object.name))
            print(collision)
            object.location = collision
        else:
            print('collision out of range between %s Face[%i] and %s'
                  % (target.name, face, object.name))
#            print(collision)


def align_objs(context):
    print("-"*20 + "align objects button pressed" + "-"*20)
    scene = context.scene
    target = scene.objects.active
    tol = scene.my_tolerance
    my_layer_objs = [
        ob for ob in scene.objects if ob.layers[scene.my_worklayer]]
    for obj in my_layer_objs:
        if (not obj == target) and (obj.type == 'MESH'):
            GetTargetVert(target, obj, tol)


class AlignOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.align_operator"
    bl_label = "Align Operator"

    @classmethod
    def poll(cls, context):
        return (context.mode == 'EDIT_MESH')

    def execute(self, context):
        align_objs(context)
        return {'FINISHED'}


class VIEW3D_PT_Solve_Intersect(Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_category = "Intersect Face"
    bl_label = "Intersect"

    def draw(self, context):

        scene = context.scene
        layout = self.layout
        row = layout.row()
        col = layout.column(align=True)
        box = col.box()
        box.label(text='Move target & collision objects to work layer')
        box.label(text='Active Layer : %s' % scene.active_layer)
        box.prop(scene, "my_worklayer", "Work Layer")
        box.label(text="Set the max movement")
        box.prop(scene, "my_tolerance", "")
        box.label(text="Enter edit mode & select target faces")
        box.label(text="Press the align objects button")
        box.operator("object.align_operator", text="align objects")
        box.label(text="Collision info available in console")


def register():
    bpy.utils.register_class(VIEW3D_PT_Solve_Intersect)
    bpy.utils.register_class(AlignOperator)
    makeprop()


def unregister():
    bpy.utils.unregister_class(VIEW3D_PT_Solve_Intersect)
    bpy.utils.unregister_class(AlignOperator)
    delprop()


if __name__ == "__main__":
    register()
  • Edited:
  • removed use of bmesh
  • corrected target face vertex positions to reference world position
  • additional check for collision withing the target face min max x,y coordinates
$\endgroup$
0
$\begingroup$

I'd just decrease Z-axis of plane 'till it collides with ground.

http://www.cbcity.de/simple-3d-collision-detection-with-python-scripting-in-blender

while check_Collision(plane, ground) == False
  plane.z  = -0.01

Then you can do your snap, or decrease Z by half the height of the plane, so it ends up where you want it.

$\endgroup$
  • $\begingroup$ With the way I have it setup it won't quite work well. Say for example that road in the other picture, the collision would leave a gap since it would collide with the whole model from that increase in height from the hills. $\endgroup$ – nathan miller Dec 11 '17 at 3:00
  • $\begingroup$ Use a vertex group for allowable faces, and search for collisions with those, instead of the entire ground object. $\endgroup$ – Doyousketch2 Dec 11 '17 at 7:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.