Bunch of fixes for UI messages.
[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         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     '''Connect selected rigid bodies to active'''
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         objs = context.selected_objects
218         return (obj and obj.rigid_body and (len(objs) > 1))
219
220     def execute(self, context):
221
222         objs = context.selected_objects
223         obj_act = context.active_object
224
225         for obj in objs:
226             if obj == obj_act:
227                 continue
228             if self.pivot_type == 'ACTIVE':
229                 loc = obj_act.location
230             elif self.pivot_type == 'SELECTED':
231                 loc = obj.location
232             else:
233                 loc = (obj_act.location + obj.location) / 2.0
234             bpy.ops.object.add(type='EMPTY', view_align=False, enter_editmode=False, location=loc)
235             bpy.ops.rigidbody.constraint_add()
236             con = context.active_object.rigid_body_constraint
237             con.type = self.con_type
238             con.object1 = obj_act
239             con.object2 = obj
240
241         return {'FINISHED'}
242
243     def invoke(self, context, event):
244         wm = context.window_manager
245         return wm.invoke_props_dialog(self)