1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
22 from bpy.types import Operator
23 from bpy.props import IntProperty
24 from bpy.props import EnumProperty
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'}
34 def poll(cls, context):
36 return (obj and obj.rigid_body)
38 def execute(self, context):
42 # deselect all but mesh objects
43 for o in context.selected_objects:
47 objects = context.selected_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)
55 if o.rigid_body is None:
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
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'}
83 frame_start = IntProperty(
85 description="Start frame for baking",
89 frame_end = IntProperty(
91 description="End frame for baking",
97 description="Frame Step",
103 def poll(cls, context):
105 return (obj and obj.rigid_body)
107 def execute(self, context):
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)
115 # filter objects selection
116 for obj in context.selected_objects:
117 if not obj.rigid_body or obj.rigid_body.type != 'ACTIVE':
120 objects = context.selected_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:
129 for i, obj in enumerate(objects):
130 mat[i] = obj.matrix_world.copy()
133 # apply transformations as keyframes
134 for i, f in enumerate(frames_step):
136 obj_prev = objects[0]
137 for j, obj in enumerate(objects):
140 obj.location = mat.to_translation()
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][:]
150 # make sure euler rotation is compatible to previous frame
151 obj.rotation_euler = mat.to_euler(rot_mode, obj_prev.rotation_euler)
155 bpy.ops.anim.keyframe_insert(type='BUILTIN_KSI_LocRot', confirm_success=False)
157 # remove baked objects from simulation
158 bpy.ops.rigidbody.objects_remove()
162 action = obj.animation_data.action
163 for fcu in action.fcurves:
164 keyframe_points = fcu.keyframe_points
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]
172 if abs(val - val_prev) + abs(val - val_next) < 0.0001:
173 keyframe_points.remove(keyframe_points[i])
176 # use linear interpolation for better visual results
177 for keyframe in keyframe_points:
178 keyframe.interpolation = 'LINEAR'
180 # return to the frame we started on
181 scene.frame_set(frame_orig)
185 def invoke(self, context, event):
186 scene = context.scene
187 self.frame_start = scene.frame_start
188 self.frame_end = scene.frame_end
190 wm = context.window_manager
191 return wm.invoke_props_dialog(self)
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'}
200 con_type = EnumProperty(
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),
208 pivot_type = EnumProperty(
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")),
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',)
224 def poll(cls, context):
226 return (obj and obj.rigid_body)
228 def _add_constraint(self, context, object1, object2):
229 if object1 == object2:
232 if self.pivot_type == 'ACTIVE':
233 loc = object1.location
234 elif self.pivot_type == 'SELECTED':
235 loc = object2.location
237 loc = (object1.location + object2.location) / 2.0
239 ob = bpy.data.objects.new("Constraint", object_data=None)
241 context.scene.objects.link(ob)
242 context.scene.objects.active = ob
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
251 con.object1 = object1
252 con.object2 = object2
254 def execute(self, context):
255 scene = context.scene
256 objects = context.selected_objects
257 obj_act = context.active_object
260 if self.connection_pattern == 'CHAIN_DISTANCE':
261 objs_sorted = [obj_act]
262 objects_tmp = context.selected_objects
264 objects_tmp.remove(obj_act)
265 objects_tmp.sort(key=lambda o: (obj_act.location - o.location).length)
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])
274 for i in range(1, len(objs_sorted)):
275 self._add_constraint(context, objs_sorted[i-1], objs_sorted[i])
278 else: # SELECTED_TO_ACTIVE
280 self._add_constraint(context, obj_act, obj)
285 bpy.ops.object.select_all(action='DESELECT')
288 scene.objects.active = obj_act
291 self.report({'WARNING'}, "No other objects selected")