Fix error in rigify property generation
[blender-addons.git] / add_mesh_extra_objects / add_mesh_round_cube.py
blobb48e9a691822131fa3442d30350461e5761cf736
1 # GPL # Author: Alain Ducharme (phymec)
3 import bpy
4 from bpy_extras import object_utils
5 from itertools import permutations
6 from math import (
7 copysign, pi,
8 sqrt,
10 from bpy.types import Operator
11 from bpy.props import (
12 BoolProperty,
13 EnumProperty,
14 FloatProperty,
15 FloatVectorProperty,
16 IntProperty,
17 StringProperty,
21 def round_cube(radius=1.0, arcdiv=4, lindiv=0., size=(0., 0., 0.),
22 div_type='CORNERS', odd_axis_align=False, info_only=False):
23 # subdiv bitmasks
24 CORNERS, EDGES, ALL = 0, 1, 2
25 try:
26 subdiv = ('CORNERS', 'EDGES', 'ALL').index(div_type)
27 except ValueError:
28 subdiv = CORNERS # fallback
30 radius = max(radius, 0.)
31 if not radius:
32 # No sphere
33 arcdiv = 1
34 odd_axis_align = False
36 if arcdiv <= 0:
37 arcdiv = max(round(pi * radius * lindiv * 0.5), 1)
38 arcdiv = max(round(arcdiv), 1)
39 if lindiv <= 0. and radius:
40 lindiv = 1. / (pi / (arcdiv * 2.) * radius)
41 lindiv = max(lindiv, 0.)
42 if not lindiv:
43 subdiv = CORNERS
45 odd = arcdiv % 2 # even = arcdiv % 2 ^ 1
46 step_size = 2. / arcdiv
48 odd_aligned = 0
49 vi = -1.
50 steps = arcdiv + 1
51 if odd_axis_align and odd:
52 odd_aligned = 1
53 vi += 0.5 * step_size
54 steps = arcdiv
55 axis_aligned = not odd or odd_aligned
57 if arcdiv == 1 and not odd_aligned and subdiv == EDGES:
58 subdiv = CORNERS
60 half_chord = 0. # ~ spherical cap base radius
61 sagitta = 0. # ~ spherical cap height
62 if not axis_aligned:
63 half_chord = sqrt(3.) * radius / (3. * arcdiv)
64 id2 = 1. / (arcdiv * arcdiv)
65 sagitta = radius - radius * sqrt(id2 * id2 / 3. - id2 + 1.)
67 # Extrusion per axis
68 exyz = [0. if s < 2. * (radius - sagitta) else (s - 2. * (radius - sagitta)) * 0.5 for s in size]
69 ex, ey, ez = exyz
71 dxyz = [0, 0, 0] # extrusion divisions per axis
72 dssxyz = [0., 0., 0.] # extrusion division step sizes per axis
74 for i in range(3):
75 sc = 2. * (exyz[i] + half_chord)
76 dxyz[i] = round(sc * lindiv) if subdiv else 0
77 if dxyz[i]:
78 dssxyz[i] = sc / dxyz[i]
79 dxyz[i] -= 1
80 else:
81 dssxyz[i] = sc
83 if info_only:
84 ec = sum(1 for n in exyz if n)
85 if subdiv:
86 fxyz = [d + (e and axis_aligned) for d, e in zip(dxyz, exyz)]
87 dvc = arcdiv * 4 * sum(fxyz)
88 if subdiv == ALL:
89 dvc += sum(p1 * p2 for p1, p2 in permutations(fxyz, 2))
90 elif subdiv == EDGES and axis_aligned:
91 # (0, 0, 2, 4) * sum(dxyz) + (0, 0, 2, 6)
92 dvc += ec * ec // 2 * sum(dxyz) + ec * (ec - 1)
93 else:
94 dvc = (arcdiv * 4) * ec + ec * (ec - 1) if axis_aligned else 0
95 vert_count = int(6 * arcdiv * arcdiv + (0 if odd_aligned else 2) + dvc)
96 if not radius and not max(size) > 0:
97 vert_count = 1
98 return arcdiv, lindiv, vert_count
100 if not radius and not max(size) > 0:
101 # Single vertex
102 return [(0, 0, 0)], []
104 # uv lookup table
105 uvlt = []
106 v = vi
107 for j in range(1, steps + 1):
108 v2 = v * v
109 uvlt.append((v, v2, radius * sqrt(18. - 6. * v2) / 6.))
110 v = vi + j * step_size # v += step_size # instead of accumulating errors
111 # clear fp errors / signs at axis
112 if abs(v) < 1e-10:
113 v = 0.0
115 # Sides built left to right bottom up
116 # xp yp zp xd yd zd
117 sides = ((0, 2, 1, (-1, 1, 1)), # Y+ Front
118 (1, 2, 0, (-1, -1, 1)), # X- Left
119 (0, 2, 1, (1, -1, 1)), # Y- Back
120 (1, 2, 0, (1, 1, 1)), # X+ Right
121 (0, 1, 2, (-1, 1, -1)), # Z- Bottom
122 (0, 1, 2, (-1, -1, 1))) # Z+ Top
124 # side vertex index table (for sphere)
125 svit = [[[] for i in range(steps)] for i in range(6)]
126 # Extend svit rows for extrusion
127 yer = zer = 0
128 if ey:
129 yer = axis_aligned + (dxyz[1] if subdiv else 0)
130 svit[4].extend([[] for i in range(yer)])
131 svit[5].extend([[] for i in range(yer)])
132 if ez:
133 zer = axis_aligned + (dxyz[2] if subdiv else 0)
134 for side in range(4):
135 svit[side].extend([[] for i in range(zer)])
136 # Extend svit rows for odd_aligned
137 if odd_aligned:
138 for side in range(4):
139 svit[side].append([])
141 hemi = steps // 2
143 # Create vertices and svit without dups
144 vert = [0., 0., 0.]
145 verts = []
147 if arcdiv == 1 and not odd_aligned and subdiv == ALL:
148 # Special case: Grid Cuboid
149 for side, (xp, yp, zp, dir) in enumerate(sides):
150 svitc = svit[side]
151 rows = len(svitc)
152 if rows < dxyz[yp] + 2:
153 svitc.extend([[] for i in range(dxyz[yp] + 2 - rows)])
154 vert[zp] = (half_chord + exyz[zp]) * dir[zp]
155 for j in range(dxyz[yp] + 2):
156 vert[yp] = (j * dssxyz[yp] - half_chord - exyz[yp]) * dir[yp]
157 for i in range(dxyz[xp] + 2):
158 vert[xp] = (i * dssxyz[xp] - half_chord - exyz[xp]) * dir[xp]
159 if (side == 5) or ((i < dxyz[xp] + 1 and j < dxyz[yp] + 1) and (side < 4 or (i and j))):
160 svitc[j].append(len(verts))
161 verts.append(tuple(vert))
162 else:
163 for side, (xp, yp, zp, dir) in enumerate(sides):
164 svitc = svit[side]
165 exr = exyz[xp]
166 eyr = exyz[yp]
167 ri = 0 # row index
168 rij = zer if side < 4 else yer
170 if side == 5:
171 span = range(steps)
172 elif side < 4 or odd_aligned:
173 span = range(arcdiv)
174 else:
175 span = range(1, arcdiv)
176 ri = 1
178 for j in span: # rows
179 v, v2, mv2 = uvlt[j]
180 tv2mh = 1. / 3. * v2 - 0.5
181 hv2 = 0.5 * v2
183 if j == hemi and rij:
184 # Jump over non-edge row indices
185 ri += rij
187 for i in span: # columns
188 u, u2, mu2 = uvlt[i]
189 vert[xp] = u * mv2
190 vert[yp] = v * mu2
191 vert[zp] = radius * sqrt(u2 * tv2mh - hv2 + 1.)
193 vert[0] = (vert[0] + copysign(ex, vert[0])) * dir[0]
194 vert[1] = (vert[1] + copysign(ey, vert[1])) * dir[1]
195 vert[2] = (vert[2] + copysign(ez, vert[2])) * dir[2]
196 rv = tuple(vert)
198 if exr and i == hemi:
199 rx = vert[xp] # save rotated x
200 vert[xp] = rxi = (-exr - half_chord) * dir[xp]
201 if axis_aligned:
202 svitc[ri].append(len(verts))
203 verts.append(tuple(vert))
204 if subdiv:
205 offsetx = dssxyz[xp] * dir[xp]
206 for k in range(dxyz[xp]):
207 vert[xp] += offsetx
208 svitc[ri].append(len(verts))
209 verts.append(tuple(vert))
210 if eyr and j == hemi and axis_aligned:
211 vert[xp] = rxi
212 vert[yp] = -eyr * dir[yp]
213 svitc[hemi].append(len(verts))
214 verts.append(tuple(vert))
215 if subdiv:
216 offsety = dssxyz[yp] * dir[yp]
217 ry = vert[yp]
218 for k in range(dxyz[yp]):
219 vert[yp] += offsety
220 svitc[hemi + axis_aligned + k].append(len(verts))
221 verts.append(tuple(vert))
222 vert[yp] = ry
223 for k in range(dxyz[xp]):
224 vert[xp] += offsetx
225 svitc[hemi].append(len(verts))
226 verts.append(tuple(vert))
227 if subdiv & ALL:
228 for l in range(dxyz[yp]):
229 vert[yp] += offsety
230 svitc[hemi + axis_aligned + l].append(len(verts))
231 verts.append(tuple(vert))
232 vert[yp] = ry
233 vert[xp] = rx # restore
235 if eyr and j == hemi:
236 vert[yp] = (-eyr - half_chord) * dir[yp]
237 if axis_aligned:
238 svitc[hemi].append(len(verts))
239 verts.append(tuple(vert))
240 if subdiv:
241 offsety = dssxyz[yp] * dir[yp]
242 for k in range(dxyz[yp]):
243 vert[yp] += offsety
244 if exr and i == hemi and not axis_aligned and subdiv & ALL:
245 vert[xp] = rxi
246 for l in range(dxyz[xp]):
247 vert[xp] += offsetx
248 svitc[hemi + k].append(len(verts))
249 verts.append(tuple(vert))
250 vert[xp] = rx
251 svitc[hemi + axis_aligned + k].append(len(verts))
252 verts.append(tuple(vert))
254 svitc[ri].append(len(verts))
255 verts.append(rv)
256 ri += 1
258 # Complete svit edges (shared vertices)
259 # Sides' right edge
260 for side, rows in enumerate(svit[:4]):
261 for j, row in enumerate(rows[:-1]):
262 svit[3 if not side else side - 1][j].append(row[0])
263 # Sides' top edge
264 svit[0][-1].extend(svit[5][0])
265 svit[2][-1].extend(svit[5][-1][::-1])
266 for row in svit[5]:
267 svit[3][-1].insert(0, row[0])
268 svit[1][-1].append(row[-1])
269 if odd_aligned:
270 for side in svit[:4]:
271 side[-1].append(-1)
272 # Bottom edges
273 if odd_aligned:
274 svit[4].insert(0, [-1] + svit[2][0][-2::-1] + [-1])
275 for i, col in enumerate(svit[3][0][:-1]):
276 svit[4][i + 1].insert(0, col)
277 svit[4][i + 1].append(svit[1][0][-i - 2])
278 svit[4].append([-1] + svit[0][0][:-1] + [-1])
279 else:
280 svit[4][0].extend(svit[2][0][::-1])
281 for i, col in enumerate(svit[3][0][1:-1]):
282 svit[4][i + 1].insert(0, col)
283 svit[4][i + 1].append(svit[1][0][-i - 2])
284 svit[4][-1].extend(svit[0][0])
286 # Build faces
287 faces = []
288 if not axis_aligned:
289 hemi -= 1
290 for side, rows in enumerate(svit):
291 xp, yp = sides[side][:2]
292 oa4 = odd_aligned and side == 4
293 if oa4: # special case
294 hemi += 1
295 for j, row in enumerate(rows[:-1]):
296 tri = odd_aligned and (oa4 and not j or rows[j + 1][-1] < 0)
297 for i, vi in enumerate(row[:-1]):
298 # odd_aligned triangle corners
299 if vi < 0:
300 if not j and not i:
301 faces.append((row[i + 1], rows[j + 1][i + 1], rows[j + 1][i]))
302 elif oa4 and not i and j == len(rows) - 2:
303 faces.append((vi, row[i + 1], rows[j + 1][i + 1]))
304 elif tri and i == len(row) - 2:
305 if j:
306 faces.append((vi, row[i + 1], rows[j + 1][i]))
307 else:
308 if oa4 or arcdiv > 1:
309 faces.append((vi, rows[j + 1][i + 1], rows[j + 1][i]))
310 else:
311 faces.append((vi, row[i + 1], rows[j + 1][i]))
312 # subdiv = EDGES (not ALL)
313 elif subdiv and len(rows[j + 1]) < len(row) and (i >= hemi):
314 if (i == hemi):
315 faces.append((vi, row[i + 1 + dxyz[xp]], rows[j + 1 + dxyz[yp]][i + 1 + dxyz[xp]],
316 rows[j + 1 + dxyz[yp]][i]))
317 elif i > hemi + dxyz[xp]:
318 faces.append((vi, row[i + 1], rows[j + 1][i + 1 - dxyz[xp]], rows[j + 1][i - dxyz[xp]]))
319 elif subdiv and len(rows[j + 1]) > len(row) and (i >= hemi):
320 if (i > hemi):
321 faces.append((vi, row[i + 1], rows[j + 1][i + 1 + dxyz[xp]], rows[j + 1][i + dxyz[xp]]))
322 elif subdiv and len(row) < len(rows[0]) and i == hemi:
323 pass
324 else:
325 # Most faces...
326 faces.append((vi, row[i + 1], rows[j + 1][i + 1], rows[j + 1][i]))
327 if oa4:
328 hemi -= 1
330 return verts, faces
333 class AddRoundCube(Operator, object_utils.AddObjectHelper):
334 bl_idname = "mesh.primitive_round_cube_add"
335 bl_label = "Add Round Cube"
336 bl_description = ("Create mesh primitives: Quadspheres, "
337 "Capsules, Rounded Cuboids, 3D Grids etc")
338 bl_options = {"REGISTER", "UNDO", "PRESET"}
340 sanity_check_verts = 200000
341 vert_count = 0
343 Roundcube : BoolProperty(name = "Roundcube",
344 default = True,
345 description = "Roundcube")
346 change : BoolProperty(name = "Change",
347 default = False,
348 description = "change Roundcube")
350 radius: FloatProperty(
351 name="Radius",
352 description="Radius of vertices for sphere, capsule or cuboid bevel",
353 default=0.2, min=0.0, soft_min=0.01, step=10
355 size: FloatVectorProperty(
356 name="Size",
357 description="Size",
358 subtype='XYZ',
359 default=(2.0, 2.0, 2.0),
361 arc_div: IntProperty(
362 name="Arc Divisions",
363 description="Arc curve divisions, per quadrant, 0=derive from Linear",
364 default=4, min=1
366 lin_div: FloatProperty(
367 name="Linear Divisions",
368 description="Linear unit divisions (Edges/Faces), 0=derive from Arc",
369 default=0.0, min=0.0, step=100, precision=1
371 div_type: EnumProperty(
372 name='Type',
373 description='Division type',
374 items=(
375 ('CORNERS', 'Corners', 'Sphere / Corners'),
376 ('EDGES', 'Edges', 'Sphere / Corners and extruded edges (size)'),
377 ('ALL', 'All', 'Sphere / Corners, extruded edges and faces (size)')),
378 default='CORNERS',
380 odd_axis_align: BoolProperty(
381 name='Odd Axis Align',
382 description='Align odd arc divisions with axes (Note: triangle corners!)',
384 no_limit: BoolProperty(
385 name='No Limit',
386 description='Do not limit to ' + str(sanity_check_verts) + ' vertices (sanity check)',
387 options={'HIDDEN'}
390 def execute(self, context):
391 # turn off 'Enter Edit Mode'
392 use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
393 bpy.context.preferences.edit.use_enter_edit_mode = False
395 if self.arc_div <= 0 and self.lin_div <= 0:
396 self.report({'ERROR'},
397 "Either Arc Divisions or Linear Divisions must be greater than zero")
398 return {'CANCELLED'}
400 if not self.no_limit:
401 if self.vert_count > self.sanity_check_verts:
402 self.report({'ERROR'}, 'More than ' + str(self.sanity_check_verts) +
403 ' vertices! Check "No Limit" to proceed')
404 return {'CANCELLED'}
406 if bpy.context.mode == "OBJECT":
407 if context.selected_objects != [] and context.active_object and \
408 (context.active_object.data is not None) and ('Roundcube' in context.active_object.data.keys()) and \
409 (self.change == True):
410 obj = context.active_object
411 oldmesh = obj.data
412 oldmeshname = obj.data.name
413 verts, faces = round_cube(self.radius, self.arc_div, self.lin_div,
414 self.size, self.div_type, self.odd_axis_align)
415 mesh = bpy.data.meshes.new('Roundcube')
416 mesh.from_pydata(verts, [], faces)
417 obj.data = mesh
418 for material in oldmesh.materials:
419 obj.data.materials.append(material)
420 bpy.data.meshes.remove(oldmesh)
421 obj.data.name = oldmeshname
422 else:
423 verts, faces = round_cube(self.radius, self.arc_div, self.lin_div,
424 self.size, self.div_type, self.odd_axis_align)
425 mesh = bpy.data.meshes.new('Roundcube')
426 mesh.from_pydata(verts, [], faces)
427 obj = object_utils.object_data_add(context, mesh, operator=self)
429 obj.data["Roundcube"] = True
430 obj.data["change"] = False
431 for prm in RoundCubeParameters():
432 obj.data[prm] = getattr(self, prm)
434 if bpy.context.mode == "EDIT_MESH":
435 active_object = context.active_object
436 name_active_object = active_object.name
437 bpy.ops.object.mode_set(mode='OBJECT')
438 verts, faces = round_cube(self.radius, self.arc_div, self.lin_div,
439 self.size, self.div_type, self.odd_axis_align)
440 mesh = bpy.data.meshes.new('Roundcube')
441 mesh.from_pydata(verts, [], faces)
442 obj = object_utils.object_data_add(context, mesh, operator=self)
443 obj.select_set(True)
444 active_object.select_set(True)
445 bpy.context.view_layer.objects.active = active_object
446 bpy.ops.object.join()
447 context.active_object.name = name_active_object
448 bpy.ops.object.mode_set(mode='EDIT')
450 if use_enter_edit_mode:
451 bpy.ops.object.mode_set(mode = 'EDIT')
453 # restore pre operator state
454 bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
456 return {'FINISHED'}
458 def check(self, context):
459 self.arcdiv, self.lindiv, self.vert_count = round_cube(
460 self.radius, self.arc_div, self.lin_div,
461 self.size, self.div_type, self.odd_axis_align,
462 True
464 return True
466 def invoke(self, context, event):
467 self.check(context)
468 return self.execute(context)
470 def draw(self, context):
471 self.check(context)
472 layout = self.layout
474 layout.prop(self, 'radius')
475 layout.column().prop(self, 'size', expand=True)
477 box = layout.box()
478 row = box.row()
479 row.alignment = 'CENTER'
480 row.scale_y = 0.1
481 row.label(text='Divisions')
482 row = box.row()
483 col = row.column()
484 col.alignment = 'RIGHT'
485 col.label(text='Arc:')
486 col.prop(self, 'arc_div', text='')
487 col.label(text='[ {} ]'.format(self.arcdiv))
488 col = row.column()
489 col.alignment = 'RIGHT'
490 col.label(text='Linear:')
491 col.prop(self, 'lin_div', text='')
492 col.label(text='[ {:.3g} ]'.format(self.lindiv))
493 box.row().prop(self, 'div_type')
494 row = box.row()
495 row.active = self.arcdiv % 2
496 row.prop(self, 'odd_axis_align')
498 row = layout.row()
499 row.alert = self.vert_count > self.sanity_check_verts
500 row.prop(self, 'no_limit', text='No limit ({})'.format(self.vert_count))
502 if self.change == False:
503 col = layout.column(align=True)
504 col.prop(self, 'align', expand=True)
505 col = layout.column(align=True)
506 col.prop(self, 'location', expand=True)
507 col = layout.column(align=True)
508 col.prop(self, 'rotation', expand=True)
510 def RoundCubeParameters():
511 RoundCubeParameters = [
512 "radius",
513 "size",
514 "arc_div",
515 "lin_div",
516 "div_type",
517 "odd_axis_align",
518 "no_limit",
520 return RoundCubeParameters