Some UI messages fixes...
[blender.git] / release / scripts / startup / bl_operators / rigidbody.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 # <pep8-80 compliant>
20
21 import bpy
22 from bpy.types import Operator
23 from bpy.props import IntProperty
24 from bpy.props import EnumProperty
25
26
27 class CopyRigidbodySettings(Operator):
28     '''Copy Rigid Body settings from active object to selected'''
29     bl_idname = "rigidbody.object_settings_copy"
30     bl_label = "Copy Rigid Body Settings"
31     bl_options = {'REGISTER', 'UNDO'}
32
33     @classmethod
34     def poll(cls, context):
35         obj = context.object
36         return (obj and obj.rigid_body)
37
38     def execute(self, context):
39         obj = context.object
40         scene = context.scene
41
42         # deselect all but mesh objects
43         for o in context.selected_objects:
44             if o.type != 'MESH':
45                 o.select = False
46
47         objects = context.selected_objects
48         if objects:
49             # add selected objects to active one groups and recalculate
50             bpy.ops.group.objects_add_active()
51             scene.frame_set(scene.frame_current)
52
53             # copy settings
54             for o in objects:
55                 if o.rigid_body is None:
56                     continue
57
58                 o.rigid_body.type = obj.rigid_body.type
59                 o.rigid_body.kinematic = obj.rigid_body.kinematic
60                 o.rigid_body.mass = obj.rigid_body.mass
61                 o.rigid_body.collision_shape = obj.rigid_body.collision_shape
62                 o.rigid_body.use_margin = obj.rigid_body.use_margin
63                 o.rigid_body.collision_margin = obj.rigid_body.collision_margin
64                 o.rigid_body.friction = obj.rigid_body.friction
65                 o.rigid_body.restitution = obj.rigid_body.restitution
66                 o.rigid_body.use_deactivation = obj.rigid_body.use_deactivation
67                 o.rigid_body.start_deactivated = obj.rigid_body.start_deactivated
68                 o.rigid_body.deactivate_linear_velocity = obj.rigid_body.deactivate_linear_velocity
69                 o.rigid_body.deactivate_angular_velocity = obj.rigid_body.deactivate_angular_velocity
70                 o.rigid_body.linear_damping = obj.rigid_body.linear_damping
71                 o.rigid_body.angular_damping = obj.rigid_body.angular_damping
72                 o.rigid_body.collision_groups = obj.rigid_body.collision_groups
73
74         return {'FINISHED'}
75
76
77 class BakeToKeyframes(Operator):
78     '''Bake rigid body transformations of selected objects to keyframes'''
79     bl_idname = "rigidbody.bake_to_keyframes"
80     bl_label = "Bake To Keyframes"
81     bl_options = {'REGISTER', 'UNDO'}
82
83     frame_start = IntProperty(
84             name="Start Frame",
85             description="Start frame for baking",
86             min=0, max=300000,
87             default=1,
88             )
89     frame_end = IntProperty(
90             name="End Frame",
91             description="End frame for baking",
92             min=1, max=300000,
93             default=250,
94             )
95     step = IntProperty(
96             name="Frame Step",
97             description="Frame Step",
98             min=1, max=120,
99             default=1,
100             )
101
102     @classmethod
103     def poll(cls, context):
104         obj = context.object
105         return (obj and obj.rigid_body)
106
107     def execute(self, context):
108         bake = []
109         objects = []
110         scene = context.scene
111         frame_orig = scene.frame_current
112         frames_step = range(self.frame_start, self.frame_end + 1, self.step)
113         frames_full = range(self.frame_start, self.frame_end + 1)
114
115         # filter objects selection
116         for obj in context.selected_objects:
117             if not obj.rigid_body or obj.rigid_body.type != 'ACTIVE':
118                 obj.select = False
119
120         objects = context.selected_objects
121
122         if objects:
123             # store transformation data
124             # need to start at scene start frame so simulation is run from the beginning
125             for f in frames_full:
126                 scene.frame_set(f)
127                 if f in frames_step:
128                     mat = {}
129                     for i, obj in enumerate(objects):
130                         mat[i] = obj.matrix_world.copy()
131                     bake.append(mat)
132
133             # apply transformations as keyframes
134             for i, f in enumerate(frames_step):
135                 scene.frame_set(f)
136                 obj_prev = objects[0]
137                 for j, obj in enumerate(objects):
138                     mat = bake[i][j]
139
140                     obj.location = mat.to_translation()
141
142                     rot_mode = obj.rotation_mode
143                     if rot_mode == 'QUATERNION':
144                         obj.rotation_quaternion = mat.to_quaternion()
145                     elif rot_mode == 'AXIS_ANGLE':
146                         # this is a little roundabout but there's no better way right now
147                         aa = mat.to_quaternion().to_axis_angle()
148                         obj.rotation_axis_angle = (aa[1], ) + aa[0][:]
149                     else: # euler
150                         # make sure euler rotation is compatible to previous frame
151                         obj.rotation_euler = mat.to_euler(rot_mode, obj_prev.rotation_euler)
152
153                     obj_prev = obj
154
155                 bpy.ops.anim.keyframe_insert(type='BUILTIN_KSI_LocRot', confirm_success=False)
156
157             # remove baked objects from simulation
158             bpy.ops.rigidbody.objects_remove()
159
160             # clean up keyframes
161             for obj in objects:
162                 action = obj.animation_data.action
163                 for fcu in action.fcurves:
164                     keyframe_points = fcu.keyframe_points
165                     i = 1
166                     # remove unneeded keyframes
167                     while i < len(keyframe_points) - 1:
168                         val_prev = keyframe_points[i - 1].co[1]
169                         val_next = keyframe_points[i + 1].co[1]
170                         val = keyframe_points[i].co[1]
171
172                         if abs(val - val_prev) + abs(val - val_next) < 0.0001:
173                             keyframe_points.remove(keyframe_points[i])
174                         else:
175                             i += 1
176                     # use linear interpolation for better visual results
177                     for keyframe in keyframe_points:
178                         keyframe.interpolation = 'LINEAR'
179
180             # return to the frame we started on
181             scene.frame_set(frame_orig)
182
183         return {'FINISHED'}
184
185     def invoke(self, context, event):
186         scene = context.scene
187         self.frame_start = scene.frame_start
188         self.frame_end = scene.frame_end
189
190         wm = context.window_manager
191         return wm.invoke_props_dialog(self)
192
193
194 class ConnectRigidBodies(Operator):
195     '''Create rigid body constraints between selected rigid bodies'''
196     bl_idname = "rigidbody.connect"
197     bl_label = "Connect Rigid Bodies"
198     bl_options = {'REGISTER', 'UNDO'}
199
200     con_type = EnumProperty(
201         name="Type",
202         description="Type of generated constraint",
203         # XXX Would be nice to get icons too, but currently not possible ;)
204         items=tuple((e.identifier, e.name, e.description, e. value)
205                     for e in bpy.types.RigidBodyConstraint.bl_rna.properties["type"].enum_items),
206         default='FIXED',)
207
208     pivot_type = EnumProperty(
209         name="Location",
210         description="Constraint pivot location",
211         items=(('CENTER', "Center", "Pivot location is between the constrained rigid bodies"),
212                ('ACTIVE', "Active", "Pivot location is at the active object position"),
213                ('SELECTED', "Selected", "Pivot location is at the selected object position")),
214         default='CENTER',)
215
216     connection_pattern = EnumProperty(
217         name="Connection Pattern",
218         description="Pattern used to connect objects",
219         items=(('SELECTED_TO_ACTIVE', "Selected to Active", "Connect selected objects to the active object"),
220                ('CHAIN_DISTANCE', "Chain by Distance", "Connect objects as a chain based on distance, starting at the active object")),
221         default='SELECTED_TO_ACTIVE',)
222
223     @classmethod
224     def poll(cls, context):
225         obj = context.object
226         return (obj and obj.rigid_body)
227
228     def _add_constraint(self, context, object1, object2):
229         if object1 == object2:
230             return
231
232         if self.pivot_type == 'ACTIVE':
233             loc = object1.location
234         elif self.pivot_type == 'SELECTED':
235             loc = object2.location
236         else:
237             loc = (object1.location + object2.location) / 2.0
238
239         ob = bpy.data.objects.new("Constraint", object_data=None)
240         ob.location = loc
241         context.scene.objects.link(ob)
242         context.scene.objects.active = ob
243         ob.select = True
244
245         bpy.ops.rigidbody.constraint_add()
246         con_obj = context.active_object
247         con_obj.empty_draw_type = 'ARROWS'
248         con = con_obj.rigid_body_constraint
249         con.type = self.con_type
250
251         con.object1 = object1
252         con.object2 = object2
253
254     def execute(self, context):
255         scene = context.scene
256         objects = context.selected_objects
257         obj_act = context.active_object
258         change = False
259
260         if self.connection_pattern == 'CHAIN_DISTANCE':
261             objs_sorted = [obj_act]
262             objects_tmp = context.selected_objects
263             if obj_act.select:
264                 objects_tmp.remove(obj_act)
265             objects_tmp.sort(key=lambda o: (obj_act.location - o.location).length)
266             last_obj = obj_act
267
268             while (len(objects_tmp)):
269                 objects_tmp.sort(key=lambda o: (last_obj.location - o.location).length)
270                 objs_sorted.append(objects_tmp[0])
271                 last_obj = objects_tmp[0]
272                 objects_tmp.remove(objects_tmp[0])
273
274             for i in range(1, len(objs_sorted)):
275                 self._add_constraint(context, objs_sorted[i-1], objs_sorted[i])
276                 change = True
277
278         else: # SELECTED_TO_ACTIVE
279             for obj in objects:
280                 self._add_constraint(context, obj_act, obj)
281                 change = True;
282
283         if change:
284             # restore selection
285             bpy.ops.object.select_all(action='DESELECT')
286             for obj in objects:
287                 obj.select = True
288             scene.objects.active = obj_act
289             return {'FINISHED'}
290         else:
291             self.report({'WARNING'}, "No other objects selected")
292             return {'CANCELLED'}