Merged changes in the trunk up to revision 54171.
[blender-staging.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 = list(range(self.frame_start, self.frame_end + 1, self.step))
113
114         # filter objects selection
115         for obj in context.selected_objects:
116             if not obj.rigid_body or obj.rigid_body.type != 'ACTIVE':
117                 obj.select = False
118
119         objects = context.selected_objects
120
121         if objects:
122             # store transformation data
123             for f in list(range(self.frame_start, self.frame_end + 1)):
124                 scene.frame_set(f)
125                 if f in frames:
126                     mat = {}
127                     for i, obj in enumerate(objects):
128                         mat[i] = obj.matrix_world.copy()
129                     bake.append(mat)
130
131             # apply transformations as keyframes
132             for i, f in enumerate(frames):
133                 scene.frame_set(f)
134                 obj_prev = objects[0]
135                 for j, obj in enumerate(objects):
136                     mat = bake[i][j]
137
138                     obj.location = mat.to_translation()
139
140                     rot_mode = obj.rotation_mode
141                     if rot_mode == 'QUATERNION':
142                         obj.rotation_quaternion = mat.to_quaternion()
143                     elif rot_mode == 'AXIS_ANGLE':
144                         # this is a little roundabout but there's no better way right now
145                         aa = mat.to_quaternion().to_axis_angle()
146                         obj.rotation_axis_angle = (aa[1], ) + aa[0][:]
147                     else: # euler
148                         # make sure euler rotation is compatible to previous frame
149                         obj.rotation_euler = mat.to_euler(rot_mode, obj_prev.rotation_euler)
150
151                     obj_prev = obj
152
153                 bpy.ops.anim.keyframe_insert(type='BUILTIN_KSI_LocRot', confirm_success=False)
154
155             # remove baked objects from simulation
156             bpy.ops.rigidbody.objects_remove()
157
158             # clean up keyframes
159             for obj in objects:
160                 action = obj.animation_data.action
161                 for fcu in action.fcurves:
162                     keyframe_points = fcu.keyframe_points
163                     i = 1
164                     # remove unneeded keyframes
165                     while i < len(keyframe_points) - 1:
166                         val_prev = keyframe_points[i - 1].co[1]
167                         val_next = keyframe_points[i + 1].co[1]
168                         val = keyframe_points[i].co[1]
169
170                         if abs(val - val_prev) + abs(val - val_next) < 0.0001:
171                             keyframe_points.remove(keyframe_points[i])
172                         else:
173                             i += 1
174                     # use linear interpolation for better visual results
175                     for keyframe in keyframe_points:
176                         keyframe.interpolation = 'LINEAR'
177
178             # return to the frame we started on
179             scene.frame_set(frame_orig)
180
181         return {'FINISHED'}
182
183     def invoke(self, context, event):
184         scene = context.scene
185         self.frame_start = scene.frame_start
186         self.frame_end = scene.frame_end
187
188         wm = context.window_manager
189         return wm.invoke_props_dialog(self)
190
191
192 class ConnectRigidBodies(Operator):
193     '''Create rigid body constraints between selected and active rigid bodies'''
194     bl_idname = "rigidbody.connect"
195     bl_label = "Connect Rigid Bodies"
196     bl_options = {'REGISTER', 'UNDO'}
197
198     con_type = EnumProperty(
199         name="Type",
200         description="Type of generated constraint",
201         # XXX Would be nice to get icons too, but currently not possible ;)
202         items=tuple((e.identifier, e.name, e.description, e. value)
203                     for e in bpy.types.RigidBodyConstraint.bl_rna.properties["type"].enum_items),
204         default='FIXED',)
205
206     pivot_type = EnumProperty(
207         name="Location",
208         description="Constraint pivot location",
209         items=(('CENTER', "Center", "Pivot location is between the constrained rigid bodies"),
210                ('ACTIVE', "Active", "Pivot location is at the active object position"),
211                ('SELECTED', "Selected", "Pivot location is at the selected object position")),
212         default='CENTER',)
213
214     @classmethod
215     def poll(cls, context):
216         obj = context.object
217         return (obj and obj.rigid_body)
218
219     def execute(self, context):
220
221         scene = context.scene
222         objects = context.selected_objects
223         obj_act = context.active_object
224         change = False
225
226         for obj in objects:
227             if obj == obj_act:
228                 continue
229             if self.pivot_type == 'ACTIVE':
230                 loc = obj_act.location
231             elif self.pivot_type == 'SELECTED':
232                 loc = obj.location
233             else:
234                 loc = (obj_act.location + obj.location) / 2.0
235             bpy.ops.object.add(type='EMPTY', view_align=False, enter_editmode=False, location=loc)
236             bpy.ops.rigidbody.constraint_add()
237             con = context.active_object.rigid_body_constraint
238             con.type = self.con_type
239             con.object1 = obj_act
240             con.object2 = obj
241             change = True
242         
243         if change:
244             # restore selection
245             bpy.ops.object.select_all(action='DESELECT')
246             for obj in objects:
247                 obj.select = True;
248             scene.objects.active = obj_act
249             return {'FINISHED'}
250         else:
251             self.report({'WARNING'}, "No other objects selected")
252             return {'CANCELLED'}