Merge branch 'blender-v3.3-release'
[blender-addons.git] / mesh_tools / mesh_mextrude_plus.py
blobc811db51805a7f66590f39d246305af1169c50a1
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Repeats extrusion + rotation + scale for one or more faces
4 # Original code by liero
5 # Update by Jimmy Hazevoet 03/2017 for Blender 2.79
6 # normal rotation, probability, scaled offset, object coords, initial and per step noise
9 bl_info = {
10 "name": "MExtrude Plus1",
11 "author": "liero, Jimmy Hazevoet",
12 "version": (1, 3, 0),
13 "blender": (2, 77, 0),
14 "location": "View3D > Tool Shelf",
15 "description": "Repeat extrusions from faces to create organic shapes",
16 "warning": "",
17 "doc_url": "",
18 "category": "Mesh",
22 import bpy
23 import bmesh
24 import random
25 from bpy.types import Operator
26 from random import gauss
27 from math import radians
28 from mathutils import (
29 Euler, Vector,
31 from bpy.props import (
32 FloatProperty,
33 IntProperty,
34 BoolProperty,
38 def gloc(self, r):
39 return Vector((self.offx, self.offy, self.offz))
42 def vloc(self, r):
43 random.seed(self.ran + r)
44 return self.off * (1 + gauss(0, self.var1 / 3))
47 def nrot(self, n):
48 return Euler((radians(self.nrotx) * n[0],
49 radians(self.nroty) * n[1],
50 radians(self.nrotz) * n[2]), 'XYZ')
53 def vrot(self, r):
54 random.seed(self.ran + r)
55 return Euler((radians(self.rotx) + gauss(0, self.var2 / 3),
56 radians(self.roty) + gauss(0, self.var2 / 3),
57 radians(self.rotz) + gauss(0, self.var2 / 3)), 'XYZ')
60 def vsca(self, r):
61 random.seed(self.ran + r)
62 return self.sca * (1 + gauss(0, self.var3 / 3))
65 class MExtrude(Operator):
66 bl_idname = "object.mextrude"
67 bl_label = "Multi Extrude"
68 bl_description = ("Extrude selected Faces with Rotation,\n"
69 "Scaling, Variation, Randomization")
70 bl_options = {"REGISTER", "UNDO", "PRESET"}
72 off: FloatProperty(
73 name="Offset",
74 soft_min=0.001, soft_max=10,
75 min=-100, max=100,
76 default=1.0,
77 description="Translation"
79 offx: FloatProperty(
80 name="Loc X",
81 soft_min=-10.0, soft_max=10.0,
82 min=-100.0, max=100.0,
83 default=0.0,
84 description="Global Translation X"
86 offy: FloatProperty(
87 name="Loc Y",
88 soft_min=-10.0, soft_max=10.0,
89 min=-100.0, max=100.0,
90 default=0.0,
91 description="Global Translation Y"
93 offz: FloatProperty(
94 name="Loc Z",
95 soft_min=-10.0, soft_max=10.0,
96 min=-100.0, max=100.0,
97 default=0.0,
98 description="Global Translation Z"
100 rotx: FloatProperty(
101 name="Rot X",
102 min=-85, max=85,
103 soft_min=-30, soft_max=30,
104 default=0,
105 description="X Rotation"
107 roty: FloatProperty(
108 name="Rot Y",
109 min=-85, max=85,
110 soft_min=-30,
111 soft_max=30,
112 default=0,
113 description="Y Rotation"
115 rotz: FloatProperty(
116 name="Rot Z",
117 min=-85, max=85,
118 soft_min=-30, soft_max=30,
119 default=-0,
120 description="Z Rotation"
122 nrotx: FloatProperty(
123 name="N Rot X",
124 min=-85, max=85,
125 soft_min=-30, soft_max=30,
126 default=0,
127 description="Normal X Rotation"
129 nroty: FloatProperty(
130 name="N Rot Y",
131 min=-85, max=85,
132 soft_min=-30, soft_max=30,
133 default=0,
134 description="Normal Y Rotation"
136 nrotz: FloatProperty(
137 name="N Rot Z",
138 min=-85, max=85,
139 soft_min=-30, soft_max=30,
140 default=-0,
141 description="Normal Z Rotation"
143 sca: FloatProperty(
144 name="Scale",
145 min=0.01, max=10,
146 soft_min=0.5, soft_max=1.5,
147 default=1.0,
148 description="Scaling of the selected faces after extrusion"
150 var1: FloatProperty(
151 name="Offset Var", min=-10, max=10,
152 soft_min=-1, soft_max=1,
153 default=0,
154 description="Offset variation"
156 var2: FloatProperty(
157 name="Rotation Var",
158 min=-10, max=10,
159 soft_min=-1, soft_max=1,
160 default=0,
161 description="Rotation variation"
163 var3: FloatProperty(
164 name="Scale Noise",
165 min=-10, max=10,
166 soft_min=-1, soft_max=1,
167 default=0,
168 description="Scaling noise"
170 var4: IntProperty(
171 name="Probability",
172 min=0, max=100,
173 default=100,
174 description="Probability, chance of extruding a face"
176 num: IntProperty(
177 name="Repeat",
178 min=1, max=500,
179 soft_max=100,
180 default=5,
181 description="Repetitions"
183 ran: IntProperty(
184 name="Seed",
185 min=-9999, max=9999,
186 default=0,
187 description="Seed to feed random values"
189 opt1: BoolProperty(
190 name="Polygon coordinates",
191 default=True,
192 description="Polygon coordinates, Object coordinates"
194 opt2: BoolProperty(
195 name="Proportional offset",
196 default=False,
197 description="Scale * Offset"
199 opt3: BoolProperty(
200 name="Per step rotation noise",
201 default=False,
202 description="Per step rotation noise, Initial rotation noise"
204 opt4: BoolProperty(
205 name="Per step scale noise",
206 default=False,
207 description="Per step scale noise, Initial scale noise"
210 @classmethod
211 def poll(cls, context):
212 obj = context.object
213 return (obj and obj.type == 'MESH')
215 def draw(self, context):
216 layout = self.layout
217 col = layout.column(align=True)
218 col.label(text="Transformations:")
219 col.prop(self, "off", slider=True)
220 col.prop(self, "offx", slider=True)
221 col.prop(self, "offy", slider=True)
222 col.prop(self, "offz", slider=True)
224 col = layout.column(align=True)
225 col.prop(self, "rotx", slider=True)
226 col.prop(self, "roty", slider=True)
227 col.prop(self, "rotz", slider=True)
228 col.prop(self, "nrotx", slider=True)
229 col.prop(self, "nroty", slider=True)
230 col.prop(self, "nrotz", slider=True)
231 col = layout.column(align=True)
232 col.prop(self, "sca", slider=True)
234 col = layout.column(align=True)
235 col.label(text="Variation settings:")
236 col.prop(self, "var1", slider=True)
237 col.prop(self, "var2", slider=True)
238 col.prop(self, "var3", slider=True)
239 col.prop(self, "var4", slider=True)
240 col.prop(self, "ran")
241 col = layout.column(align=False)
242 col.prop(self, 'num')
244 col = layout.column(align=True)
245 col.label(text="Options:")
246 col.prop(self, "opt1")
247 col.prop(self, "opt2")
248 col.prop(self, "opt3")
249 col.prop(self, "opt4")
251 def execute(self, context):
252 obj = bpy.context.object
253 om = obj.mode
254 bpy.context.tool_settings.mesh_select_mode = [False, False, True]
255 origin = Vector([0.0, 0.0, 0.0])
257 # bmesh operations
258 bpy.ops.object.mode_set()
259 bm = bmesh.new()
260 bm.from_mesh(obj.data)
261 sel = [f for f in bm.faces if f.select]
263 after = []
265 # faces loop
266 for i, of in enumerate(sel):
267 nro = nrot(self, of.normal)
268 off = vloc(self, i)
269 loc = gloc(self, i)
270 of.normal_update()
272 # initial rotation noise
273 if self.opt3 is False:
274 rot = vrot(self, i)
275 # initial scale noise
276 if self.opt4 is False:
277 s = vsca(self, i)
279 # extrusion loop
280 for r in range(self.num):
281 # random probability % for extrusions
282 if self.var4 > int(random.random() * 100):
283 nf = of.copy()
284 nf.normal_update()
285 no = nf.normal.copy()
287 # face/obj coördinates
288 if self.opt1 is True:
289 ce = nf.calc_center_bounds()
290 else:
291 ce = origin
293 # per step rotation noise
294 if self.opt3 is True:
295 rot = vrot(self, i + r)
296 # per step scale noise
297 if self.opt4 is True:
298 s = vsca(self, i + r)
300 # proportional, scale * offset
301 if self.opt2 is True:
302 off = s * off
304 for v in nf.verts:
305 v.co -= ce
306 v.co.rotate(nro)
307 v.co.rotate(rot)
308 v.co += ce + loc + no * off
309 v.co = v.co.lerp(ce, 1 - s)
311 # extrude code from TrumanBlending
312 for a, b in zip(of.loops, nf.loops):
313 sf = bm.faces.new((a.vert, a.link_loop_next.vert,
314 b.link_loop_next.vert, b.vert))
315 sf.normal_update()
316 bm.faces.remove(of)
317 of = nf
319 after.append(of)
321 for v in bm.verts:
322 v.select = False
323 for e in bm.edges:
324 e.select = False
326 for f in after:
327 if f not in sel:
328 f.select = True
329 else:
330 f.select = False
332 bm.to_mesh(obj.data)
333 obj.data.update()
335 # restore user settings
336 bpy.ops.object.mode_set(mode=om)
338 if not len(sel):
339 self.report({"WARNING"},
340 "No suitable Face selection found. Operation cancelled")
341 return {'CANCELLED'}
343 return {'FINISHED'}
346 def register():
347 bpy.utils.register_module(__name__)
350 def unregister():
351 bpy.utils.unregister_module(__name__)
354 if __name__ == '__main__':
355 register()