diff --git a/add_mesh_solid.py b/add_mesh_solid.py new file mode 100644 index 0000000000000000000000000000000000000000..ac501bcd985f8f5fbe5a7f54ab68b49de02a0236 --- /dev/null +++ b/add_mesh_solid.py @@ -0,0 +1,920 @@ +import bpy +from bpy.props import FloatProperty,EnumProperty,BoolProperty +from math import sqrt +from mathutils import Vector,Matrix +#from rawMeshUtils import * +from functools import reduce + +bl_addon_info = { + 'name': 'Add Mesh: Regular Solids', + 'author': 'DreamPainter', + 'version': '1', + 'blender': (2, 5, 3), + 'location': 'View3D > Add > Mesh > Regular Solids', + 'description': 'Add a Regular Solid mesh.', + 'url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \ + 'Scripts/Add_Mesh/Add_Solid', + 'category': 'Add Mesh'} + +# Stores the values of a list of properties and the +# operator id in a property group ('recall_op') inside the object. +# Could (in theory) be used for non-objects. +# Note: Replaces any existing property group with the same name! +# ob ... Object to store the properties in. +# op ... The operator that should be used. +# op_args ... A dictionary with valid Blender +# properties (operator arguments/parameters). +def store_recall_properties(ob, op, op_args): + if ob and op and op_args: + recall_properties = {} + + # Add the operator identifier and op parameters to the properties. + recall_properties['op'] = op.bl_idname + recall_properties['args'] = op_args + + # Store new recall properties. + ob['recall'] = recall_properties + + +# Apply view rotation to objects if "Align To" for +# new objects was set to "VIEW" in the User Preference. +def apply_object_align(context, ob): + obj_align = bpy.context.user_preferences.edit.object_align + + if (context.space_data.type == 'VIEW_3D' + and obj_align == 'VIEW'): + view3d = context.space_data + region = view3d.region_3d + viewMatrix = region.view_matrix + rot = viewMatrix.rotation_part() + ob.rotation_euler = rot.invert().to_euler() + + +# Create a new mesh (object) from verts/edges/faces. +# verts/edges/faces ... List of vertices/edges/faces for the +# new mesh (as used in from_pydata). +# name ... Name of the new mesh (& object). +# edit ... Replace existing mesh data. +# Note: Using "edit" will destroy/delete existing mesh data. +def create_mesh_object(context, verts, edges, faces, name, edit): + scene = context.scene + obj_act = scene.objects.active + + # Can't edit anything, unless we have an active obj. + if edit and not obj_act: + return None + + # Create new mesh + mesh = bpy.data.meshes.new(name) + + # Make a mesh from a list of verts/edges/faces. + mesh.from_pydata(verts, edges, faces) + + # Update mesh geometry after adding stuff. + mesh.update() + + # Deselect all objects. + bpy.ops.object.select_all(action='DESELECT') + + if edit: + # Replace geometry of existing object + + # Use the active obj and select it. + ob_new = obj_act + ob_new.selected = True + + if obj_act.mode == 'OBJECT': + # Get existing mesh datablock. + old_mesh = ob_new.data + + # Set object data to nothing + ob_new.data = None + + # Clear users of existing mesh datablock. + old_mesh.user_clear() + + # Remove old mesh datablock if no users are left. + if (old_mesh.users == 0): + bpy.data.meshes.remove(old_mesh) + + # Assign new mesh datablock. + ob_new.data = mesh + + else: + # Create new object + ob_new = bpy.data.objects.new(name, mesh) + + # Link new object to the given scene and select it. + scene.objects.link(ob_new) + ob_new.selected = True + + # Place the object at the 3D cursor location. + ob_new.location = scene.cursor_location + + apply_object_align(context, ob_new) + + if obj_act and obj_act.mode == 'EDIT': + if not edit: + # We are in EditMode, switch to ObjectMode. + bpy.ops.object.mode_set(mode='OBJECT') + + # Select the active object as well. + obj_act.selected = True + + # Apply location of new object. + scene.update() + + # Join new object into the active. + bpy.ops.object.join() + + # Switching back to EditMode. + bpy.ops.object.mode_set(mode='EDIT') + + ob_new = obj_act + + else: + # We are in ObjectMode. + # Make the new object the active one. + scene.objects.active = ob_new + + return ob_new + + +# A very simple "bridge" tool. +# Connects two equally long vertex rows with faces. +# Returns a list of the new faces (list of lists) +# +# vertIdx1 ... First vertex list (list of vertex indices). +# vertIdx2 ... Second vertex list (list of vertex indices). +# closed ... Creates a loop (first & last are closed). +# flipped ... Invert the normal of the face(s). +# +# Note: You can set vertIdx1 to a single vertex index to create +# a fan/star of faces. +# Note: If both vertex idx list are the same length they have +# to have at least 2 vertices. +def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False): + faces = [] + + if not vertIdx1 or not vertIdx2: + return None + + if len(vertIdx1) < 2 and len(vertIdx2) < 2: + return None + + fan = False + if (len(vertIdx1) != len(vertIdx2)): + if (len(vertIdx1) == 1 and len(vertIdx2) > 1): + fan = True + else: + return None + + total = len(vertIdx2) + + if closed: + # Bridge the start with the end. + if flipped: + face = [ + vertIdx1[0], + vertIdx2[0], + vertIdx2[total - 1]] + if not fan: + face.append(vertIdx1[total - 1]) + faces.append(face) + + else: + face = [vertIdx2[0], vertIdx1[0]] + if not fan: + face.append(vertIdx1[total - 1]) + face.append(vertIdx2[total - 1]) + faces.append(face) + + # Bridge the rest of the faces. + for num in range(total - 1): + if flipped: + if fan: + face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]] + else: + face = [vertIdx2[num], vertIdx1[num], + vertIdx1[num + 1], vertIdx2[num + 1]] + faces.append(face) + else: + if fan: + face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]] + else: + face = [vertIdx1[num], vertIdx2[num], + vertIdx2[num + 1], vertIdx1[num + 1]] + faces.append(face) + + return faces +# this function creates a chain of quads and, when necessary, a remaining tri +# for each polygon created in this script. be aware though, that this function +# assumes each polygon is convex. +# poly: list of faces, or a single face, like those +# needed for mesh.from_pydata. +# returns the tesselated faces. +def createPolys(poly): + # check for faces + if len(poly) == 0: + return [] + # one or more faces + if type(poly[0]) == type(1): + poly = [poly] # if only one, make it a list of one face + faces = [] + for i in poly: + l = len(i) + # let all faces of 3 or 4 verts be + if l < 5: + faces.append(i) + # split all polygons in half and bridge the two halves + else: + half = int(l/2) + f = createFaces(i[:half],[i[-1-j] for j in range(half)]) + faces.extend(f) + # if the polygon has an odd number of verts, add the last tri + if l%2 == 1: + faces.append([i[half-1],i[half],i[half+1]]) + return faces + +# function to make the reduce function work as a workaround to sum a list of vectors +def Asum(list): + return reduce(lambda a,b: a+b, list) + +# creates the 5 platonic solids as a base for the rest +# plato: should be one of {"4","6","8","12","20"}. decides what solid the +# outcome will be. +# returns a list of vertices and faces and the appropriate name +def source(plato): + verts = [] + faces = [] + + # Tetrahedron + if plato == "4": + # Calculate the necessary constants + s = sqrt(2)/3.0 + t = -1/3 + u = sqrt(6)/3 + + # create the vertices and faces + v = [(0,0,1),(2*s,0,t),(-s,u,t),(-s,-u,t)] + faces = [[0,1,2],[0,2,3],[0,3,1],[1,3,2]] + + # Hexahedron (cube) + elif plato == "6": + # Calculate the necessary constants + s = 1/sqrt(3) + + # create the vertices and faces + v = [(-s,-s,-s),(s,-s,-s),(s,s,-s),(-s,s,-s),(-s,-s,s),(s,-s,s),(s,s,s),(-s,s,s)] + faces = [[0,3,2,1],[0,1,5,4],[0,4,7,3],[6,5,1,2],[6,2,3,7],[6,7,4,5]] + + # Octahedron + elif plato == "8": + # create the vertices and faces + v = [(1,0,0),(-1,0,0),(0,1,0),(0,-1,0),(0,0,1),(0,0,-1)] + faces = [[4,0,2],[4,2,1],[4,1,3],[4,3,0],[5,2,0],[5,1,2],[5,3,1],[5,0,3]] + + # Dodecahedron + elif plato == "12": + # Calculate the necessary constants + s = 1/sqrt(3) + t = sqrt((3-sqrt(5))/6) + u = sqrt((3+sqrt(5))/6) + + # create the vertices and faces + v = [(s,s,s),(s,s,-s),(s,-s,s),(s,-s,-s),(-s,s,s),(-s,s,-s),(-s,-s,s),(-s,-s,-s), + (t,u,0),(-t,u,0),(t,-u,0),(-t,-u,0),(u,0,t),(u,0,-t),(-u,0,t),(-u,0,-t),(0,t,u), + (0,-t,u),(0,t,-u),(0,-t,-u)] + faces = [[0,8,9,4,16],[0,12,13,1,8],[0,16,17,2,12],[8,1,18,5,9],[12,2,10,3,13], + [16,4,14,6,17],[9,5,15,14,4],[6,11,10,2,17],[3,19,18,1,13],[7,15,5,18,19], + [7,11,6,14,15],[7,19,3,10,11]] + + # Icosahedron + elif plato == "20": + # Calculate the necessary constants + s = (1+sqrt(5))/2 + t = sqrt(1+s*s) + s = s/t + t = 1/t + + # create the vertices and faces + v = [(s,t,0),(-s,t,0),(s,-t,0),(-s,-t,0),(t,0,s),(t,0,-s),(-t,0,s),(-t,0,-s), + (0,s,t),(0,-s,t),(0,s,-t),(0,-s,-t)] + faces = [[0,8,4],[0,5,10],[2,4,9],[2,11,5],[1,6,8],[1,10,7],[3,9,6],[3,7,11], + [0,10,8],[1,8,10],[2,9,11],[3,11,9],[4,2,0],[5,0,2],[6,1,3],[7,3,1], + [8,6,4],[9,4,6],[10,5,7],[11,7,5]] + + # handles faulty values of plato + else: + print("Choose keyword 'plato' from {'4','6','8','12','20'}") + return None + + # convert the tuples to Vectors + verts = [Vector(i) for i in v] + + return verts,faces + +# processes the raw data from source +def createSolid(plato,vtrunc,etrunc,dual,snub): + verts = [] + faces = [] + edges = [] + # the duals from each platonic solid + dualSource = {"4":"4", + "6":"8", + "8":"6", + "12":"20", + "20":"12"} + + # constants saving space and readability + vtrunc *= 0.5 + etrunc *= 0.5 + supposed_size = 0 + noSnub = (snub == "0") or (etrunc == 0.5) or (etrunc == 0) + lSnub = (snub == "L") and (0 < etrunc < 0.5) + rSnub = (snub == "R") and (0 < etrunc < 0.5) + + # no truncation + if vtrunc == 0: + if dual: # dual is as simple as another, but mirrored platonic solid + vInput,fInput = source(dualSource[plato]) + supposed_size = Asum([vInput[i] for i in fInput[0]]).length/len(fInput[0]) + vInput = [-i*supposed_size for i in vInput] # mirror it + return vInput,fInput + return source(plato) + # simple truncation of the source + elif 0.5 >= vtrunc > 0: + vInput,fInput = source(plato) + # truncation is now equal to simple truncation of the dual of the source + elif vtrunc > 0.5: + vInput,fInput = source(dualSource[plato]) + supposed_size = Asum([vInput[i] for i in fInput[0]]).length/len(fInput[0]) + # account for the source being a dual + vtrunc = 1-vtrunc + if vtrunc == 0: # no truncation + if dual: + vInput,fInput = source(plato) + vInput = [i*supposed_size for i in vInput] + return vInput,fInput,sourceName + vInput = [-i*supposed_size for i in vInput] + return vInput,fInput + + # generate a database for creating the faces. this exists out of a list for + # every vertex in the source + # 0 : vertex id + # 1 : vertices connected to this vertex, listed ccw(Counter Clock Wise) + # 2 : vertices generated to form the faces of this vertex + # 3 : faces connected to this vertex, listed ccw + # 4 : dictionairy containing the verts used by the connected faces + # 5 : list of edges that use this vertex, listed ccw + # 6 : dictionairy containing the verts used by the connected edges + v = [[i,[],[],[],{},[],{}] for i in range(len(vInput))] + + # this piece of code, generates the database and the lists in ccw order + for x in range(len(fInput)): + i = fInput[x] + # in every faces, check which vertices connect the each vert and sort + # in ccw order + for j in range(-1,len(i)-1): + # only generate an edge dict, if edge truncation is needed + if etrunc: + # list edges as [min,max], to evade confusion + first = min([i[j-1],i[j]]) + last = max([i[j-1],i[j]]) + # if an edge is not allready in, add it and give the index + try: + y = edges.index([first,last]) + except: + edges.append([first,last]) + y = len(edges)-1 + # add a dict item + v[i[j]][6][str(y)] = [0,0] + # the vertex before and after the current vertex, check whether they + # are allready in the database + after = i[j+1] not in v[i[j]][1] + before = i[j-1] not in v[i[j]][1] + # sort them and add faces and, when necessary, edges in the database + if after: + if before: + v[i[j]][1].append(i[j+1]) + v[i[j]][1].append(i[j-1]) + v[i[j]][3].append(x) + if etrunc: v[i[j]][5].append(y) + else: + z = v[i[j]][1].index(i[j-1]) + v[i[j]][1].insert(z,i[j+1]) + v[i[j]][3].insert(z,x) + if etrunc: v[i[j]][5].insert(z,y) + else: + z = v[i[j]][1].index(i[j+1]) + v[i[j]][3].insert(z,x) + if etrunc: v[i[j]][5].insert(z,y) + if before: + v[i[j]][1].insert(z+1,i[j-1]) + # add the current face to the current vertex in the dict + v[i[j]][4][str(x)] = [0,0] + + # generate vert-only truncated vertices by linear interpolation + for i in v: + for j in range(len(i[1])): + verts.append(vInput[i[0]]*(1-vtrunc)+vInput[i[1][j]]*vtrunc) + l = len(verts)-1 + # face resulting from truncating this vertex + i[2].append(l) + # this vertex is used by both faces using this edge + i[4][str(i[3][j])][1] = l + i[4][str(i[3][j-1])][0] = l + + # only truncate edges when needed + vert_faces = [] + if etrunc: + # generate a new list of vertices, by linear interpolating each vert-face + nVerts = [] + for i in v: + f = [] + # weird range so we dont run out of array bounds + for j in range(-1,len(i[2])-1): + # making use of the fact that the snub operation takes only + # one of the two vertices per edge. so rSnub only takes the + # first, lSnub only takes the second, and noSnub takes both + if rSnub or noSnub: + # interpolate + nVerts.append((1-etrunc)*verts[i[2][j]] + etrunc*verts[i[2][j-1]]) + # add last vertex to the vert-face, face-face and edge-face + l = len(nVerts)-1 + f.append(l) + i[4][str(i[3][j-1])][0] = l + i[6][str(i[5][j-1])][1] = l + if lSnub or noSnub: + # interpolate + nVerts.append((1-etrunc)*verts[i[2][j]] + etrunc*verts[i[2][j+1]]) + # add last vertex to the vert-face, face-face and edge-face + l = len(nVerts)-1 + f.append(l) + i[4][str(i[3][j])][1] = l + i[6][str(i[5][j-1])][0] = l + # add vert-face + vert_faces.append(f) + + # snub operator creates 2 tri's instead of a planar quad, needing the + # next piece of code. making use of the dictionairy to create them. + if lSnub or rSnub: + edge_faces = [] + for x in range(len(edges)): + one = v[edges[x][0]] # the first vertex of this edge + two = v[edges[x][1]] # the second + # using max() since the dict consists of one filled spot and one + # empty('cause only one vert is created) + f = [max(two[6][str(x)]),max(one[6][str(x)])] + index = one[5].index(x) + # create this tri from the middle line and the the previous edge + # on this vertex + if lSnub: + f.append(max(one[6][str(one[5][index-1])])) + else: # or in this case, the next + if index+1 >= len(one[5]): index = -1 + f.append(max(one[6][str(one[5][index+1])])) + edge_faces.append(f) + + # do the same for the other end of the edge + f = [max(one[6][str(x)]),max(two[6][str(x)])] + index = two[5].index(x) + if lSnub: + f.append(max(two[6][str(two[5][index-1])])) + else: + if index+1 >= len(one[5]): index = -1 + f.append(max(two[6][str(two[5][index+1])])) + edge_faces.append(f) + else: + # generate edge-faces from the dictionairy, simple quads for noSnub + edge_faces = [] + for i in range(len(edges)): + f = [] + for j in edges[i]: + f.extend(v[j][6][str(i)]) + edge_faces.append(f) + verts = nVerts + else: + # generate vert-faces for non-edge-truncation + vert_faces = [i[2] for i in v] + + # calculate supposed vertex length to ensure continuity + if supposed_size: + supposed_size *= len(vert_faces[0])/Asum([verts[i] for i in vert_faces[0]]).length + verts = [-i*supposed_size for i in verts] + + # generate face-faces by looking up the old verts and replacing them with + # the vertices in the dictionairy + face_faces = [] + for x in range(len(fInput)): + f = [] + for j in fInput[x]: + # again using the fact, that only one of the two verts is used + # for snub operation + if rSnub and etrunc: + f.append(v[j][4][str(x)][0]) + elif lSnub and etrunc: + f.append(v[j][4][str(x)][1]) + else: + # for cool graphics, comment the first line and uncomment the second line + # then work the vTrunc property, leave the other properties at 0 + # (can also change 0 to 1 in second line to change from ccw to cw) + f.extend(v[j][4][str(x)]) # first + #f.append(v[j][4][str(x)][0]) # second + face_faces.append(f) + + if dual: + # create verts by taking the average of all vertices that make up each + # face. do it in this order to ease the following face creation + nVerts = [] + for i in vert_faces: + nVerts.append(Asum([verts[j] for j in i])/len(i)) + if etrunc: + eStart = len(nVerts) + for i in edge_faces: + nVerts.append(Asum([verts[j] for j in i])/len(i)) + fStart = len(nVerts) + for i in face_faces: + nVerts.append(Asum([verts[j] for j in i])/len(i)) + # the special face generation for snub duals, it sucks, even i dont get it + if lSnub or rSnub: + for x in range(len(fInput)): + i = fInput[x] + for j in range(-1,len(i)-1): + + if i[j] > i[j+1]: + eNext = edges.index([i[j+1],i[j]]) + [a,b] = [1,0] + else: + eNext = edges.index([i[j],i[j+1]]) + [a,b] = [0,1] + if i[j] > i[j-1]: + ePrev = edges.index([i[j-1],i[j]]) + [c,d] = [0,1] + else: + ePrev = edges.index([i[j],i[j-1]]) + [c,d] = [1,0] + if lSnub: + f = [eStart+2*eNext+b,eStart+2*eNext+a,i[j]] + f.append(eStart+2*ePrev+d) + f.append(fStart + x) + else: + f = [eStart+2*ePrev+c,eStart+2*ePrev+d,i[j]] + f.append(eStart+2*eNext+a) + f.append(fStart + x) + if supposed_size: faces.append(f) + else: faces.append(f[2:]+f[:2]) + else: + # for noSnub situations, the face generation is somewhat easier. + # first calculate what order faces must be added to ensure convex solids + # this by calculating the angle between the middle of the four vertices + # and the first face. if the face is above the middle, use that diagonal + # otherwise use the other diagonal + if etrunc: + f = [v[0][0],eStart+v[0][5][-1],fStart+v[0][3][0],eStart+v[0][5][0]] + else: + f = [v[0][0],fStart+v[0][3][0],v[0][1][0],fStart+v[0][3][-1]] + p = [nVerts[i] for i in f] + mid = 0.25*Asum(p) + norm = (p[1]-p[0]).cross(p[2]-p[0]) + dot = norm.dot(mid-p[0])/(norm.length*(mid-p[0]).length) + tollerance = 0.001 # ~ cos(0.06 degrees) + if ((dot > tollerance) and (not supposed_size)) or ((dot < -tollerance) and (supposed_size)): + direction = 1 # first diagonal + elif ((dot < -tollerance) and (not supposed_size)) or ((dot > tollerance) and (supposed_size)): + direction = -1 # second diagonal + else: + direction = 0 # no diagonal, face is planar (somewhat) + + if etrunc: # for every vertex + for i in v: # add the face, consisting of the vert,edge,next + # edge and face between those edges + for j in range(len(i[1])): + f = [i[0],eStart+i[5][j-1],fStart+i[3][j],eStart+i[5][j]] + if direction == 1: # first diagonal + faces.extend([[f[0],f[1],f[3]],[f[1],f[2],f[3]]]) + elif direction == -1: # first diagonal + faces.extend([[f[0],f[1],f[2]],[f[0],f[2],f[3]]]) + else: + faces.append(f) # no diagonal + else: + for i in v: # for every vertex + for j in range(len(i[1])): + if i[0] < i[1][j]: # face consists of vert, vert on other + # end of edge and both faces using that + # edge, so exclude verts allready used + f = [i[0],fStart+i[3][j], i[1][j],fStart+i[3][j-1]] + if direction == -1: # secong diagonal + faces.extend([[f[0],f[1],f[3]],[f[1],f[2],f[3]]]) + elif direction == 1: # first diagonal + faces.extend([[f[0],f[1],f[2]],[f[0],f[2],f[3]]]) + else: + faces.append(f) # no diagonal + verts = nVerts # use new vertices + else: + # concatenate all faces, since they dont have to be used sepperately anymore + faces = face_faces + if etrunc: faces += edge_faces + faces += vert_faces + + return verts,faces + + +class Solids(bpy.types.Operator): + """Add one of the (regular) solids (mesh)""" + bl_idname = "mesh.primitive_solid_add" + bl_label = "(Regular) solids" + bl_description = "Add one of the platoic or archimedean solids" + bl_options = {'REGISTER', 'UNDO'} + + source = EnumProperty(items = (("4","Tetrahedron",""), + ("6","Hexahedron",""), + ("8","Octahedron",""), + ("12","Dodecahedron",""), + ("20","Icosahedron","")), + name = "Source", + description = "Starting point of your solid") + size = FloatProperty(name = "Size", + description = "Radius of the sphere through the vertices", + min = 0.01, + soft_min = 0.01, + max = 100, + soft_max = 100, + default = 1.0) + vTrunc = FloatProperty(name = "Vertex Truncation", + description = "Ammount of vertex truncation", + min = 0.0, + soft_min = 0.0, + max = 2.0, + soft_max = 2.0, + default = 0.0, + precision = 3, + step = 0.5) + eTrunc = FloatProperty(name = "Edge Truncation", + description = "Ammount of edge truncation", + min = 0.0, + soft_min = 0.0, + max = 1.0, + soft_max = 1.0, + default = 0.0, + precision = 3, + step = 0.2) + snub = EnumProperty(items = (("0","No Snub",""), + ("L","Left Snub",""), + ("R","Right Snub","")), + name = "Snub", + description = "Create the snub version") + dual = BoolProperty(name="Dual", + description="Create the dual of the current solid", + default=False) + keepSize = BoolProperty(name="Keep Size", + description="Keep the whole solid at a constant size", + default=False) + preset = EnumProperty(items = (("0","Custom",""), + ("t4","Truncated Tetrahedron",""), + ("r4","Cuboctahedron",""), + ("t6","Truncated Cube",""), + ("t8","Truncated Octahedron",""), + ("b6","Rhombicuboctahedron",""), + ("c6","Truncated Cuboctahedron",""), + ("s6","Snub Cube",""), + ("r12","Icosidodecahedron",""), + ("t12","Truncated Dodecahedron",""), + ("t20","Truncated Icosahedron",""), + ("b12","Rhombicosidodecahedron",""), + ("c12","Truncated Icosidodecahedron",""), + ("s12","Snub Dodecahedron",""), + ("dt4","Triakis Tetrahedron",""), + ("dr4","Rhombic Dodecahedron",""), + ("dt6","Triakis Octahedron",""), + ("dt8","Triakis Hexahedron",""), + ("db6","Deltoidal Icositetrahedron",""), + ("dc6","Disdyakis Dodecahedron",""), + ("ds6","Pentagonal Icositetrahedron",""), + ("dr12","Rhombic Triacontahedron",""), + ("dt12","Triakis Icosahedron",""), + ("dt20","Pentakis Dodecahedron",""), + ("db12","Deltoidal Hexecontahedron",""), + ("dc12","Disdyakis Triacontahedron",""), + ("ds12","Pentagonal Hexecontahedron",""), + ("c","Cube",""), + ("sb","Soccer ball","")), + name = "Presets", + description = "Parameters for some hard names") + + # actual preset values + p = {"t4":["4",2/3,0,0,"0"], + "r4":["4",1,1,0,"0"], + "t6":["6",2/3,0,0,"0"], + "t8":["8",2/3,0,0,"0"], + "b6":["6",1.0938,1,0,"0"], + "c6":["6",1.0572,0.585786,0,"0"], + "s6":["6",1.0875,0.704,0,"L"], + "r12":["12",1,0,0,"0"], + "t12":["12",2/3,0,0,"0"], + "t20":["20",2/3,0,0,"0"], + "b12":["12",1.1338,1,0,"0"], + "c12":["20",0.921,0.553,0,"0"], + "s12":["12",1.1235,0.68,0,"L"], + "dt4":["4",2/3,0,1,"0"], + "dr4":["4",1,2/3,1,"0"], + "dt6":["6",4/3,0,1,"0"], + "dt8":["8",1,0,1,"0"], + "db6":["6",1.0938,0.756,1,"0"], + "dc6":["6",1,1,1,"0"], + "ds6":["6",1.0875,0.704,1,"L"], + "dr12":["12",1.54,0,1,"0"], + "dt12":["12",5/3,0,1,"0"], + "dt20":["20",2/3,0,1,"0"], + "db12":["12",1,0.912,1,"0"], + "dc12":["20",0.921,1,1,"0"], + "ds12":["12",1.1235,0.68,1,"L"], + "c":["6",0,0,0,"0"], + "sb":["20",2/3,0,0,"0"]} + + edit = BoolProperty(name="", + description="", + default=False, + options={'HIDDEN'}) + + def execute(self,context): + # turn off undo for better performance (3 - 5x faster), also makes sure + # that mesh ops are undoable and entire script acts as one operator + bpy.context.user_preferences.edit.global_undo = False + + props = self.properties + + #if preset, set preset + if props.preset != "0": + using = self.p[props.preset] + props.source = using[0] + props.vTrunc = using[1] + props.eTrunc = using[2] + props.dual = using[3] + props.snub = using[4] + props.preset = "0" + + # generate mesh + verts,faces = createSolid(props.source, + props.vTrunc, + props.eTrunc, + props.dual, + props.snub) + + # turn n-gons in quads and tri's + faces = createPolys(faces) + + # resize to normal size, or if keepSize, make sure all verts are of length 'size' + if props.keepSize: + rad = props.size/verts[0].length + else: rad = props.size + verts = [i*rad for i in verts] + + # generate object + obj = create_mesh_object(context,verts,[],faces,"Solid",props.edit) + + # vertices will be on top of each other in some cases, + # so remove doubles then + if ((props.vTrunc == 1) and (props.eTrunc == 0)) or (props.eTrunc == 1): + current_mode = obj.mode + if current_mode == 'OBJECT': + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.remove_doubles() + bpy.ops.object.mode_set(mode=current_mode) + + # snub duals suck, so make all normals point outwards + if props.dual and (props.snub != "0"): + current_mode = obj.mode + if current_mode == 'OBJECT': + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.normals_make_consistent() + bpy.ops.object.mode_set(mode=current_mode) + + # store recall properties in object + recall_args_list = { + "edit": True, + "source" : props.source, + "size" : props.size, + "vTrunc": props.vTrunc, + "eTrunc": props.eTrunc, + "dual": props.dual, + "snub": props.snub} + store_recall_properties(obj,self,recall_args_list) + + # turn undo back on + bpy.context.user_preferences.edit.global_undo = True + + return {'FINISHED'} + +class Solids_add_menu(bpy.types.Menu): + """Define the menu with presets""" + bl_idname = "Solids_add_menu" + bl_label = "Solids" + + def draw(self,context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator(Solids.bl_idname, text = "Solid") + layout.menu(PlatonicMenu.bl_idname, text = "Platonic") + layout.menu(ArchiMenu.bl_idname, text = "Archimeadean") + layout.menu(CatalanMenu.bl_idname, text = "Catalan") + layout.menu(OtherMenu.bl_idname, text = "Others") + +class PlatonicMenu(bpy.types.Menu): + """Define Platonic menu""" + bl_idname = "Platonic_calls" + bl_label = "Platonic" + + def draw(self,context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator(Solids.bl_idname, text = "Tetrahedron").source = "4" + layout.operator(Solids.bl_idname, text = "Hexahedron").source = "6" + layout.operator(Solids.bl_idname, text = "Octahedron").source = "8" + layout.operator(Solids.bl_idname, text = "Dodecahedron").source = "12" + layout.operator(Solids.bl_idname, text = "Icosahedron").source = "20" + +class ArchiMenu(bpy.types.Menu): + """Defines Achimedean preset menu""" + bl_idname = "Achimedean_calls" + bl_label = "Archimedean" + + def draw(self,context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator(Solids.bl_idname, text = "Truncated Tetrahedron").preset = "t4" + layout.operator(Solids.bl_idname, text = "Cuboctahedron").preset = "r4" + layout.operator(Solids.bl_idname, text = "Truncated Cube").preset = "t6" + layout.operator(Solids.bl_idname, text = "Truncated Octahedron").preset = "t8" + layout.operator(Solids.bl_idname, text = "Rhombicuboctahedron").preset = "b6" + layout.operator(Solids.bl_idname, text = "Truncated Cuboctahedron").preset = "c6" + layout.operator(Solids.bl_idname, text = "Snub Cube").preset = "s6" + layout.operator(Solids.bl_idname, text = "Icosidodecahedron").preset = "r12" + layout.operator(Solids.bl_idname, text = "Truncated Dodecahedron").preset = "t12" + layout.operator(Solids.bl_idname, text = "Truncated Icosahedron").preset = "t20" + layout.operator(Solids.bl_idname, text = "Rhombicosidodecahedron").preset = "b12" + layout.operator(Solids.bl_idname, text = "Truncated Icosidodecahedron").preset = "c12" + layout.operator(Solids.bl_idname, text = "Snub Dodecahedron").preset = "s12" + +class CatalanMenu(bpy.types.Menu): + """Defines Catalan preset menu""" + bl_idname = "Catalan_calls" + bl_label = "Catalan" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator(Solids.bl_idname, text = "Triakis Tetrahedron").preset = "dt4" + layout.operator(Solids.bl_idname, text = "Rhombic Dodecahedron").preset = "dr4" + layout.operator(Solids.bl_idname, text = "Triakis Octahedron").preset = "dt6" + layout.operator(Solids.bl_idname, text = "Triakis Hexahedron").preset = "dt8" + layout.operator(Solids.bl_idname, text = "Deltoidal Icositetrahedron").preset = "db6" + layout.operator(Solids.bl_idname, text = "Disdyakis Dodecahedron").preset = "dc6" + layout.operator(Solids.bl_idname, text = "Pentagonal Icositetrahedron").preset = "ds6" + layout.operator(Solids.bl_idname, text = "Rhombic Triacontahedron").preset = "dr12" + layout.operator(Solids.bl_idname, text = "Triakis Icosahedron").preset = "dt12" + layout.operator(Solids.bl_idname, text = "Pentakis Dodecahedron").preset = "dt20" + layout.operator(Solids.bl_idname, text = "Deltoidal Hexecontahedron").preset = "dt20" + layout.operator(Solids.bl_idname, text = "Disdyakis Triacontahedron").preset = "db12" + layout.operator(Solids.bl_idname, text = "Pentagonal Hexecontahedron").preset = "ds12" + +class OtherMenu(bpy.types.Menu): + """Defines Others preset menu""" + bl_idname = "Others_calls" + bl_label = "Others" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator(Solids.bl_idname, text = "Cube").preset = "c" + layout.operator(Solids.bl_idname, text = "Soccer ball").preset = "sb" + + +import space_info + +classes = [ + Solids, + Solids_add_menu, + PlatonicMenu, + ArchiMenu, + CatalanMenu, + OtherMenu +] + +menu_func = (lambda self, + context: self.layout.menu(Solids_add_menu.bl_idname, icon="PLUGIN")) + +def register(): + for i in classes: + bpy.types.register(i) + space_info.INFO_MT_mesh_add.append(menu_func) + +def unregister(): + for i in classes: + bpy.types.unregister(i) + space_info.INFO_MT_mesh_add.remove(menu_func) + +if __name__ == "__main__": + register()