Bug Fix in drivers
[blender-addons-contrib.git] / mesh_mextrude_plus.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 ################################################################################
20 # Repeats extrusion + rotation + scale for one or more faces                   #
21
22 ################################################################################
23
24 bl_info = {
25     "name": "MExtrude Plus",
26     "author": "liero",
27     "version": (1, 2, 8),
28     "blender": (2, 6, 2),
29     "location": "View3D > Tool Shelf",
30     "description": "Repeat extrusions from faces to create organic shapes",
31     "warning": "",
32     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts",
33     "tracker_url": "http://projects.blender.org/tracker/index.php?"\
34         "func=detail&aid=28570",
35     "category": "Mesh"}
36
37 import  bpy, bmesh, mathutils, random
38 from random import gauss
39 from math import radians
40 from mathutils import Euler, Vector
41 from bpy.props import BoolProperty, FloatProperty, IntProperty, StringProperty
42
43 def vloc(self, r):
44     random.seed(self.ran + r)
45     return self.off * (1 + random.gauss(0, self.var1 / 3))
46
47 def vrot(self,r):
48     random.seed(self.ran+r)
49     return Euler((radians(self.rotx) + random.gauss(0, self.var2 / 3), \
50         radians(self.roty) + random.gauss(0, self.var2 / 3), \
51         radians(self.rotz) + random.gauss(0,self.var2 / 3)), 'XYZ')
52
53 def vsca(self, r):
54     random.seed(self.ran + r)
55     return [self.sca * (1 + random.gauss(0, self.var3 / 3))] * 3
56 # centroide de una seleccion de vertices
57 def centro(ver):
58     vvv = [v for v in ver if v.select]
59     if not vvv or len(vvv) == len(ver): return ('error')
60     x = sum([round(v.co[0],4) for v in vvv]) / len(vvv)
61     y = sum([round(v.co[1],4) for v in vvv]) / len(vvv)
62     z = sum([round(v.co[2],4) for v in vvv]) / len(vvv)
63     return (x,y,z)
64
65 # recuperar el estado original del objeto
66 def volver(obj, copia, om, msm, msv):
67     for i in copia: obj.data.vertices[i].select = True
68     bpy.context.tool_settings.mesh_select_mode = msm
69     for i in range(len(msv)):
70         obj.modifiers[i].show_viewport = msv[i]
71
72 class MExtrude(bpy.types.Operator):
73     bl_idname = 'object.mextrude'
74     bl_label = 'MExtrude'
75     bl_description = 'Multi Extrude'
76     bl_options = {'REGISTER', 'UNDO'}
77
78     off = FloatProperty(name='Offset', min=-2, soft_min=0.001, \
79         soft_max=2, max=5, default=.5, description='Translation')
80     rotx = FloatProperty(name='Rot X', min=-85, soft_min=-30, \
81         soft_max=30, max=85, default=0, description='X rotation')
82     roty = FloatProperty(name='Rot Y', min=-85, soft_min=-30, \
83         soft_max=30, max=85, default=0, description='Y rotation')
84     rotz = FloatProperty(name='Rot Z', min=-85, soft_min=-30, \
85         soft_max=30, max=85, default=-0, description='Z rotation')
86     sca = FloatProperty(name='Scale', min=0.1, soft_min=0.5, \
87         soft_max=1.2, max =2, default=.9, description='Scaling')
88     var1 = FloatProperty(name='Offset Var', min=-5, soft_min=-1, \
89         soft_max=1, max=5, default=0, description='Offset variation')
90     var2 = FloatProperty(name='Rotation Var', min=-5, soft_min=-1, \
91         soft_max=1, max=5, default=0, description='Rotation variation')
92     var3 = FloatProperty(name='Scale Noise', min=-5, soft_min=-1, \
93         soft_max=1, max=5, default=0, description='Scaling noise')
94     num = IntProperty(name='Repeat', min=1, max=50, soft_max=100, \
95         default=5, description='Repetitions')
96     ran = IntProperty(name='Seed', min=-9999, max=9999, default=0, \
97         description='Seed to feed random values')
98
99     @classmethod
100     def poll(cls, context):
101         return (context.object and context.object.type == 'MESH')
102
103     def draw(self, context):
104         layout = self.layout
105         column = layout.column(align=True)
106         column.label(text='Transformations:')
107         column.prop(self, 'off', slider=True)
108         column.prop(self, 'rotx', slider=True)
109         column.prop(self, 'roty', slider=True)
110         column.prop(self, 'rotz', slider=True)
111         column.prop(self, 'sca', slider=True)
112         column = layout.column(align=True)
113         column.label(text='Variation settings:')
114         column.prop(self, 'var1', slider=True)
115         column.prop(self, 'var2', slider=True)
116         column.prop(self, 'var3', slider=True)
117         column.prop(self, 'ran')
118         column = layout.column(align=False)
119         column.prop(self, 'num')
120
121     def execute(self, context):
122         obj = bpy.context.object
123         data, om, msv =  obj.data, obj.mode, []
124         msm = bpy.context.tool_settings.mesh_select_mode
125         bpy.context.tool_settings.mesh_select_mode = [False, False, True]
126
127         # disable modifiers
128         for i in range(len(obj.modifiers)):
129             msv.append(obj.modifiers[i].show_viewport)
130             obj.modifiers[i].show_viewport = False
131
132         # isolate selection
133         bpy.ops.object.mode_set()
134         bpy.ops.object.mode_set(mode='EDIT')
135         total = data.total_face_sel
136         try: bpy.ops.mesh.select_inverse()
137         except: bpy.ops.mesh.select_all(action='INVERT')
138         bpy.ops.object.vertex_group_assign(new=True)
139         bpy.ops.mesh.hide()
140
141         # faces loop
142         for i in range(total):
143             bpy.ops.object.editmode_toggle()
144             # is bmesh..?
145             try:
146                 faces = data.polygons
147             except:
148                 faces = data.faces
149             for f in faces:
150                 if not f.hide:
151                     f.select = True
152                     break
153             norm = f.normal.copy()
154             rot, loc = vrot(self, i), vloc(self, i)
155             norm.rotate(obj.matrix_world.to_quaternion())
156             bpy.ops.object.editmode_toggle()
157
158             # extrude loop
159             for a in range(self.num):
160                 norm.rotate(rot)
161                 r2q = rot.to_quaternion()
162                 bpy.ops.mesh.extrude_faces_move()
163                 bpy.ops.transform.translate(value = norm * loc)
164                 bpy.ops.transform.rotate(value = [r2q.angle], axis = r2q.axis)
165                 bpy.ops.transform.resize(value = vsca(self, i + a))
166             bpy.ops.object.vertex_group_remove_from()
167             bpy.ops.mesh.hide()
168
169         # keep just last faces selected
170         bpy.ops.mesh.reveal()
171         bpy.ops.object.vertex_group_deselect()
172         bpy.ops.object.vertex_group_remove()
173         bpy.ops.object.mode_set()
174
175
176         # restore user settings
177         for i in range(len(obj.modifiers)): 
178             obj.modifiers[i].show_viewport = msv[i]
179         bpy.context.tool_settings.mesh_select_mode = msm
180         bpy.ops.object.mode_set(mode=om)
181         if not total:
182             self.report({'INFO'}, 'Select one or more faces...')
183         return{'FINISHED'}
184
185 class BB(bpy.types.Operator):
186     bl_idname = 'object.mesh2bones'
187     bl_label = 'Create Armature'
188     bl_description = 'Create an armature rig based on mesh selection'
189     bl_options = {'REGISTER', 'UNDO'}
190
191     numb = IntProperty(name='Max Bones', min=1, max=1000, soft_max=100, default=5, description='Max number of bones')
192     skip = IntProperty(name='Skip Loops', min=0, max=5, default=0, description='Skip some edges to get longer bones')
193     long = FloatProperty(name='Min Length', min=0.01, max=5, default=0.15, description='Discard bones shorter than this value')
194     ika = BoolProperty(name='IK constraints', default=True, description='Add IK constraint and Empty as target')
195     rotk = BoolProperty(name='IK Rotation', default=False, description='IK constraint follows target rotation')
196     auto = BoolProperty(name='Auto weight', default=True, description='Auto weight and assign vertices')
197     env = BoolProperty(name='Envelopes', default=False, description='Use envelopes instead of weights')
198     rad = FloatProperty(name='Radius', min=0.01, max=5, default=0.25, description='Envelope deform radius')
199     nam = StringProperty(name='', default='hueso', description='Default name for bones / groups')
200
201     @classmethod
202     def poll(cls, context):
203         obj = bpy.context.object
204         return (obj and obj.type == 'MESH')
205
206     def draw(self, context):
207         layout = self.layout
208         column = layout.column(align=True)
209         column.prop(self,'numb')
210         column.prop(self,'skip')
211         column.prop(self,'long')
212         column = layout.column(align=True)
213         column.prop(self,'auto')
214         if self.auto:
215             column.prop(self,'env')
216             if self.env: column.prop(self,'rad')
217         column.prop(self,'ika')
218         if self.ika: column.prop(self,'rotk')
219         layout.prop(self,'nam')
220
221     def execute(self, context):
222         scn = bpy.context.scene
223         obj = bpy.context.object
224         fac = obj.data.polygons
225         # guardar estado y seleccion
226         ver, om = obj.data.vertices, obj.mode
227         msm, msv = list(bpy.context.tool_settings.mesh_select_mode), []
228         for i in range(len(obj.modifiers)):
229             msv.append(obj.modifiers[i].show_viewport)
230             obj.modifiers[i].show_viewport = False
231         bpy.ops.object.mode_set(mode='OBJECT')
232         copia = [v.index for v in ver if v.select]
233         sel = [f.index for f in fac if f.select]
234         bpy.ops.object.mode_set(mode='EDIT')
235         bpy.context.tool_settings.mesh_select_mode = [True, False, False]
236         bpy.ops.mesh.select_all(action='DESELECT')
237         txt = 'Select a face or a vertex where the chain should end...'
238         bpy.ops.object.mode_set(mode='OBJECT')
239
240         # crear rig unico -desde vertice/s y no desde caras-
241         if sel == []:
242             sel = ['simple']
243             for i in copia:
244                 obj.data.vertices[i].select = True
245
246         # reciclar el rig en cada refresco...
247         try: scn.objects.unlink(rig)
248         except: pass
249
250         # loop de caras
251         for i in sel:
252             if sel[0] != 'simple':
253                 for v in ver: v.select = False
254                 for v in fac[i].vertices: ver[v].select = True
255             lista = [centro(ver)]
256             if lista[0] == 'error':
257                 self.report({'INFO'}, txt)
258                 volver(obj, copia, om, msm, msv)
259                 return{'FINISHED'}
260
261             # crear lista de coordenadas para los huesos
262             scn.objects.active = obj
263             for t in range(self.numb):
264                 bpy.ops.object.mode_set(mode='EDIT')
265                 bpy.ops.object.vertex_group_assign(new=True)
266                 for m in range(self.skip+1):
267                     bpy.ops.mesh.select_more()
268                 bpy.ops.object.vertex_group_deselect()
269                 bpy.ops.object.mode_set(mode='OBJECT')
270                 lista.append(centro(ver))
271                 bpy.ops.object.mode_set(mode='EDIT')
272                 bpy.ops.object.vertex_group_select()
273                 bpy.ops.object.vertex_group_remove()
274                 if lista[-1] == 'error':
275                     self.numb = t
276                     lista.pop()
277                     break
278                 if len(lista) > 1:
279                     delta = Vector(lista[-2]) - Vector(lista[-1])
280                     if delta.length < self.long:
281                         lista.pop()
282
283             bpy.ops.mesh.select_all(action='DESELECT')
284             bpy.ops.object.mode_set(mode='OBJECT')
285
286             # crear armature y copiar transformaciones del objeto
287             lista.reverse()
288             if len(lista) < 2:
289                 self.report({'INFO'}, txt)
290                 volver(obj, copia, om, msm, msv)
291                 return{'FINISHED'}
292             try: arm
293             except:
294                 arm = bpy.data.armatures.new('arm')
295                 if self.env: arm.draw_type = 'ENVELOPE'
296                 else: arm.draw_type = 'STICK'
297                 rig = bpy.data.objects.new(obj.name+'_rig', arm)
298                 rig.matrix_world = obj.matrix_world
299                 if self.env: rig.draw_type = 'WIRE'
300                 rig.show_x_ray = True
301                 scn.objects.link(rig)
302             scn.objects.active = rig
303             bpy.ops.object.mode_set(mode='EDIT')
304
305             # crear la cadena de huesos desde la lista
306             for i in range(len(lista)-1):
307                 bon = arm.edit_bones.new(self.nam+'.000')
308                 bon.use_connect = True
309                 bon.tail = lista[i+1]
310                 bon.head = lista[i]
311                 if self.auto and self.env:
312                     bon.tail_radius = self.rad
313                     bon.head_radius = self.rad
314                 if i: bon.parent = padre
315                 padre = bon
316             bpy.ops.object.mode_set(mode='OBJECT')
317
318             # crear IK constraint y un Empty como target
319             if self.ika:
320                 ik = rig.data.bones[-1].name
321                 loc = rig.matrix_world * Vector(lista[-1])
322                 rot = rig.matrix_world * rig.data.bones[-1].matrix_local
323                 bpy.ops.object.add(type='EMPTY', location=loc, rotation=rot.to_euler())
324                 tgt = bpy.context.object
325                 tgt.name = obj.name+'_target.000'
326                 if len(sel) > 1:
327                     try: mega
328                     except:
329                         bpy.ops.object.add(type='EMPTY', location = obj.location)
330                         mega = bpy.context.object
331                         mega.name = obj.name+'_Controls'
332                         tgt.select = True
333                     scn.objects.active = mega
334                     bpy.ops.object.parent_set(type='OBJECT')
335
336                 scn.objects.active = rig
337                 bpy.ops.object.mode_set(mode='POSE')
338                 con = rig.pose.bones[ik].constraints.new('IK')
339                 con.target = tgt
340                 if self.rotk: con.use_rotation = True
341                 tgt.select = False
342                 bpy.ops.object.mode_set(mode='OBJECT')
343
344         obj.select = True
345         if self.auto:
346             if self.env: bpy.ops.object.parent_set(type='ARMATURE_ENVELOPE')
347             else: bpy.ops.object.parent_set(type='ARMATURE_AUTO')
348         scn.objects.active = obj
349         volver(obj, copia, om, msm, msv)
350         return{'FINISHED'}
351
352
353
354 class BotonME(bpy.types.Panel):
355     bl_label = 'Multi Extrude Plus'
356     bl_space_type = 'VIEW_3D'
357     bl_region_type = 'TOOLS'
358
359     def draw(self, context):
360         layout = self.layout
361         layout.operator('object.mextrude')
362         layout.operator('object.mesh2bones')
363
364 def register():
365     bpy.utils.register_class(MExtrude)
366     bpy.utils.register_class(BotonME)
367     bpy.utils.register_class(BB)
368
369 def unregister():
370     bpy.utils.unregister_class(MExtrude)
371     bpy.utils.unregister_class(BotonME)
372     bpy.utils.unregister_class(BB)
373
374
375 if __name__ == '__main__':
376     register()