77 lines
2.4 KiB
Python
77 lines
2.4 KiB
Python
import bpy
|
|
import bmesh
|
|
from mathutils import Vector
|
|
from mathutils.bvhtree import BVHTree
|
|
|
|
DEBUG_LEVEL = 2 # 0=minimal, 1=per-object, 2=verbose
|
|
|
|
def dbg(level, *args):
|
|
if DEBUG_LEVEL >= level:
|
|
print(*args)
|
|
|
|
def get_world_bvhtree(obj):
|
|
if obj.type != 'MESH':
|
|
return None
|
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
eval_obj = obj.evaluated_get(depsgraph)
|
|
mesh = eval_obj.to_mesh()
|
|
bm = bmesh.new()
|
|
bm.from_mesh(mesh)
|
|
bm.transform(obj.matrix_world)
|
|
bvh = BVHTree.FromBMesh(bm)
|
|
bm.free()
|
|
eval_obj.to_mesh_clear()
|
|
return bvh
|
|
|
|
def snap_objects_down_z():
|
|
active_obj = bpy.context.view_layer.objects.active
|
|
selected_objs = [o for o in bpy.context.selected_objects if o != active_obj]
|
|
|
|
if not active_obj or active_obj.type != 'MESH':
|
|
print("ERROR: Active object must be a mesh.")
|
|
return
|
|
|
|
bvh = get_world_bvhtree(active_obj)
|
|
if not bvh:
|
|
print("ERROR: Could not build BVH for active object.")
|
|
return
|
|
|
|
dbg(1, f"Active object: {active_obj.name}")
|
|
dbg(1, f"Selected objects: {[o.name for o in selected_objs]}")
|
|
|
|
for obj in selected_objs:
|
|
if obj.type != 'MESH':
|
|
dbg(1, f"Skipping '{obj.name}': not a mesh")
|
|
continue
|
|
|
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
eval_obj = obj.evaluated_get(depsgraph)
|
|
mesh = eval_obj.to_mesh()
|
|
if not mesh.vertices:
|
|
eval_obj.to_mesh_clear()
|
|
dbg(1, f"Skipping '{obj.name}': no vertices")
|
|
continue
|
|
|
|
# Find lowest vertex in world coordinates
|
|
world_verts = [obj.matrix_world @ v.co for v in mesh.vertices]
|
|
lowest_vert = min(world_verts, key=lambda v: v.z)
|
|
dbg(2, f"'{obj.name}' lowest vertex: {lowest_vert}")
|
|
|
|
# Cast a ray straight down from the lowest vertex
|
|
ray_origin = lowest_vert + Vector((0,0,0.01)) # tiny offset to avoid self-intersection
|
|
ray_dir = Vector((0,0,-1))
|
|
hit_loc, hit_normal, hit_index, hit_dist = bvh.ray_cast(ray_origin, ray_dir, 10000.0)
|
|
|
|
if hit_loc:
|
|
dbg(2, f"'{obj.name}' hit at {hit_loc}")
|
|
z_offset = hit_loc.z - lowest_vert.z
|
|
dbg(1, f"'{obj.name}' moving down by {z_offset:.6f} in Z")
|
|
obj.location.z += z_offset
|
|
else:
|
|
dbg(1, f"'{obj.name}' did not hit active object when casting down")
|
|
|
|
eval_obj.to_mesh_clear()
|
|
|
|
print("Snap down complete.")
|
|
|
|
snap_objects_down_z()
|