1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Original Authors: Evan J. Rosky (syrux), Chichiri, Jace Priester
8 from bpy
.types
import (
12 from mathutils
import (
16 from bpy
.props
import (
23 # ################### Globals #################### #
27 # Datas in which we will build the new discombobulated mesh
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 ############### #
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
49 A
, B
, C
, D
= a
, b
, c
, d
51 A
, B
, C
, D
= a
, d
, c
, b
55 vecAB
= Verts
[B
] - Verts
[A
]
56 E
= Verts
[A
] + vecAB
* i
58 vecDC
= Verts
[C
] - Verts
[D
]
59 F
= Verts
[D
] + vecDC
* i
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.
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())
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.
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
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. """
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
)
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.
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:
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 """
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
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
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
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"""
252 poly
= obpolygon
.vertices
255 divide_one(nPolygons
, nVerts
, verts
, poly
, len(nVerts
))
257 divide_two(nPolygons
, nVerts
, verts
, poly
, len(nVerts
))
259 divide_three(nPolygons
, nVerts
, verts
, poly
, len(nVerts
), GetPolyCentroid(obpolygon
, verts
))
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
)
274 def division(obpolygons
, verts
, sf1
, sf2
, sf3
, sf4
):
275 """Function to divide each of the selected polygons"""
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"""
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):
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()
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)
326 def doodads(self
, object1
, mesh1
, dmin
, dmax
):
327 """function to generate the doodads"""
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:
337 doods_nbr
= random
.randint(dmin
, dmax
)
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)
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
:
367 vertex
+= origin_dood
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
)
380 def protusions_repeat(object1
, mesh1
, r_prot
):
383 if j
< len(object1
.data
.polygons
):
384 object1
.data
.polygons
[j
].select
= True
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
]
395 origObj
.material_slots
[topProtMat
]
396 origObj
.material_slots
[sideProtMat
]
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
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
:
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
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
):
447 bpy
.ops
.object.mode_set(mode
="OBJECT")
449 # start by cleaning up doodads that don"t exist anymore
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
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
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)
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
:
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"}
554 def poll(cls
, context
):
555 obj
= bpy
.context
.active_object
556 if (obj
is not None and obj
.type == "MESH"):
559 for polygon
in mesh
.polygons
:
560 is_ok
= len(polygon
.vertices
)
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
)
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",
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
))
606 msg
= "Removed all Doodads"
608 msg
= "No Doodads to Remove"
610 self
.report({"INFO"}, message
=msg
)
612 def invoke(self
, context
, event
):
613 self
.execute(context
)
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
):
627 while i
< self
.repeatprot
:
629 if i
== self
.repeatprot
- 1:
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
)
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
):
647 DISC_doodads
= context
.scene
.discombobulator
.DISC_doodads
649 doodle
= len(DISC_doodads
)
650 layout
.label(text
="Saved doodads : {}".format(doodle
))
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
):
665 layout
.label(text
="Usage Information:", icon
="INFO")
667 layout
.label(text
="Quads only, not Triangles or Ngons", icon
="ERROR")
668 layout
.label(text
="Works only with Mesh object that have faces")
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")
674 layout
.label(text
="Doodads - additional objects layered on the mesh surface")
675 layout
.label(text
="(Similar to dupliverts - but as one separate object)")
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"}
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",
704 subpolygon1
: BoolProperty(
708 subpolygon2
: BoolProperty(
712 subpolygon3
: BoolProperty(
716 subpolygon4
: BoolProperty(
720 polygonschangedpercent
: FloatProperty(
722 description
="Percentage of changed polygons",
725 minHeight
: FloatProperty(
727 description
="Minimal height of the protusions",
730 maxHeight
: FloatProperty(
732 description
="Maximal height of the protusions",
735 minTaper
: FloatProperty(
737 description
="Minimal height of the protusions",
738 default
=0.15, min=0.0, max=1.0,
741 maxTaper
: FloatProperty(
743 description
="Maximal height of the protusions",
744 default
=0.35, min=0.0, max=1.0,
748 dodoodads
: BoolProperty(
750 description
="Check if we want to generate doodads",
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,
768 doodMaxScale
: FloatProperty(
770 description
="Maximum scaling of doodad",
771 default
=1.0, min=0.0, max=1.0,
775 sideProtMat
: IntProperty(
776 name
="Side's prot mat",
777 description
="Material of protusion's sides",
780 topProtMat
: IntProperty(
781 name
="Prot's top mat",
782 description
="Material of protusion's top",
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
):
794 self
.DISC_doodads
= bpy
.context
.scene
.discombobulator
.DISC_doodads
797 row
.menu("HELP_MT_discombobulator", icon
="INFO")
799 box
.label(text
="Protusions settings")
801 row
.prop(self
, "doprots")
803 row
.prop(self
, "minHeight")
805 row
.prop(self
, "maxHeight")
807 row
.prop(self
, "minTaper")
809 row
.prop(self
, "maxTaper")
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")
820 row
.prop(self
, "repeatprot")
822 box
.label(text
="Doodads settings")
824 is_doodad
= self
.dodoodads
825 row
.prop(self
, "dodoodads")
828 row
.enabled
= is_doodad
829 row
.prop(self
, "mindoodads")
831 row
.enabled
= is_doodad
832 row
.prop(self
, "maxdoodads")
834 row
.enabled
= is_doodad
835 oper
= row
.operator("object.discombobulate_set_doodad", text
="Pick doodad")
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
))
853 box
.label(text
="Materials settings")
855 row
.prop(self
, "topProtMat")
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
868 while i
< self
.repeatprot
:
870 if i
== self
.repeatprot
- 1:
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
)
877 #bpy.ops.object.discombobulate("INVOKE_DEFAULT")