Fix for a crash when freeing copied scenes.
[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 Rigidbody 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         scn = 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         sel = context.selected_objects
48         if sel:
49             # add selected objects to active one groups and recalculate
50             bpy.ops.group.objects_add_active()
51             scn.frame_set(scn.frame_current)
52
53             # copy settings
54             for o in sel:
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         objs = []
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         objs = context.selected_objects
120
121         if objs:
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(objs):
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 = objs[0]
135                 for j, obj in enumerate(objs):
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 objs:
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
194
195     '''Connect selected rigid bodies to active'''
196     bl_idname = "rigidbody.connect"
197     bl_label = "ConnectRigidBodies"
198     bl_options = {'REGISTER', 'UNDO'}
199
200     con_type = EnumProperty(
201         name="Type",
202         description="Type of generated contraint",
203         items=(('FIXED', "Fixed", "Glues ridig bodies together"),
204                ('POINT', "Point", "Constrains rigid bodies to move aound common pivot point"),
205                ('HINGE', "Hinge", "Restricts rigid body rotation to one axis"),
206                ('SLIDER', "Slider", "Restricts rigid boddy translation to one axis"),
207                ('PISTON', "Piston", "Restricts rigid boddy translation and rotation to one axis"),
208                ('GENERIC', "Generic", "Restricts translation and rotation to specified axes"),
209                ('GENERIC_SPRING', "Generic Spring", "Restricts translation and rotation to specified axes with springs")),
210         default='FIXED',)
211
212     pivot_type = EnumProperty(
213         name="Location",
214         description="Constraint pivot location",
215         items=(('CENTER', "Center", "Pivot location is between the constrained rigid bodies"),
216                ('ACTIVE', "Active", "Pivot location is at the active object position"),
217                ('SELECTED', "Selected", "Pivot location is at the slected object position")),
218         default='CENTER',)
219
220     @classmethod
221     def poll(cls, context):
222         obj = context.object
223         objs = context.selected_objects
224         return (obj and obj.rigid_body and (len(objs) > 1))
225
226     def execute(self, context):
227
228         objs = context.selected_objects
229         obj_act = context.active_object
230
231         for obj in objs:
232             if obj == obj_act:
233                 continue
234             if self.pivot_type == 'ACTIVE':
235                 loc = obj_act.location
236             elif self.pivot_type == 'SELECTED':
237                 loc = obj.location
238             else:
239                 loc = (obj_act.location + obj.location) / 2.0
240             bpy.ops.object.add(type='EMPTY', view_align=False, enter_editmode=False, location=loc)
241             bpy.ops.rigidbody.constraint_add()
242             con = context.active_object.rigid_body_constraint
243             con.type = self.con_type
244             con.object1 = obj_act
245             con.object2 = obj
246
247         return {'FINISHED'}
248
249     def invoke(self, context, event):
250         wm = context.window_manager
251         return wm.invoke_props_dialog(self)