Skip to content

Instantly share code, notes, and snippets.

@iperson
Forked from batFINGER/drop_to_ground.py
Last active January 8, 2025 09:40
Show Gist options
  • Select an option

  • Save iperson/0d972fbe91c1275d3b3938b02bb4b8ae to your computer and use it in GitHub Desktop.

Select an option

Save iperson/0d972fbe91c1275d3b3938b02bb4b8ae to your computer and use it in GitHub Desktop.
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
'name': 'Drop to Ground',
'author': 'Unnikrishnan(kodemax), Florian Meyer(testscreenings)',
'version': (1, 2),
"blender": (2, 83, 0),
'location': '3D View > Toolshelf > Tools Tab',
'description': 'Drop selected objects on active object',
'warning': '',
'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.6/Py/'
'Scripts/Object/Drop_to_ground',
"tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
'category': 'Object'}
import bpy, bmesh
from mathutils import *
from bpy.types import Operator
from bpy.props import *
def get_align_matrix(location, normal):
up = Vector((0,0,1))
angle = normal.angle(up)
axis = up.cross(normal)
mat_rot = Matrix.Rotation(angle, 4, axis)
mat_loc = Matrix.Translation(location)
mat_align = mat_rot @ mat_loc
return mat_align
def transform_ground_to_world(layer, ground):
depsgraph = bpy.context.evaluated_depsgraph_get()
object_eval = ground.evaluated_get(depsgraph)
tmpMesh = bpy.data.meshes.new_from_object(object_eval)
tmpMesh.transform(ground.matrix_world)
tmp_ground = bpy.data.objects.new(name='tmpGround', object_data=tmpMesh)
layer.objects.link(tmp_ground)
layer.objects.update()
return tmp_ground
def get_lowest_world_co_from_mesh(ob, mat_parent=None):
bme = bmesh.new()
bme.from_mesh(ob.data)
mat_to_world = ob.matrix_world.copy()
if mat_parent:
mat_to_world = mat_parent @ mat_to_world
lowest=None
#bme.verts.index_update() #probably not needed
for v in bme.verts:
if not lowest:
lowest = v
if (mat_to_world @ v.co).z < (mat_to_world @ lowest.co).z:
lowest = v
lowest_co = mat_to_world @ lowest.co
bme.free()
return lowest_co
def get_lowest_world_co(context, ob, mat_parent=None):
if ob.type == 'MESH':
return get_lowest_world_co_from_mesh(ob)
elif ob.type == 'EMPTY' and ob.dupli_type == 'GROUP':
if not ob.dupli_group:
return None
else:
lowest_co = None
for ob_l in ob.dupli_group.objects:
if ob_l.type == 'MESH':
lowest_ob_l = get_lowest_world_co_from_mesh(ob_l, ob.matrix_world)
if not lowest_co:
lowest_co = lowest_ob_l
if lowest_ob_l.z < lowest_co.z:
lowest_co = lowest_ob_l
return lowest_co
def drop_objectsall(self, context):
ground = bpy.context.active_object
name = ground.name
for obs in bpy.context.scene.objects:
obs.select_set(state=True)
if obs.name == name:
obs.select_set(state=False)
obs2 = context.selected_objects
#obs.remove(ground)
tmp_ground = transform_ground_to_world(context.scene.collection, ground)
down = Vector((0, 0, -10000))
for ob in obs2:
if self.use_origin:
lowest_world_co = ob.location
else:
lowest_world_co = get_lowest_world_co(context, ob)
if not lowest_world_co:
print(ob.type, 'is not supported. Failed to drop', ob.name)
continue
is_hit, hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co, down)
if not is_hit:
print(ob.name, 'didn\'t hit the ground')
continue
# simple drop down
to_ground_vec = hit_location - lowest_world_co
ob.location += to_ground_vec
# drop with align to hit normal
if self.align:
to_center_vec = ob.location - hit_location #vec: hit_loc to origin
# rotate object to align with face normal
mat_normal = get_align_matrix(hit_location, hit_normal)
rot_euler = mat_normal.to_euler()
mat_ob_tmp = ob.matrix_world.copy().to_3x3()
mat_ob_tmp.rotate(rot_euler)
mat_ob_tmp = mat_ob_tmp.to_4x4()
ob.matrix_world = mat_ob_tmp
# move_object to hit_location
ob.location = hit_location
# move object above surface again
to_center_vec.rotate(rot_euler)
ob.location += to_center_vec
#cleanup
bpy.ops.object.select_all(action='DESELECT')
tmp_ground.select_set(state=True)
bpy.ops.object.delete('EXEC_DEFAULT')
for ob in obs2:
ob.select_set(state=True)
#############################################################################
def drop_objects(self, context):
ground = context.object
obs = context.selected_objects
obs.remove(ground)
tmp_ground = transform_ground_to_world(context.scene.collection, ground)
down = Vector((0, 0, -10000))
for ob in obs:
if self.use_origin:
lowest_world_co = ob.location
else:
lowest_world_co = get_lowest_world_co(context, ob)
if not lowest_world_co:
print(ob.type, 'is not supported. Failed to drop', ob.name)
continue
is_hit, hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co, down)
if not is_hit:
print(ob.name, 'didn\'t hit the ground')
continue
# simple drop down
to_ground_vec = hit_location - lowest_world_co
ob.location += to_ground_vec
# drop with align to hit normal
if self.align:
to_center_vec = ob.location - hit_location #vec: hit_loc to origin
# rotate object to align with face normal
mat_normal = get_align_matrix(hit_location, hit_normal)
rot_euler = mat_normal.to_euler()
mat_ob_tmp = ob.matrix_world.copy().to_3x3()
mat_ob_tmp.rotate(rot_euler)
mat_ob_tmp = mat_ob_tmp.to_4x4()
ob.matrix_world = mat_ob_tmp
# move_object to hit_location
ob.location = hit_location
# move object above surface again
to_center_vec.rotate(rot_euler)
ob.location += to_center_vec
#cleanup
bpy.ops.object.select_all(action='DESELECT')
bpy.context.active_object.select_set(state=True)
bpy.ops.object.delete('EXEC_DEFAULT')
for ob in obs:
ob.select_set(state=True)
#################################################################
class OBJECT_OT_drop_to_ground(Operator):
"""Drop selected objects on active object"""
bl_idname = "object.drop_on_active"
bl_label = "Drop to Ground"
bl_options = {'REGISTER', 'UNDO'}
bl_description = "Drop selected objects on active object"
align = BoolProperty(
name="Align to ground",
description="Aligns the object to the ground",
default=True)
use_origin = BoolProperty(
name="Use Center",
description="Drop to objects origins",
default=False)
##### POLL #####
@classmethod
def poll(cls, context):
return len(context.selected_objects) >= 2
##### EXECUTE #####
def execute(self, context):
print('\nDropping Objects')
drop_objects(self, context)
return {'FINISHED'}
#################################################################
#################################################################
class OBJECT_OT_drop_all_ground(Operator):
"""Drop selected objects on active object"""
bl_idname = "object.drop_all_active"
bl_label = "Drop to Ground"
bl_options = {'REGISTER', 'UNDO'}
bl_description = "Drop all objects in the scene to active object"
align = BoolProperty(
name="Align to ground",
description="Aligns the object to the ground",
default=True)
use_origin = BoolProperty(
name="Use Center",
description="Drop to objects origins",
default=False)
##### POLL #####
##### EXECUTE #####
def execute(self, context):
print('\nDropping Objects')
drop_objectsall(self, context)
return {'FINISHED'}
#################################################################
class PANEL_PT_Operator_Panel(bpy.types.Panel):
bl_label = "Drop To Ground"
bl_region_type = 'UI'
bl_space_type = "VIEW_3D"
bl_options = {'DEFAULT_CLOSED'}
bl_context = "objectmode"
bl_category = "Tools"
def draw(self,context):
sce = context.scene
layout = self.layout
row = layout.row()
row = layout.split()
row.operator(OBJECT_OT_drop_to_ground.bl_idname,
text="Drop Selected")
row = layout.row()
row.operator(OBJECT_OT_drop_all_ground.bl_idname,
text="Drop All")
# register the class
def register():
bpy.utils.register_class(PANEL_PT_Operator_Panel)
bpy.utils.register_class(OBJECT_OT_drop_all_ground)
bpy.utils.register_class(OBJECT_OT_drop_to_ground)
def unregister():
bpy.utils.unregister_class(PANEL_PT_Operator_Panel)
bpy.utils.unregister_class(OBJECT_OT_drop_all_ground)
bpy.utils.unregister_class(OBJECT_OT_drop_to_ground)
if __name__ == "__main__":
register()
@iperson

iperson commented Jul 29, 2020

Copy link
Copy Markdown
Author

Updated to 2.83c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment