1
# ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17
# ##### END GPL LICENSE BLOCK #####
19
################################################################################
20
# Repeats extrusion + rotation + scale for one or more faces #
22
################################################################################
29
"location": "View3D > Tool Shelf",
30
"description": "Repeat extrusions from faces to create organic shapes",
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",
38
from random import gauss
39
from math import radians
40
from mathutils import Euler
41
from bpy.props import FloatProperty, IntProperty
44
random.seed(self.ran + r)
45
return self.off * (1 + random.gauss(0, self.var1 / 3))
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')
54
random.seed(self.ran + r)
55
return [self.sca * (1 + random.gauss(0, self.var3 / 3))] * 3
57
class MExtrude(bpy.types.Operator):
58
bl_idname = 'object.mextrude'
60
bl_description = 'Multi Extrude'
61
bl_options = {'REGISTER', 'UNDO'}
63
off = FloatProperty(name='Offset', min=-2, soft_min=0.001, \
64
soft_max=2, max=5, default=.5, description='Translation')
65
rotx = FloatProperty(name='Rot X', min=-85, soft_min=-30, \
66
soft_max=30, max=85, default=0, description='X rotation')
67
roty = FloatProperty(name='Rot Y', min=-85, soft_min=-30, \
68
soft_max=30, max=85, default=0, description='Y rotation')
69
rotz = FloatProperty(name='Rot Z', min=-85, soft_min=-30, \
70
soft_max=30, max=85, default=-0, description='Z rotation')
71
sca = FloatProperty(name='Scale', min=0.1, soft_min=0.5, \
72
soft_max=1.2, max =2, default=.9, description='Scaling')
73
var1 = FloatProperty(name='Offset Var', min=-5, soft_min=-1, \
74
soft_max=1, max=5, default=0, description='Offset variation')
75
var2 = FloatProperty(name='Rotation Var', min=-5, soft_min=-1, \
76
soft_max=1, max=5, default=0, description='Rotation variation')
77
var3 = FloatProperty(name='Scale Noise', min=-5, soft_min=-1, \
78
soft_max=1, max=5, default=0, description='Scaling noise')
79
num = IntProperty(name='Repeat', min=1, max=50, soft_max=100, \
80
default=5, description='Repetitions')
81
ran = IntProperty(name='Seed', min=-9999, max=9999, default=0, \
82
description='Seed to feed random values')
85
def poll(cls, context):
86
return (context.object and context.object.type == 'MESH')
88
def draw(self, context):
90
column = layout.column(align=True)
91
column.label(text='Transformations:')
92
column.prop(self, 'off', slider=True)
93
column.prop(self, 'rotx', slider=True)
94
column.prop(self, 'roty', slider=True)
95
column.prop(self, 'rotz', slider=True)
96
column.prop(self, 'sca', slider=True)
97
column = layout.column(align=True)
98
column.label(text='Variation settings:')
99
column.prop(self, 'var1', slider=True)
100
column.prop(self, 'var2', slider=True)
101
column.prop(self, 'var3', slider=True)
102
column.prop(self, 'ran')
103
column = layout.column(align=False)
104
column.prop(self, 'num')
106
def execute(self, context):
107
obj = bpy.context.object
108
data, om, msv = obj.data, obj.mode, []
109
msm = bpy.context.tool_settings.mesh_select_mode
110
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
113
for i in range(len(obj.modifiers)):
114
msv.append(obj.modifiers[i].show_viewport)
115
obj.modifiers[i].show_viewport = False
118
bpy.ops.object.mode_set()
119
bpy.ops.object.mode_set(mode='EDIT')
120
total = data.total_face_sel
121
try: bpy.ops.mesh.select_inverse()
122
except: bpy.ops.mesh.select_all(action='INVERT')
123
bpy.ops.object.vertex_group_assign(new=True)
127
for i in range(total):
128
bpy.ops.object.editmode_toggle()
131
faces = data.polygons
138
norm = f.normal.copy()
139
rot, loc = vrot(self, i), vloc(self, i)
140
norm.rotate(obj.matrix_world.to_quaternion())
141
bpy.ops.object.editmode_toggle()
144
for a in range(self.num):
146
r2q = rot.to_quaternion()
147
bpy.ops.mesh.extrude_faces_move()
148
bpy.ops.transform.translate(value = norm * loc)
149
bpy.ops.transform.rotate(value = [r2q.angle], axis = r2q.axis)
150
bpy.ops.transform.resize(value = vsca(self, i + a))
151
bpy.ops.object.vertex_group_remove_from()
154
# keep just last faces selected
155
bpy.ops.mesh.reveal()
156
bpy.ops.object.vertex_group_deselect()
157
bpy.ops.object.vertex_group_remove()
158
bpy.ops.object.mode_set()
161
# restore user settings
162
for i in range(len(obj.modifiers)):
163
obj.modifiers[i].show_viewport = msv[i]
164
bpy.context.tool_settings.mesh_select_mode = msm
165
bpy.ops.object.mode_set(mode=om)
167
self.report({'INFO'}, 'Select one or more faces...')
170
class BotonME(bpy.types.Panel):
171
bl_label = 'Multi Extrude'
172
bl_space_type = 'VIEW_3D'
173
bl_region_type = 'TOOLS'
175
def draw(self, context):
177
layout.operator('object.mextrude')
180
bpy.utils.register_class(MExtrude)
181
bpy.utils.register_class(BotonME)
184
bpy.utils.unregister_class(MExtrude)
185
bpy.utils.unregister_class(BotonME)
187
if __name__ == '__main__':