fix for crash when setting a shapekeys name in rna, (probably other properties too).
[blender.git] / release / scripts / op / object.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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22 from bpy.props import *
23
24
25 class SelectPattern(bpy.types.Operator):
26     '''Select object matching a naming pattern.'''
27     bl_idname = "object.select_pattern"
28     bl_label = "Select Pattern"
29     bl_register = True
30     bl_undo = True
31
32     pattern = StringProperty(name="Pattern", description="Name filter using '*' and '?' wildcard chars", maxlen=32, default="*")
33     case_sensitive = BoolProperty(name="Case Sensitive", description="Do a case sensitive compare", default=False)
34     extend = BoolProperty(name="Extend", description="Extend the existing selection", default=True)
35
36     def execute(self, context):
37
38         import fnmatch
39
40         if self.properties.case_sensitive:
41             pattern_match = fnmatch.fnmatchcase
42         else:
43             pattern_match = lambda a, b: fnmatch.fnmatchcase(a.upper(), b.upper())
44
45         obj = context.object
46         if obj and obj.mode == 'POSE':
47             items = obj.data.bones
48         elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT':
49             items = obj.data.edit_bones
50         else:
51             items = context.visible_objects
52
53         # Can be pose bones or objects
54         for item in items:
55             if pattern_match(item.name, self.properties.pattern):
56                 item.selected = True
57             elif not self.properties.extend:
58                 item.selected = False
59
60         return {'FINISHED'}
61
62     def invoke(self, context, event):
63         wm = context.manager
64         # return wm.invoke_props_popup(self, event)
65         wm.invoke_props_popup(self, event)
66         return {'RUNNING_MODAL'}
67
68     def draw(self, context):
69         layout = self.layout
70         props = self.properties
71
72         layout.prop(props, "pattern")
73         row = layout.row()
74         row.prop(props, "case_sensitive")
75         row.prop(props, "extend")
76
77
78 class SubdivisionSet(bpy.types.Operator):
79     '''Sets a Subdivision Surface Level (1-5)'''
80
81     bl_idname = "object.subdivision_set"
82     bl_label = "Subdivision Set"
83     bl_register = True
84     bl_undo = True
85
86     level = IntProperty(name="Level",
87             default=1, min=-100, max=100, soft_min=-6, soft_max=6)
88
89     relative = BoolProperty(name="Relative", description="Apply the subsurf level as an offset relative to the current level", default=False)
90
91     def poll(self, context):
92         obs = context.selected_editable_objects
93         return (obs is not None)
94
95     def execute(self, context):
96         level = self.properties.level
97         relative = self.properties.relative
98
99         if relative and level == 0:
100             return {'CANCELLED'} # nothing to do
101
102         def set_object_subd(obj):
103             for mod in obj.modifiers:
104                 if mod.type == 'MULTIRES':
105                     if level <= mod.total_levels:
106                         if obj.mode == 'SCULPT':
107                             if relative:
108                                 mod.sculpt_levels += level
109                             else:
110                                 if mod.sculpt_levels != level:
111                                     mod.sculpt_levels = level
112                         elif obj.mode == 'OBJECT':
113                             if relative:
114                                 mod.levels += level
115                             else:
116                                 if mod.levels != level:
117                                     mod.levels = level
118                     return
119                 elif mod.type == 'SUBSURF':
120                     if relative:
121                         mod.levels += level
122                     else:
123                         if mod.levels != level:
124                             mod.levels = level
125
126                     return
127
128             # adda new modifier
129             mod = obj.modifiers.new("Subsurf", 'SUBSURF')
130             mod.levels = level
131
132         for obj in context.selected_editable_objects:
133             set_object_subd(obj)
134
135         return {'FINISHED'}
136
137
138 class Retopo(bpy.types.Operator):
139     '''TODO - doc'''
140
141     bl_idname = "object.retopology"
142     bl_label = "Retopology from Grease Pencil"
143     bl_register = True
144     bl_undo = True
145
146     def execute(self, context):
147         import retopo
148         retopo.main()
149         return {'FINISHED'}
150
151
152 class ShapeTransfer(bpy.types.Operator):
153     '''Copy the active objects current shape to other selected objects with the same number of verts'''
154
155     bl_idname = "object.shape_key_transfer"
156     bl_label = "Transfer Shape Key"
157     bl_register = True
158     bl_undo = True
159
160     mode = EnumProperty(items=(
161                         ('OFFSET', "Offset", "Apply the relative positional offset"),
162                         ('RELATIVE_FACE', "Relative Face", "Calculate the geometricly relative position (using faces)."),
163                         ('RELATIVE_EDGE', "Relative Edge", "Calculate the geometricly relative position (using edges).")),
164                 name="Transformation Mode",
165                 description="Method to apply relative shape positions to the new shape",
166                 default='OFFSET')
167
168     use_clamp = BoolProperty(name="Clamp Offset",
169                 description="Clamp the transformation to the distance each vertex moves in the original shape.",
170                 default=False)
171
172     def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
173         def me_nos(verts):
174             return [v.normal.copy() for v in verts]
175
176         def me_cos(verts):
177             return [v.co.copy() for v in verts]
178
179         def ob_add_shape(ob, name):
180             me = ob.data
181             key = ob.add_shape_key(from_mix=False)
182             if len(me.shape_keys.keys) == 1:
183                 key = ob.add_shape_key(from_mix=False) # we need a rest
184             key.name = name
185             ob.active_shape_key_index = len(me.shape_keys.keys) - 1
186             ob.shape_key_lock = True
187
188         from Geometry import BarycentricTransform
189         from Mathutils import Vector
190
191         if use_clamp and mode == 'OFFSET':
192             use_clamp = False
193
194         me = ob_act.data
195         orig_key_name = ob_act.active_shape_key.name
196
197         orig_shape_coords = me_cos(ob_act.active_shape_key.data)
198
199         orig_normals = me_nos(me.verts)
200         orig_coords = me_cos(me.verts)
201
202         for ob_other in objects:
203             me_other = ob_other.data
204             if len(me_other.verts) != len(me.verts):
205                 self.report({'WARNING'}, "Skipping '%s', vertex count differs" % ob_other.name)
206                 continue
207
208             target_normals = me_nos(me_other.verts)
209             target_coords = me_cos(me_other.verts)
210
211             ob_add_shape(ob_other, orig_key_name)
212
213             # editing the final coords, only list that stores wrapped coords
214             target_shape_coords = [v.co for v in ob_other.active_shape_key.data]
215
216             median_coords = [[] for i in range(len(me.verts))]
217
218             # Method 1, edge
219             if mode == 'OFFSET':
220                 for i, vert_cos in enumerate(median_coords):
221                     vert_cos.append(target_coords[i] + (orig_shape_coords[i] - orig_coords[i]))
222
223             elif mode == 'RELATIVE_FACE':
224                 for face in me.faces:
225                     i1, i2, i3, i4 = face.verts_raw
226                     if i4 != 0:
227                         pt = BarycentricTransform(orig_shape_coords[i1],
228                             orig_coords[i4], orig_coords[i1], orig_coords[i2],
229                             target_coords[i4], target_coords[i1], target_coords[i2])
230                         median_coords[i1].append(pt)
231
232                         pt = BarycentricTransform(orig_shape_coords[i2],
233                             orig_coords[i1], orig_coords[i2], orig_coords[i3],
234                             target_coords[i1], target_coords[i2], target_coords[i3])
235                         median_coords[i2].append(pt)
236
237                         pt = BarycentricTransform(orig_shape_coords[i3],
238                             orig_coords[i2], orig_coords[i3], orig_coords[i4],
239                             target_coords[i2], target_coords[i3], target_coords[i4])
240                         median_coords[i3].append(pt)
241
242                         pt = BarycentricTransform(orig_shape_coords[i4],
243                             orig_coords[i3], orig_coords[i4], orig_coords[i1],
244                             target_coords[i3], target_coords[i4], target_coords[i1])
245                         median_coords[i4].append(pt)
246
247                     else:
248                         pt = BarycentricTransform(orig_shape_coords[i1],
249                             orig_coords[i3], orig_coords[i1], orig_coords[i2],
250                             target_coords[i3], target_coords[i1], target_coords[i2])
251                         median_coords[i1].append(pt)
252
253                         pt = BarycentricTransform(orig_shape_coords[i2],
254                             orig_coords[i1], orig_coords[i2], orig_coords[i3],
255                             target_coords[i1], target_coords[i2], target_coords[i3])
256                         median_coords[i2].append(pt)
257
258                         pt = BarycentricTransform(orig_shape_coords[i3],
259                             orig_coords[i2], orig_coords[i3], orig_coords[i1],
260                             target_coords[i2], target_coords[i3], target_coords[i1])
261                         median_coords[i3].append(pt)
262
263             elif mode == 'RELATIVE_EDGE':
264                 for ed in me.edges:
265                     i1, i2 = ed.verts
266                     v1, v2 = orig_coords[i1], orig_coords[i2]
267                     edge_length = (v1 - v2).length
268                     n1loc = v1 + orig_normals[i1] * edge_length
269                     n2loc = v2 + orig_normals[i2] * edge_length
270
271
272                     # now get the target nloc's
273                     v1_to, v2_to = target_coords[i1], target_coords[i2]
274                     edlen_to = (v1_to - v2_to).length
275                     n1loc_to = v1_to + target_normals[i1] * edlen_to
276                     n2loc_to = v2_to + target_normals[i2] * edlen_to
277
278                     pt = BarycentricTransform(orig_shape_coords[i1],
279                         v2, v1, n1loc,
280                         v2_to, v1_to, n1loc_to)
281                     median_coords[i1].append(pt)
282
283                     pt = BarycentricTransform(orig_shape_coords[i2],
284                         v1, v2, n2loc,
285                         v1_to, v2_to, n2loc_to)
286                     median_coords[i2].append(pt)
287
288             # apply the offsets to the new shape
289             from functools import reduce
290             VectorAdd = Vector.__add__
291
292             for i, vert_cos in enumerate(median_coords):
293                 if vert_cos:
294                     co = reduce(VectorAdd, vert_cos) / len(vert_cos)
295
296                     if use_clamp:
297                         # clamp to the same movement as the original
298                         # breaks copy between different scaled meshes.
299                         len_from = (orig_shape_coords[i] - orig_coords[i]).length
300                         ofs = co - target_coords[i]
301                         ofs.length = len_from
302                         co = target_coords[i] + ofs
303
304                     target_shape_coords[i][:] = co
305
306         return {'FINISHED'}
307
308     def poll(self, context):
309         obj = context.active_object
310         return (obj and obj.mode != 'EDIT')
311
312     def execute(self, context):
313         C = bpy.context
314         ob_act = C.active_object
315         if ob_act.active_shape_key is None:
316             self.report({'ERROR'}, "Active object has no shape key")
317             return {'CANCELLED'}
318         objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
319         return self._main(ob_act, objects, self.properties.mode, self.properties.use_clamp)
320
321
322 bpy.types.register(SelectPattern)
323 bpy.types.register(SubdivisionSet)
324 bpy.types.register(Retopo)
325 bpy.types.register(ShapeTransfer)