use new preferences standard and fixes for various blender changes
[blender-addons-contrib.git] / object_drop_to_ground.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18 bl_info = {
19     'name': 'Drop to Ground',
20     'author': 'Unnikrishnan(kodemax), Florian Meyer(testscreenings)',
21     'version': (1,2),
22     "blender": (2, 63, 0),
23     'location': '3D View -> Tool Shelf -> Object Tools Panel (at the bottom)',
24     'description': 'Drop selected objects on active object',
25     'warning': '',
26     'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Object/Drop_to_ground',
27     "tracker_url": "http://projects.blender.org/tracker/?func=detail&atid=25349",
28     'category': 'Object'}
29 #################################################################
30 import bpy, bmesh
31 from mathutils import *
32 from bpy.types import Operator
33 from bpy.props import *
34 #################################################################
35 def get_align_matrix(location, normal):
36     up = Vector((0,0,1))                      
37     angle = normal.angle(up)
38     axis = up.cross(normal)                            
39     mat_rot = Matrix.Rotation(angle, 4, axis) 
40     mat_loc = Matrix.Translation(location)
41     mat_align = mat_rot * mat_loc                      
42     return mat_align
43
44 def transform_ground_to_world(sc, ground):
45     tmpMesh = ground.to_mesh(sc, True, 'PREVIEW')
46     tmpMesh.transform(ground.matrix_world)
47     tmp_ground = bpy.data.objects.new('tmpGround', tmpMesh)
48     sc.objects.link(tmp_ground)
49     sc.update()
50     return tmp_ground
51
52 def get_lowest_world_co_from_mesh(ob, mat_parent=None):
53     bme = bmesh.new()
54     bme.from_mesh(ob.data)
55     mat_to_world = ob.matrix_world.copy()
56     if mat_parent:
57         mat_to_world = mat_parent * mat_to_world
58     lowest=None
59     #bme.verts.index_update() #probably not needed
60     for v in bme.verts:
61         if not lowest:
62             lowest = v
63         if (mat_to_world * v.co).z < (mat_to_world * lowest.co).z:
64             lowest = v
65     lowest_co = mat_to_world * lowest.co
66     bme.free()
67     return lowest_co
68
69 def get_lowest_world_co(context, ob, mat_parent=None):
70     if ob.type == 'MESH':
71         return get_lowest_world_co_from_mesh(ob)
72     
73     elif ob.type == 'EMPTY' and ob.dupli_type == 'GROUP':
74         if not ob.dupli_group:
75             return None
76         
77         else:
78             lowest_co = None
79             for ob_l in ob.dupli_group.objects:
80                 if ob_l.type == 'MESH':
81                     lowest_ob_l = get_lowest_world_co_from_mesh(ob_l, ob.matrix_world)
82                     if not lowest_co:
83                         lowest_co = lowest_ob_l
84                     if lowest_ob_l.z < lowest_co.z:
85                         lowest_co = lowest_ob_l
86                         
87             return lowest_co
88
89 def drop_objects(self, context):
90     ground = context.object
91     obs = context.selected_objects
92     obs.remove(ground)
93     tmp_ground = transform_ground_to_world(context.scene, ground)
94     down = Vector((0, 0, -10000))
95     
96     for ob in obs:
97         if self.use_origin:
98             lowest_world_co = ob.location
99         else:
100             lowest_world_co = get_lowest_world_co(context, ob)
101         if not lowest_world_co:
102             print(ob.type, 'is not supported. Failed to drop', ob.name)
103             continue
104         hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co,
105                                                                   lowest_world_co + down)
106         if hit_index == -1:
107             print(ob.name, 'didn\'t hit the ground')
108             continue
109         
110         # simple drop down
111         to_ground_vec =  hit_location - lowest_world_co
112         ob.location += to_ground_vec
113         
114         # drop with align to hit normal
115         if self.align:
116             to_center_vec = ob.location - hit_location #vec: hit_loc to origin
117             # rotate object to align with face normal
118             mat_normal = get_align_matrix(hit_location, hit_normal)
119             rot_euler = mat_normal.to_euler()
120             mat_ob_tmp = ob.matrix_world.copy().to_3x3()
121             mat_ob_tmp.rotate(rot_euler)
122             mat_ob_tmp = mat_ob_tmp.to_4x4()
123             ob.matrix_world = mat_ob_tmp
124             # move_object to hit_location
125             ob.location = hit_location
126             # move object above surface again
127             to_center_vec.rotate(rot_euler)
128             ob.location += to_center_vec
129         
130
131     #cleanup
132     bpy.ops.object.select_all(action='DESELECT')
133     tmp_ground.select = True
134     bpy.ops.object.delete('EXEC_DEFAULT')
135     for ob in obs:
136         ob.select = True
137     ground.select = True
138     
139 #################################################################
140 class OBJECT_OT_drop_to_ground(Operator):
141     """Drop selected objects on active object"""
142     bl_idname = "object.drop_on_active"
143     bl_label = "Drop to Ground"
144     bl_options = {'REGISTER', 'UNDO'}
145     bl_description = "Drop selected objects on active object"
146
147     align = BoolProperty(
148             name="Align to ground",
149             description="Aligns the object to the ground",
150             default=True)
151     use_origin = BoolProperty(
152             name="Use Center",
153             description="Drop to objects origins",
154             default=False)
155
156     ##### POLL #####
157     @classmethod
158     def poll(cls, context):
159         return len(context.selected_objects) >= 2
160     
161     ##### EXECUTE #####
162     def execute(self, context):
163         print('\nDropping Objects')
164         drop_objects(self, context)
165         return {'FINISHED'}
166
167 #################################################################
168 def drop_to_ground_button(self, context):
169     self.layout.operator(OBJECT_OT_drop_to_ground.bl_idname,
170                          text="Drop to Ground")
171     
172 def register():
173     bpy.utils.register_module(__name__)
174     bpy.types.VIEW3D_PT_tools_objectmode.append(drop_to_ground_button)
175     
176 def unregister():
177     bpy.utils.unregister_module(__name__)
178     bpy.types.VIEW3D_PT_tools_objectmode.remove(drop_to_ground_button)
179
180 if __name__ == '__main__':
181     register()