1 # GPL # "author": "DreamPainter"
5 from mathutils
import Vector
6 from functools
import reduce
7 from bpy
.props
import (
12 from bpy_extras
.object_utils
import object_data_add
15 # this function creates a chain of quads and, when necessary, a remaining tri
16 # for each polygon created in this script. be aware though, that this function
17 # assumes each polygon is convex.
18 # poly: list of faces, or a single face, like those
19 # needed for mesh.from_pydata.
20 # returns the tessellated faces.
22 def createPolys(poly
):
27 if type(poly
[0]) == type(1):
28 poly
= [poly
] # if only one, make it a list of one face
32 # let all faces of 3 or 4 verts be
35 # split all polygons in half and bridge the two halves
37 f
= [[i
[x
], i
[x
+ 1], i
[L
- 2 - x
], i
[L
- 1 - x
]] for x
in range(L
// 2 - 1)]
40 faces
.append([i
[L
// 2 - 1 + x
] for x
in [0, 1, 2]])
44 # function to make the reduce function work as a workaround to sum a list of vectors
47 return reduce(lambda a
, b
: a
+ b
, list)
50 # creates the 5 platonic solids as a base for the rest
51 # plato: should be one of {"4","6","8","12","20"}. decides what solid the
53 # returns a list of vertices and faces
61 # Calculate the necessary constants
66 # create the vertices and faces
67 v
= [(0, 0, 1), (2 * s
, 0, t
), (-s
, u
, t
), (-s
, -u
, t
)]
68 faces
= [[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]]
72 # Calculate the necessary constants
75 # create the vertices and faces
76 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
)]
77 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]]
81 # create the vertices and faces
82 v
= [(1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)]
83 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]]
87 # Calculate the necessary constants
89 t
= sqrt((3 - sqrt(5)) / 6)
90 u
= sqrt((3 + sqrt(5)) / 6)
92 # create the vertices and faces
93 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
),
94 (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
),
95 (0, -t
, u
), (0, t
, -u
), (0, -t
, -u
)]
96 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],
97 [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],
98 [7, 11, 6, 14, 15], [7, 19, 3, 10, 11]]
102 # Calculate the necessary constants
103 s
= (1 + sqrt(5)) / 2
108 # create the vertices and faces
109 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
),
110 (0, s
, t
), (0, -s
, t
), (0, s
, -t
), (0, -s
, -t
)]
111 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],
112 [0, 10, 8], [1, 8, 10], [2, 9, 11], [3, 11, 9], [4, 2, 0], [5, 0, 2], [6, 1, 3], [7, 3, 1],
113 [8, 6, 4], [9, 4, 6], [10, 5, 7], [11, 7, 5]]
115 # convert the tuples to Vectors
116 verts
= [Vector(i
) for i
in v
]
121 # processes the raw data from source
123 def createSolid(plato
, vtrunc
, etrunc
, dual
, snub
):
124 # the duals from each platonic solid
125 dualSource
= {"4": "4",
131 # constants saving space and readability
135 noSnub
= (snub
== "None") or (etrunc
== 0.5) or (etrunc
== 0)
136 lSnub
= (snub
== "Left") and (0 < etrunc
< 0.5)
137 rSnub
= (snub
== "Right") and (0 < etrunc
< 0.5)
141 if dual
: # dual is as simple as another, but mirrored platonic solid
142 vInput
, fInput
= source(dualSource
[plato
])
143 supposedSize
= vSum(vInput
[i
] for i
in fInput
[0]).length
/ len(fInput
[0])
144 vInput
= [-i
* supposedSize
for i
in vInput
] # mirror it
145 return vInput
, fInput
147 elif 0 < vtrunc
<= 0.5: # simple truncation of the source
148 vInput
, fInput
= source(plato
)
150 # truncation is now equal to simple truncation of the dual of the source
151 vInput
, fInput
= source(dualSource
[plato
])
152 supposedSize
= vSum(vInput
[i
] for i
in fInput
[0]).length
/ len(fInput
[0])
153 vtrunc
= 1 - vtrunc
# account for the source being a dual
154 if vtrunc
== 0: # no truncation needed
156 vInput
, fInput
= source(plato
)
157 vInput
= [i
* supposedSize
for i
in vInput
]
158 return vInput
, fInput
159 vInput
= [-i
* supposedSize
for i
in vInput
]
160 return vInput
, fInput
162 # generate connection database
163 vDict
= [{} for i
in vInput
]
164 # for every face, store what vertex comes after and before the current vertex
165 for x
in range(len(fInput
)):
167 for j
in range(len(i
)):
168 vDict
[i
[j
- 1]][i
[j
]] = [i
[j
- 2], x
]
169 if len(vDict
[i
[j
- 1]]) == 1:
170 vDict
[i
[j
- 1]][-1] = i
[j
]
172 # the actual connection database: exists out of:
173 # [vtrunc pos, etrunc pos, connected vert IDs, connected face IDs]
174 vData
= [[[], [], [], []] for i
in vInput
]
175 fvOutput
= [] # faces created from truncated vertices
176 feOutput
= [] # faces created from truncated edges
177 vOutput
= [] # newly created vertices
178 for x
in range(len(vInput
)):
179 i
= vDict
[x
] # lookup the current vertex
181 while True: # follow the chain to get a ccw order of connected verts and faces
182 vData
[x
][2].append(i
[current
][0])
183 vData
[x
][3].append(i
[current
][1])
184 # create truncated vertices
185 vData
[x
][0].append((1 - vtrunc
) * vInput
[x
] + vtrunc
* vInput
[vData
[x
][2][-1]])
186 current
= i
[current
][0]
188 break # if we're back at the first: stop the loop
189 fvOutput
.append([]) # new face from truncated vert
190 fOffset
= x
* (len(i
) - 1) # where to start off counting faceVerts
191 # only create one vert where one is needed (v1 todo: done)
193 for j
in range(len(i
) - 1):
194 vOutput
.append((vData
[x
][0][j
] + vData
[x
][0][j
- 1]) * etrunc
) # create vert
195 fvOutput
[x
].append(fOffset
+ j
) # add to face
196 fvOutput
[x
] = fvOutput
[x
][1:] + [fvOutput
[x
][0]] # rotate face for ease later on
197 # create faces from truncated edges.
198 for j
in range(len(i
) - 1):
199 if x
> vData
[x
][2][j
]: # only create when other vertex has been added
200 index
= vData
[vData
[x
][2][j
]][2].index(x
)
201 feOutput
.append([fvOutput
[x
][j
], fvOutput
[x
][j
- 1],
202 fvOutput
[vData
[x
][2][j
]][index
],
203 fvOutput
[vData
[x
][2][j
]][index
- 1]])
204 # edge truncation between none and full
206 for j
in range(len(i
) - 1):
207 # create snubs from selecting verts from rectified meshes
209 vOutput
.append(etrunc
* vData
[x
][0][j
] + (1 - etrunc
) * vData
[x
][0][j
- 1])
210 fvOutput
[x
].append(fOffset
+ j
)
212 vOutput
.append((1 - etrunc
) * vData
[x
][0][j
] + etrunc
* vData
[x
][0][j
- 1])
213 fvOutput
[x
].append(fOffset
+ j
)
214 else: # noSnub, select both verts from rectified mesh
215 vOutput
.append(etrunc
* vData
[x
][0][j
] + (1 - etrunc
) * vData
[x
][0][j
- 1])
216 vOutput
.append((1 - etrunc
) * vData
[x
][0][j
] + etrunc
* vData
[x
][0][j
- 1])
217 fvOutput
[x
].append(2 * fOffset
+ 2 * j
)
218 fvOutput
[x
].append(2 * fOffset
+ 2 * j
+ 1)
219 # rotate face for ease later on
221 fvOutput
[x
] = fvOutput
[x
][2:] + fvOutput
[x
][:2]
223 fvOutput
[x
] = fvOutput
[x
][1:] + [fvOutput
[x
][0]]
224 # create single face for each edge
226 for j
in range(len(i
) - 1):
227 if x
> vData
[x
][2][j
]:
228 index
= vData
[vData
[x
][2][j
]][2].index(x
)
229 feOutput
.append([fvOutput
[x
][j
* 2], fvOutput
[x
][2 * j
- 1],
230 fvOutput
[vData
[x
][2][j
]][2 * index
],
231 fvOutput
[vData
[x
][2][j
]][2 * index
- 1]])
232 # create 2 tri's for each edge for the snubs
234 for j
in range(len(i
) - 1):
235 if x
> vData
[x
][2][j
]:
236 index
= vData
[vData
[x
][2][j
]][2].index(x
)
237 feOutput
.append([fvOutput
[x
][j
], fvOutput
[x
][j
- 1],
238 fvOutput
[vData
[x
][2][j
]][index
]])
239 feOutput
.append([fvOutput
[x
][j
], fvOutput
[vData
[x
][2][j
]][index
],
240 fvOutput
[vData
[x
][2][j
]][index
- 1]])
242 for j
in range(len(i
) - 1):
243 if x
> vData
[x
][2][j
]:
244 index
= vData
[vData
[x
][2][j
]][2].index(x
)
245 feOutput
.append([fvOutput
[x
][j
], fvOutput
[x
][j
- 1],
246 fvOutput
[vData
[x
][2][j
]][index
- 1]])
247 feOutput
.append([fvOutput
[x
][j
- 1], fvOutput
[vData
[x
][2][j
]][index
],
248 fvOutput
[vData
[x
][2][j
]][index
- 1]])
249 # special rules for birectified mesh (v1 todo: done)
251 for j
in range(len(i
) - 1):
252 if x
< vData
[x
][2][j
]: # use current vert, since other one has not passed yet
253 vOutput
.append(vData
[x
][0][j
])
254 fvOutput
[x
].append(len(vOutput
) - 1)
256 # search for other edge to avoid duplicity
257 connectee
= vData
[x
][2][j
]
258 fvOutput
[x
].append(fvOutput
[connectee
][vData
[connectee
][2].index(x
)])
259 else: # vert truncation only
260 vOutput
.extend(vData
[x
][0]) # use generated verts from way above
261 for j
in range(len(i
) - 1): # create face from them
262 fvOutput
[x
].append(fOffset
+ j
)
264 # calculate supposed vertex length to ensure continuity
265 if supposedSize
and not dual
: # this to make the vtrunc > 1 work
266 supposedSize
*= len(fvOutput
[0]) / vSum(vOutput
[i
] for i
in fvOutput
[0]).length
267 vOutput
= [-i
* supposedSize
for i
in vOutput
]
269 # create new faces by replacing old vert IDs by newly generated verts
270 ffOutput
= [[] for i
in fInput
]
271 for x
in range(len(fInput
)):
272 # only one generated vert per vertex, so choose accordingly
273 if etrunc
== 0.5 or (etrunc
== 0 and vtrunc
== 0.5) or lSnub
or rSnub
:
274 ffOutput
[x
] = [fvOutput
[i
][vData
[i
][3].index(x
) - 1] for i
in fInput
[x
]]
275 # two generated verts per vertex
278 ffOutput
[x
].append(fvOutput
[i
][2 * vData
[i
][3].index(x
) - 1])
279 ffOutput
[x
].append(fvOutput
[i
][2 * vData
[i
][3].index(x
) - 2])
280 else: # cutting off corners also makes 2 verts
282 ffOutput
[x
].append(fvOutput
[i
][vData
[i
][3].index(x
)])
283 ffOutput
[x
].append(fvOutput
[i
][vData
[i
][3].index(x
) - 1])
286 return vOutput
, fvOutput
+ feOutput
+ ffOutput
288 # do the same procedure as above, only now on the generated mesh
289 # generate connection database
290 vDict
= [{} for i
in vOutput
]
291 dvOutput
= [0 for i
in fvOutput
+ feOutput
+ ffOutput
]
294 for x
in range(len(dvOutput
)): # for every face
295 i
= (fvOutput
+ feOutput
+ ffOutput
)[x
] # choose face to work with
296 # find vertex from face
297 normal
= (vOutput
[i
[0]] - vOutput
[i
[1]]).cross(vOutput
[i
[2]] - vOutput
[i
[1]]).normalized()
298 dvOutput
[x
] = normal
/ (normal
.dot(vOutput
[i
[0]]))
299 for j
in range(len(i
)): # create vert chain
300 vDict
[i
[j
- 1]][i
[j
]] = [i
[j
- 2], x
]
301 if len(vDict
[i
[j
- 1]]) == 1:
302 vDict
[i
[j
- 1]][-1] = i
[j
]
304 # calculate supposed size for continuity
305 supposedSize
= vSum([vInput
[i
] for i
in fInput
[0]]).length
/ len(fInput
[0])
306 supposedSize
/= dvOutput
[-1].length
307 dvOutput
= [i
* supposedSize
for i
in dvOutput
]
309 # use chains to create faces
310 for x
in range(len(vOutput
)):
315 face
.append(i
[current
][1])
316 current
= i
[current
][0]
319 dfOutput
.append(face
)
321 return dvOutput
, dfOutput
324 class Solids(bpy
.types
.Operator
):
325 """Add one of the (regular) solids (mesh)"""
326 bl_idname
= "mesh.primitive_solid_add"
327 bl_label
= "(Regular) solids"
328 bl_description
= "Add one of the Platonic, Archimedean or Catalan solids"
329 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
331 source
: EnumProperty(
332 items
=(("4", "Tetrahedron", ""),
333 ("6", "Hexahedron", ""),
334 ("8", "Octahedron", ""),
335 ("12", "Dodecahedron", ""),
336 ("20", "Icosahedron", "")),
338 description
="Starting point of your solid"
342 description
="Radius of the sphere through the vertices",
349 vTrunc
: FloatProperty(
350 name
="Vertex Truncation",
351 description
="Amount of vertex truncation",
360 eTrunc
: FloatProperty(
361 name
="Edge Truncation",
362 description
="Amount of edge truncation",
372 items
=(("None", "No Snub", ""),
373 ("Left", "Left Snub", ""),
374 ("Right", "Right Snub", "")),
376 description
="Create the snub version"
380 description
="Create the dual of the current solid",
383 keepSize
: BoolProperty(
385 description
="Keep the whole solid at a constant size",
388 preset
: EnumProperty(
389 items
=(("0", "Custom", ""),
390 ("t4", "Truncated Tetrahedron", ""),
391 ("r4", "Cuboctahedron", ""),
392 ("t6", "Truncated Cube", ""),
393 ("t8", "Truncated Octahedron", ""),
394 ("b6", "Rhombicuboctahedron", ""),
395 ("c6", "Truncated Cuboctahedron", ""),
396 ("s6", "Snub Cube", ""),
397 ("r12", "Icosidodecahedron", ""),
398 ("t12", "Truncated Dodecahedron", ""),
399 ("t20", "Truncated Icosahedron", ""),
400 ("b12", "Rhombicosidodecahedron", ""),
401 ("c12", "Truncated Icosidodecahedron", ""),
402 ("s12", "Snub Dodecahedron", ""),
403 ("dt4", "Triakis Tetrahedron", ""),
404 ("dr4", "Rhombic Dodecahedron", ""),
405 ("dt6", "Triakis Octahedron", ""),
406 ("dt8", "Tetrakis Hexahedron", ""),
407 ("db6", "Deltoidal Icositetrahedron", ""),
408 ("dc6", "Disdyakis Dodecahedron", ""),
409 ("ds6", "Pentagonal Icositetrahedron", ""),
410 ("dr12", "Rhombic Triacontahedron", ""),
411 ("dt12", "Triakis Icosahedron", ""),
412 ("dt20", "Pentakis Dodecahedron", ""),
413 ("db12", "Deltoidal Hexecontahedron", ""),
414 ("dc12", "Disdyakis Triacontahedron", ""),
415 ("ds12", "Pentagonal Hexecontahedron", "")),
417 description
="Parameters for some hard names"
420 # actual preset values
421 p
= {"t4": ["4", 2 / 3, 0, 0, "None"],
422 "r4": ["4", 1, 1, 0, "None"],
423 "t6": ["6", 2 / 3, 0, 0, "None"],
424 "t8": ["8", 2 / 3, 0, 0, "None"],
425 "b6": ["6", 1.0938, 1, 0, "None"],
426 "c6": ["6", 1.0572, 0.585786, 0, "None"],
427 "s6": ["6", 1.0875, 0.704, 0, "Left"],
428 "r12": ["12", 1, 0, 0, "None"],
429 "t12": ["12", 2 / 3, 0, 0, "None"],
430 "t20": ["20", 2 / 3, 0, 0, "None"],
431 "b12": ["12", 1.1338, 1, 0, "None"],
432 "c12": ["20", 0.921, 0.553, 0, "None"],
433 "s12": ["12", 1.1235, 0.68, 0, "Left"],
434 "dt4": ["4", 2 / 3, 0, 1, "None"],
435 "dr4": ["4", 1, 1, 1, "None"],
436 "dt6": ["6", 2 / 3, 0, 1, "None"],
437 "dt8": ["8", 2 / 3, 0, 1, "None"],
438 "db6": ["6", 1.0938, 1, 1, "None"],
439 "dc6": ["6", 1.0572, 0.585786, 1, "None"],
440 "ds6": ["6", 1.0875, 0.704, 1, "Left"],
441 "dr12": ["12", 1, 0, 1, "None"],
442 "dt12": ["12", 2 / 3, 0, 1, "None"],
443 "dt20": ["20", 2 / 3, 0, 1, "None"],
444 "db12": ["12", 1.1338, 1, 1, "None"],
445 "dc12": ["20", 0.921, 0.553, 1, "None"],
446 "ds12": ["12", 1.1235, 0.68, 1, "Left"]}
448 # previous preset, for User-friendly reasons
451 def execute(self
, context
):
452 # piece of code to make presets remain until parameters are changed
453 if self
.preset
!= "0":
454 # if preset, set preset
455 if self
.previousSetting
!= self
.preset
:
456 using
= self
.p
[self
.preset
]
457 self
.source
= using
[0]
458 self
.vTrunc
= using
[1]
459 self
.eTrunc
= using
[2]
463 using
= self
.p
[self
.preset
]
464 result0
= self
.source
== using
[0]
465 result1
= abs(self
.vTrunc
- using
[1]) < 0.004
466 result2
= abs(self
.eTrunc
- using
[2]) < 0.0015
467 result4
= using
[4] == self
.snub
or ((using
[4] == "Left") and
468 self
.snub
in ["Left", "Right"])
469 if (result0
and result1
and result2
and result4
):
470 if self
.p
[self
.previousSetting
][3] != self
.dual
:
471 if self
.preset
[0] == "d":
472 self
.preset
= self
.preset
[1:]
474 self
.preset
= "d" + self
.preset
478 self
.previousSetting
= self
.preset
481 verts
, faces
= createSolid(self
.source
,
488 # turn n-gons in quads and tri's
489 faces
= createPolys(faces
)
491 # resize to normal size, or if keepSize, make sure all verts are of length 'size'
493 rad
= self
.size
/ verts
[-1 if self
.dual
else 0].length
496 verts
= [i
* rad
for i
in verts
]
500 mesh
= bpy
.data
.meshes
.new("Solid")
502 # Make a mesh from a list of verts/edges/faces.
503 mesh
.from_pydata(verts
, [], faces
)
505 # Update mesh geometry after adding stuff.
508 object_data_add(context
, mesh
, operator
=None)
509 # object generation done