File headers: use SPDX license identifiers
[blender-addons.git] / add_mesh_discombobulator / mesh_discombobulator.py
blob09c6eae0ff44cddcd4b02d67bc6dc68f95cd0e31
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Original Authors: Evan J. Rosky (syrux), Chichiri, Jace Priester
5 import bpy
6 import random
7 import math
8 from bpy.types import (
9 Operator,
10 Menu,
12 from mathutils import (
13 Vector,
14 Quaternion,
16 from bpy.props import (
17 BoolProperty,
18 IntProperty,
19 FloatProperty,
20 StringProperty,
23 # ################### Globals #################### #
25 doprots = True
27 # Datas in which we will build the new discombobulated mesh
28 nPolygons = []
29 nVerts = []
30 Verts = []
31 Polygons = []
32 dVerts = []
33 dPolygons = []
34 i_prots = [] # index of the top polygons on which we"ll generate the doodads
35 i_dood_type = [] # type of doodad (given by index of the doodad obj)
38 # ############### Utility Functions ############### #
40 def randnum(a, b):
41 return random.random() * (b - a) + a
44 def randVertex(a, b, c, d, Verts):
45 """Return a vector of a random vertex on a quad-polygon"""
46 i = random.randint(1, 2)
47 A, B, C, D = 0, 0, 0, 0
48 if(a == 1):
49 A, B, C, D = a, b, c, d
50 else:
51 A, B, C, D = a, d, c, b
53 i = randnum(0.1, 0.9)
55 vecAB = Verts[B] - Verts[A]
56 E = Verts[A] + vecAB * i
58 vecDC = Verts[C] - Verts[D]
59 F = Verts[D] + vecDC * i
61 i = randnum(0.1, 0.9)
62 vecEF = F - E
64 O = E + vecEF * i
65 return O
68 # ################## Protusions #################### #
70 def fill_older_datas(verts, polygon):
71 """ Specifically coded to be called by the function addProtusionToPolygon,
72 its sets up a tuple which contains the vertices from the base and the top of the protusions.
73 """
74 temp_vertices = []
75 temp_vertices.append(verts[polygon[0]].copy())
76 temp_vertices.append(verts[polygon[1]].copy())
77 temp_vertices.append(verts[polygon[2]].copy())
78 temp_vertices.append(verts[polygon[3]].copy())
79 temp_vertices.append(verts[polygon[0]].copy())
80 temp_vertices.append(verts[polygon[1]].copy())
81 temp_vertices.append(verts[polygon[2]].copy())
82 temp_vertices.append(verts[polygon[3]].copy())
83 return temp_vertices
86 def extrude_top(temp_vertices, normal, height):
87 """ This function extrude the polygon composed of the four first members of the tuple
88 temp_vertices along the normal multiplied by the height of the extrusion.
89 """
90 j = 0
91 while j < 3:
92 temp_vertices[0][j] += normal[j] * height
93 temp_vertices[1][j] += normal[j] * height
94 temp_vertices[2][j] += normal[j] * height
95 temp_vertices[3][j] += normal[j] * height
96 j += 1
99 def scale_top(temp_vertices, center, normal, height, scale_ratio):
100 """ This function scale the polygon composed of the four first members of the tuple temp_vertices. """
101 vec1 = [0, 0, 0]
102 vec2 = [0, 0, 0]
103 vec3 = [0, 0, 0]
104 vec4 = [0, 0, 0]
106 j = 0
107 while j < 3:
108 center[j] += normal[j] * height
109 vec1[j] = temp_vertices[0][j] - center[j]
110 vec2[j] = temp_vertices[1][j] - center[j]
111 vec3[j] = temp_vertices[2][j] - center[j]
112 vec4[j] = temp_vertices[3][j] - center[j]
113 temp_vertices[0][j] = center[j] + vec1[j] * (1 - scale_ratio)
114 temp_vertices[1][j] = center[j] + vec2[j] * (1 - scale_ratio)
115 temp_vertices[2][j] = center[j] + vec3[j] * (1 - scale_ratio)
116 temp_vertices[3][j] = center[j] + vec4[j] * (1 - scale_ratio)
117 j += 1
120 def add_prot_polygons(temp_vertices):
121 """ Specifically coded to be called by addProtusionToPolygon, this function
122 put the data from the generated protusion at the end the tuples Verts and Polygons,
123 which will later used to generate the final mesh.
125 global Verts
126 global Polygons
127 global i_prots
129 findex = len(Verts)
130 Verts += temp_vertices
132 polygontop = [findex + 0, findex + 1, findex + 2, findex + 3]
133 polygon1 = [findex + 0, findex + 1, findex + 5, findex + 4]
134 polygon2 = [findex + 1, findex + 2, findex + 6, findex + 5]
135 polygon3 = [findex + 2, findex + 3, findex + 7, findex + 6]
136 polygon4 = [findex + 3, findex + 0, findex + 4, findex + 7]
138 Polygons.append(polygontop)
139 i_prots.append(len(Polygons) - 1)
140 Polygons.append(polygon1)
141 Polygons.append(polygon2)
142 Polygons.append(polygon3)
143 Polygons.append(polygon4)
146 def addProtusionToPolygon(obpolygon, verts, minHeight, maxHeight, minTaper, maxTaper):
147 """Create a protusion from the polygon "obpolygon" of the original object and use
148 several values sent by the user. It calls in this order the following functions:
149 - fill_older_data;
150 - extrude_top;
151 - scale_top;
152 - add_prot_polygons;
154 # some useful variables
155 polygon = obpolygon.vertices
157 tVerts = list(fill_older_datas(verts, polygon)) # list of temp vertices
158 height = randnum(minHeight, maxHeight) # height of generated protusion
159 scale_ratio = randnum(minTaper, maxTaper)
161 # extrude the top polygon
162 extrude_top(tVerts, obpolygon.normal, height)
163 # Now, we scale, the top polygon along its normal
164 scale_top(tVerts, GetPolyCentroid(obpolygon, verts), obpolygon.normal, height, scale_ratio)
165 # Finally, we add the protusions to the list of polygons
166 add_prot_polygons(tVerts)
169 # ################# Divide a polygon ############### #
171 def divide_one(list_polygons, list_vertices, verts, polygon, findex):
172 """ called by divide_polygon, to generate a polygon from one polygon, maybe I could simplify this process """
173 temp_vertices = []
174 temp_vertices.append(verts[polygon[0]].copy())
175 temp_vertices.append(verts[polygon[1]].copy())
176 temp_vertices.append(verts[polygon[2]].copy())
177 temp_vertices.append(verts[polygon[3]].copy())
179 list_vertices += temp_vertices
181 list_polygons.append([findex + 0, findex + 1, findex + 2, findex + 3])
184 def divide_two(list_polygons, list_vertices, verts, polygon, findex):
185 """ called by divide_polygon, to generate two polygons from one polygon and
186 add them to the list of polygons and vertices which form the discombobulated mesh
188 temp_vertices = []
189 temp_vertices.append(verts[polygon[0]].copy())
190 temp_vertices.append(verts[polygon[1]].copy())
191 temp_vertices.append(verts[polygon[2]].copy())
192 temp_vertices.append(verts[polygon[3]].copy())
193 temp_vertices.append((verts[polygon[0]] + verts[polygon[1]]) / 2)
194 temp_vertices.append((verts[polygon[2]] + verts[polygon[3]]) / 2)
196 list_vertices += temp_vertices
198 list_polygons.append([findex + 0, findex + 4, findex + 5, findex + 3])
199 list_polygons.append([findex + 1, findex + 2, findex + 5, findex + 4])
202 def divide_three(list_polygons, list_vertices, verts, polygon, findex, center):
203 """ called by divide_polygon, to generate three polygons from one polygon and
204 add them to the list of polygons and vertices which form the discombobulated mesh
206 temp_vertices = []
207 temp_vertices.append(verts[polygon[0]].copy())
208 temp_vertices.append(verts[polygon[1]].copy())
209 temp_vertices.append(verts[polygon[2]].copy())
210 temp_vertices.append(verts[polygon[3]].copy())
211 temp_vertices.append((verts[polygon[0]] + verts[polygon[1]]) / 2)
212 temp_vertices.append((verts[polygon[2]] + verts[polygon[3]]) / 2)
213 temp_vertices.append((verts[polygon[1]] + verts[polygon[2]]) / 2)
214 temp_vertices.append(center.copy())
216 list_vertices += temp_vertices
218 list_polygons.append([findex + 0, findex + 4, findex + 5, findex + 3])
219 list_polygons.append([findex + 1, findex + 6, findex + 7, findex + 4])
220 list_polygons.append([findex + 6, findex + 2, findex + 5, findex + 7])
223 def divide_four(list_polygons, list_vertices, verts, polygon, findex, center):
224 """ called by divide_polygon, to generate four polygons from one polygon and
225 add them to the list of polygons and vertices which form the discombobulated mesh
227 temp_vertices = []
228 temp_vertices.append(verts[polygon[0]].copy())
229 temp_vertices.append(verts[polygon[1]].copy())
230 temp_vertices.append(verts[polygon[2]].copy())
231 temp_vertices.append(verts[polygon[3]].copy())
232 temp_vertices.append((verts[polygon[0]] + verts[polygon[1]]) / 2)
233 temp_vertices.append((verts[polygon[2]] + verts[polygon[3]]) / 2)
234 temp_vertices.append((verts[polygon[1]] + verts[polygon[2]]) / 2)
235 temp_vertices.append(center.copy())
236 temp_vertices.append((verts[polygon[0]] + verts[polygon[3]]) / 2)
237 temp_vertices.append(center.copy())
239 list_vertices += temp_vertices
241 list_polygons.append([findex + 0, findex + 4, findex + 7, findex + 8])
242 list_polygons.append([findex + 1, findex + 6, findex + 7, findex + 4])
243 list_polygons.append([findex + 6, findex + 2, findex + 5, findex + 7])
244 list_polygons.append([findex + 8, findex + 7, findex + 5, findex + 3])
247 def dividepolygon(obpolygon, verts, number):
248 """Divide the poly into the wanted number of polygons"""
249 global nPolygons
250 global nVerts
252 poly = obpolygon.vertices
254 if(number == 1):
255 divide_one(nPolygons, nVerts, verts, poly, len(nVerts))
256 elif(number == 2):
257 divide_two(nPolygons, nVerts, verts, poly, len(nVerts))
258 elif(number == 3):
259 divide_three(nPolygons, nVerts, verts, poly, len(nVerts), GetPolyCentroid(obpolygon, verts))
260 elif(number == 4):
261 divide_four(nPolygons, nVerts, verts, poly, len(nVerts), GetPolyCentroid(obpolygon, verts))
264 # ################## Discombobulate ################ #
266 def GetPolyCentroid(obpolygon, allvertcoords):
267 centroid = Vector((0, 0, 0))
268 for vindex in obpolygon.vertices:
269 centroid += Vector(allvertcoords[vindex])
270 centroid /= len(obpolygon.vertices)
271 return centroid
274 def division(obpolygons, verts, sf1, sf2, sf3, sf4):
275 """Function to divide each of the selected polygons"""
276 divide = []
277 if (sf1):
278 divide.append(1)
279 if (sf2):
280 divide.append(2)
281 if (sf3):
282 divide.append(3)
283 if (sf4):
284 divide.append(4)
286 for poly in obpolygons:
287 if(poly.select is True and len(poly.vertices) == 4):
288 a = random.randint(0, len(divide) - 1)
289 dividepolygon(poly, verts, divide[a])
292 def protusion(obverts, obpolygons, minHeight, maxHeight, minTaper, maxTaper):
293 """function to generate the protusions"""
294 verts = []
295 for vertex in obverts:
296 verts.append(vertex.co)
298 for polygon in obpolygons:
299 if(polygon.select is True):
300 if(len(polygon.vertices) == 4):
301 addProtusionToPolygon(polygon, verts, minHeight, maxHeight, minTaper, maxTaper)
304 def test_v2_near_v1(v1, v2):
305 if (v1.x - 0.1 <= v2.x <= v1.x + 0.1 and
306 v1.y - 0.1 <= v2.y <= v1.y + 0.1 and
307 v1.z - 0.1 <= v2.z <= v1.z + 0.1):
308 return True
310 return False
313 def angle_between_nor(nor_orig, nor_result):
314 angle = math.acos(nor_orig.dot(nor_result))
315 axis = nor_orig.cross(nor_result).normalized()
317 q = Quaternion()
318 q.x = axis.x * math.sin(angle / 2)
319 q.y = axis.y * math.sin(angle / 2)
320 q.z = axis.z * math.sin(angle / 2)
321 q.w = math.cos(angle / 2)
323 return q
326 def doodads(self, object1, mesh1, dmin, dmax):
327 """function to generate the doodads"""
328 global dVerts
329 global dPolygons
330 i = 0
331 # on parcoure cette boucle pour ajouter des doodads a toutes les polygons
332 # english translation: this loops adds doodads to all polygons
333 while(i < len(object1.data.polygons)):
334 if object1.data.polygons[i].select is False:
335 continue
337 doods_nbr = random.randint(dmin, dmax)
338 j = 0
340 while(j <= doods_nbr):
341 origin_dood = randVertex(object1.data.polygons[i].vertices[0], object1.data.polygons[i].vertices[1],
342 object1.data.polygons[i].vertices[2], object1.data.polygons[i].vertices[3], Verts)
343 type_dood = random.randint(0, len(self.DISC_doodads) - 1)
344 polygons_add = []
345 verts_add = []
347 # First we have to apply scaling and rotation to the mesh
348 bpy.ops.object.select_pattern(pattern=self.DISC_doodads[type_dood], extend=False)
349 bpy.context.view_layer.objects.active = bpy.data.objects[self.DISC_doodads[type_dood]]
350 bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
352 for polygon in bpy.data.objects[self.DISC_doodads[type_dood]].data.polygons:
353 polygons_add.append(polygon.vertices)
354 for vertex in bpy.data.objects[self.DISC_doodads[type_dood]].data.vertices:
355 verts_add.append(vertex.co.copy())
356 normal_original_polygon = object1.data.polygons[i].normal
358 nor_def = Vector((0.0, 0.0, 1.0))
359 qr = nor_def.rotation_difference(normal_original_polygon.normalized())
361 if(test_v2_near_v1(nor_def, -normal_original_polygon)):
362 qr = Quaternion((0.0, 0.0, 0.0, 0.0))
364 # qr = angle_between_nor(nor_def, normal_original_polygon)
365 for vertex in verts_add:
366 vertex.rotate(qr)
367 vertex += origin_dood
368 findex = len(dVerts)
370 for polygon in polygons_add:
371 dPolygons.append([polygon[0] + findex, polygon[1] + findex, polygon[2] + findex, polygon[3] + findex])
372 i_dood_type.append(bpy.data.objects[self.DISC_doodads[type_dood]].name)
374 for vertex in verts_add:
375 dVerts.append(vertex)
376 j += 1
377 i += 5
380 def protusions_repeat(object1, mesh1, r_prot):
382 for j in i_prots:
383 if j < len(object1.data.polygons):
384 object1.data.polygons[j].select = True
385 else:
386 print("Warning: hit end of polygons in object1")
389 # add material to discombobulated mesh
390 def setMatProt(discObj, origObj, sideProtMat, topProtMat):
391 # First we put the materials in their slots
392 bpy.ops.object.select_pattern(pattern=discObj.name, extend=False)
393 bpy.context.view_layer.objects.active = bpy.data.objects[discObj.name]
394 try:
395 origObj.material_slots[topProtMat]
396 origObj.material_slots[sideProtMat]
397 except:
398 return
400 bpy.ops.object.material_slot_add()
401 bpy.ops.object.material_slot_add()
402 discObj.material_slots[0].material = origObj.material_slots[topProtMat].material
403 discObj.material_slots[1].material = origObj.material_slots[sideProtMat].material
405 # Then we assign materials to protusions
406 for polygon in discObj.data.polygons:
407 if polygon.index in i_prots:
408 polygon.material_index = 0
409 else:
410 polygon.material_index = 1
413 def setMatDood(self, doodObj):
414 # First we add the materials slots
415 bpy.ops.object.select_pattern(pattern=doodObj.name, extend=False)
416 bpy.context.view_layer.objects.active = doodObj
417 for name in self.DISC_doodads:
418 try:
419 bpy.ops.object.material_slot_add()
420 doodObj.material_slots[-1].material = bpy.data.objects[name].material_slots[0].material
421 for polygon in doodObj.data.polygons:
422 if i_dood_type[polygon.index] == name:
423 polygon.material_index = len(doodObj.material_slots) - 1
424 except:
425 print()
428 def clean_doodads(self):
429 current_doodads = list(self.DISC_doodads)
431 for name in current_doodads:
432 if name not in bpy.data.objects:
433 self.DISC_doodads.remove(name)
436 def discombobulate(self, minHeight, maxHeight, minTaper, maxTaper, sf1, sf2, sf3, sf4,
437 dmin, dmax, r_prot, sideProtMat, topProtMat, isLast):
438 global doprots
439 global nVerts
440 global nPolygons
441 global Verts
442 global Polygons
443 global dVerts
444 global dPolygons
445 global i_prots
447 bpy.ops.object.mode_set(mode="OBJECT")
449 # start by cleaning up doodads that don"t exist anymore
450 clean_doodads(self)
452 # Create the discombobulated mesh
453 mesh = bpy.data.meshes.new("tmp")
454 object = bpy.data.objects.new("tmp", mesh)
455 bpy.context.collection.objects.link(object)
457 # init final verts and polygons tuple
458 nPolygons = []
459 nVerts = []
460 Polygons = []
461 Verts = []
462 dPolygons = []
463 dVerts = []
465 origObj = bpy.context.active_object
467 # There we collect the rotation, translation and scaling datas from the original mesh
468 to_translate = bpy.context.active_object.location
469 to_scale = bpy.context.active_object.scale
470 to_rotate = bpy.context.active_object.rotation_euler
472 # First, we collect all the information we will need from the previous mesh
473 obverts = bpy.context.active_object.data.vertices
474 obpolygons = bpy.context.active_object.data.polygons
475 verts = []
476 for vertex in obverts:
477 verts.append(vertex.co)
479 division(obpolygons, verts, sf1, sf2, sf3, sf4)
481 # Fill in the discombobulated mesh with the new polygons
482 mesh.from_pydata(nVerts, [], nPolygons)
483 mesh.update(calc_edges=True)
485 # Reload the datas
486 bpy.ops.object.select_all(action="DESELECT")
487 bpy.ops.object.select_pattern(pattern=object.name, extend=False)
488 bpy.context.view_layer.objects.active = bpy.data.objects[object.name]
489 obverts = bpy.context.active_object.data.vertices
490 obpolygons = bpy.context.active_object.data.polygons
492 protusion(obverts, obpolygons, minHeight, maxHeight, minTaper, maxTaper)
494 # Fill in the discombobulated mesh with the new polygons
495 mesh1 = bpy.data.meshes.new("discombobulated_object")
496 object1 = bpy.data.objects.new("discombobulated_mesh", mesh1)
497 bpy.context.collection.objects.link(object1)
498 mesh1.from_pydata(Verts, [], Polygons)
499 mesh1.update(calc_edges=True)
501 # Set the material"s of discombobulated object
502 setMatProt(object1, origObj, sideProtMat, topProtMat)
504 bpy.ops.object.select_pattern(pattern=object1.name, extend=False)
505 bpy.context.view_layer.objects.active = bpy.data.objects[object1.name]
506 bpy.ops.object.mode_set(mode="EDIT")
507 bpy.ops.mesh.normals_make_consistent(inside=False)
508 bpy.ops.mesh.select_all(action="DESELECT")
509 bpy.ops.object.mode_set(mode="OBJECT")
511 # if(bpy.context.scene.repeatprot):
512 protusions_repeat(object1, mesh1, r_prot)
514 if(len(self.DISC_doodads) != 0 and self.dodoodads and isLast):
515 doodads(self, object1, mesh1, dmin, dmax)
516 mesh2 = bpy.data.meshes.new("dood_mesh")
517 object2 = bpy.data.objects.new("dood_obj", mesh2)
518 bpy.context.collection.objects.link(object2)
519 mesh2.from_pydata(dVerts, [], dPolygons)
520 mesh2.update(calc_edges=True)
521 setMatDood(self, object2)
522 object2.location = to_translate
523 object2.rotation_euler = to_rotate
524 object2.scale = to_scale
526 bpy.ops.object.select_pattern(pattern=object.name, extend=False)
527 bpy.context.view_layer.objects.active = bpy.data.objects[object.name]
528 bpy.ops.object.delete()
530 bpy.ops.object.select_pattern(pattern=object1.name, extend=False)
531 bpy.context.view_layer.objects.active = bpy.data.objects[object1.name]
532 bpy.context.view_layer.update()
534 # translate, scale and rotate discombobulated results
535 object1.location = to_translate
536 object1.rotation_euler = to_rotate
537 object1.scale = to_scale
539 # set all polys to selected. this allows recursive discombobulating.
540 for poly in mesh1.polygons:
541 poly.select = True
544 # ### Operators for selecting and deselecting an object as a doodad ### #
546 class chooseDoodad(Operator):
547 bl_idname = "object.discombobulate_set_doodad"
548 bl_label = "Discombobulate set doodad object"
549 bl_description = ("Save the Active Object as Doodad \n"
550 "Object has to be quads only")
551 bl_options = {"REGISTER"}
553 @classmethod
554 def poll(cls, context):
555 obj = bpy.context.active_object
556 if (obj is not None and obj.type == "MESH"):
557 mesh = obj.data
559 for polygon in mesh.polygons:
560 is_ok = len(polygon.vertices)
561 if is_ok != 4:
562 return False
563 return True
565 return False
567 def execute(self, context):
568 obj_name = bpy.context.active_object.name
569 msg = "Object with this name already saved"
571 DISC_doodads = context.scene.discombobulator.DISC_doodads
573 if obj_name not in DISC_doodads:
574 DISC_doodads.append(obj_name)
575 msg = "Saved Doodad object: {}".format(obj_name)
577 self.report({"INFO"}, message=msg)
579 def invoke(self, context, event):
580 self.execute(context)
581 return {"FINISHED"}
584 class unchooseDoodad(Operator):
585 bl_idname = "object.discombobulate_unset_doodad"
586 bl_label = "Discombobulate unset doodad object"
587 bl_description = "Remove the saved Doodad Object(s)"
588 bl_options = {"REGISTER"}
590 remove_all: bpy.props.BoolProperty(
591 name="Remove all Doodads",
592 default=False,
595 def execute(self, context):
596 msg = ("No doodads to remove")
597 DISC_doodads = context.scene.discombobulator.DISC_doodads
598 if len(DISC_doodads) > 0:
599 if not self.remove_all:
600 name = bpy.context.active_object.name
601 if name in DISC_doodads:
602 DISC_doodads.remove(name)
603 msg = ("Removed Doodad object: {}".format(name))
604 else:
605 DISC_doodads[:] = []
606 msg = "Removed all Doodads"
607 else:
608 msg = "No Doodads to Remove"
610 self.report({"INFO"}, message=msg)
612 def invoke(self, context, event):
613 self.execute(context)
614 return {"FINISHED"}
617 # ################## Interpolygon ################## #
619 class discombobulator(Operator):
620 bl_idname = "object.discombobulate"
621 bl_label = "Discombobulate"
622 bl_description = "Apply"
623 bl_options = {"REGISTER", "UNDO"}
625 def execute(self, context):
626 i = 0
627 while i < self.repeatprot:
628 isLast = False
629 if i == self.repeatprot - 1:
630 isLast = True
631 discombobulate(self.minHeight, self.maxHeight, self.minTaper, self.maxTaper, self.subpolygon1,
632 self.subpolygon2, self.subpolygon3, self.subpolygon4, self.mindoodads, self.maxdoodads,
633 self.repeatprot, self.sideProtMat, self.topProtMat, isLast)
634 i += 1
635 return {"FINISHED"}
638 class discombobulator_dodads_list(Menu):
639 bl_idname = "OBJECT_MT_discombobulator_dodad_list"
640 bl_label = "List of saved Doodads"
641 bl_description = "List of the saved Doodad Object Names"
642 bl_options = {"REGISTER"}
644 def draw(self, context):
645 layout = self.layout
647 DISC_doodads = context.scene.discombobulator.DISC_doodads
649 doodle = len(DISC_doodads)
650 layout.label(text="Saved doodads : {}".format(doodle))
651 layout.separator()
652 if doodle > 0:
653 for name in DISC_doodads:
654 layout.label(text=name)
657 class discombob_help(Menu):
658 bl_idname = "HELP_MT_discombobulator"
659 bl_label = "Usage Information"
660 bl_description = "Help"
661 bl_options = {"REGISTER"}
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")