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
10 from bpy
.types
import (
14 from mathutils
import (
18 from bpy
.props
import (
25 # ################### Globals #################### #
29 # Datas in which we will build the new discombobulated mesh
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 ############### #
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
51 A
, B
, C
, D
= a
, b
, c
, d
53 A
, B
, C
, D
= a
, d
, c
, b
57 vecAB
= Verts
[B
] - Verts
[A
]
58 E
= Verts
[A
] + vecAB
* i
60 vecDC
= Verts
[C
] - Verts
[D
]
61 F
= Verts
[D
] + vecDC
* i
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.
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())
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.
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
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. """
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
)
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.
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:
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 """
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
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
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
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"""
254 poly
= obpolygon
.vertices
257 divide_one(nPolygons
, nVerts
, verts
, poly
, len(nVerts
))
259 divide_two(nPolygons
, nVerts
, verts
, poly
, len(nVerts
))
261 divide_three(nPolygons
, nVerts
, verts
, poly
, len(nVerts
), GetPolyCentroid(obpolygon
, verts
))
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
)
276 def division(obpolygons
, verts
, sf1
, sf2
, sf3
, sf4
):
277 """Function to divide each of the selected polygons"""
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"""
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):
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()
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)
328 def doodads(self
, object1
, mesh1
, dmin
, dmax
):
329 """function to generate the doodads"""
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:
339 doods_nbr
= random
.randint(dmin
, dmax
)
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)
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
:
369 vertex
+= origin_dood
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
)
382 def protusions_repeat(object1
, mesh1
, r_prot
):
385 if j
< len(object1
.data
.polygons
):
386 object1
.data
.polygons
[j
].select
= True
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
]
397 origObj
.material_slots
[topProtMat
]
398 origObj
.material_slots
[sideProtMat
]
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
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
:
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
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
):
449 bpy
.ops
.object.mode_set(mode
="OBJECT")
451 # start by cleaning up doodads that don"t exist anymore
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
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
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)
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
:
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"}
556 def poll(cls
, context
):
557 obj
= bpy
.context
.active_object
558 if (obj
is not None and obj
.type == "MESH"):
561 for polygon
in mesh
.polygons
:
562 is_ok
= len(polygon
.vertices
)
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
)
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",
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
))
608 msg
= "Removed all Doodads"
610 msg
= "No Doodads to Remove"
612 self
.report({"INFO"}, message
=msg
)
614 def invoke(self
, context
, event
):
615 self
.execute(context
)
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
):
629 while i
< self
.repeatprot
:
631 if i
== self
.repeatprot
- 1:
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
)
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
):
648 DISC_doodads
= context
.scene
.discombobulator
.DISC_doodads
650 doodle
= len(DISC_doodads
)
651 layout
.label(text
="Saved doodads : {}".format(doodle
))
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
):
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")