420e7661789226283e9f6343b2ca180ec0c7f2b9
[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 """ \
196     """selected and active rigid bodies"""
197     bl_idname = "rigidbody.connect"
198     bl_label = "Connect Rigid Bodies"
199     bl_options = {'REGISTER', 'UNDO'}
200
201     con_type = EnumProperty(
202         name="Type",
203         description="Type of generated constraint",
204         # XXX Would be nice to get icons too, but currently not possible ;)
205         items=tuple((e.identifier, e.name, e.description, e. value)
206                     for e in bpy.types.RigidBodyConstraint.bl_rna.properties["type"].enum_items),
207         default='FIXED',)
208
209     pivot_type = EnumProperty(
210         name="Location",
211         description="Constraint pivot location",
212         items=(('CENTER', "Center", "Pivot location is between the constrained rigid bodies"),
213                ('ACTIVE', "Active", "Pivot location is at the active object position"),
214                ('SELECTED', "Selected", "Pivot location is at the selected object position")),
215         default='CENTER',)
216
217     @classmethod
218     def poll(cls, context):
219         obj = context.object
220         return (obj and obj.rigid_body)
221
222     def execute(self, context):
223
224         scene = context.scene
225         objects = context.selected_objects
226         obj_act = context.active_object
227         change = False
228
229         for obj in objects:
230             if obj == obj_act:
231                 continue
232             if self.pivot_type == 'ACTIVE':
233                 loc = obj_act.location
234             elif self.pivot_type == 'SELECTED':
235                 loc = obj.location
236             else:
237                 loc = (obj_act.location + obj.location) / 2.0
238             # TODO: use bpy.data.objects.new(...)
239             bpy.ops.object.add(type='EMPTY',
240                                view_align=False,
241                                enter_editmode=False,
242                                location=loc)
243             bpy.ops.rigidbody.constraint_add()
244             con_obj = context.active_object
245             con_obj.empty_draw_type = 'ARROWS'
246             con = con_obj.rigid_body_constraint
247             con.type = self.con_type
248             con.object1 = obj_act
249             con.object2 = obj
250             change = True
251
252         if change:
253             # restore selection
254             bpy.ops.object.select_all(action='DESELECT')
255             for obj in objects:
256                 obj.select = True
257             scene.objects.active = obj_act
258             return {'FINISHED'}
259         else:
260             self.report({'WARNING'}, "No other objects selected")
261             return {'CANCELLED'}