- object.add_shape_key(name="Key", from_mix=True)
[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):
180             me = ob.data
181             ob.add_shape_key(from_mix=False)
182             if len(me.shape_keys.keys) == 1:
183                 ob.add_shape_key(from_mix=False) # we need a rest
184             ob.active_shape_key_index = len(me.shape_keys.keys) - 1
185             ob.shape_key_lock = True
186
187         from Geometry import BarycentricTransform
188         from Mathutils import Vector
189
190         if use_clamp and mode == 'OFFSET':
191             use_clamp = False
192
193         me = ob_act.data
194
195         orig_shape_coords = me_cos(ob_act.active_shape_key.data)
196
197         orig_normals = me_nos(me.verts)
198         orig_coords = me_cos(me.verts)
199
200         for ob_other in objects:
201             me_other = ob_other.data
202             if len(me_other.verts) != len(me.verts):
203                 self.report({'WARNING'}, "Skipping '%s', vertex count differs" % ob_other.name)
204                 continue
205
206             target_normals = me_nos(me_other.verts)
207             target_coords = me_cos(me_other.verts)
208
209             ob_add_shape(ob_other)
210
211             # editing the final coords, only list that stores wrapped coords
212             target_shape_coords = [v.co for v in ob_other.active_shape_key.data]
213
214             median_coords = [[] for i in range(len(me.verts))]
215
216             # Method 1, edge
217             if mode == 'OFFSET':
218                 for i, vert_cos in enumerate(median_coords):
219                     vert_cos.append(target_coords[i] + (orig_shape_coords[i] - orig_coords[i]))
220
221             elif mode == 'RELATIVE_FACE':
222                 for face in me.faces:
223                     i1, i2, i3, i4 = face.verts_raw
224                     if i4 != 0:
225                         pt = BarycentricTransform(orig_shape_coords[i1],
226                             orig_coords[i4], orig_coords[i1], orig_coords[i2],
227                             target_coords[i4], target_coords[i1], target_coords[i2])
228                         median_coords[i1].append(pt)
229
230                         pt = BarycentricTransform(orig_shape_coords[i2],
231                             orig_coords[i1], orig_coords[i2], orig_coords[i3],
232                             target_coords[i1], target_coords[i2], target_coords[i3])
233                         median_coords[i2].append(pt)
234
235                         pt = BarycentricTransform(orig_shape_coords[i3],
236                             orig_coords[i2], orig_coords[i3], orig_coords[i4],
237                             target_coords[i2], target_coords[i3], target_coords[i4])
238                         median_coords[i3].append(pt)
239
240                         pt = BarycentricTransform(orig_shape_coords[i4],
241                             orig_coords[i3], orig_coords[i4], orig_coords[i1],
242                             target_coords[i3], target_coords[i4], target_coords[i1])
243                         median_coords[i4].append(pt)
244
245                     else:
246                         pt = BarycentricTransform(orig_shape_coords[i1],
247                             orig_coords[i3], orig_coords[i1], orig_coords[i2],
248                             target_coords[i3], target_coords[i1], target_coords[i2])
249                         median_coords[i1].append(pt)
250
251                         pt = BarycentricTransform(orig_shape_coords[i2],
252                             orig_coords[i1], orig_coords[i2], orig_coords[i3],
253                             target_coords[i1], target_coords[i2], target_coords[i3])
254                         median_coords[i2].append(pt)
255
256                         pt = BarycentricTransform(orig_shape_coords[i3],
257                             orig_coords[i2], orig_coords[i3], orig_coords[i1],
258                             target_coords[i2], target_coords[i3], target_coords[i1])
259                         median_coords[i3].append(pt)
260
261             elif mode == 'RELATIVE_EDGE':
262                 for ed in me.edges:
263                     i1, i2 = ed.verts
264                     v1, v2 = orig_coords[i1], orig_coords[i2]
265                     edge_length = (v1 - v2).length
266                     n1loc = v1 + orig_normals[i1] * edge_length
267                     n2loc = v2 + orig_normals[i2] * edge_length
268
269
270                     # now get the target nloc's
271                     v1_to, v2_to = target_coords[i1], target_coords[i2]
272                     edlen_to = (v1_to - v2_to).length
273                     n1loc_to = v1_to + target_normals[i1] * edlen_to
274                     n2loc_to = v2_to + target_normals[i2] * edlen_to
275
276                     pt = BarycentricTransform(orig_shape_coords[i1],
277                         v2, v1, n1loc,
278                         v2_to, v1_to, n1loc_to)
279                     median_coords[i1].append(pt)
280
281                     pt = BarycentricTransform(orig_shape_coords[i2],
282                         v1, v2, n2loc,
283                         v1_to, v2_to, n2loc_to)
284                     median_coords[i2].append(pt)
285
286             # apply the offsets to the new shape
287             from functools import reduce
288             VectorAdd = Vector.__add__
289
290             for i, vert_cos in enumerate(median_coords):
291                 if vert_cos:
292                     co = reduce(VectorAdd, vert_cos) / len(vert_cos)
293
294                     if use_clamp:
295                         # clamp to the same movement as the original
296                         # breaks copy between different scaled meshes.
297                         len_from = (orig_shape_coords[i] - orig_coords[i]).length
298                         ofs = co - target_coords[i]
299                         ofs.length = len_from
300                         co = target_coords[i] + ofs
301
302                     target_shape_coords[i][:] = co
303
304         return {'FINISHED'}
305
306     def poll(self, context):
307         obj = context.active_object
308         return (obj and obj.mode != 'EDIT')
309
310     def execute(self, context):
311         C = bpy.context
312         ob_act = C.active_object
313         objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
314         return self._main(ob_act, objects, self.properties.mode, self.properties.use_clamp)
315
316
317 bpy.types.register(SelectPattern)
318 bpy.types.register(SubdivisionSet)
319 bpy.types.register(Retopo)
320 bpy.types.register(ShapeTransfer)