Fix #104941: Node Wrangler cannot use both bump and normal
[blender-addons.git] / add_mesh_discombobulator / mesh_discombobulator.py
blobd17b10b91da82a070dd43ae0e53604cb068d9b2f
1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Original Authors: Evan J. Rosky (syrux), Chichiri, Jace Priester
7 import bpy
8 import random
9 import math
10 from bpy.types import (
11 Operator,
12 Menu,
14 from mathutils import (
15 Vector,
16 Quaternion,
18 from bpy.props import (
19 BoolProperty,
20 IntProperty,
21 FloatProperty,
22 StringProperty,
25 # ################### Globals #################### #
27 doprots = True
29 # Datas in which we will build the new discombobulated mesh
30 nPolygons = []
31 nVerts = []
32 Verts = []
33 Polygons = []
34 dVerts = []
35 dPolygons = []
36 i_prots = [] # index of the top polygons on which we"ll generate the doodads
37 i_dood_type = [] # type of doodad (given by index of the doodad obj)
40 # ############### Utility Functions ############### #
42 def randnum(a, b):
43 return random.random() * (b - a) + a
46 def randVertex(a, b, c, d, Verts):
47 """Return a vector of a random vertex on a quad-polygon"""
48 i = random.randint(1, 2)
49 A, B, C, D = 0, 0, 0, 0
50 if(a == 1):
51 A, B, C, D = a, b, c, d
52 else:
53 A, B, C, D = a, d, c, b
55 i = randnum(0.1, 0.9)
57 vecAB = Verts[B] - Verts[A]
58 E = Verts[A] + vecAB * i
60 vecDC = Verts[C] - Verts[D]
61 F = Verts[D] + vecDC * i
63 i = randnum(0.1, 0.9)
64 vecEF = F - E
66 O = E + vecEF * i
67 return O
70 # ################## Protusions #################### #
72 def fill_older_datas(verts, polygon):
73 """ Specifically coded to be called by the function addProtusionToPolygon,
74 its sets up a tuple which contains the vertices from the base and the top of the protusions.
75 """
76 temp_vertices = []
77 temp_vertices.append(verts[polygon[0]].copy())
78 temp_vertices.append(verts[polygon[1]].copy())
79 temp_vertices.append(verts[polygon[2]].copy())
80 temp_vertices.append(verts[polygon[3]].copy())
81 temp_vertices.append(verts[polygon[0]].copy())
82 temp_vertices.append(verts[polygon[1]].copy())
83 temp_vertices.append(verts[polygon[2]].copy())
84 temp_vertices.append(verts[polygon[3]].copy())
85 return temp_vertices
88 def extrude_top(temp_vertices, normal, height):
89 """ This function extrude the polygon composed of the four first members of the tuple
90 temp_vertices along the normal multiplied by the height of the extrusion.
91 """
92 j = 0
93 while j < 3:
94 temp_vertices[0][j] += normal[j] * height
95 temp_vertices[1][j] += normal[j] * height
96 temp_vertices[2][j] += normal[j] * height
97 temp_vertices[3][j] += normal[j] * height
98 j += 1
101 def scale_top(temp_vertices, center, normal, height, scale_ratio):
102 """ This function scale the polygon composed of the four first members of the tuple temp_vertices. """
103 vec1 = [0, 0, 0]
104 vec2 = [0, 0, 0]
105 vec3 = [0, 0, 0]
106 vec4 = [0, 0, 0]
108 j = 0
109 while j < 3:
110 center[j] += normal[j] * height
111 vec1[j] = temp_vertices[0][j] - center[j]
112 vec2[j] = temp_vertices[1][j] - center[j]
113 vec3[j] = temp_vertices[2][j] - center[j]
114 vec4[j] = temp_vertices[3][j] - center[j]
115 temp_vertices[0][j] = center[j] + vec1[j] * (1 - scale_ratio)
116 temp_vertices[1][j] = center[j] + vec2[j] * (1 - scale_ratio)
117 temp_vertices[2][j] = center[j] + vec3[j] * (1 - scale_ratio)
118 temp_vertices[3][j] = center[j] + vec4[j] * (1 - scale_ratio)
119 j += 1
122 def add_prot_polygons(temp_vertices):
123 """ Specifically coded to be called by addProtusionToPolygon, this function
124 put the data from the generated protusion at the end the tuples Verts and Polygons,
125 which will later used to generate the final mesh.
127 global Verts
128 global Polygons
129 global i_prots
131 findex = len(Verts)
132 Verts += temp_vertices
134 polygontop = [findex + 0, findex + 1, findex + 2, findex + 3]
135 polygon1 = [findex + 0, findex + 1, findex + 5, findex + 4]
136 polygon2 = [findex + 1, findex + 2, findex + 6, findex + 5]
137 polygon3 = [findex + 2, findex + 3, findex + 7, findex + 6]
138 polygon4 = [findex + 3, findex + 0, findex + 4, findex + 7]
140 Polygons.append(polygontop)
141 i_prots.append(len(Polygons) - 1)
142 Polygons.append(polygon1)
143 Polygons.append(polygon2)
144 Polygons.append(polygon3)
145 Polygons.append(polygon4)
148 def addProtusionToPolygon(obpolygon, verts, minHeight, maxHeight, minTaper, maxTaper):
149 """Create a protusion from the polygon "obpolygon" of the original object and use
150 several values sent by the user. It calls in this order the following functions:
151 - fill_older_data;
152 - extrude_top;
153 - scale_top;
154 - add_prot_polygons;
156 # some useful variables
157 polygon = obpolygon.vertices
159 tVerts = list(fill_older_datas(verts, polygon)) # list of temp vertices
160 height = randnum(minHeight, maxHeight) # height of generated protusion
161 scale_ratio = randnum(minTaper, maxTaper)
163 # extrude the top polygon
164 extrude_top(tVerts, obpolygon.normal, height)
165 # Now, we scale, the top polygon along its normal
166 scale_top(tVerts, GetPolyCentroid(obpolygon, verts), obpolygon.normal, height, scale_ratio)
167 # Finally, we add the protusions to the list of polygons
168 add_prot_polygons(tVerts)
171 # ################# Divide a polygon ############### #
173 def divide_one(list_polygons, list_vertices, verts, polygon, findex):
174 """ called by divide_polygon, to generate a polygon from one polygon, maybe I could simplify this process """
175 temp_vertices = []
176 temp_vertices.append(verts[polygon[0]].copy())
177 temp_vertices.append(verts[polygon[1]].copy())
178 temp_vertices.append(verts[polygon[2]].copy())
179 temp_vertices.append(verts[polygon[3]].copy())
181 list_vertices += temp_vertices
183 list_polygons.append([findex + 0, findex + 1, findex + 2, findex + 3])
186 def divide_two(list_polygons, list_vertices, verts, polygon, findex):
187 """ called by divide_polygon, to generate two polygons from one polygon and
188 add them to the list of polygons and vertices which form the discombobulated mesh
190 temp_vertices = []
191 temp_vertices.append(verts[polygon[0]].copy())
192 temp_vertices.append(verts[polygon[1]].copy())
193 temp_vertices.append(verts[polygon[2]].copy())
194 temp_vertices.append(verts[polygon[3]].copy())
195 temp_vertices.append((verts[polygon[0]] + verts[polygon[1]]) / 2)
196 temp_vertices.append((verts[polygon[2]] + verts[polygon[3]]) / 2)
198 list_vertices += temp_vertices
200 list_polygons.append([findex + 0, findex + 4, findex + 5, findex + 3])
201 list_polygons.append([findex + 1, findex + 2, findex + 5, findex + 4])
204 def divide_three(list_polygons, list_vertices, verts, polygon, findex, center):
205 """ called by divide_polygon, to generate three polygons from one polygon and
206 add them to the list of polygons and vertices which form the discombobulated mesh
208 temp_vertices = []
209 temp_vertices.append(verts[polygon[0]].copy())
210 temp_vertices.append(verts[polygon[1]].copy())
211 temp_vertices.append(verts[polygon[2]].copy())
212 temp_vertices.append(verts[polygon[3]].copy())
213 temp_vertices.append((verts[polygon[0]] + verts[polygon[1]]) / 2)
214 temp_vertices.append((verts[polygon[2]] + verts[polygon[3]]) / 2)
215 temp_vertices.append((verts[polygon[1]] + verts[polygon[2]]) / 2)
216 temp_vertices.append(center.copy())
218 list_vertices += temp_vertices
220 list_polygons.append([findex + 0, findex + 4, findex + 5, findex + 3])
221 list_polygons.append([findex + 1, findex + 6, findex + 7, findex + 4])
222 list_polygons.append([findex + 6, findex + 2, findex + 5, findex + 7])
225 def divide_four(list_polygons, list_vertices, verts, polygon, findex, center):
226 """ called by divide_polygon, to generate four polygons from one polygon and
227 add them to the list of polygons and vertices which form the discombobulated mesh
229 temp_vertices = []
230 temp_vertices.append(verts[polygon[0]].copy())
231 temp_vertices.append(verts[polygon[1]].copy())
232 temp_vertices.append(verts[polygon[2]].copy())
233 temp_vertices.append(verts[polygon[3]].copy())
234 temp_vertices.append((verts[polygon[0]] + verts[polygon[1]]) / 2)
235 temp_vertices.append((verts[polygon[2]] + verts[polygon[3]]) / 2)
236 temp_vertices.append((verts[polygon[1]] + verts[polygon[2]]) / 2)
237 temp_vertices.append(center.copy())
238 temp_vertices.append((verts[polygon[0]] + verts[polygon[3]]) / 2)
239 temp_vertices.append(center.copy())
241 list_vertices += temp_vertices
243 list_polygons.append([findex + 0, findex + 4, findex + 7, findex + 8])
244 list_polygons.append([findex + 1, findex + 6, findex + 7, findex + 4])
245 list_polygons.append([findex + 6, findex + 2, findex + 5, findex + 7])
246 list_polygons.append([findex + 8, findex + 7, findex + 5, findex + 3])
249 def dividepolygon(obpolygon, verts, number):
250 """Divide the poly into the wanted number of polygons"""
251 global nPolygons
252 global nVerts
254 poly = obpolygon.vertices
256 if(number == 1):
257 divide_one(nPolygons, nVerts, verts, poly, len(nVerts))
258 elif(number == 2):
259 divide_two(nPolygons, nVerts, verts, poly, len(nVerts))
260 elif(number == 3):
261 divide_three(nPolygons, nVerts, verts, poly, len(nVerts), GetPolyCentroid(obpolygon, verts))
262 elif(number == 4):
263 divide_four(nPolygons, nVerts, verts, poly, len(nVerts), GetPolyCentroid(obpolygon, verts))
266 # ################## Discombobulate ################ #
268 def GetPolyCentroid(obpolygon, allvertcoords):
269 centroid = Vector((0, 0, 0))
270 for vindex in obpolygon.vertices:
271 centroid += Vector(allvertcoords[vindex])
272 centroid /= len(obpolygon.vertices)
273 return centroid
276 def division(obpolygons, verts, sf1, sf2, sf3, sf4):
277 """Function to divide each of the selected polygons"""
278 divide = []
279 if (sf1):
280 divide.append(1)
281 if (sf2):
282 divide.append(2)
283 if (sf3):
284 divide.append(3)
285 if (sf4):
286 divide.append(4)
288 for poly in obpolygons:
289 if(poly.select is True and len(poly.vertices) == 4):
290 a = random.randint(0, len(divide) - 1)
291 dividepolygon(poly, verts, divide[a])
294 def protusion(obverts, obpolygons, minHeight, maxHeight, minTaper, maxTaper):
295 """function to generate the protusions"""
296 verts = []
297 for vertex in obverts:
298 verts.append(vertex.co)
300 for polygon in obpolygons:
301 if(polygon.select is True):
302 if(len(polygon.vertices) == 4):
303 addProtusionToPolygon(polygon, verts, minHeight, maxHeight, minTaper, maxTaper)
306 def test_v2_near_v1(v1, v2):
307 if (v1.x - 0.1 <= v2.x <= v1.x + 0.1 and
308 v1.y - 0.1 <= v2.y <= v1.y + 0.1 and
309 v1.z - 0.1 <= v2.z <= v1.z + 0.1):
310 return True
312 return False
315 def angle_between_nor(nor_orig, nor_result):
316 angle = math.acos(nor_orig.dot(nor_result))
317 axis = nor_orig.cross(nor_result).normalized()
319 q = Quaternion()
320 q.x = axis.x * math.sin(angle / 2)
321 q.y = axis.y * math.sin(angle / 2)
322 q.z = axis.z * math.sin(angle / 2)
323 q.w = math.cos(angle / 2)
325 return q
328 def doodads(self, object1, mesh1, dmin, dmax):
329 """function to generate the doodads"""
330 global dVerts
331 global dPolygons
332 i = 0
333 # on parcoure cette boucle pour ajouter des doodads a toutes les polygons
334 # english translation: this loops adds doodads to all polygons
335 while(i < len(object1.data.polygons)):
336 if object1.data.polygons[i].select is False:
337 continue
339 doods_nbr = random.randint(dmin, dmax)
340 j = 0
342 while(j <= doods_nbr):
343 origin_dood = randVertex(object1.data.polygons[i].vertices[0], object1.data.polygons[i].vertices[1],
344 object1.data.polygons[i].vertices[2], object1.data.polygons[i].vertices[3], Verts)
345 type_dood = random.randint(0, len(self.DISC_doodads) - 1)
346 polygons_add = []
347 verts_add = []
349 # First we have to apply scaling and rotation to the mesh
350 bpy.ops.object.select_pattern(pattern=self.DISC_doodads[type_dood], extend=False)
351 bpy.context.view_layer.objects.active = bpy.data.objects[self.DISC_doodads[type_dood]]
352 bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
354 for polygon in bpy.data.objects[self.DISC_doodads[type_dood]].data.polygons:
355 polygons_add.append(polygon.vertices)
356 for vertex in bpy.data.objects[self.DISC_doodads[type_dood]].data.vertices:
357 verts_add.append(vertex.co.copy())
358 normal_original_polygon = object1.data.polygons[i].normal
360 nor_def = Vector((0.0, 0.0, 1.0))
361 qr = nor_def.rotation_difference(normal_original_polygon.normalized())
363 if(test_v2_near_v1(nor_def, -normal_original_polygon)):
364 qr = Quaternion((0.0, 0.0, 0.0, 0.0))
366 # qr = angle_between_nor(nor_def, normal_original_polygon)
367 for vertex in verts_add:
368 vertex.rotate(qr)
369 vertex += origin_dood
370 findex = len(dVerts)
372 for polygon in polygons_add:
373 dPolygons.append([polygon[0] + findex, polygon[1] + findex, polygon[2] + findex, polygon[3] + findex])
374 i_dood_type.append(bpy.data.objects[self.DISC_doodads[type_dood]].name)
376 for vertex in verts_add:
377 dVerts.append(vertex)
378 j += 1
379 i += 5
382 def protusions_repeat(object1, mesh1, r_prot):
384 for j in i_prots:
385 if j < len(object1.data.polygons):
386 object1.data.polygons[j].select = True
387 else:
388 print("Warning: hit end of polygons in object1")
391 # add material to discombobulated mesh
392 def setMatProt(discObj, origObj, sideProtMat, topProtMat):
393 # First we put the materials in their slots
394 bpy.ops.object.select_pattern(pattern=discObj.name, extend=False)
395 bpy.context.view_layer.objects.active = bpy.data.objects[discObj.name]
396 try:
397 origObj.material_slots[topProtMat]
398 origObj.material_slots[sideProtMat]
399 except:
400 return
402 bpy.ops.object.material_slot_add()
403 bpy.ops.object.material_slot_add()
404 discObj.material_slots[0].material = origObj.material_slots[topProtMat].material
405 discObj.material_slots[1].material = origObj.material_slots[sideProtMat].material
407 # Then we assign materials to protusions
408 for polygon in discObj.data.polygons:
409 if polygon.index in i_prots:
410 polygon.material_index = 0
411 else:
412 polygon.material_index = 1
415 def setMatDood(self, doodObj):
416 # First we add the materials slots
417 bpy.ops.object.select_pattern(pattern=doodObj.name, extend=False)
418 bpy.context.view_layer.objects.active = doodObj
419 for name in self.DISC_doodads:
420 try:
421 bpy.ops.object.material_slot_add()
422 doodObj.material_slots[-1].material = bpy.data.objects[name].material_slots[0].material
423 for polygon in doodObj.data.polygons:
424 if i_dood_type[polygon.index] == name:
425 polygon.material_index = len(doodObj.material_slots) - 1
426 except:
427 print()
430 def clean_doodads(self):
431 current_doodads = list(self.DISC_doodads)
433 for name in current_doodads:
434 if name not in bpy.data.objects:
435 self.DISC_doodads.remove(name)
438 def discombobulate(self, minHeight, maxHeight, minTaper, maxTaper, sf1, sf2, sf3, sf4,
439 dmin, dmax, r_prot, sideProtMat, topProtMat, isLast):
440 global doprots
441 global nVerts
442 global nPolygons
443 global Verts
444 global Polygons
445 global dVerts
446 global dPolygons
447 global i_prots
449 bpy.ops.object.mode_set(mode="OBJECT")
451 # start by cleaning up doodads that don"t exist anymore
452 clean_doodads(self)
454 # Create the discombobulated mesh
455 mesh = bpy.data.meshes.new("tmp")
456 object = bpy.data.objects.new("tmp", mesh)
457 bpy.context.collection.objects.link(object)
459 # init final verts and polygons tuple
460 nPolygons = []
461 nVerts = []
462 Polygons = []
463 Verts = []
464 dPolygons = []
465 dVerts = []
467 origObj = bpy.context.active_object
469 # There we collect the rotation, translation and scaling datas from the original mesh
470 to_translate = bpy.context.active_object.location
471 to_scale = bpy.context.active_object.scale
472 to_rotate = bpy.context.active_object.rotation_euler
474 # First, we collect all the information we will need from the previous mesh
475 obverts = bpy.context.active_object.data.vertices
476 obpolygons = bpy.context.active_object.data.polygons
477 verts = []
478 for vertex in obverts:
479 verts.append(vertex.co)
481 division(obpolygons, verts, sf1, sf2, sf3, sf4)
483 # Fill in the discombobulated mesh with the new polygons
484 mesh.from_pydata(nVerts, [], nPolygons)
485 mesh.update(calc_edges=True)
487 # Reload the datas
488 bpy.ops.object.select_all(action="DESELECT")
489 bpy.ops.object.select_pattern(pattern=object.name, extend=False)
490 bpy.context.view_layer.objects.active = bpy.data.objects[object.name]
491 obverts = bpy.context.active_object.data.vertices
492 obpolygons = bpy.context.active_object.data.polygons
494 protusion(obverts, obpolygons, minHeight, maxHeight, minTaper, maxTaper)
496 # Fill in the discombobulated mesh with the new polygons
497 mesh1 = bpy.data.meshes.new("discombobulated_object")
498 object1 = bpy.data.objects.new("discombobulated_mesh", mesh1)
499 bpy.context.collection.objects.link(object1)
500 mesh1.from_pydata(Verts, [], Polygons)
501 mesh1.update(calc_edges=True)
503 # Set the material"s of discombobulated object
504 setMatProt(object1, origObj, sideProtMat, topProtMat)
506 bpy.ops.object.select_pattern(pattern=object1.name, extend=False)
507 bpy.context.view_layer.objects.active = bpy.data.objects[object1.name]
508 bpy.ops.object.mode_set(mode="EDIT")
509 bpy.ops.mesh.normals_make_consistent(inside=False)
510 bpy.ops.mesh.select_all(action="DESELECT")
511 bpy.ops.object.mode_set(mode="OBJECT")
513 # if(bpy.context.scene.repeatprot):
514 protusions_repeat(object1, mesh1, r_prot)
516 if(len(self.DISC_doodads) != 0 and self.dodoodads and isLast):
517 doodads(self, object1, mesh1, dmin, dmax)
518 mesh2 = bpy.data.meshes.new("dood_mesh")
519 object2 = bpy.data.objects.new("dood_obj", mesh2)
520 bpy.context.collection.objects.link(object2)
521 mesh2.from_pydata(dVerts, [], dPolygons)
522 mesh2.update(calc_edges=True)
523 setMatDood(self, object2)
524 object2.location = to_translate
525 object2.rotation_euler = to_rotate
526 object2.scale = to_scale
528 bpy.ops.object.select_pattern(pattern=object.name, extend=False)
529 bpy.context.view_layer.objects.active = bpy.data.objects[object.name]
530 bpy.ops.object.delete()
532 bpy.ops.object.select_pattern(pattern=object1.name, extend=False)
533 bpy.context.view_layer.objects.active = bpy.data.objects[object1.name]
534 bpy.context.view_layer.update()
536 # translate, scale and rotate discombobulated results
537 object1.location = to_translate
538 object1.rotation_euler = to_rotate
539 object1.scale = to_scale
541 # set all polys to selected. this allows recursive discombobulating.
542 for poly in mesh1.polygons:
543 poly.select = True
546 # ### Operators for selecting and deselecting an object as a doodad ### #
548 class chooseDoodad(Operator):
549 bl_idname = "object.discombobulate_set_doodad"
550 bl_label = "Discombobulate set doodad object"
551 bl_description = ("Save the Active Object as Doodad \n"
552 "Object has to be quads only")
553 bl_options = {"REGISTER"}
555 @classmethod
556 def poll(cls, context):
557 obj = bpy.context.active_object
558 if (obj is not None and obj.type == "MESH"):
559 mesh = obj.data
561 for polygon in mesh.polygons:
562 is_ok = len(polygon.vertices)
563 if is_ok != 4:
564 return False
565 return True
567 return False
569 def execute(self, context):
570 obj_name = bpy.context.active_object.name
571 msg = "Object with this name already saved"
573 DISC_doodads = context.scene.discombobulator.DISC_doodads
575 if obj_name not in DISC_doodads:
576 DISC_doodads.append(obj_name)
577 msg = "Saved Doodad object: {}".format(obj_name)
579 self.report({"INFO"}, message=msg)
581 def invoke(self, context, event):
582 self.execute(context)
583 return {"FINISHED"}
586 class unchooseDoodad(Operator):
587 bl_idname = "object.discombobulate_unset_doodad"
588 bl_label = "Discombobulate unset doodad object"
589 bl_description = "Remove the saved Doodad Object(s)"
590 bl_options = {"REGISTER"}
592 remove_all: bpy.props.BoolProperty(
593 name="Remove all Doodads",
594 default=False,
597 def execute(self, context):
598 msg = ("No doodads to remove")
599 DISC_doodads = context.scene.discombobulator.DISC_doodads
600 if len(DISC_doodads) > 0:
601 if not self.remove_all:
602 name = bpy.context.active_object.name
603 if name in DISC_doodads:
604 DISC_doodads.remove(name)
605 msg = ("Removed Doodad object: {}".format(name))
606 else:
607 DISC_doodads[:] = []
608 msg = "Removed all Doodads"
609 else:
610 msg = "No Doodads to Remove"
612 self.report({"INFO"}, message=msg)
614 def invoke(self, context, event):
615 self.execute(context)
616 return {"FINISHED"}
619 # ################## Interpolygon ################## #
621 class discombobulator(Operator):
622 bl_idname = "object.discombobulate"
623 bl_label = "Discombobulate"
624 bl_description = "Apply"
625 bl_options = {"REGISTER", "UNDO"}
627 def execute(self, context):
628 i = 0
629 while i < self.repeatprot:
630 isLast = False
631 if i == self.repeatprot - 1:
632 isLast = True
633 discombobulate(self.minHeight, self.maxHeight, self.minTaper, self.maxTaper, self.subpolygon1,
634 self.subpolygon2, self.subpolygon3, self.subpolygon4, self.mindoodads, self.maxdoodads,
635 self.repeatprot, self.sideProtMat, self.topProtMat, isLast)
636 i += 1
637 return {"FINISHED"}
640 class discombobulator_dodads_list(Menu):
641 bl_idname = "OBJECT_MT_discombobulator_dodad_list"
642 bl_label = "List of saved Doodads"
643 bl_description = "List of the saved Doodad Object Names"
645 def draw(self, context):
646 layout = self.layout
648 DISC_doodads = context.scene.discombobulator.DISC_doodads
650 doodle = len(DISC_doodads)
651 layout.label(text="Saved doodads : {}".format(doodle))
652 layout.separator()
653 if doodle > 0:
654 for name in DISC_doodads:
655 layout.label(text=name)
658 class discombob_help(Menu):
659 bl_idname = "HELP_MT_discombobulator"
660 bl_label = "Usage Information"
661 bl_description = "Help"
663 def draw(self, context):
664 layout = self.layout
665 layout.label(text="Usage Information:", icon="INFO")
666 layout.separator()
667 layout.label(text="Quads only, not Triangles or Ngons", icon="ERROR")
668 layout.label(text="Works only with Mesh object that have faces")
669 layout.separator()
670 layout.label(text="Select a face or faces")
671 layout.label(text="Press Discombobulate to create greebles")
672 layout.label(text="In object mode, still needs a selection in Edit Mode")
673 layout.separator()
674 layout.label(text="Doodads - additional objects layered on the mesh surface")
675 layout.label(text="(Similar to dupliverts - but as one separate object)")
676 layout.separator()
677 layout.label(text="Limitations:", icon="MOD_EXPLODE")
678 layout.label(text="Be careful with the repeat protusions setting")
679 layout.label(text="(Runs reqursively)")
680 layout.label(text="If possible, avoid using on a high polycount base mesh")
681 layout.label(text="(It can run out of memory and take a long time to compute)")
683 class VIEW3D_OT_tools_discombobulate(Operator):
684 bl_idname = "discombobulate.ops"
685 bl_label = "Discombobulator"
686 bl_description = ("Easily add sci-fi details to a surface \n"
687 "Needs an existing active Mesh with Faces")
688 bl_options = {"REGISTER", "UNDO"}
690 executing = False
692 # Protusions Buttons:
693 repeatprot: IntProperty(
694 name="Repeat protusions",
695 description=("Make several layers of protusion \n"
696 "Use carefully, runs recursively the discombulator"),
697 default=1, min=1, max=4 # set to 4 because it's 2**n reqursive
699 doprots: BoolProperty(
700 name="Make protusions",
701 description="Check if we want to add protusions to the mesh",
702 default=True
704 subpolygon1: BoolProperty(
705 name="1",
706 default=True
708 subpolygon2: BoolProperty(
709 name="2",
710 default=True
712 subpolygon3: BoolProperty(
713 name="3",
714 default=True
716 subpolygon4: BoolProperty(
717 name="4",
718 default=True
720 polygonschangedpercent: FloatProperty(
721 name="Polygon %",
722 description="Percentage of changed polygons",
723 default=1.0
725 minHeight: FloatProperty(
726 name="Min height",
727 description="Minimal height of the protusions",
728 default=0.2
730 maxHeight: FloatProperty(
731 name="Max height",
732 description="Maximal height of the protusions",
733 default=0.4
735 minTaper: FloatProperty(
736 name="Min taper",
737 description="Minimal height of the protusions",
738 default=0.15, min=0.0, max=1.0,
739 subtype='PERCENTAGE'
741 maxTaper: FloatProperty(
742 name="Max taper",
743 description="Maximal height of the protusions",
744 default=0.35, min=0.0, max=1.0,
745 subtype='PERCENTAGE'
747 # Doodads buttons:
748 dodoodads: BoolProperty(
749 name="Make doodads",
750 description="Check if we want to generate doodads",
751 default=False
753 mindoodads: IntProperty(
754 name="Minimum doodads number",
755 description="Ask for the minimum number of doodads to generate per polygon",
756 default=1, min=0, max=50
758 maxdoodads: IntProperty(
759 name="Maximum doodads number",
760 description="Ask for the maximum number of doodads to generate per polygon",
761 default=6, min=1, max=50
763 doodMinScale: FloatProperty(
764 name="Scale min", description="Minimum scaling of doodad",
765 default=0.5, min=0.0, max=1.0,
766 subtype='PERCENTAGE'
768 doodMaxScale: FloatProperty(
769 name="Scale max",
770 description="Maximum scaling of doodad",
771 default=1.0, min=0.0, max=1.0,
772 subtype='PERCENTAGE'
774 # Materials buttons:
775 sideProtMat: IntProperty(
776 name="Side's prot mat",
777 description="Material of protusion's sides",
778 default=0, min=0
780 topProtMat: IntProperty(
781 name="Prot's top mat",
782 description="Material of protusion's top",
783 default=0, min=0
786 @classmethod
787 def poll(cls, context):
788 return (context.active_object is not None and
789 context.active_object.type == "MESH")
791 def draw(self, context):
792 layout = self.layout
794 self.DISC_doodads = bpy.context.scene.discombobulator.DISC_doodads
796 row = layout.row()
797 row.menu("HELP_MT_discombobulator", icon="INFO")
798 box = layout.box()
799 box.label(text="Protusions settings")
800 row = box.row()
801 row.prop(self, "doprots")
802 row = box.row()
803 row.prop(self, "minHeight")
804 row = box.row()
805 row.prop(self, "maxHeight")
806 row = box.row()
807 row.prop(self, "minTaper")
808 row = box.row()
809 row.prop(self, "maxTaper")
810 row = box.row()
811 col1 = row.column(align=True)
812 col1.prop(self, "subpolygon1")
813 col2 = row.column(align=True)
814 col2.prop(self, "subpolygon2")
815 col3 = row.column(align=True)
816 col3.prop(self, "subpolygon3")
817 col4 = row.column(align=True)
818 col4.prop(self, "subpolygon4")
819 row = box.row()
820 row.prop(self, "repeatprot")
821 box = layout.box()
822 box.label(text="Doodads settings")
823 row = box.row()
824 is_doodad = self.dodoodads
825 row.prop(self, "dodoodads")
827 row = box.row()
828 row.enabled = is_doodad
829 row.prop(self, "mindoodads")
830 row = box.row()
831 row.enabled = is_doodad
832 row.prop(self, "maxdoodads")
833 row = box.row()
834 row.enabled = is_doodad
835 oper = row.operator("object.discombobulate_set_doodad", text="Pick doodad")
837 row = box.row()
838 splits = row.split(factor = 0.5)
839 splits.enabled = is_doodad
840 splits.operator("object.discombobulate_unset_doodad",
841 text="Remove active doodad").remove_all = False
842 splits.operator("object.discombobulate_unset_doodad",
843 text="Remove all doodads").remove_all = True
845 col = box.column(align=True)
846 doodle = len(self.DISC_doodads)
848 col.enabled = (True if doodle > 0 else False)
849 col.menu("OBJECT_MT_discombobulator_dodad_list",
850 text="List of saved Doodads ({})".format(doodle))
852 box = layout.box()
853 box.label(text="Materials settings")
854 row = box.row()
855 row.prop(self, "topProtMat")
856 row = box.row()
857 row.prop(self, "sideProtMat")
859 def invoke(self, context, event):
860 return context.window_manager.invoke_props_dialog(self, width=300)
862 def check(self, context):
863 return not self.executing
865 def execute(self, context):
866 self.executing = True
867 i = 0
868 while i < self.repeatprot:
869 isLast = False
870 if i == self.repeatprot - 1:
871 isLast = True
872 discombobulate(self, self.minHeight, self.maxHeight, self.minTaper, self.maxTaper, self.subpolygon1,
873 self.subpolygon2, self.subpolygon3, self.subpolygon4, self.mindoodads, self.maxdoodads,
874 self.repeatprot, self.sideProtMat, self.topProtMat, isLast)
875 i += 1
876 return {"FINISHED"}
877 #bpy.ops.object.discombobulate("INVOKE_DEFAULT")