Fix for access of undefined ground object in UI code giving pointless
[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
19 bl_info = {
20     'name': 'Drop to Ground',
21     'author': 'Unnikrishnan(kodemax), Florian Meyer(testscreenings)',
22     'version': (1, 2),
23     "blender": (2, 71, 0),
24     'location': '3D View > Toolshelf > Addons Tab',
25     'description': 'Drop selected objects on active object',
26     'warning': '',
27     'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.6/Py/'
28         'Scripts/Object/Drop_to_ground',
29     'tracker_url': 'https://developer.blender.org/T25349',
30     'category': 'Object'}
31
32
33 import bpy, bmesh
34 from mathutils import *
35 from bpy.types import Operator
36 from bpy.props import *
37
38 def get_align_matrix(location, normal):
39     up = Vector((0,0,1))
40     angle = normal.angle(up)
41     axis = up.cross(normal)
42     mat_rot = Matrix.Rotation(angle, 4, axis)
43     mat_loc = Matrix.Translation(location)
44     mat_align = mat_rot * mat_loc
45     return mat_align
46
47 def transform_ground_to_world(sc, ground):
48     tmpMesh = ground.to_mesh(sc, True, 'PREVIEW')
49     tmpMesh.transform(ground.matrix_world)
50     tmp_ground = bpy.data.objects.new('tmpGround', tmpMesh)
51     sc.objects.link(tmp_ground)
52     sc.update()
53     return tmp_ground
54
55 def get_lowest_world_co_from_mesh(ob, mat_parent=None):
56     bme = bmesh.new()
57     bme.from_mesh(ob.data)
58     mat_to_world = ob.matrix_world.copy()
59     if mat_parent:
60         mat_to_world = mat_parent * mat_to_world
61     lowest=None
62     #bme.verts.index_update() #probably not needed
63     for v in bme.verts:
64         if not lowest:
65             lowest = v
66         if (mat_to_world * v.co).z < (mat_to_world * lowest.co).z:
67             lowest = v
68     lowest_co = mat_to_world * lowest.co
69     bme.free()
70     return lowest_co
71
72 def get_lowest_world_co(context, ob, mat_parent=None):
73     if ob.type == 'MESH':
74         return get_lowest_world_co_from_mesh(ob)
75
76     elif ob.type == 'EMPTY' and ob.dupli_type == 'GROUP':
77         if not ob.dupli_group:
78             return None
79
80         else:
81             lowest_co = None
82             for ob_l in ob.dupli_group.objects:
83                 if ob_l.type == 'MESH':
84                     lowest_ob_l = get_lowest_world_co_from_mesh(ob_l, ob.matrix_world)
85                     if not lowest_co:
86                         lowest_co = lowest_ob_l
87                     if lowest_ob_l.z < lowest_co.z:
88                         lowest_co = lowest_ob_l
89
90             return lowest_co
91
92 def drop_objects(self, context):
93     ground = context.object
94     obs = context.selected_objects
95     obs.remove(ground)
96     tmp_ground = transform_ground_to_world(context.scene, ground)
97     down = Vector((0, 0, -10000))
98
99     for ob in obs:
100         if self.use_origin:
101             lowest_world_co = ob.location
102         else:
103             lowest_world_co = get_lowest_world_co(context, ob)
104         if not lowest_world_co:
105             print(ob.type, 'is not supported. Failed to drop', ob.name)
106             continue
107         hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co,
108                                                                   lowest_world_co + down)
109         if hit_index == -1:
110             print(ob.name, 'didn\'t hit the ground')
111             continue
112
113         # simple drop down
114         to_ground_vec =  hit_location - lowest_world_co
115         ob.location += to_ground_vec
116
117         # drop with align to hit normal
118         if self.align:
119             to_center_vec = ob.location - hit_location #vec: hit_loc to origin
120             # rotate object to align with face normal
121             mat_normal = get_align_matrix(hit_location, hit_normal)
122             rot_euler = mat_normal.to_euler()
123             mat_ob_tmp = ob.matrix_world.copy().to_3x3()
124             mat_ob_tmp.rotate(rot_euler)
125             mat_ob_tmp = mat_ob_tmp.to_4x4()
126             ob.matrix_world = mat_ob_tmp
127             # move_object to hit_location
128             ob.location = hit_location
129             # move object above surface again
130             to_center_vec.rotate(rot_euler)
131             ob.location += to_center_vec
132
133
134     #cleanup
135     bpy.ops.object.select_all(action='DESELECT')
136     tmp_ground.select = True
137     bpy.ops.object.delete('EXEC_DEFAULT')
138     for ob in obs:
139         ob.select = True
140     ground.select = True
141
142 #################################################################
143 class OBJECT_OT_drop_to_ground(Operator):
144     """Drop selected objects on active object"""
145     bl_idname = "object.drop_on_active"
146     bl_label = "Drop to Ground"
147     bl_options = {'REGISTER', 'UNDO'}
148     bl_description = "Drop selected objects on active object"
149
150     align = BoolProperty(
151             name="Align to ground",
152             description="Aligns the object to the ground",
153             default=True)
154     use_origin = BoolProperty(
155             name="Use Center",
156             description="Drop to objects origins",
157             default=False)
158
159     ##### POLL #####
160     @classmethod
161     def poll(cls, context):
162         return len(context.selected_objects) >= 2
163
164     ##### EXECUTE #####
165     def execute(self, context):
166         print('\nDropping Objects')
167         drop_objects(self, context)
168         return {'FINISHED'}
169
170 #################################################################
171 class drop_help(bpy.types.Operator):
172         bl_idname = 'help.drop'
173         bl_label = ''
174
175         def draw(self, context):
176                 layout = self.layout
177                 layout.label("To use:")
178                 layout.label("Name the base object 'Ground'")
179                 layout.label("Select the object/s to drop")
180                 layout.label("Then Shift Select 'Ground'")
181
182         def execute(self, context):
183                 return {'FINISHED'}
184
185         def invoke(self, context, event):
186                 return context.window_manager.invoke_popup(self, width = 300)
187                 
188 class Drop_Operator_Panel(bpy.types.Panel):
189     bl_label = "Drop To Ground"
190     bl_region_type = "TOOLS" #UI possible too
191     bl_space_type = "VIEW_3D"
192     bl_options = {'DEFAULT_CLOSED'}
193     bl_context = "objectmode"
194     bl_category = "Addons"
195
196     def draw(self,context):
197         sce = context.scene
198         layout = self.layout
199         row = layout.row()
200         row = layout.split(0.80)
201         row.operator(OBJECT_OT_drop_to_ground.bl_idname,
202                          text="Drop to Ground")
203         row.operator('help.drop', icon = 'INFO')
204
205
206
207 # register the class
208 def register():
209     bpy.utils.register_module(__name__)
210
211     pass
212
213 def unregister():
214     bpy.utils.unregister_module(__name__)
215
216     pass
217
218 if __name__ == "__main__":
219     register()