copy of docs from 2.4x for python modules that have been kept
[blender-staging.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 not relative:
106                         if level <= mod.total_levels:
107                             if obj.mode == 'SCULPT':
108                                 if mod.sculpt_levels != level:
109                                     mod.sculpt_levels = level
110                             elif obj.mode == 'OBJECT':
111                                 if mod.levels != level:
112                                     mod.levels = level                          
113                         return
114                     else:
115                         if obj.mode == 'SCULPT':
116                             if mod.sculpt_levels+level <= mod.total_levels:
117                                 mod.sculpt_levels += level
118                         elif obj.mode == 'OBJECT':
119                             if mod.levels+level <= mod.total_levels:
120                                 mod.levels += level
121                         return
122                 
123                 elif mod.type == 'SUBSURF':
124                     if relative:
125                         mod.levels += level
126                     else:
127                         if mod.levels != level:
128                             mod.levels = level
129
130                     return
131
132             # adda new modifier
133             mod = obj.modifiers.new("Subsurf", 'SUBSURF')
134             mod.levels = level
135
136         for obj in context.selected_editable_objects:
137             set_object_subd(obj)
138
139         return {'FINISHED'}
140
141
142 class Retopo(bpy.types.Operator):
143     '''TODO - doc'''
144
145     bl_idname = "object.retopology"
146     bl_label = "Retopology from Grease Pencil"
147     bl_register = True
148     bl_undo = True
149
150     def execute(self, context):
151         import retopo
152         reload(retopo)
153         retopo.main()
154         return {'FINISHED'}
155
156
157 class ShapeTransfer(bpy.types.Operator):
158     '''Copy the active objects current shape to other selected objects with the same number of verts'''
159
160     bl_idname = "object.shape_key_transfer"
161     bl_label = "Transfer Shape Key"
162     bl_register = True
163     bl_undo = True
164
165     mode = EnumProperty(items=(
166                         ('OFFSET', "Offset", "Apply the relative positional offset"),
167                         ('RELATIVE_FACE', "Relative Face", "Calculate the geometricly relative position (using faces)."),
168                         ('RELATIVE_EDGE', "Relative Edge", "Calculate the geometricly relative position (using edges).")),
169                 name="Transformation Mode",
170                 description="Method to apply relative shape positions to the new shape",
171                 default='OFFSET')
172
173     use_clamp = BoolProperty(name="Clamp Offset",
174                 description="Clamp the transformation to the distance each vertex moves in the original shape.",
175                 default=False)
176
177     def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
178         def me_nos(verts):
179             return [v.normal.copy() for v in verts]
180
181         def me_cos(verts):
182             return [v.co.copy() for v in verts]
183
184         def ob_add_shape(ob, name):
185             me = ob.data
186             key = ob.add_shape_key(from_mix=False)
187             if len(me.shape_keys.keys) == 1:
188                 key = ob.add_shape_key(from_mix=False) # we need a rest
189             key.name = name
190             ob.active_shape_key_index = len(me.shape_keys.keys) - 1
191             ob.shape_key_lock = True
192
193         from Geometry import BarycentricTransform
194         from Mathutils import Vector
195
196         if use_clamp and mode == 'OFFSET':
197             use_clamp = False
198
199         me = ob_act.data
200         orig_key_name = ob_act.active_shape_key.name
201
202         orig_shape_coords = me_cos(ob_act.active_shape_key.data)
203
204         orig_normals = me_nos(me.verts)
205         orig_coords = me_cos(me.verts)
206
207         for ob_other in objects:
208             me_other = ob_other.data
209             if len(me_other.verts) != len(me.verts):
210                 self.report({'WARNING'}, "Skipping '%s', vertex count differs" % ob_other.name)
211                 continue
212
213             target_normals = me_nos(me_other.verts)
214             target_coords = me_cos(me_other.verts)
215
216             ob_add_shape(ob_other, orig_key_name)
217
218             # editing the final coords, only list that stores wrapped coords
219             target_shape_coords = [v.co for v in ob_other.active_shape_key.data]
220
221             median_coords = [[] for i in range(len(me.verts))]
222
223             # Method 1, edge
224             if mode == 'OFFSET':
225                 for i, vert_cos in enumerate(median_coords):
226                     vert_cos.append(target_coords[i] + (orig_shape_coords[i] - orig_coords[i]))
227
228             elif mode == 'RELATIVE_FACE':
229                 for face in me.faces:
230                     i1, i2, i3, i4 = face.verts_raw
231                     if i4 != 0:
232                         pt = BarycentricTransform(orig_shape_coords[i1],
233                             orig_coords[i4], orig_coords[i1], orig_coords[i2],
234                             target_coords[i4], target_coords[i1], target_coords[i2])
235                         median_coords[i1].append(pt)
236
237                         pt = BarycentricTransform(orig_shape_coords[i2],
238                             orig_coords[i1], orig_coords[i2], orig_coords[i3],
239                             target_coords[i1], target_coords[i2], target_coords[i3])
240                         median_coords[i2].append(pt)
241
242                         pt = BarycentricTransform(orig_shape_coords[i3],
243                             orig_coords[i2], orig_coords[i3], orig_coords[i4],
244                             target_coords[i2], target_coords[i3], target_coords[i4])
245                         median_coords[i3].append(pt)
246
247                         pt = BarycentricTransform(orig_shape_coords[i4],
248                             orig_coords[i3], orig_coords[i4], orig_coords[i1],
249                             target_coords[i3], target_coords[i4], target_coords[i1])
250                         median_coords[i4].append(pt)
251
252                     else:
253                         pt = BarycentricTransform(orig_shape_coords[i1],
254                             orig_coords[i3], orig_coords[i1], orig_coords[i2],
255                             target_coords[i3], target_coords[i1], target_coords[i2])
256                         median_coords[i1].append(pt)
257
258                         pt = BarycentricTransform(orig_shape_coords[i2],
259                             orig_coords[i1], orig_coords[i2], orig_coords[i3],
260                             target_coords[i1], target_coords[i2], target_coords[i3])
261                         median_coords[i2].append(pt)
262
263                         pt = BarycentricTransform(orig_shape_coords[i3],
264                             orig_coords[i2], orig_coords[i3], orig_coords[i1],
265                             target_coords[i2], target_coords[i3], target_coords[i1])
266                         median_coords[i3].append(pt)
267
268             elif mode == 'RELATIVE_EDGE':
269                 for ed in me.edges:
270                     i1, i2 = ed.verts
271                     v1, v2 = orig_coords[i1], orig_coords[i2]
272                     edge_length = (v1 - v2).length
273                     n1loc = v1 + orig_normals[i1] * edge_length
274                     n2loc = v2 + orig_normals[i2] * edge_length
275
276
277                     # now get the target nloc's
278                     v1_to, v2_to = target_coords[i1], target_coords[i2]
279                     edlen_to = (v1_to - v2_to).length
280                     n1loc_to = v1_to + target_normals[i1] * edlen_to
281                     n2loc_to = v2_to + target_normals[i2] * edlen_to
282
283                     pt = BarycentricTransform(orig_shape_coords[i1],
284                         v2, v1, n1loc,
285                         v2_to, v1_to, n1loc_to)
286                     median_coords[i1].append(pt)
287
288                     pt = BarycentricTransform(orig_shape_coords[i2],
289                         v1, v2, n2loc,
290                         v1_to, v2_to, n2loc_to)
291                     median_coords[i2].append(pt)
292
293             # apply the offsets to the new shape
294             from functools import reduce
295             VectorAdd = Vector.__add__
296
297             for i, vert_cos in enumerate(median_coords):
298                 if vert_cos:
299                     co = reduce(VectorAdd, vert_cos) / len(vert_cos)
300
301                     if use_clamp:
302                         # clamp to the same movement as the original
303                         # breaks copy between different scaled meshes.
304                         len_from = (orig_shape_coords[i] - orig_coords[i]).length
305                         ofs = co - target_coords[i]
306                         ofs.length = len_from
307                         co = target_coords[i] + ofs
308
309                     target_shape_coords[i][:] = co
310
311         return {'FINISHED'}
312
313     def poll(self, context):
314         obj = context.active_object
315         return (obj and obj.mode != 'EDIT')
316
317     def execute(self, context):
318         C = bpy.context
319         ob_act = C.active_object
320         if ob_act.active_shape_key is None:
321             self.report({'ERROR'}, "Active object has no shape key")
322             return {'CANCELLED'}
323         objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
324         return self._main(ob_act, objects, self.properties.mode, self.properties.use_clamp)
325
326
327 class JoinUVs(bpy.types.Operator):
328     '''Copy UV Layout to objects with matching geometry'''
329     bl_idname = "object.join_uvs"
330     bl_label = "Join as UVs"
331
332     def poll(self, context):
333         obj = context.active_object
334         return (obj and obj.type == 'MESH')
335
336     def _main(self, context):
337         import array
338         obj = context.active_object
339         mesh = obj.data
340
341         is_editmode = (obj.mode == 'EDIT')
342         if is_editmode:
343             bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
344
345         len_faces = len(mesh.faces)
346         
347         uv_array = array.array('f', [0.0] * 8) * len_faces # seems to be the fastest way to create an array
348         mesh.active_uv_texture.data.foreach_get("uv_raw", uv_array)
349
350         objects = context.selected_editable_objects[:]
351         
352         for obj_other in objects:
353             if obj_other.type == 'MESH':
354                 obj_other.data.tag = False
355
356         for obj_other in objects:
357             if obj_other != obj and obj_other.type == 'MESH':
358                 mesh_other = obj_other.data
359                 if mesh_other != mesh:
360                     if mesh_other.tag == False:
361                         mesh_other.tag = True
362                         
363                         if len(mesh_other.faces) != len_faces:
364                             self.report({'WARNING'}, "Object: %s, Mesh: '%s' has %d faces, expected %d\n" % (obj_other.name, mesh_other.name, len(mesh_other.faces), len_faces))
365                         else:
366                             uv_other = mesh_other.active_uv_texture
367                             if not uv_other:
368                                 mesh_other.uv_texture_add() # should return the texture it adds
369                                 uv_other = mesh_other.active_uv_texture
370                             
371                             # finally do the copy
372                             uv_other.data.foreach_set("uv_raw", uv_array)
373
374         if is_editmode:
375             bpy.ops.object.mode_set(mode='EDIT', toggle=False)
376
377     def execute(self, context):
378         self._main(context)
379         return {'FINISHED'}
380
381 if __name__ == "__main__":
382     bpy.ops.uv.simple_operator()
383
384
385 bpy.types.register(SelectPattern)
386 bpy.types.register(SubdivisionSet)
387 bpy.types.register(Retopo)
388 bpy.types.register(ShapeTransfer)
389 bpy.types.register(JoinUVs)