diff options
author | Spivak Vladimir (cwolf3d) <cwolf3d@gmail.com> | 2019-04-21 14:05:35 +0300 |
---|---|---|
committer | Spivak Vladimir (cwolf3d) <cwolf3d@gmail.com> | 2019-04-21 14:05:35 +0300 |
commit | 2cc1dd0409ae1f36cb45be1261a2c7ab5f41bec6 (patch) | |
tree | 44ba4514adc5a48ec58aef8eaa4a9f3e7c24b981 /add_mesh_discombobulator | |
parent | e49613a5f9c879b1b525e8ac3e3c8af31c3eabcd (diff) |
Separated mesh_discombobulator from add_mesh_extra_objects
Diffstat (limited to 'add_mesh_discombobulator')
-rw-r--r-- | add_mesh_discombobulator/__init__.py | 90 | ||||
-rw-r--r-- | add_mesh_discombobulator/mesh_discombobulator.py | 870 |
2 files changed, 960 insertions, 0 deletions
diff --git a/add_mesh_discombobulator/__init__.py b/add_mesh_discombobulator/__init__.py new file mode 100644 index 00000000..853dbc89 --- /dev/null +++ b/add_mesh_discombobulator/__init__.py @@ -0,0 +1,90 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### +# Contributed to by: +# Pontiac, Fourmadmen, varkenvarken, tuga3d, meta-androcto, metalliandy # +# dreampainter, cotejrp1, liero, Kayo Phoenix, sugiany, dommetysk, Jambay # +# Phymec, Anthony D'Agostino, Pablo Vazquez, Richard Wilks, lijenstina, # +# Sjaak-de-Draak, Phil Cote, cotejrp1, xyz presets by elfnor, revolt_randy, # + + +bl_info = { + "name": "Discombobulator", + "author": "Multiple Authors", + "version": (0, 0, 8), + "blender": (2, 80, 0), + "location": "View3D > Add > Mesh", + "description": "Add Discombobulator", + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" + "Py/Scripts/Add_Mesh/Add_Extra", + "category": "Add Mesh", +} + +# Note: Blocks has to be loaded before the WallFactory or the script +# will not work properly after (F8) reload + +if "bpy" in locals(): + import importlib + importlib.reload(mesh_discombobulator) +else: + from . import mesh_discombobulator + +import bpy +from bpy.types import Menu + +# Register all operators and panels + +# Define "Extras" menu +def menu_func(self, context): + lay_out = self.layout + lay_out.operator_context = 'INVOKE_REGION_WIN' + + lay_out.separator() + lay_out.operator("discombobulate.ops", + text="Discombobulator") + +# Register +classes = [ + + mesh_discombobulator.discombobulator, + mesh_discombobulator.discombobulator_dodads_list, + mesh_discombobulator.discombob_help, + mesh_discombobulator.VIEW3D_OT_tools_discombobulate, + mesh_discombobulator.chooseDoodad, + mesh_discombobulator.unchooseDoodad +] + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + + # Add "Extras" menu to the "Add Mesh" menu + bpy.types.VIEW3D_MT_mesh_add.append(menu_func) + + +def unregister(): + # Remove "Extras" menu from the "Add Mesh" menu. + bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) + + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + +if __name__ == "__main__": + register() diff --git a/add_mesh_discombobulator/mesh_discombobulator.py b/add_mesh_discombobulator/mesh_discombobulator.py new file mode 100644 index 00000000..f1d564ac --- /dev/null +++ b/add_mesh_discombobulator/mesh_discombobulator.py @@ -0,0 +1,870 @@ +# GPL # 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(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(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.scene.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" + + if obj_name not in self.DISC_doodads: + self.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") + doodadery = self.DISC_doodads + if len(doodadery) > 0: + if not self.remove_all: + name = bpy.context.active_object.name + if name in doodadery: + self.DISC_doodads.remove(name) + msg = ("Removed Doodad object: {}".format(name)) + else: + self.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 + + doodle = len(self.DISC_doodads) + layout.label(text="Saved doodads : {}".format(doodle)) + layout.separator() + if doodle > 0: + for name in self.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("(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("(Runs reqursively)") + layout.label(text="If possible, avoid using on a high polycount base mesh") + layout.label("(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"} + + executing = False + + DISC_doodads = [] + # 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 + + 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") |