fix for error in line 164
[blender-addons-contrib.git] / mesh_extra_tools / 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 mextrude_help(bpy.types.Operator):
186         bl_idname = 'help.mextrude'
187         bl_label = ''
188
189         def draw(self, context):
190                 layout = self.layout
191                 layout.label('To use:')
192                 layout.label('Make a selection or selection of Faces.')
193                 layout.label('Extrude, rotate extrusions & more.')
194                 layout.label('For rigging capabilities, see Multi Extrude panel.')
195         
196         def execute(self, context):
197                 return {'FINISHED'}
198
199         def invoke(self, context, event):
200                 return context.window_manager.invoke_popup(self, width = 300)
201                 
202 class BB(bpy.types.Operator):
203     bl_idname = 'object.mesh2bones'
204     bl_label = 'Create Armature'
205     bl_description = 'Create an armature rig based on mesh selection'
206     bl_options = {'REGISTER', 'UNDO'}
207
208     numb = IntProperty(name='Max Bones', min=1, max=1000, soft_max=100, default=5, description='Max number of bones')
209     skip = IntProperty(name='Skip Loops', min=0, max=5, default=0, description='Skip some edges to get longer bones')
210     long = FloatProperty(name='Min Length', min=0.01, max=5, default=0.15, description='Discard bones shorter than this value')
211     ika = BoolProperty(name='IK constraints', default=True, description='Add IK constraint and Empty as target')
212     rotk = BoolProperty(name='IK Rotation', default=False, description='IK constraint follows target rotation')
213     auto = BoolProperty(name='Auto weight', default=True, description='Auto weight and assign vertices')
214     env = BoolProperty(name='Envelopes', default=False, description='Use envelopes instead of weights')
215     rad = FloatProperty(name='Radius', min=0.01, max=5, default=0.25, description='Envelope deform radius')
216     nam = StringProperty(name='', default='hueso', description='Default name for bones / groups')
217
218     @classmethod
219     def poll(cls, context):
220         obj = bpy.context.object
221         return (obj and obj.type == 'MESH')
222
223     def draw(self, context):
224         layout = self.layout
225         column = layout.column(align=True)
226         column.prop(self,'numb')
227         column.prop(self,'skip')
228         column.prop(self,'long')
229         column = layout.column(align=True)
230         column.prop(self,'auto')
231         if self.auto:
232             column.prop(self,'env')
233             if self.env: column.prop(self,'rad')
234         column.prop(self,'ika')
235         if self.ika: column.prop(self,'rotk')
236         layout.prop(self,'nam')
237
238     def execute(self, context):
239         scn = bpy.context.scene
240         obj = bpy.context.object
241         fac = obj.data.polygons
242         # guardar estado y seleccion
243         ver, om = obj.data.vertices, obj.mode
244         msm, msv = list(bpy.context.tool_settings.mesh_select_mode), []
245         for i in range(len(obj.modifiers)):
246             msv.append(obj.modifiers[i].show_viewport)
247             obj.modifiers[i].show_viewport = False
248         bpy.ops.object.mode_set(mode='OBJECT')
249         copia = [v.index for v in ver if v.select]
250         sel = [f.index for f in fac if f.select]
251         bpy.ops.object.mode_set(mode='EDIT')
252         bpy.context.tool_settings.mesh_select_mode = [True, False, False]
253         bpy.ops.mesh.select_all(action='DESELECT')
254         txt = 'Select a face or a vertex where the chain should end...'
255         bpy.ops.object.mode_set(mode='OBJECT')
256
257         # crear rig unico -desde vertice/s y no desde caras-
258         if sel == []:
259             sel = ['simple']
260             for i in copia:
261                 obj.data.vertices[i].select = True
262
263         # reciclar el rig en cada refresco...
264         try: scn.objects.unlink(rig)
265         except: pass
266
267         # loop de caras
268         for i in sel:
269             if sel[0] != 'simple':
270                 for v in ver: v.select = False
271                 for v in fac[i].vertices: ver[v].select = True
272             lista = [centro(ver)]
273             if lista[0] == 'error':
274                 self.report({'INFO'}, txt)
275                 volver(obj, copia, om, msm, msv)
276                 return{'FINISHED'}
277
278             # crear lista de coordenadas para los huesos
279             scn.objects.active = obj
280             for t in range(self.numb):
281                 bpy.ops.object.mode_set(mode='EDIT')
282                 bpy.ops.object.vertex_group_assign(new=True)
283                 for m in range(self.skip+1):
284                     bpy.ops.mesh.select_more()
285                 bpy.ops.object.vertex_group_deselect()
286                 bpy.ops.object.mode_set(mode='OBJECT')
287                 lista.append(centro(ver))
288                 bpy.ops.object.mode_set(mode='EDIT')
289                 bpy.ops.object.vertex_group_select()
290                 bpy.ops.object.vertex_group_remove()
291                 if lista[-1] == 'error':
292                     self.numb = t
293                     lista.pop()
294                     break
295                 if len(lista) > 1:
296                     delta = Vector(lista[-2]) - Vector(lista[-1])
297                     if delta.length < self.long:
298                         lista.pop()
299
300             bpy.ops.mesh.select_all(action='DESELECT')
301             bpy.ops.object.mode_set(mode='OBJECT')
302
303             # crear armature y copiar transformaciones del objeto
304             lista.reverse()
305             if len(lista) < 2:
306                 self.report({'INFO'}, txt)
307                 volver(obj, copia, om, msm, msv)
308                 return{'FINISHED'}
309             try: arm
310             except:
311                 arm = bpy.data.armatures.new('arm')
312                 if self.env: arm.draw_type = 'ENVELOPE'
313                 else: arm.draw_type = 'STICK'
314                 rig = bpy.data.objects.new(obj.name+'_rig', arm)
315                 rig.matrix_world = obj.matrix_world
316                 if self.env: rig.draw_type = 'WIRE'
317                 rig.show_x_ray = True
318                 scn.objects.link(rig)
319             scn.objects.active = rig
320             bpy.ops.object.mode_set(mode='EDIT')
321
322             # crear la cadena de huesos desde la lista
323             for i in range(len(lista)-1):
324                 bon = arm.edit_bones.new(self.nam+'.000')
325                 bon.use_connect = True
326                 bon.tail = lista[i+1]
327                 bon.head = lista[i]
328                 if self.auto and self.env:
329                     bon.tail_radius = self.rad
330                     bon.head_radius = self.rad
331                 if i: bon.parent = padre
332                 padre = bon
333             bpy.ops.object.mode_set(mode='OBJECT')
334
335             # crear IK constraint y un Empty como target
336             if self.ika:
337                 ik = rig.data.bones[-1].name
338                 loc = rig.matrix_world * Vector(lista[-1])
339                 rot = rig.matrix_world * rig.data.bones[-1].matrix_local
340                 bpy.ops.object.add(type='EMPTY', location=loc, rotation=rot.to_euler())
341                 tgt = bpy.context.object
342                 tgt.name = obj.name+'_target.000'
343                 if len(sel) > 1:
344                     try: mega
345                     except:
346                         bpy.ops.object.add(type='EMPTY', location = obj.location)
347                         mega = bpy.context.object
348                         mega.name = obj.name+'_Controls'
349                         tgt.select = True
350                     scn.objects.active = mega
351                     bpy.ops.object.parent_set(type='OBJECT')
352
353                 scn.objects.active = rig
354                 bpy.ops.object.mode_set(mode='POSE')
355                 con = rig.pose.bones[ik].constraints.new('IK')
356                 con.target = tgt
357                 if self.rotk: con.use_rotation = True
358                 tgt.select = False
359                 bpy.ops.object.mode_set(mode='OBJECT')
360
361         obj.select = True
362         if self.auto:
363             if self.env: bpy.ops.object.parent_set(type='ARMATURE_ENVELOPE')
364             else: bpy.ops.object.parent_set(type='ARMATURE_AUTO')
365         scn.objects.active = obj
366         volver(obj, copia, om, msm, msv)
367         return{'FINISHED'}
368 '''
369 def register():
370     bpy.utils.register_class(MExtrude)
371
372     bpy.utils.register_class(BB)
373
374 def unregister():
375     bpy.utils.unregister_class(MExtrude)
376
377     bpy.utils.unregister_class(BB)
378
379
380 if __name__ == '__main__':
381     register()
382 '''