Welcome to mirror list, hosted at ThFree Co, Russian Federation.

mesh_discombobulator.py « add_mesh_discombobulator - git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 09c6eae0ff44cddcd4b02d67bc6dc68f95cd0e31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
# SPDX-License-Identifier: GPL-2.0-or-later

# Original Authors: Evan J. Rosky (syrux), Chichiri, Jace Priester

import bpy
import random
import math
from bpy.types import (
                Operator,
                Menu,
                )
from mathutils import (
                Vector,
                Quaternion,
                )
from bpy.props import (
                BoolProperty,
                IntProperty,
                FloatProperty,
                StringProperty,
                )

# ################### Globals #################### #

doprots = True

# Datas in which we will build the new discombobulated mesh
nPolygons = []
nVerts = []
Verts = []
Polygons = []
dVerts = []
dPolygons = []
i_prots = []        # index of the top polygons on which we"ll generate the doodads
i_dood_type = []    # type of doodad (given by index of the doodad obj)


# ############### Utility Functions ############### #

def randnum(a, b):
    return random.random() * (b - a) + a


def randVertex(a, b, c, d, Verts):
    """Return a vector of a random vertex on a quad-polygon"""
    i = random.randint(1, 2)
    A, B, C, D = 0, 0, 0, 0
    if(a == 1):
        A, B, C, D = a, b, c, d
    else:
        A, B, C, D = a, d, c, b

    i = randnum(0.1, 0.9)

    vecAB = Verts[B] - Verts[A]
    E = Verts[A] + vecAB * i

    vecDC = Verts[C] - Verts[D]
    F = Verts[D] + vecDC * i

    i = randnum(0.1, 0.9)
    vecEF = F - E

    O = E + vecEF * i
    return O


# ################## Protusions #################### #

def fill_older_datas(verts, polygon):
    """ Specifically coded to be called by the function addProtusionToPolygon,
        its sets up a tuple which contains the vertices from the base and the top of the protusions.
    """
    temp_vertices = []
    temp_vertices.append(verts[polygon[0]].copy())
    temp_vertices.append(verts[polygon[1]].copy())
    temp_vertices.append(verts[polygon[2]].copy())
    temp_vertices.append(verts[polygon[3]].copy())
    temp_vertices.append(verts[polygon[0]].copy())
    temp_vertices.append(verts[polygon[1]].copy())
    temp_vertices.append(verts[polygon[2]].copy())
    temp_vertices.append(verts[polygon[3]].copy())
    return temp_vertices


def extrude_top(temp_vertices, normal, height):
    """ This function extrude the polygon composed of the four first members of the tuple
        temp_vertices along the normal multiplied by the height of the extrusion.
    """
    j = 0
    while j < 3:
        temp_vertices[0][j] += normal[j] * height
        temp_vertices[1][j] += normal[j] * height
        temp_vertices[2][j] += normal[j] * height
        temp_vertices[3][j] += normal[j] * height
        j += 1


def scale_top(temp_vertices, center, normal, height, scale_ratio):
    """ This function scale the polygon composed of the four first members of the tuple temp_vertices. """
    vec1 = [0, 0, 0]
    vec2 = [0, 0, 0]
    vec3 = [0, 0, 0]
    vec4 = [0, 0, 0]

    j = 0
    while j < 3:
        center[j] += normal[j] * height
        vec1[j] = temp_vertices[0][j] - center[j]
        vec2[j] = temp_vertices[1][j] - center[j]
        vec3[j] = temp_vertices[2][j] - center[j]
        vec4[j] = temp_vertices[3][j] - center[j]
        temp_vertices[0][j] = center[j] + vec1[j] * (1 - scale_ratio)
        temp_vertices[1][j] = center[j] + vec2[j] * (1 - scale_ratio)
        temp_vertices[2][j] = center[j] + vec3[j] * (1 - scale_ratio)
        temp_vertices[3][j] = center[j] + vec4[j] * (1 - scale_ratio)
        j += 1


def add_prot_polygons(temp_vertices):
    """ Specifically coded to be called by addProtusionToPolygon, this function
        put the data from the generated protusion at the end the tuples Verts and Polygons,
        which will later used to generate the final mesh.
    """
    global Verts
    global Polygons
    global i_prots

    findex = len(Verts)
    Verts += temp_vertices

    polygontop = [findex + 0, findex + 1, findex + 2, findex + 3]
    polygon1 = [findex + 0, findex + 1, findex + 5, findex + 4]
    polygon2 = [findex + 1, findex + 2, findex + 6, findex + 5]
    polygon3 = [findex + 2, findex + 3, findex + 7, findex + 6]
    polygon4 = [findex + 3, findex + 0, findex + 4, findex + 7]

    Polygons.append(polygontop)
    i_prots.append(len(Polygons) - 1)
    Polygons.append(polygon1)
    Polygons.append(polygon2)
    Polygons.append(polygon3)
    Polygons.append(polygon4)


def addProtusionToPolygon(obpolygon, verts, minHeight, maxHeight, minTaper, maxTaper):
    """Create a protusion from the polygon "obpolygon" of the original object and use
       several values sent by the user. It calls in this order the following functions:
       - fill_older_data;
       - extrude_top;
       - scale_top;
       - add_prot_polygons;
   """
    # some useful variables
    polygon = obpolygon.vertices

    tVerts = list(fill_older_datas(verts, polygon))     # list of temp vertices
    height = randnum(minHeight, maxHeight)              # height of generated protusion
    scale_ratio = randnum(minTaper, maxTaper)

    # extrude the top polygon
    extrude_top(tVerts, obpolygon.normal, height)
    # Now, we scale, the top polygon along its normal
    scale_top(tVerts, GetPolyCentroid(obpolygon, verts), obpolygon.normal, height, scale_ratio)
    # Finally, we add the protusions to the list of polygons
    add_prot_polygons(tVerts)


# ################# Divide a polygon ############### #

def divide_one(list_polygons, list_vertices, verts, polygon, findex):
    """ called by divide_polygon, to generate a polygon from one polygon, maybe I could simplify this process """
    temp_vertices = []
    temp_vertices.append(verts[polygon[0]].copy())
    temp_vertices.append(verts[polygon[1]].copy())
    temp_vertices.append(verts[polygon[2]].copy())
    temp_vertices.append(verts[polygon[3]].copy())

    list_vertices += temp_vertices

    list_polygons.append([findex + 0, findex + 1, findex + 2, findex + 3])


def divide_two(list_polygons, list_vertices, verts, polygon, findex):
    """ called by divide_polygon, to generate two polygons from one polygon and
        add them to the list of polygons and vertices which form the discombobulated mesh
    """
    temp_vertices = []
    temp_vertices.append(verts[polygon[0]].copy())
    temp_vertices.append(verts[polygon[1]].copy())
    temp_vertices.append(verts[polygon[2]].copy())
    temp_vertices.append(verts[polygon[3]].copy())
    temp_vertices.append((verts[polygon[0]] + verts[polygon[1]]) / 2)
    temp_vertices.append((verts[polygon[2]] + verts[polygon[3]]) / 2)

    list_vertices += temp_vertices

    list_polygons.append([findex + 0, findex + 4, findex + 5, findex + 3])
    list_polygons.append([findex + 1, findex + 2, findex + 5, findex + 4])


def divide_three(list_polygons, list_vertices, verts, polygon, findex, center):
    """ called by divide_polygon, to generate three polygons from one polygon and
        add them to the list of polygons and vertices which form the discombobulated mesh
    """
    temp_vertices = []
    temp_vertices.append(verts[polygon[0]].copy())
    temp_vertices.append(verts[polygon[1]].copy())
    temp_vertices.append(verts[polygon[2]].copy())
    temp_vertices.append(verts[polygon[3]].copy())
    temp_vertices.append((verts[polygon[0]] + verts[polygon[1]]) / 2)
    temp_vertices.append((verts[polygon[2]] + verts[polygon[3]]) / 2)
    temp_vertices.append((verts[polygon[1]] + verts[polygon[2]]) / 2)
    temp_vertices.append(center.copy())

    list_vertices += temp_vertices

    list_polygons.append([findex + 0, findex + 4, findex + 5, findex + 3])
    list_polygons.append([findex + 1, findex + 6, findex + 7, findex + 4])
    list_polygons.append([findex + 6, findex + 2, findex + 5, findex + 7])


def divide_four(list_polygons, list_vertices, verts, polygon, findex, center):
    """ called by divide_polygon, to generate four polygons from one polygon and
        add them to the list of polygons and vertices which form the discombobulated mesh
    """
    temp_vertices = []
    temp_vertices.append(verts[polygon[0]].copy())
    temp_vertices.append(verts[polygon[1]].copy())
    temp_vertices.append(verts[polygon[2]].copy())
    temp_vertices.append(verts[polygon[3]].copy())
    temp_vertices.append((verts[polygon[0]] + verts[polygon[1]]) / 2)
    temp_vertices.append((verts[polygon[2]] + verts[polygon[3]]) / 2)
    temp_vertices.append((verts[polygon[1]] + verts[polygon[2]]) / 2)
    temp_vertices.append(center.copy())
    temp_vertices.append((verts[polygon[0]] + verts[polygon[3]]) / 2)
    temp_vertices.append(center.copy())

    list_vertices += temp_vertices

    list_polygons.append([findex + 0, findex + 4, findex + 7, findex + 8])
    list_polygons.append([findex + 1, findex + 6, findex + 7, findex + 4])
    list_polygons.append([findex + 6, findex + 2, findex + 5, findex + 7])
    list_polygons.append([findex + 8, findex + 7, findex + 5, findex + 3])


def dividepolygon(obpolygon, verts, number):
    """Divide the poly into the wanted number of polygons"""
    global nPolygons
    global nVerts

    poly = obpolygon.vertices

    if(number == 1):
        divide_one(nPolygons, nVerts, verts, poly, len(nVerts))
    elif(number == 2):
        divide_two(nPolygons, nVerts, verts, poly, len(nVerts))
    elif(number == 3):
        divide_three(nPolygons, nVerts, verts, poly, len(nVerts), GetPolyCentroid(obpolygon, verts))
    elif(number == 4):
        divide_four(nPolygons, nVerts, verts, poly, len(nVerts), GetPolyCentroid(obpolygon, verts))


# ################## Discombobulate ################ #

def GetPolyCentroid(obpolygon, allvertcoords):
    centroid = Vector((0, 0, 0))
    for vindex in obpolygon.vertices:
        centroid += Vector(allvertcoords[vindex])
    centroid /= len(obpolygon.vertices)
    return centroid


def division(obpolygons, verts, sf1, sf2, sf3, sf4):
    """Function to divide each of the selected polygons"""
    divide = []
    if (sf1):
        divide.append(1)
    if (sf2):
        divide.append(2)
    if (sf3):
        divide.append(3)
    if (sf4):
        divide.append(4)

    for poly in obpolygons:
        if(poly.select is True and len(poly.vertices) == 4):
            a = random.randint(0, len(divide) - 1)
            dividepolygon(poly, verts, divide[a])


def protusion(obverts, obpolygons, minHeight, maxHeight, minTaper, maxTaper):
    """function to generate the protusions"""
    verts = []
    for vertex in obverts:
        verts.append(vertex.co)

    for polygon in obpolygons:
        if(polygon.select is True):
            if(len(polygon.vertices) == 4):
                addProtusionToPolygon(polygon, verts, minHeight, maxHeight, minTaper, maxTaper)


def test_v2_near_v1(v1, v2):
    if (v1.x - 0.1 <= v2.x <= v1.x + 0.1 and
       v1.y - 0.1 <= v2.y <= v1.y + 0.1 and
       v1.z - 0.1 <= v2.z <= v1.z + 0.1):
        return True

    return False


def angle_between_nor(nor_orig, nor_result):
    angle = math.acos(nor_orig.dot(nor_result))
    axis = nor_orig.cross(nor_result).normalized()

    q = Quaternion()
    q.x = axis.x * math.sin(angle / 2)
    q.y = axis.y * math.sin(angle / 2)
    q.z = axis.z * math.sin(angle / 2)
    q.w = math.cos(angle / 2)

    return q


def doodads(self, object1, mesh1, dmin, dmax):
    """function to generate the doodads"""
    global dVerts
    global dPolygons
    i = 0
    # on parcoure cette boucle pour ajouter des doodads a toutes les polygons
    # english translation: this loops adds doodads to all polygons
    while(i < len(object1.data.polygons)):
        if object1.data.polygons[i].select is False:
            continue

        doods_nbr = random.randint(dmin, dmax)
        j = 0

        while(j <= doods_nbr):
            origin_dood = randVertex(object1.data.polygons[i].vertices[0], object1.data.polygons[i].vertices[1],
                                     object1.data.polygons[i].vertices[2], object1.data.polygons[i].vertices[3], Verts)
            type_dood = random.randint(0, len(self.DISC_doodads) - 1)
            polygons_add = []
            verts_add = []

            # First we have to apply scaling and rotation to the mesh
            bpy.ops.object.select_pattern(pattern=self.DISC_doodads[type_dood], extend=False)
            bpy.context.view_layer.objects.active = bpy.data.objects[self.DISC_doodads[type_dood]]
            bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)

            for polygon in bpy.data.objects[self.DISC_doodads[type_dood]].data.polygons:
                polygons_add.append(polygon.vertices)
            for vertex in bpy.data.objects[self.DISC_doodads[type_dood]].data.vertices:
                verts_add.append(vertex.co.copy())
            normal_original_polygon = object1.data.polygons[i].normal

            nor_def = Vector((0.0, 0.0, 1.0))
            qr = nor_def.rotation_difference(normal_original_polygon.normalized())

            if(test_v2_near_v1(nor_def, -normal_original_polygon)):
                qr = Quaternion((0.0, 0.0, 0.0, 0.0))

            # qr = angle_between_nor(nor_def, normal_original_polygon)
            for vertex in verts_add:
                vertex.rotate(qr)
                vertex += origin_dood
            findex = len(dVerts)

            for polygon in polygons_add:
                dPolygons.append([polygon[0] + findex, polygon[1] + findex, polygon[2] + findex, polygon[3] + findex])
                i_dood_type.append(bpy.data.objects[self.DISC_doodads[type_dood]].name)

            for vertex in verts_add:
                dVerts.append(vertex)
            j += 1
        i += 5


def protusions_repeat(object1, mesh1, r_prot):

        for j in i_prots:
            if j < len(object1.data.polygons):
                object1.data.polygons[j].select = True
            else:
                print("Warning: hit end of polygons in object1")


# add material to discombobulated mesh
def setMatProt(discObj, origObj, sideProtMat, topProtMat):
    # First we put the materials in their slots
    bpy.ops.object.select_pattern(pattern=discObj.name, extend=False)
    bpy.context.view_layer.objects.active = bpy.data.objects[discObj.name]
    try:
        origObj.material_slots[topProtMat]
        origObj.material_slots[sideProtMat]
    except:
        return

    bpy.ops.object.material_slot_add()
    bpy.ops.object.material_slot_add()
    discObj.material_slots[0].material = origObj.material_slots[topProtMat].material
    discObj.material_slots[1].material = origObj.material_slots[sideProtMat].material

    # Then we assign materials to protusions
    for polygon in discObj.data.polygons:
        if polygon.index in i_prots:
            polygon.material_index = 0
        else:
            polygon.material_index = 1


def setMatDood(self, doodObj):
    # First we add the materials slots
    bpy.ops.object.select_pattern(pattern=doodObj.name, extend=False)
    bpy.context.view_layer.objects.active = doodObj
    for name in self.DISC_doodads:
        try:
            bpy.ops.object.material_slot_add()
            doodObj.material_slots[-1].material = bpy.data.objects[name].material_slots[0].material
            for polygon in doodObj.data.polygons:
                if i_dood_type[polygon.index] == name:
                    polygon.material_index = len(doodObj.material_slots) - 1
        except:
            print()


def clean_doodads(self):
    current_doodads = list(self.DISC_doodads)

    for name in current_doodads:
        if name not in bpy.data.objects:
            self.DISC_doodads.remove(name)


def discombobulate(self, minHeight, maxHeight, minTaper, maxTaper, sf1, sf2, sf3, sf4,
                   dmin, dmax, r_prot, sideProtMat, topProtMat, isLast):
    global doprots
    global nVerts
    global nPolygons
    global Verts
    global Polygons
    global dVerts
    global dPolygons
    global i_prots

    bpy.ops.object.mode_set(mode="OBJECT")

    # start by cleaning up doodads that don"t exist anymore
    clean_doodads(self)

    # Create the discombobulated mesh
    mesh = bpy.data.meshes.new("tmp")
    object = bpy.data.objects.new("tmp", mesh)
    bpy.context.collection.objects.link(object)

    # init final verts and polygons tuple
    nPolygons = []
    nVerts = []
    Polygons = []
    Verts = []
    dPolygons = []
    dVerts = []

    origObj = bpy.context.active_object

    # There we collect the rotation, translation and scaling datas from the original mesh
    to_translate = bpy.context.active_object.location
    to_scale = bpy.context.active_object.scale
    to_rotate = bpy.context.active_object.rotation_euler

    # First, we collect all the information we will need from the previous mesh
    obverts = bpy.context.active_object.data.vertices
    obpolygons = bpy.context.active_object.data.polygons
    verts = []
    for vertex in obverts:
        verts.append(vertex.co)

    division(obpolygons, verts, sf1, sf2, sf3, sf4)

    # Fill in the discombobulated mesh with the new polygons
    mesh.from_pydata(nVerts, [], nPolygons)
    mesh.update(calc_edges=True)

    # Reload the datas
    bpy.ops.object.select_all(action="DESELECT")
    bpy.ops.object.select_pattern(pattern=object.name, extend=False)
    bpy.context.view_layer.objects.active = bpy.data.objects[object.name]
    obverts = bpy.context.active_object.data.vertices
    obpolygons = bpy.context.active_object.data.polygons

    protusion(obverts, obpolygons, minHeight, maxHeight, minTaper, maxTaper)

    # Fill in the discombobulated mesh with the new polygons
    mesh1 = bpy.data.meshes.new("discombobulated_object")
    object1 = bpy.data.objects.new("discombobulated_mesh", mesh1)
    bpy.context.collection.objects.link(object1)
    mesh1.from_pydata(Verts, [], Polygons)
    mesh1.update(calc_edges=True)

    # Set the material"s of discombobulated object
    setMatProt(object1, origObj, sideProtMat, topProtMat)

    bpy.ops.object.select_pattern(pattern=object1.name, extend=False)
    bpy.context.view_layer.objects.active = bpy.data.objects[object1.name]
    bpy.ops.object.mode_set(mode="EDIT")
    bpy.ops.mesh.normals_make_consistent(inside=False)
    bpy.ops.mesh.select_all(action="DESELECT")
    bpy.ops.object.mode_set(mode="OBJECT")

    # if(bpy.context.scene.repeatprot):
    protusions_repeat(object1, mesh1, r_prot)

    if(len(self.DISC_doodads) != 0 and self.dodoodads and isLast):
        doodads(self, object1, mesh1, dmin, dmax)
        mesh2 = bpy.data.meshes.new("dood_mesh")
        object2 = bpy.data.objects.new("dood_obj", mesh2)
        bpy.context.collection.objects.link(object2)
        mesh2.from_pydata(dVerts, [], dPolygons)
        mesh2.update(calc_edges=True)
        setMatDood(self, object2)
        object2.location = to_translate
        object2.rotation_euler = to_rotate
        object2.scale = to_scale

    bpy.ops.object.select_pattern(pattern=object.name, extend=False)
    bpy.context.view_layer.objects.active = bpy.data.objects[object.name]
    bpy.ops.object.delete()

    bpy.ops.object.select_pattern(pattern=object1.name, extend=False)
    bpy.context.view_layer.objects.active = bpy.data.objects[object1.name]
    bpy.context.view_layer.update()

    # translate, scale and rotate discombobulated results
    object1.location = to_translate
    object1.rotation_euler = to_rotate
    object1.scale = to_scale

    # set all polys to selected. this allows recursive discombobulating.
    for poly in mesh1.polygons:
        poly.select = True


# ### Operators for selecting and deselecting an object as a doodad ### #

class chooseDoodad(Operator):
    bl_idname = "object.discombobulate_set_doodad"
    bl_label = "Discombobulate set doodad object"
    bl_description = ("Save the Active Object as Doodad \n"
                      "Object has to be quads only")
    bl_options = {"REGISTER"}

    @classmethod
    def poll(cls, context):
        obj = bpy.context.active_object
        if (obj is not None and obj.type == "MESH"):
            mesh = obj.data

            for polygon in mesh.polygons:
                is_ok = len(polygon.vertices)
                if is_ok != 4:
                    return False
            return True

        return False

    def execute(self, context):
        obj_name = bpy.context.active_object.name
        msg = "Object with this name already saved"

        DISC_doodads = context.scene.discombobulator.DISC_doodads

        if obj_name not in DISC_doodads:
            DISC_doodads.append(obj_name)
            msg = "Saved Doodad object: {}".format(obj_name)

        self.report({"INFO"}, message=msg)

    def invoke(self, context, event):
        self.execute(context)
        return {"FINISHED"}


class unchooseDoodad(Operator):
    bl_idname = "object.discombobulate_unset_doodad"
    bl_label = "Discombobulate unset doodad object"
    bl_description = "Remove the saved Doodad Object(s)"
    bl_options = {"REGISTER"}

    remove_all: bpy.props.BoolProperty(
                        name="Remove all Doodads",
                        default=False,
                        )

    def execute(self, context):
        msg = ("No doodads to remove")
        DISC_doodads = context.scene.discombobulator.DISC_doodads
        if len(DISC_doodads) > 0:
            if not self.remove_all:
                name = bpy.context.active_object.name
                if name in DISC_doodads:
                    DISC_doodads.remove(name)
                    msg = ("Removed Doodad object: {}".format(name))
            else:
                DISC_doodads[:] = []
                msg = "Removed all Doodads"
        else:
            msg = "No Doodads to Remove"

        self.report({"INFO"}, message=msg)

    def invoke(self, context, event):
        self.execute(context)
        return {"FINISHED"}


# ################## Interpolygon ################## #

class discombobulator(Operator):
    bl_idname = "object.discombobulate"
    bl_label = "Discombobulate"
    bl_description = "Apply"
    bl_options = {"REGISTER", "UNDO"}

    def execute(self, context):
        i = 0
        while i < self.repeatprot:
            isLast = False
            if i == self.repeatprot - 1:
                isLast = True
            discombobulate(self.minHeight, self.maxHeight, self.minTaper, self.maxTaper, self.subpolygon1,
                           self.subpolygon2, self.subpolygon3, self.subpolygon4, self.mindoodads, self.maxdoodads,
                           self.repeatprot, self.sideProtMat, self.topProtMat, isLast)
            i += 1
        return {"FINISHED"}


class discombobulator_dodads_list(Menu):
    bl_idname = "OBJECT_MT_discombobulator_dodad_list"
    bl_label = "List of saved Doodads"
    bl_description = "List of the saved Doodad Object Names"
    bl_options = {"REGISTER"}

    def draw(self, context):
        layout = self.layout

        DISC_doodads = context.scene.discombobulator.DISC_doodads

        doodle = len(DISC_doodads)
        layout.label(text="Saved doodads : {}".format(doodle))
        layout.separator()
        if doodle > 0:
            for name in DISC_doodads:
                layout.label(text=name)


class discombob_help(Menu):
    bl_idname = "HELP_MT_discombobulator"
    bl_label = "Usage Information"
    bl_description = "Help"
    bl_options = {"REGISTER"}

    def draw(self, context):
        layout = self.layout
        layout.label(text="Usage Information:", icon="INFO")
        layout.separator()
        layout.label(text="Quads only, not Triangles or Ngons", icon="ERROR")
        layout.label(text="Works only with Mesh object that have faces")
        layout.separator()
        layout.label(text="Select a face or faces")
        layout.label(text="Press Discombobulate to create greebles")
        layout.label(text="In object mode, still needs a selection in Edit Mode")
        layout.separator()
        layout.label(text="Doodads - additional objects layered on the mesh surface")
        layout.label(text="(Similar to dupliverts - but as one separate object)")
        layout.separator()
        layout.label(text="Limitations:", icon="MOD_EXPLODE")
        layout.label(text="Be careful with the repeat protusions setting")
        layout.label(text="(Runs reqursively)")
        layout.label(text="If possible, avoid using on a high polycount base mesh")
        layout.label(text="(It can run out of memory and take a long time to compute)")

class VIEW3D_OT_tools_discombobulate(Operator):
    bl_idname = "discombobulate.ops"
    bl_label = "Discombobulator"
    bl_description = ("Easily add sci-fi details to a surface \n"
                      "Needs an existing active Mesh with Faces")
    bl_options = {"REGISTER", "UNDO"}

    executing = False

    # Protusions Buttons:
    repeatprot: IntProperty(
            name="Repeat protusions",
            description=("Make several layers of protusion \n"
                         "Use carefully, runs recursively the discombulator"),
            default=1, min=1, max=4  # set to 4 because it's 2**n reqursive
            )
    doprots: BoolProperty(
            name="Make protusions",
            description="Check if we want to add protusions to the mesh",
            default=True
            )
    subpolygon1: BoolProperty(
            name="1",
            default=True
            )
    subpolygon2: BoolProperty(
            name="2",
            default=True
            )
    subpolygon3: BoolProperty(
            name="3",
            default=True
            )
    subpolygon4: BoolProperty(
            name="4",
            default=True
            )
    polygonschangedpercent: FloatProperty(
            name="Polygon %",
            description="Percentage of changed polygons",
            default=1.0
            )
    minHeight: FloatProperty(
            name="Min height",
            description="Minimal height of the protusions",
            default=0.2
            )
    maxHeight: FloatProperty(
            name="Max height",
            description="Maximal height of the protusions",
            default=0.4
            )
    minTaper: FloatProperty(
            name="Min taper",
            description="Minimal height of the protusions",
            default=0.15, min=0.0, max=1.0,
            subtype='PERCENTAGE'
            )
    maxTaper: FloatProperty(
            name="Max taper",
            description="Maximal height of the protusions",
            default=0.35, min=0.0, max=1.0,
            subtype='PERCENTAGE'
            )
    # Doodads buttons:
    dodoodads: BoolProperty(
            name="Make doodads",
            description="Check if we want to generate doodads",
            default=False
            )
    mindoodads: IntProperty(
            name="Minimum doodads number",
            description="Ask for the minimum number of doodads to generate per polygon",
            default=1, min=0, max=50
            )
    maxdoodads: IntProperty(
            name="Maximum doodads number",
            description="Ask for the maximum number of doodads to generate per polygon",
            default=6, min=1, max=50
            )
    doodMinScale: FloatProperty(
            name="Scale min", description="Minimum scaling of doodad",
            default=0.5, min=0.0, max=1.0,
            subtype='PERCENTAGE'
            )
    doodMaxScale: FloatProperty(
            name="Scale max",
            description="Maximum scaling of doodad",
            default=1.0, min=0.0, max=1.0,
            subtype='PERCENTAGE'
            )
    # Materials buttons:
    sideProtMat: IntProperty(
            name="Side's prot mat",
            description="Material of protusion's sides",
            default=0, min=0
            )
    topProtMat: IntProperty(
            name="Prot's top mat",
            description="Material of protusion's top",
            default=0, min=0
            )

    @classmethod
    def poll(cls, context):
        return (context.active_object is not None and
                context.active_object.type == "MESH")

    def draw(self, context):
        layout = self.layout

        self.DISC_doodads = bpy.context.scene.discombobulator.DISC_doodads

        row = layout.row()
        row.menu("HELP_MT_discombobulator", icon="INFO")
        box = layout.box()
        box.label(text="Protusions settings")
        row = box.row()
        row.prop(self, "doprots")
        row = box.row()
        row.prop(self, "minHeight")
        row = box.row()
        row.prop(self, "maxHeight")
        row = box.row()
        row.prop(self, "minTaper")
        row = box.row()
        row.prop(self, "maxTaper")
        row = box.row()
        col1 = row.column(align=True)
        col1.prop(self, "subpolygon1")
        col2 = row.column(align=True)
        col2.prop(self, "subpolygon2")
        col3 = row.column(align=True)
        col3.prop(self, "subpolygon3")
        col4 = row.column(align=True)
        col4.prop(self, "subpolygon4")
        row = box.row()
        row.prop(self, "repeatprot")
        box = layout.box()
        box.label(text="Doodads settings")
        row = box.row()
        is_doodad = self.dodoodads
        row.prop(self, "dodoodads")

        row = box.row()
        row.enabled = is_doodad
        row.prop(self, "mindoodads")
        row = box.row()
        row.enabled = is_doodad
        row.prop(self, "maxdoodads")
        row = box.row()
        row.enabled = is_doodad
        oper = row.operator("object.discombobulate_set_doodad", text="Pick doodad")

        row = box.row()
        splits = row.split(factor = 0.5)
        splits.enabled = is_doodad
        splits.operator("object.discombobulate_unset_doodad",
                        text="Remove active doodad").remove_all = False
        splits.operator("object.discombobulate_unset_doodad",
                        text="Remove all doodads").remove_all = True

        col = box.column(align=True)
        doodle = len(self.DISC_doodads)

        col.enabled = (True if doodle > 0 else False)
        col.menu("OBJECT_MT_discombobulator_dodad_list",
                     text="List of saved Doodads ({})".format(doodle))

        box = layout.box()
        box.label(text="Materials settings")
        row = box.row()
        row.prop(self, "topProtMat")
        row = box.row()
        row.prop(self, "sideProtMat")

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self, width=300)

    def check(self, context):
        return not self.executing

    def execute(self, context):
        self.executing = True
        i = 0
        while i < self.repeatprot:
            isLast = False
            if i == self.repeatprot - 1:
                isLast = True
            discombobulate(self, self.minHeight, self.maxHeight, self.minTaper, self.maxTaper, self.subpolygon1,
                           self.subpolygon2, self.subpolygon3, self.subpolygon4, self.mindoodads, self.maxdoodads,
                           self.repeatprot, self.sideProtMat, self.topProtMat, isLast)
            i += 1
        return {"FINISHED"}
        #bpy.ops.object.discombobulate("INVOKE_DEFAULT")