Fix error in rigify property generation
[blender-addons.git] / add_mesh_extra_objects / Blocks.py
blob31db78072ec2d4928011febc6db2a86d92649f4d
1 # GPL # "authors": dudecon, jambay
3 # Module notes:
5 # Grout needs to be implemented.
6 # consider removing wedge crit for small "c" and "cl" values
7 # wrap around for openings on radial stonework?
8 # auto-clip wall edge to SMALL for radial and domes.
9 # unregister doesn't release all references.
10 # repeat for opening doesn't distribute evenly when radialized - see wrap around
11 # note above.
12 # if opening width == indent*2 the edge blocks fail (row of blocks cross opening).
13 # if openings overlap fills inverse with blocks - see h/v slots.
14 # Negative grout width creates a pair of phantom blocks, separated by grout
15 # width, inside the edges.
16 # if block width variance is 0, and edging is on, right edge blocks create a "vertical seam"
19 import bpy
20 from random import random
21 from math import (
22 fmod, sqrt,
23 sin, cos, atan,
24 pi as PI,
27 # Set to True to enable debug_prints
28 DEBUG = False
30 # A few constants
31 SMALL = 0.000000000001
32 # for values that must be != 0; see UI options/variables - sort of a bug to be fixed
33 NOTZERO = 0.01
35 # Global variables
37 # General masonry Settings
38 # ------------------------
39 settings = {
40 'w': 1.2, 'wv': 0.3, 'h': .6, 'hv': 0.3, 'd': 0.3, 'dv': 0.1,
41 'g': 0.1, 'gv': 0.07, 'gd': 0.01, 'gdv': 0.0, 'b': 0, 'bv': 0,
42 'f': 0.0, 'fv': 0.0, 't': 0.0, 'sdv': 0.1, 'hwt': 0.5, 'aln': 0,
43 'wm': 0.8, 'hm': 0.3, 'dm': 0.1,
44 'woff': 0.0, 'woffv': 0.0, 'eoff': 0.3, 'eoffv': 0.0, 'rwhl': 1,
45 'hb': 0, 'ht': 0, 'ge': 0, 'physics': 0
47 """
48 settings DOCUMENTATION:
49 'w':width 'wv':widthVariation
50 'h':height 'hv':heightVariation
51 'd':depth 'dv':depthVariation
52 'g':grout 'gv':groutVariation 'gd':groutDepth 'gdv':groutDepthVariation
53 'b':bevel 'bv':bevelVariation
54 'f':flawSize 'fv':flawSizeVariation 'ff':flawFraction
55 't':taper
56 'sdv':subdivision(distance or angle)
57 'hwt':row height effect on block widths in the row (0=no effect,
58 1=1:1 relationship, negative values allowed, 0.5 works well)
59 'aln':alignment(0=none, 1=rows w/features, 2=features w/rows)
60 (currently unused)
61 'wm':width minimum 'hm':height minimum 'dm':depth minimum
62 'woff':row start offset(fraction of width)
63 'woffv':width offset variation(fraction of width)
64 'eoff':edge offset 'eoffv':edge offset variation
65 'rwhl':row height lock(1 is all blocks in row have same height)
66 'hb':bottom row height 'ht': top row height 'ge': grout the edges
67 'physics': set up for physics
68 """
70 # dims = area of wall (face)
71 # ------------------------
72 dims = {
73 's': 0, 'e': PI * 3 / 2, 'b': 0.1, 't': 12.3
74 } # radial
75 """
76 dims DOCUMENTATION:
77 's':start x or theta 'e':end x or theta 'b':bottom z or r 't':top z or r
78 'w' = e-s and h = t-b; calculated to optimize for various operations/usages
79 dims = {'s':-12, 'e':15, 'w':27, 'b':-15., 't':15., 'h':30}
80 dims = {'s':-bayDim/2, 'e':bayDim/2, 'b':-5., 't':10.} # bay settings?
81 """
83 # ------------------------
84 radialized = 0 # Radiating from one point - round/disc; instead of square
85 slope = 0 # Warp/slope; curved over like a vaulted tunnel
87 # 'bigblock': merge adjacent blocks into single large blocks
88 bigBlock = 0 # Merge blocks
91 # Gaps in blocks for various apertures
92 # ------------------------
93 # openingSpecs = []
94 openingSpecs = [
95 {'w': 0.5, 'h': 0.5, 'x': 0.8, 'z': 2.7, 'rp': 1, 'b': 0.0,
96 'v': 0, 'vl': 0, 't': 0, 'tl': 0}
98 """
99 openingSpecs DOCUMENTATION:
100 'w': opening width, 'h': opening height,
101 'x': horizontal position, 'z': vertical position,
102 'rp': make multiple openings, with a spacing of x,
103 'b': bevel the opening, inside only, like an arrow slit.
104 'v': height of the top arch, 'vl':height of the bottom arch,
105 't': thickness of the top arch, 'tl': thickness of the bottom arch
108 # Add blocks to make platforms
109 # ------------------------
110 shelfExt = 0
112 shelfSpecs = {
113 'w': 0.5, 'h': 0.5, 'd': 0.3, 'x': 0.8, 'z': 2.7
116 shelfSpecs DOCUMENTATION:
117 'w': block width, 'h': block height, 'd': block depth (shelf size; offset from wall)
118 'x': horizontal start position, 'z': vertical start position
121 # Add blocks to make steps
122 # ------------------------
123 stepMod = 0
125 stepSpecs = {
126 'x': 0.0, 'z': -10, 'w': 10.0, 'h': 10.0,
127 'v': 0.7, 't': 1.0, 'd': 1.0
130 stepSpecs DOCUMENTATION:
131 'x': horizontal start position, 'z': vertical start position,
132 'w': step area width, 'h': step area height,
133 'v': riser height, 't': tread width, 'd': block depth (step size; offset from wall)
135 stepLeft = 0
136 shelfBack = 0
137 stepOnly = 0
138 stepBack = 0
141 # switchable prints
142 def debug_prints(func="", text="Message", var=None):
143 global DEBUG
144 if DEBUG:
145 print("\n[{}]\nmessage: {}".format(func, text))
146 if var:
147 print("Error: ", var)
150 # pass variables just like for the regular prints
151 def debug_print_vars(*args, **kwargs):
152 global DEBUG
153 if DEBUG:
154 print(*args, **kwargs)
157 # easier way to get to the random function
158 def rnd():
159 return random()
162 # random number from -0.5 to 0.5
163 def rndc():
164 return (random() - 0.5)
167 # random number from -1.0 to 1.0
168 def rndd():
169 return (random() - 0.5) * 2.0
172 # Opening Test suite
173 # opening test function
175 def test(TestN=13):
176 dims = {'s': -29., 'e': 29., 'b': -6., 't': TestN * 7.5}
177 openingSpecs = []
178 for i in range(TestN):
179 x = (random() - 0.5) * 6
180 z = i * 7.5
181 v = .2 + i * (3. / TestN)
182 vl = 3.2 - i * (3. / TestN)
183 t = 0.3 + random()
184 tl = 0.3 + random()
185 rn = random() * 2
186 openingSpecs += [{'w': 3.1 + rn, 'h': 0.3 + rn, 'x': float(x),
187 'z': float(z), 'rp': 0, 'b': 0.,
188 'v': float(v), 'vl': float(vl),
189 't': float(t), 'tl': float(tl)}]
190 return dims, openingSpecs
193 # dims, openingSpecs = test(15)
196 # For filling a linear space with divisions
197 def fill(left, right, avedst, mindst=0.0, dev=0.0, pad=(0.0, 0.0), num=0,
198 center=0):
199 __doc__ = """\
200 Fills a linear range with points and returns an ordered list of those points
201 including the end points.
203 left: the lower boundary
204 right: the upper boundary
205 avedst: the average distance between points
206 mindst: the minimum distance between points
207 dev: the maximum random deviation from avedst
208 pad: tends to move the points near the bounds right (positive) or
209 left (negative).
210 element 0 pads the lower bounds, element 1 pads the upper bounds
211 num: substitutes a numerical limit for the right limit. fill will then make
212 a num+1 element list
213 center: flag to center the elements in the range, 0 == disabled
216 poslist = [left]
217 curpos = left + pad[0]
219 # Set offset by average spacing, then add blocks (fall through);
220 # if not at right edge.
221 if center:
222 curpos += ((right - left - mindst * 2) % avedst) / 2 + mindst
223 if curpos - poslist[-1] < mindst:
224 curpos = poslist[-1] + mindst + rnd() * dev / 2
226 # clip to right edge.
227 if (right - curpos < mindst) or (right - curpos < mindst - pad[1]):
228 poslist.append(right)
229 return poslist
231 else:
232 poslist.append(curpos)
234 # unused... for now.
235 if num:
236 idx = len(poslist)
238 while idx < num + 1:
239 curpos += avedst + rndd() * dev
240 if curpos - poslist[-1] < mindst:
241 curpos = poslist[-1] + mindst + rnd() * dev / 2
242 poslist.append(curpos)
243 idx += 1
245 return poslist
247 # make block edges
248 else:
249 while True: # loop for blocks
250 curpos += avedst + rndd() * dev
251 if curpos - poslist[-1] < mindst:
252 curpos = poslist[-1] + mindst + rnd() * dev / 2
253 # close off edges at limit
254 if (right - curpos < mindst) or (right - curpos < mindst - pad[1]):
255 poslist.append(right)
256 return poslist
257 else:
258 poslist.append(curpos)
261 # For generating block geometry
262 def MakeABlock(bounds, segsize, vll=0, Offsets=None, FaceExclude=[],
263 bevel=0, xBevScl=1):
264 __doc__ = """\
265 MakeABlock returns lists of points and faces to be made into a square
266 cornered block, subdivided along the length, with optional bevels.
267 bounds: a list of boundary positions:
268 0:left, 1:right, 2:bottom, 3:top, 4:back, 5:front
269 segsize: the maximum size before lengthwise subdivision occurs
270 vll: the number of vertexes already in the mesh. len(mesh.verts) should
271 give this number.
272 Offsets: list of coordinate delta values.
273 Offsets are lists, [x,y,z] in
275 0:left_bottom_back,
276 1:left_bottom_front,
277 2:left_top_back,
278 3:left_top_front,
279 4:right_bottom_back,
280 5:right_bottom_front,
281 6:right_top_back,
282 7:right_top_front,
284 FaceExclude: list of faces to exclude from the faces list. see bounds above for indices
285 xBevScl: how much to divide the end (+- x axis) bevel dimensions. Set to current average
286 radius to compensate for angular distortion on curved blocks
289 slices = fill(bounds[0], bounds[1], segsize, segsize, center=1)
290 points = []
291 faces = []
293 if Offsets is None:
294 points.append([slices[0], bounds[4], bounds[2]])
295 points.append([slices[0], bounds[5], bounds[2]])
296 points.append([slices[0], bounds[5], bounds[3]])
297 points.append([slices[0], bounds[4], bounds[3]])
299 for x in slices[1:-1]:
300 points.append([x, bounds[4], bounds[2]])
301 points.append([x, bounds[5], bounds[2]])
302 points.append([x, bounds[5], bounds[3]])
303 points.append([x, bounds[4], bounds[3]])
305 points.append([slices[-1], bounds[4], bounds[2]])
306 points.append([slices[-1], bounds[5], bounds[2]])
307 points.append([slices[-1], bounds[5], bounds[3]])
308 points.append([slices[-1], bounds[4], bounds[3]])
310 else:
311 points.append([slices[0] + Offsets[0][0], bounds[4] + Offsets[0][1], bounds[2] + Offsets[0][2]])
312 points.append([slices[0] + Offsets[1][0], bounds[5] + Offsets[1][1], bounds[2] + Offsets[1][2]])
313 points.append([slices[0] + Offsets[3][0], bounds[5] + Offsets[3][1], bounds[3] + Offsets[3][2]])
314 points.append([slices[0] + Offsets[2][0], bounds[4] + Offsets[2][1], bounds[3] + Offsets[2][2]])
316 for x in slices[1: -1]:
317 xwt = (x - bounds[0]) / (bounds[1] - bounds[0])
318 points.append([x + Offsets[0][0] * (1 - xwt) + Offsets[4][0] * xwt,
319 bounds[4] + Offsets[0][1] * (1 - xwt) + Offsets[4][1] * xwt,
320 bounds[2] + Offsets[0][2] * (1 - xwt) + Offsets[4][2] * xwt])
321 points.append([x + Offsets[1][0] * (1 - xwt) + Offsets[5][0] * xwt,
322 bounds[5] + Offsets[1][1] * (1 - xwt) + Offsets[5][1] * xwt,
323 bounds[2] + Offsets[1][2] * (1 - xwt) + Offsets[5][2] * xwt])
324 points.append([x + Offsets[3][0] * (1 - xwt) + Offsets[7][0] * xwt,
325 bounds[5] + Offsets[3][1] * (1 - xwt) + Offsets[7][1] * xwt,
326 bounds[3] + Offsets[3][2] * (1 - xwt) + Offsets[7][2] * xwt])
327 points.append([x + Offsets[2][0] * (1 - xwt) + Offsets[6][0] * xwt,
328 bounds[4] + Offsets[2][1] * (1 - xwt) + Offsets[6][1] * xwt,
329 bounds[3] + Offsets[2][2] * (1 - xwt) + Offsets[6][2] * xwt])
331 points.append([slices[-1] + Offsets[4][0], bounds[4] + Offsets[4][1], bounds[2] + Offsets[4][2]])
332 points.append([slices[-1] + Offsets[5][0], bounds[5] + Offsets[5][1], bounds[2] + Offsets[5][2]])
333 points.append([slices[-1] + Offsets[7][0], bounds[5] + Offsets[7][1], bounds[3] + Offsets[7][2]])
334 points.append([slices[-1] + Offsets[6][0], bounds[4] + Offsets[6][1], bounds[3] + Offsets[6][2]])
336 faces.append([vll, vll + 3, vll + 2, vll + 1])
338 for x in range(len(slices) - 1):
339 faces.append([vll, vll + 1, vll + 5, vll + 4])
340 vll += 1
341 faces.append([vll, vll + 1, vll + 5, vll + 4])
342 vll += 1
343 faces.append([vll, vll + 1, vll + 5, vll + 4])
344 vll += 1
345 faces.append([vll, vll - 3, vll + 1, vll + 4])
346 vll += 1
348 faces.append([vll, vll + 1, vll + 2, vll + 3])
350 return points, faces
353 # For generating Keystone Geometry
355 def MakeAKeystone(xpos, width, zpos, ztop, zbtm, thick, bevel, vll=0, FaceExclude=[], xBevScl=1):
356 __doc__ = """\
357 MakeAKeystone returns lists of points and faces to be made into a
358 square cornered keystone, with optional bevels.
359 xpos: x position of the centerline
360 width: x width of the keystone at the widest point (discounting bevels)
361 zpos: z position of the widest point
362 ztop: distance from zpos to the top
363 zbtm: distance from zpos to the bottom
364 thick: thickness
365 bevel: the amount to raise the back vertex to account for arch beveling
366 vll: the number of vertexes already in the mesh. len(mesh.verts) should give this number
367 faceExclude: list of faces to exclude from the faces list.
368 0:left, 1:right, 2:bottom, 3:top, 4:back, 5:front
369 xBevScl: how much to divide the end (+- x axis) bevel dimensions.
370 Set to current average radius to compensate for angular distortion on curved blocks
373 points = []
374 faces = []
375 faceinclude = [1 for x in range(6)]
376 for x in FaceExclude:
377 faceinclude[x] = 0
378 Top = zpos + ztop
379 Btm = zpos - zbtm
380 Wid = width / 2.0
381 Thk = thick / 2.0
383 # The front top point
384 points.append([xpos, Thk, Top])
385 # The front left point
386 points.append([xpos - Wid, Thk, zpos])
387 # The front bottom point
388 points.append([xpos, Thk, Btm])
389 # The front right point
390 points.append([xpos + Wid, Thk, zpos])
392 MirrorPoints = []
393 for i in points:
394 MirrorPoints.append([i[0], -i[1], i[2]])
395 points += MirrorPoints
396 points[6][2] += bevel
398 faces.append([3, 2, 1, 0])
399 faces.append([4, 5, 6, 7])
400 faces.append([4, 7, 3, 0])
401 faces.append([5, 4, 0, 1])
402 faces.append([6, 5, 1, 2])
403 faces.append([7, 6, 2, 3])
404 # Offset the vertex numbers by the number of vertices already in the list
405 for i in range(len(faces)):
406 for j in range(len(faces[i])):
407 faces[i][j] += vll
409 return points, faces
412 # for finding line/circle intercepts
414 def circ(offs=0., r=1.):
415 __doc__ = """\
416 offs is the distance perpendicular to the line to the center of the circle
417 r is the radius of the circle
418 circ returns the distance parallel to the line to the center of the circle at the intercept.
420 offs = abs(offs)
421 if offs > r:
422 return None
423 elif offs == r:
424 return 0.
425 else:
426 return sqrt(r ** 2 - offs ** 2)
429 # class openings in the wall
431 class opening:
432 __doc__ = """\
433 This is the class for holding the data for the openings in the wall.
434 It has methods for returning the edges of the opening for any given position value,
435 as well as bevel settings and top and bottom positions.
436 It stores the 'style' of the opening, and all other pertinent information.
438 # x = 0. # x position of the opening
439 # z = 0. # x position of the opening
440 # w = 0. # width of the opening
441 # h = 0. # height of the opening
442 r = 0 # top radius of the arch (derived from 'v')
443 rl = 0 # lower radius of the arch (derived from 'vl')
444 rt = 0 # top arch thickness
445 rtl = 0 # lower arch thickness
446 ts = 0 # Opening side thickness, if greater than average width, replaces it.
447 c = 0 # top arch corner position (for low arches), distance from the top of the straight sides
448 cl = 0 # lower arch corner position (for low arches), distance from the top of the straight sides
449 # form = 0 # arch type (unused for now)
450 # b = 0. # back face bevel distance, like an arrow slit
451 v = 0. # top arch height
452 vl = 0. # lower arch height
453 # variable "s" is used for "side" in the "edge" function.
454 # it is a signed int, multiplied by the width to get + or - of the center
456 def btm(self):
457 if self.vl <= self.w / 2:
458 return self.z - self.h / 2 - self.vl - self.rtl
459 else:
460 return self.z - sqrt((self.rl + self.rtl) ** 2 - (self.rl - self.w / 2) ** 2) - self.h / 2
462 def top(self):
463 if self.v <= self.w / 2:
464 return self.z + self.h / 2 + self.v + self.rt
465 else:
466 return sqrt((self.r + self.rt) ** 2 - (self.r - self.w / 2) ** 2) + self.z + self.h / 2
468 # crits returns the critical split points, or discontinuities, used for making rows
469 def crits(self):
470 critlist = []
471 if self.vl > 0: # for lower arch
472 # add the top point if it is pointed
473 # if self.vl >= self.w/2.: critlist.append(self.btm())
474 if self.vl < self.w / 2.: # else: for low arches, with wedge blocks under them
475 # critlist.append(self.btm())
476 critlist.append(self.z - self.h / 2 - self.cl)
478 if self.h > 0: # if it has a height, append points at the top and bottom of the main square section
479 critlist += [self.z - self.h / 2, self.z + self.h / 2]
480 else: # otherwise, append just one in the center
481 critlist.append(self.z)
483 if self.v > 0: # for the upper arch
484 if self.v < self.w / 2: # add the splits for the upper wedge blocks, if needed
485 critlist.append(self.z + self.h / 2 + self.c)
486 # critlist.append(self.top())
487 # otherwise just add the top point, if it is pointed
488 # else: critlist.append(self.top())
490 return critlist
492 # get the side position of the opening.
493 # ht is the z position; s is the side: 1 for right, -1 for left
494 # if the height passed is above or below the opening, return None
495 def edgeS(self, ht, s):
497 # set the row radius: 1 for standard wall (flat)
498 if radialized:
499 if slope:
500 r1 = abs(dims['t'] * sin(ht * PI / (dims['t'] * 2)))
501 else:
502 r1 = abs(ht)
503 else:
504 r1 = 1
506 # Go through all the options, and return the correct value
507 if ht < self.btm(): # too low
508 return None
509 elif ht > self.top(): # too high
510 return None
512 # Check for circ returning None - prevent TypeError (script failure) with float.
513 # in this range, pass the lower arch info
514 elif ht <= self.z - self.h / 2 - self.cl:
515 if self.vl > self.w / 2:
516 circVal = circ(ht - self.z + self.h / 2, self.rl + self.rtl)
517 if circVal is None:
518 return None
519 else:
520 return self.x + s * (self.w / 2. - self.rl + circVal) / r1
521 else:
522 circVal = circ(ht - self.z + self.h / 2 + self.vl - self.rl, self.rl + self.rtl)
523 if circVal is None:
524 return None
525 else:
526 return self.x + s * circVal / r1
528 # in this range, pass the top arch info
529 elif ht >= self.z + self.h / 2 + self.c:
530 if self.v > self.w / 2:
531 circVal = circ(ht - self.z - self.h / 2, self.r + self.rt)
532 if circVal is None:
533 return None
534 else:
535 return self.x + s * (self.w / 2. - self.r + circVal) / r1
536 else:
537 circVal = circ(ht - (self.z + self.h / 2 + self.v - self.r), self.r + self.rt)
538 if circVal is None:
539 return None
540 else:
541 return self.x + s * circVal / r1
543 # in this range pass the lower corner edge info
544 elif ht <= self.z - self.h / 2:
545 d = sqrt(self.rtl ** 2 - self.cl ** 2)
546 if self.cl > self.rtl / sqrt(2.):
547 return self.x + s * (self.w / 2 + (self.z - self.h / 2 - ht) * d / self.cl) / r1
548 else:
549 return self.x + s * (self.w / 2 + d) / r1
551 # in this range pass the upper corner edge info
552 elif ht >= self.z + self.h / 2:
553 d = sqrt(self.rt ** 2 - self.c ** 2)
554 if self.c > self.rt / sqrt(2.):
555 return self.x + s * (self.w / 2 + (ht - self.z - self.h / 2) * d / self.c) / r1
556 else:
557 return self.x + s * (self.w / 2 + d) / r1
559 # in this range, pass the middle info (straight sides)
560 else:
561 return self.x + s * self.w / 2 / r1
563 # get the top or bottom of the opening
564 # ht is the x position; s is the side: 1 for top, -1 for bottom
565 def edgeV(self, ht, s):
567 dist = abs(self.x - ht)
569 def radialAdjust(dist, sideVal):
570 # take the distance and adjust for radial geometry, return dist
571 if radialized:
572 if slope:
573 dist = dist * abs(dims['t'] * sin(sideVal * PI / (dims['t'] * 2)))
574 else:
575 dist = dist * sideVal
576 return dist
578 if s > 0: # and (dist <= self.edgeS(self.z + self.h / 2 + self.c, 1) - self.x): # check top down
579 # hack for radialized masonry, import approx Z instead of self.top()
580 dist = radialAdjust(dist, self.top())
582 # no arch on top, flat
583 if not self.r:
584 return self.z + self.h / 2
586 # pointed arch on top
587 elif self.v > self.w / 2:
588 circVal = circ(dist - self.w / 2 + self.r, self.r + self.rt)
589 if circVal is None:
590 return None
591 else:
592 return self.z + self.h / 2 + circVal
594 # domed arch on top
595 else:
596 circVal = circ(dist, self.r + self.rt)
597 if circVal is None:
598 return None
599 else:
600 return self.z + self.h / 2 + self.v - self.r + circVal
602 else: # and (dist <= self.edgeS(self.z - self.h / 2 - self.cl, 1) - self.x): # check bottom up
603 # hack for radialized masonry, import approx Z instead of self.top()
604 dist = radialAdjust(dist, self.btm())
606 # no arch on bottom
607 if not self.rl:
608 return self.z - self.h / 2
610 # pointed arch on bottom
611 elif self.vl > self.w / 2:
612 circVal = circ(dist - self.w / 2 + self.rl, self.rl + self.rtl)
613 if circVal is None:
614 return None
615 else:
616 return self.z - self.h / 2 - circVal
618 # old conditional? if (dist-self.w / 2 + self.rl) <= (self.rl + self.rtl):
619 # domed arch on bottom
620 else:
621 circVal = circ(dist, self.rl + self.rtl) # dist-self.w / 2 + self.rl
622 if circVal is None:
623 return None
624 else:
625 return self.z - self.h / 2 - self.vl + self.rl - circVal
627 # and this never happens - but, leave it as failsafe :)
628 debug_prints(func="opening.EdgeV",
629 text="Got all the way out of the edgeV! Not good!")
630 debug_print_vars("opening x = ", self.x, ", opening z = ", self.z)
632 return 0.0
634 def edgeBev(self, ht):
635 if ht > (self.z + self.h / 2):
636 return 0.0
637 if ht < (self.z - self.h / 2):
638 return 0.0
639 if radialized:
640 if slope:
641 r1 = abs(dims['t'] * sin(ht * PI / (dims['t'] * 2)))
642 else:
643 r1 = abs(ht)
644 else:
645 r1 = 1
646 bevel = self.b / r1
647 return bevel
649 def __init__(self, xpos, zpos, width, height, archHeight=0, archThk=0,
650 archHeightLower=0, archThkLower=0, bevel=0, edgeThk=0):
651 self.x = float(xpos)
652 self.z = float(zpos)
653 self.w = float(width)
654 self.h = float(height)
655 self.rt = archThk
656 self.rtl = archThkLower
657 self.v = archHeight
658 self.vl = archHeightLower
659 if self.w <= 0:
660 self.w = SMALL
662 # find the upper arch radius
663 if archHeight >= width / 2:
664 # just one arch, low long
665 self.r = (self.v ** 2) / self.w + self.w / 4
666 elif archHeight <= 0:
667 # No arches
668 self.r = 0
669 self.v = 0
670 else:
671 # Two arches
672 self.r = (self.w ** 2) / (8 * self.v) + self.v / 2.
673 self.c = self.rt * cos(atan(self.w / (2 * (self.r - self.v))))
675 # find the lower arch radius
676 if archHeightLower >= width / 2:
677 self.rl = (self.vl ** 2) / self.w + self.w / 4
678 elif archHeightLower <= 0:
679 self.rl = 0
680 self.vl = 0
681 else:
682 self.rl = (self.w ** 2) / (8 * self.vl) + self.vl / 2.
683 self.cl = self.rtl * cos(atan(self.w / (2 * (self.rl - self.vl))))
685 # self.form = something?
686 self.b = float(bevel)
687 self.ts = edgeThk
690 # class for the whole wall boundaries; a sub-class of "opening"
691 class openingInvert(opening):
692 # this is supposed to switch the sides of the opening
693 # so the wall will properly enclose the whole wall.
695 def edgeS(self, ht, s):
696 return opening.edgeS(self, ht, -s)
698 def edgeV(self, ht, s):
699 return opening.edgeV(self, ht, -s)
702 # class rows in the wall
704 class rowOb:
705 __doc__ = """\
706 This is the class for holding the data for individual rows of blocks.
707 each row is required to have some edge blocks, and can also have
708 intermediate sections of "normal" blocks.
710 radius = 1
711 EdgeOffset = 0.
713 def FillBlocks(self):
714 # Set the radius variable, in the case of radial geometry
715 if radialized:
716 if slope:
717 self.radius = dims['t'] * (sin(self.z * PI / (dims['t'] * 2)))
718 else:
719 self.radius = self.z
721 # initialize internal variables from global settings
723 SetH = settings['h']
724 SetHwt = settings['hwt']
725 SetWid = settings['w']
726 SetWidMin = settings['wm']
727 SetWidVar = settings['wv']
728 SetGrt = settings['g']
729 SetGrtVar = settings['gv']
730 SetRowHeightLink = settings['rwhl']
731 SetDepth = settings['d']
732 SetDepthVar = settings['dv']
734 # height weight, used for making shorter rows have narrower blocks, and vice-versa
735 hwt = ((self.h / SetH - 1) * SetHwt + 1)
737 # set variables for persistent values: loop optimization, readability, single ref for changes.
739 avgDist = hwt * SetWid / self.radius
740 minDist = SetWidMin / self.radius
741 deviation = hwt * SetWidVar / self.radius
742 grtOffset = SetGrt / (2 * self.radius)
744 # init loop variables that may change...
746 grt = (SetGrt + rndc() * SetGrtVar) / (self.radius)
747 ThisBlockHeight = self.h + rndc() * (1 - SetRowHeightLink) * SetGrtVar
748 ThisBlockDepth = rndd() * SetDepthVar + SetDepth
750 for segment in self.RowSegments:
751 divs = fill(segment[0] + grtOffset, segment[1] - grtOffset, avgDist, minDist, deviation)
753 # loop through the divisions, adding blocks for each one
754 for i in range(len(divs) - 1):
755 ThisBlockx = (divs[i] + divs[i + 1]) / 2
756 ThisBlockw = divs[i + 1] - divs[i] - grt
758 self.BlocksNorm.append([ThisBlockx, self.z, ThisBlockw, ThisBlockHeight, ThisBlockDepth, None])
760 if SetDepthVar: # vary depth
761 ThisBlockDepth = rndd() * SetDepthVar + SetDepth
763 if SetGrtVar: # vary grout
764 grt = (SetGrt + rndc() * SetGrtVar) / (self.radius)
765 ThisBlockHeight = self.h + rndc() * (1 - SetRowHeightLink) * SetGrtVar
767 def __init__(self, centerheight, rowheight, edgeoffset=0.):
768 self.z = float(centerheight)
769 self.h = float(rowheight)
770 self.EdgeOffset = float(edgeoffset)
772 # THIS INITIALIZATION IS IMPORTANT! OTHERWISE ALL OBJECTS WILL HAVE THE SAME LISTS!
773 self.BlocksEdge = []
774 self.RowSegments = []
775 self.BlocksNorm = []
778 def arch(ra, rt, x, z, archStart, archEnd, bevel, bevAngle, vll):
779 __doc__ = """\
780 Makes a list of faces and vertexes for arches.
781 ra: the radius of the arch, to the center of the bricks
782 rt: the thickness of the arch
783 x: x center location of the circular arc, as if the arch opening were centered on x = 0
784 z: z center location of the arch
785 anglebeg: start angle of the arch, in radians, from vertical?
786 angleend: end angle of the arch, in radians, from vertical?
787 bevel: how much to bevel the inside of the arch.
788 vll: how long is the vertex list already?
790 avlist = []
791 aflist = []
793 # initialize internal variables for global settings
794 SetGrt = settings['g']
795 SetGrtVar = settings['gv']
796 SetDepth = settings['d']
797 SetDepthVar = settings['dv']
799 # Init loop variables
801 def bevelEdgeOffset(offsets, bevel, side):
803 Take the block offsets and modify it for the correct bevel.
805 offsets = the offset list. See MakeABlock
806 bevel = how much to offset the edge
807 side = -1 for left (right side), 1 for right (left side)
809 left = (0, 2, 3)
810 right = (4, 6, 7)
811 if side == 1:
812 pointsToAffect = right
813 else:
814 pointsToAffect = left
815 for num in pointsToAffect:
816 offsets[num] = offsets[num][:]
817 offsets[num][0] += -bevel * side
819 ArchInner = ra - rt / 2
820 ArchOuter = ra + rt / 2 - SetGrt + rndc() * SetGrtVar
822 DepthBack = - SetDepth / 2 - rndc() * SetDepthVar
823 DepthFront = SetDepth / 2 + rndc() * SetDepthVar
825 if radialized:
826 subdivision = settings['sdv']
827 else:
828 subdivision = 0.12
830 grt = (SetGrt + rndc() * SetGrtVar) / (2 * ra) # init grout offset for loop
831 # set up the offsets, it will be the same for every block
832 offsets = ([[0] * 2 + [bevel]] + [[0] * 3] * 3) * 2
834 # make the divisions in the "length" of the arch
835 divs = fill(archStart, archEnd, settings['w'] / ra, settings['wm'] / ra, settings['wv'] / ra)
837 for i in range(len(divs) - 1):
838 if i == 0:
839 ThisOffset = offsets[:]
840 bevelEdgeOffset(ThisOffset, bevAngle, - 1)
841 elif i == len(divs) - 2:
842 ThisOffset = offsets[:]
843 bevelEdgeOffset(ThisOffset, bevAngle, 1)
844 else:
845 ThisOffset = offsets
847 geom = MakeABlock(
848 [divs[i] + grt, divs[i + 1] - grt, ArchInner, ArchOuter, DepthBack, DepthFront],
849 subdivision, len(avlist) + vll, ThisOffset, [], None, ra
852 avlist += geom[0]
853 aflist += geom[1]
855 if SetDepthVar: # vary depth
856 DepthBack = -SetDepth / 2 - rndc() * SetDepthVar
857 DepthFront = SetDepth / 2 + rndc() * SetDepthVar
859 if SetGrtVar: # vary grout
860 grt = (settings['g'] + rndc() * SetGrtVar) / (2 * ra)
861 ArchOuter = ra + rt / 2 - SetGrt + rndc() * SetGrtVar
863 for i, vert in enumerate(avlist):
864 v0 = vert[2] * sin(vert[0]) + x
865 v1 = vert[1]
866 v2 = vert[2] * cos(vert[0]) + z
868 if radialized == 1:
869 if slope == 1:
870 r1 = dims['t'] * (sin(v2 * PI / (dims['t'] * 2)))
871 else:
872 r1 = v2
873 v0 = v0 / r1
875 avlist[i] = [v0, v1, v2]
877 return (avlist, aflist)
880 def sketch():
881 __doc__ = """ \
882 The 'sketch' function creates a list of openings from the general specifications passed to it.
883 It takes curved and domed walls into account, placing the openings at the appropriate angular locations
885 boundlist = []
886 for x in openingSpecs:
887 if x['rp']:
888 if radialized:
889 r1 = x['z']
890 else:
891 r1 = 1
893 if x['x'] > (x['w'] + settings['wm']):
894 spacing = x['x'] / r1
895 else:
896 spacing = (x['w'] + settings['wm']) / r1
898 minspacing = (x['w'] + settings['wm']) / r1
900 divs = fill(dims['s'], dims['e'], spacing, minspacing, center=1)
902 for posidx in range(len(divs) - 2):
903 boundlist.append(opening(divs[posidx + 1], x['z'], x['w'], x['h'],
904 x['v'], x['t'], x['vl'], x['tl'], x['b']))
905 else:
906 boundlist.append(opening(x['x'], x['z'], x['w'], x['h'], x['v'], x['t'], x['vl'], x['tl'], x['b']))
907 # check for overlapping edges?
909 return boundlist
912 def wedgeBlocks(row, opening, leftPos, rightPos, edgeBinary, r1):
913 __doc__ = """\
914 Makes wedge blocks for the left and right sides, depending
915 example:
916 wedgeBlocks(row, LeftWedgeEdge, LNerEdge, LEB, r1)
917 wedgeBlocks(row, RNerEdge, RightWedgeEdge, REB, r1)
919 wedgeEdges = fill(leftPos, rightPos, settings['w'] / r1, settings['wm'] / r1,
920 settings['wv'] / r1)
922 for i in range(len(wedgeEdges) - 1):
923 x = (wedgeEdges[i + 1] + wedgeEdges[i]) / 2
924 grt = (settings['g'] + rndd() * settings['gv']) / r1
925 w = wedgeEdges[i + 1] - wedgeEdges[i] - grt
927 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
929 # edgeV may return "None" - causing TypeError for math op.
930 # use 0 until wedgeBlocks operation worked out
931 edgeVal = opening.edgeV(x - w / 2, edgeBinary)
932 if edgeVal is None:
933 edgeVal = 0.0
935 LeftVertOffset = -(row.z - (row.h / 2) * edgeBinary - edgeVal)
937 # edgeV may return "None" - causing TypeError for math op.
938 # use 0 until wedgeBlocks operation worked out
939 edgeVal = opening.edgeV(x + w / 2, edgeBinary)
940 if edgeVal is None:
941 edgeVal = 0.0
943 RightVertOffset = -(row.z - (row.h / 2) * edgeBinary - edgeVal)
945 # Wedges are on top = off, blank, off, blank
946 # Wedges are on btm = blank, off, blank, off
947 ThisBlockOffsets = [[0, 0, LeftVertOffset]] * 2 + [[0] * 3] * 2 + [[0, 0, RightVertOffset]] * 2
949 # Insert or append "blank" for top or bottom wedges.
950 if edgeBinary == 1:
951 ThisBlockOffsets = ThisBlockOffsets + [[0] * 3] * 2
952 else:
953 ThisBlockOffsets = [[0] * 3] * 2 + ThisBlockOffsets
955 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, ThisBlockOffsets])
957 return None
960 def bevelBlockOffsets(offsets, bevel, side):
962 Take the block offsets and modify it for the correct bevel.
964 offsets = the offset list. See MakeABlock
965 bevel = how much to offset the edge
966 side = -1 for left (right side), 1 for right (left side)
968 if side == 1:
969 pointsToAffect = (0, 2) # right
970 else:
971 pointsToAffect = (4, 6) # left
972 for num in pointsToAffect:
973 offsets[num] = offsets[num][:]
974 offsets[num][0] += bevel * side
977 def rowProcessing(row, Thesketch, WallBoundaries):
978 __doc__ = """\
979 Take row and opening data and process a single row, adding edge and fill blocks to the row data.
981 # set end blocks
982 # check for openings, record top and bottom of row for right and left of each
983 # if both top and bottom intersect create blocks on each edge, appropriate to the size of the overlap
984 # if only one side intersects, run fill to get edge positions, but this should never happen
986 if radialized: # this checks for radial stonework, and sets the row radius if required
987 if slope:
988 r1 = abs(dims['t'] * sin(row.z * PI / (dims['t'] * 2)))
989 else:
990 r1 = abs(row.z)
991 else:
992 r1 = 1
994 # set the edge grout thickness, especially with radial stonework in mind
995 edgrt = settings['ge'] * (settings['g'] / 2 + rndc() * settings['gv']) / (2 * r1)
997 # Sets up a list of intersections of top of row with openings,
998 # from left to right [left edge of opening, right edge of opening, etc...]
999 # initially just the left and right edge of the wall
1000 edgetop = [[dims['s'] + row.EdgeOffset / r1 + edgrt, WallBoundaries],
1001 [dims['e'] + row.EdgeOffset / r1 - edgrt, WallBoundaries]]
1003 # Same as edgetop, but for the bottms of the rows
1004 edgebtm = [[dims['s'] + row.EdgeOffset / r1 + edgrt, WallBoundaries],
1005 [dims['e'] + row.EdgeOffset / r1 - edgrt, WallBoundaries]]
1007 # set up some useful values for the top and bottom of the rows.
1008 rowTop = row.z + row.h / 2
1009 rowBtm = row.z - row.h / 2
1011 for hole in Thesketch:
1012 # check the top and bottom of the row, looking at the opening from the right
1013 e = [hole.edgeS(rowTop, -1), hole.edgeS(rowBtm, -1)]
1015 # If either one hit the opening, make split points for the left side of the opening.
1016 if e[0] or e[1]:
1017 e += [hole.edgeS(rowTop, 1), hole.edgeS(rowBtm, 1)]
1019 # If one of them missed for some reason, set that value to
1020 # the middle of the opening.
1021 for i, pos in enumerate(e):
1022 if pos is None:
1023 e[i] = hole.x
1025 # add the intersects to the list of edge points
1026 edgetop.append([e[0], hole])
1027 edgetop.append([e[2], hole])
1028 edgebtm.append([e[1], hole])
1029 edgebtm.append([e[3], hole])
1031 # We want to make the walls in order, so sort the intersects.
1032 # This is where you would want to remove edge points that are out of order
1033 # so that you don't get the "oddity where overlapping openings
1034 # create blocks inversely" problem
1036 # Note: sort ended up comparing function pointers
1037 # if both Openings and Slots were enabled with Repeats in one of them
1038 try:
1039 edgetop.sort(key=lambda x: x[0])
1040 edgebtm.sort(key=lambda x: x[0])
1041 except Exception as ex:
1042 debug_prints(func="rowProcessing",
1043 text="Sorting has failed", var=ex)
1045 # these two loops trim the edges to the limits of the wall.
1046 # This way openings extending outside the wall don't enlarge the wall.
1047 while True:
1048 try:
1049 if ((edgetop[-1][0] > dims['e'] + row.EdgeOffset / r1) or
1050 (edgebtm[-1][0] > dims['e'] + row.EdgeOffset / r1)):
1051 edgetop[-2:] = []
1052 edgebtm[-2:] = []
1053 else:
1054 break
1055 except IndexError:
1056 break
1057 # still trimming the edges...
1058 while True:
1059 try:
1060 if ((edgetop[0][0] < dims['s'] + row.EdgeOffset / r1) or
1061 (edgebtm[0][0] < dims['s'] + row.EdgeOffset / r1)):
1062 edgetop[:2] = []
1063 edgebtm[:2] = []
1064 else:
1065 break
1066 except IndexError:
1067 break
1069 # make those edge blocks and rows! Wooo!
1070 # This loop goes through each section, (a pair of points in edgetop)
1071 # and places the edge blocks and inbetween normal block zones into the row object
1072 for OpnSplitNo in range(int(len(edgetop) / 2)):
1073 # left edge is edge<x>[2*OpnSplitNo], right edge edgex[2*OpnSplitNo+1]
1074 leftEdgeIndex = 2 * OpnSplitNo
1075 rightEdgeIndex = 2 * OpnSplitNo + 1
1077 # get the openings, to save time and confusion
1078 leftOpening = edgetop[leftEdgeIndex][1]
1079 rightOpening = edgetop[rightEdgeIndex][1]
1081 # find the difference between the edge top and bottom on both sides
1082 LTop = edgetop[leftEdgeIndex][0]
1083 LBtm = edgebtm[leftEdgeIndex][0]
1084 RTop = edgetop[rightEdgeIndex][0]
1085 RBtm = edgebtm[rightEdgeIndex][0]
1086 LDiff = LBtm - LTop
1087 RDiff = RTop - RBtm
1089 # which is further out on each side, top or bottom?
1090 if LDiff > 0:
1091 LNerEdge = LBtm # the nearer edge left
1092 LEB = 1 # Left Edge Boolean, set to 1 if furthest edge is top, -1 if it is bottom
1093 else:
1094 LNerEdge = LTop
1095 LEB = -1
1097 if RDiff > 0:
1098 RNerEdge = RBtm # the nearer edge right
1099 REB = 1 # Right Edge Boolean, set to 1 if furthest edge is top, -1 if it is bottom
1101 else:
1102 RNerEdge = RTop
1103 REB = -1 # Right Edge Boolean, set to 1 if furthest edge is top, -1 if it is bottom
1105 # The space between the closest edges of the openings in this section of the row
1106 InnerDiff = RNerEdge - LNerEdge
1107 # The mid point between the nearest edges
1108 InnerMid = (RNerEdge + LNerEdge) / 2
1110 # maximum distance to span with one block
1111 MaxWid = (settings['w'] + settings['wv']) / r1
1112 AveWid = settings['w']
1114 # check the left and right sides for wedge blocks
1115 # Check and run the left edge first
1116 # find the edge of the correct side, offset for minimum block height. The LEB decides top or bottom
1117 ZPositionCheck = row.z + (row.h / 2 - settings['hm']) * LEB
1119 # edgeS may return "None"
1120 LeftWedgeEdge = leftOpening.edgeS(ZPositionCheck, 1)
1122 if (abs(LDiff) > AveWid) or (not LeftWedgeEdge):
1123 # make wedge blocks
1124 if not LeftWedgeEdge:
1125 LeftWedgeEdge = leftOpening.x
1126 wedgeBlocks(row, leftOpening, LeftWedgeEdge, LNerEdge, LEB, r1)
1127 # set the near and far edge settings to vertical, so the other edge blocks don't interfere
1128 LTop, LBtm = LNerEdge, LNerEdge
1129 LDiff = 0
1131 # Now do the wedge blocks for the right, same drill... repeated code?
1132 # find the edge of the correct side, offset for minimum block height. The REB decides top or bottom
1133 ZPositionCheck = row.z + (row.h / 2 - settings['hm']) * REB
1135 # edgeS may return "None"
1136 RightWedgeEdge = rightOpening.edgeS(ZPositionCheck, -1)
1137 if (abs(RDiff) > AveWid) or (not RightWedgeEdge):
1138 # make wedge blocks
1139 if not RightWedgeEdge:
1140 RightWedgeEdge = rightOpening.x
1141 wedgeBlocks(row, rightOpening, RNerEdge, RightWedgeEdge, REB, r1)
1142 # set the near and far edge settings to vertical, so the other edge blocks don't interfere
1143 RTop, RBtm = RNerEdge, RNerEdge
1144 RDiff = 0
1146 # Check to see if the edges are close enough toegther to warrant a single block filling it
1147 if (InnerDiff < MaxWid):
1148 # if this is true, then this row is just one block!
1149 x = (LNerEdge + RNerEdge) / 2.
1150 w = InnerDiff
1151 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1152 BtmOff = LBtm - LNerEdge
1153 TopOff = LTop - LNerEdge
1154 ThisBlockOffsets = [[BtmOff, 0, 0]] * 2 + [[TopOff, 0, 0]] * 2
1155 BtmOff = RBtm - RNerEdge
1156 TopOff = RTop - RNerEdge
1157 ThisBlockOffsets += [[BtmOff, 0, 0]] * 2 + [[TopOff, 0, 0]] * 2
1158 bevel = leftOpening.edgeBev(rowTop)
1159 bevelBlockOffsets(ThisBlockOffsets, bevel, 1)
1160 bevel = rightOpening.edgeBev(rowTop)
1161 bevelBlockOffsets(ThisBlockOffsets, bevel, -1)
1162 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, ThisBlockOffsets])
1163 continue
1165 # it's not one block, must be two or more
1166 # set up the offsets for the left
1167 BtmOff = LBtm - LNerEdge
1168 TopOff = LTop - LNerEdge
1169 leftOffsets = [[BtmOff, 0, 0]] * 2 + [[TopOff, 0, 0]] * 2 + [[0] * 3] * 4
1170 bevelL = leftOpening.edgeBev(rowTop)
1171 bevelBlockOffsets(leftOffsets, bevelL, 1)
1172 # and now for the right
1173 BtmOff = RBtm - RNerEdge
1174 TopOff = RTop - RNerEdge
1175 rightOffsets = [[0] * 3] * 4 + [[BtmOff, 0, 0]] * 2 + [[TopOff, 0, 0]] * 2
1176 bevelR = rightOpening.edgeBev(rowTop)
1177 bevelBlockOffsets(rightOffsets, bevelR, -1)
1178 # check to see if it is only two blocks
1179 if (InnerDiff < MaxWid * 2):
1180 # this row is just two blocks! Left block, then right block
1181 # div is the x position of the dividing point between the two bricks
1182 div = InnerMid + (rndd() * settings['wv']) / r1
1183 # set the grout distance, since we need grout separation between the blocks
1184 grt = (settings['g'] + rndc() * settings['gv']) / r1
1185 # set the x position and width for the left block
1186 x = (div + LNerEdge) / 2 - grt / 4
1187 w = (div - LNerEdge) - grt / 2
1188 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1189 # For reference: EdgeBlocks = [[x, z, w, h, d, [corner offset matrix]], [etc.]]
1190 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, leftOffsets])
1191 # Initialize for the block on the right side
1192 x = (div + RNerEdge) / 2 + grt / 4
1193 w = (RNerEdge - div) - grt / 2
1194 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1195 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, rightOffsets])
1196 continue
1198 # program should only get here if there are more than two blocks in the row, and no wedge blocks
1199 # make Left edge block
1200 # set the grout
1201 grt = (settings['g'] + rndc() * settings['gv']) / r1
1202 # set the x position and width for the left block
1203 widOptions = [settings['w'], bevelL + settings['wm'], leftOpening.ts]
1204 baseWid = max(widOptions)
1205 w = (rndd() * settings['wv'] + baseWid + row. EdgeOffset)
1206 widOptions[0] = settings['wm']
1207 widOptions[2] = w
1208 w = max(widOptions) / r1 - grt
1209 x = w / 2 + LNerEdge + grt / 2
1210 BlockRowL = x + w / 2
1211 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1212 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, leftOffsets])
1214 # make Right edge block
1215 # set the grout
1216 grt = (settings['g'] + rndc() * settings['gv']) / r1
1217 # set the x position and width for the left block
1218 widOptions = [settings['w'], bevelR + settings['wm'], rightOpening.ts]
1219 baseWid = max(widOptions)
1220 w = (rndd() * settings['wv'] + baseWid + row.EdgeOffset)
1221 widOptions[0] = settings['wm']
1222 widOptions[2] = w
1223 w = max(widOptions) / r1 - grt
1224 x = RNerEdge - w / 2 - grt / 2
1225 BlockRowR = x - w / 2
1226 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1227 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, rightOffsets])
1229 row.RowSegments.append([BlockRowL, BlockRowR])
1230 return None
1233 def plan(Thesketch, oldrows=0):
1234 __doc__ = """\
1235 The 'plan' function takes the data generated by the sketch function and the global settings
1236 and creates a list of blocks.
1237 It passes out a list of row heights, edge positions, edge blocks, and rows of blocks.
1239 # if we were passed a list of rows already, use those; else make a list.
1240 if oldrows:
1241 rows = oldrows
1242 else:
1243 # rows holds the important information common to all rows
1244 # rows = [list of row objects]
1245 rows = []
1247 # splits are places where we NEED a row division, to accomidate openings
1248 # add a split for the bottom row
1249 splits = [dims['b'] + settings['hb']]
1251 # add a split for each critical point on each opening
1252 for hole in Thesketch:
1253 splits += hole.crits()
1255 # and, a split for the top row
1256 splits.append(dims['t'] - settings['ht'])
1257 splits.sort()
1259 # divs are the normal old row divisions, add them between the top and bottom split
1260 divs = fill(splits[0], splits[-1], settings['h'], settings['hm'] + settings['g'], settings['hv'])[1: -1]
1262 # remove the divisions that are too close to the splits, so we don't get tiny thin rows
1263 for i in range(len(divs) - 1, -1, -1):
1264 for j in range(len(splits)):
1265 diff = abs(divs[i] - splits[j])
1266 if diff < (settings['h'] - settings['hv'] + settings['g']):
1267 del(divs[i])
1268 break
1270 # now merge the divs and splits lists
1271 divs += splits
1273 # add bottom and/or top points, if bottom and/or top row heights are more than zero
1274 if settings['hb'] > 0:
1275 divs.insert(0, dims['b'])
1276 if settings['ht'] > 0:
1277 divs.append(dims['t'])
1279 divs.sort()
1281 # trim the rows to the bottom and top of the wall
1282 if divs[0] < dims['b']:
1283 divs[:1] = []
1284 if divs[-1] > dims['t']:
1285 divs[-1:] = []
1287 # now, make the data for each row
1288 # rows = [[center height,row height,edge offset],[etc.]]
1290 divCount = len(divs) - 1 # number of divs to check
1291 divCheck = 0 # current div entry
1293 while divCheck < divCount:
1294 RowZ = (divs[divCheck] + divs[divCheck + 1]) / 2
1295 RowHeight = divs[divCheck + 1] - divs[divCheck] - settings['g'] + rndc() * \
1296 settings['rwhl'] * settings['gv']
1297 EdgeOffset = settings['eoff'] * (fmod(divCheck, 2) - 0.5) + settings['eoffv'] * rndd()
1299 # if row height is too shallow: delete next div entry, decrement total, and recheck current entry.
1300 if RowHeight < settings['hm']:
1301 del(divs[divCheck + 1])
1302 divCount -= 1 # Adjust count for removed div entry.
1303 continue
1305 rows.append(rowOb(RowZ, RowHeight, EdgeOffset))
1307 divCheck += 1 # on to next div entry
1309 # set up a special opening object to handle the edges of the wall
1310 x = (dims['s'] + dims['e']) / 2
1311 z = (dims['t'] + dims['b']) / 2
1312 w = (dims['e'] - dims['s'])
1313 h = (dims['t'] - dims['b'])
1314 WallBoundaries = openingInvert(x, z, w, h)
1316 # Go over each row in the list, set up edge blocks and block sections
1317 for rownum in range(len(rows)):
1318 rowProcessing(rows[rownum], Thesketch, WallBoundaries)
1320 # now return the things everyone needs
1321 # return [rows,edgeBlocks,blockRows,Asketch]
1322 return [rows, Thesketch]
1325 def archGeneration(hole, vlist, flist, sideSign):
1326 __doc__ = """\
1327 Makes arches for the top and bottom, depending on sideSign
1328 example, Lower arch:
1329 archGeneration(hole, vlist, flist, -1)
1330 example, Upper arch:
1331 archGeneration(hole, vlist, flist, 1)
1332 hole is the opening object that the arch is for
1333 add the vertices to vlist
1334 add the faces to flist
1335 sideSign is + or - 1, for the top or bottom arch. Other values may cause errors.
1338 # working arrays for vectors and faces
1339 avlist = []
1340 aflist = []
1342 # Top (1) or bottom (-1)
1343 if sideSign == -1:
1344 r = hole.rl # radius of the arch
1345 rt = hole.rtl # thickness of the arch (stone height)
1346 v = hole.vl # height of the arch
1347 c = hole.cl
1348 else:
1349 r = hole.r # radius of the arch
1350 rt = hole.rt # thickness of the arch (stone height)
1351 v = hole.v # height of the arch
1352 c = hole.c
1354 ra = r + rt / 2 # average radius of the arch
1355 x = hole.x
1356 w = hole.w
1357 h = hole.h
1358 z = hole.z
1359 bev = hole.b
1360 sideSignInv = -sideSign
1362 if v > w / 2: # two arcs, to make a pointed arch
1363 # positioning
1364 zpos = z + (h / 2) * sideSign
1365 xoffset = r - w / 2
1366 # left side top, right side bottom
1367 # angles reference straight up, and are in radians
1368 bevRad = r + bev
1369 bevHt = sqrt(bevRad ** 2 - (bevRad - (w / 2 + bev)) ** 2)
1370 midHalfAngle = atan(v / (r - w / 2))
1371 midHalfAngleBevel = atan(bevHt / (r - w / 2))
1372 bevelAngle = midHalfAngle - midHalfAngleBevel
1373 anglebeg = (PI / 2) * (sideSignInv)
1374 angleend = (PI / 2) * (sideSignInv) + midHalfAngle
1376 avlist, aflist = arch(ra, rt, (xoffset) * (sideSign), zpos, anglebeg, angleend, bev, bevelAngle, len(vlist))
1378 for i, vert in enumerate(avlist):
1379 avlist[i] = [vert[0] + hole.x, vert[1], vert[2]]
1380 vlist += avlist
1381 flist += aflist
1383 # right side top, left side bottom
1385 # angles reference straight up, and are in radians
1386 anglebeg = (PI / 2) * (sideSign) - midHalfAngle
1387 angleend = (PI / 2) * (sideSign)
1389 avlist, aflist = arch(ra, rt, (xoffset) * (sideSignInv), zpos, anglebeg, angleend, bev, bevelAngle, len(vlist))
1391 for i, vert in enumerate(avlist):
1392 avlist[i] = [vert[0] + hole.x, vert[1], vert[2]]
1394 vlist += avlist
1395 flist += aflist
1397 # keystone
1398 Dpth = settings['d'] + rndc() * settings['dv']
1399 Grout = settings['g'] + rndc() * settings['gv']
1400 angleBevel = (PI / 2) * (sideSign) - midHalfAngle
1401 Wdth = (rt - Grout - bev) * 2 * sin(angleBevel) * sideSign # note, sin may be negative
1402 MidZ = ((sideSign) * (bevHt + h / 2.0) + z) + (rt - Grout - bev) \
1403 * cos(angleBevel) # note, cos may come out negative
1404 nearCorner = sideSign * (MidZ - z) - v - h / 2
1406 if sideSign == 1:
1407 TopHt = hole.top() - MidZ - Grout
1408 BtmHt = nearCorner
1409 else:
1410 BtmHt = - (hole.btm() - MidZ) - Grout
1411 TopHt = nearCorner
1413 # set the amount to bevel the keystone
1414 keystoneBevel = (bevHt - v) * sideSign
1415 if Wdth >= settings['hm']:
1416 avlist, aflist = MakeAKeystone(x, Wdth, MidZ, TopHt, BtmHt, Dpth, keystoneBevel, len(vlist))
1418 if radialized:
1419 for i, vert in enumerate(avlist):
1420 if slope:
1421 r1 = dims['t'] * sin(vert[2] * PI / (dims['t'] * 2))
1422 else:
1423 r1 = vert[2]
1424 avlist[i] = [((vert[0] - hole.x) / r1) + hole.x, vert[1], vert[2]]
1426 vlist += avlist
1427 flist += aflist
1428 # remove "debug note" once bevel is finalized.
1429 else:
1430 debug_prints(func="archGeneration",
1431 text="Keystone was too narrow - " + str(Wdth))
1433 else: # only one arc - curve not peak.
1434 # bottom (sideSign -1) arch has poorly sized blocks...
1436 zpos = z + (sideSign * (h / 2 + v - r)) # single arc positioning
1438 # angles reference straight up, and are in radians
1439 if sideSign == -1:
1440 angleOffset = PI
1441 else:
1442 angleOffset = 0.0
1444 if v < w / 2:
1445 halfangle = atan(w / (2 * (r - v)))
1447 anglebeg = angleOffset - halfangle
1448 angleend = angleOffset + halfangle
1449 else:
1450 anglebeg = angleOffset - PI / 2
1451 angleend = angleOffset + PI / 2
1453 avlist, aflist = arch(ra, rt, 0, zpos, anglebeg, angleend, bev, 0.0, len(vlist))
1455 for i, vert in enumerate(avlist):
1456 avlist[i] = [vert[0] + x, vert[1], vert[2]]
1458 vlist += avlist
1459 flist += aflist
1461 # Make the Side Stones
1462 grt = (settings['g'] + rndc() * settings['gv'])
1463 width = sqrt(rt ** 2 - c ** 2) - grt
1465 if c > settings['hm'] + grt and c < width + grt:
1466 if radialized:
1467 subdivision = settings['sdv'] * (zpos + (h / 2) * sideSign)
1468 else:
1469 subdivision = settings['sdv']
1471 # set the height of the block, it should be as high as the max corner position, minus grout
1472 height = c - grt * (0.5 + c / (width + grt))
1474 # the vertical offset for the short side of the block
1475 voff = sideSign * (settings['hm'] - height)
1476 xstart = w / 2
1477 zstart = z + sideSign * (h / 2 + grt / 2)
1478 woffset = width * (settings['hm'] + grt / 2) / (c - grt / 2)
1479 depth = rndd() * settings['dv'] + settings['d']
1481 if sideSign == 1:
1482 offsets = [[0] * 3] * 6 + [[0] * 2 + [voff]] * 2
1483 topSide = zstart + height
1484 btmSide = zstart
1485 else:
1486 offsets = [[0] * 3] * 4 + [[0] * 2 + [voff]] * 2 + [[0] * 3] * 2
1487 topSide = zstart
1488 btmSide = zstart - height
1489 # Do some stuff to incorporate bev here
1490 bevelBlockOffsets(offsets, bev, -1)
1492 avlist, aflist = MakeABlock(
1493 [x - xstart - width, x - xstart - woffset, btmSide, topSide,
1494 -depth / 2, depth / 2], subdivision, len(vlist),
1495 Offsets=offsets, xBevScl=1
1498 # top didn't use radialized in prev version;
1499 # just noting for clarity - may need to revise for "sideSign == 1"
1500 if radialized:
1501 for i, vert in enumerate(avlist):
1502 avlist[i] = [((vert[0] - x) / vert[2]) + x, vert[1], vert[2]]
1504 vlist += avlist
1505 flist += aflist
1507 # keep sizing same - neat arches = master masons :)
1508 # grt = (settings['g'] + rndc()*settings['gv'])
1509 # height = c - grt*(0.5 + c/(width + grt))
1510 # if grout varies may as well change width too... width = sqrt(rt**2 - c**2) - grt
1511 # voff = sideSign * (settings['hm'] - height)
1512 # woffset = width*(settings['hm'] + grt/2)/(c - grt/2)
1514 if sideSign == 1:
1515 offsets = [[0] * 3] * 2 + [[0] * 2 + [voff]] * 2 + [[0] * 3] * 4
1516 topSide = zstart + height
1517 btmSide = zstart
1518 else:
1519 offsets = [[0] * 2 + [voff]] * 2 + [[0] * 3] * 6
1520 topSide = zstart
1521 btmSide = zstart - height
1522 # Do some stuff to incorporate bev here
1523 bevelBlockOffsets(offsets, bev, 1)
1525 avlist, aflist = MakeABlock(
1526 [x + xstart + woffset, x + xstart + width, btmSide, topSide,
1527 -depth / 2, depth / 2], subdivision, len(vlist),
1528 Offsets=offsets, xBevScl=1
1531 # top didn't use radialized in prev version;
1532 # just noting for clarity - may need to revise for "sideSign == 1"
1533 if radialized:
1534 for i, vert in enumerate(avlist):
1535 avlist[i] = [((vert[0] - x) / vert[2]) + x, vert[1], vert[2]]
1537 vlist += avlist
1538 flist += aflist
1539 return None
1542 def build(Aplan):
1543 __doc__ = """\
1544 Build creates the geometry for the wall, based on the
1545 "Aplan" object from the "plan" function. If physics is
1546 enabled, then it make a number of individual blocks with
1547 physics interaction enabled. Otherwise it creates
1548 geometry for the blocks, arches, etc. of the wall.
1550 vlist = []
1551 flist = []
1552 rows = Aplan[0]
1554 # all the edge blocks, redacted
1555 # AllBlocks = [[x, z, w, h, d, [corner offset matrix]], [etc.]]
1557 # loop through each row, adding the normal old blocks
1558 for rowidx in range(len(rows)):
1559 rows[rowidx].FillBlocks()
1561 AllBlocks = []
1563 # If the wall is set to merge blocks, check all the blocks to see if you can merge any
1564 # seems to only merge vertical, should do horizontal too
1565 if bigBlock:
1566 for rowidx in range(len(rows) - 1):
1567 if radialized:
1568 if slope:
1569 r1 = dims['t'] * sin(abs(rows[rowidx].z) * PI / (dims['t'] * 2))
1570 else:
1571 r1 = abs(rows[rowidx].z)
1572 else:
1573 r1 = 1
1575 Tolerance = settings['g'] / r1
1576 idxThis = len(rows[rowidx].BlocksNorm[:]) - 1
1577 idxThat = len(rows[rowidx + 1].BlocksNorm[:]) - 1
1579 while True:
1580 # end loop when either array idx wraps
1581 if idxThis < 0 or idxThat < 0:
1582 break
1584 blockThis = rows[rowidx].BlocksNorm[idxThis]
1585 blockThat = rows[rowidx + 1].BlocksNorm[idxThat]
1587 # seems to only merge vertical, should do horizontal too...
1588 cx, cz, cw, ch, cd = blockThis[:5]
1589 ox, oz, ow, oh, od = blockThat[:5]
1591 if (abs(cw - ow) < Tolerance) and (abs(cx - ox) < Tolerance):
1592 if cw > ow:
1593 BlockW = ow
1594 else:
1595 BlockW = cw
1597 AllBlocks.append([(cx + ox) / 2, (cz + oz + (oh - ch) / 2) / 2,
1598 BlockW, abs(cz - oz) + (ch + oh) / 2, (cd + od) / 2, None])
1600 rows[rowidx].BlocksNorm.pop(idxThis)
1601 rows[rowidx + 1].BlocksNorm.pop(idxThat)
1602 idxThis -= 1
1603 idxThat -= 1
1605 elif cx > ox:
1606 idxThis -= 1
1607 else:
1608 idxThat -= 1
1610 # Add blocks to create a "shelf/platform".
1611 # Does not account for openings (crosses gaps - which is a good thing)
1612 if shelfExt:
1613 SetGrtOff = settings['g'] / 2 # half grout for block size modifier
1615 # Use wall block settings for shelf
1616 SetBW = settings['w']
1617 SetBWVar = settings['wv']
1618 SetBWMin = settings['wm']
1619 SetBH = settings['h']
1621 # Shelf area settings
1622 ShelfLft = shelfSpecs['x']
1623 ShelfBtm = shelfSpecs['z']
1624 ShelfEnd = ShelfLft + shelfSpecs['w']
1625 ShelfTop = ShelfBtm + shelfSpecs['h']
1626 ShelfThk = shelfSpecs['d'] * 2 # use double-depth due to offsets to position at cursor.
1628 # Use "corners" to adjust position so not centered on depth.
1629 # Facing shelf, at cursor (middle of wall blocks)
1630 # - this way no gaps between platform and wall face due to wall block depth.
1631 wallDepth = settings['d'] / 2 # offset by wall depth so step depth matches UI setting :)
1632 if shelfBack: # place blocks on backside of wall
1633 ShelfOffsets = [
1634 [0, ShelfThk / 2, 0], [0, wallDepth, 0],
1635 [0, ShelfThk / 2, 0], [0, wallDepth, 0],
1636 [0, ShelfThk / 2, 0], [0, wallDepth, 0],
1637 [0, ShelfThk / 2, 0], [0, wallDepth, 0]
1639 else:
1640 ShelfOffsets = [
1641 [0, -wallDepth, 0], [0, -ShelfThk / 2, 0],
1642 [0, -wallDepth, 0], [0, -ShelfThk / 2, 0],
1643 [0, -wallDepth, 0], [0, -ShelfThk / 2, 0],
1644 [0, -wallDepth, 0], [0, -ShelfThk / 2, 0]
1647 # Add blocks for each "shelf row" in area
1648 while ShelfBtm < ShelfTop:
1650 # Make blocks for each row - based on rowOb::fillblocks
1651 # Does not vary grout.
1652 divs = fill(ShelfLft, ShelfEnd, SetBW, SetBWMin, SetBWVar)
1654 # loop through the row divisions, adding blocks for each one
1655 for i in range(len(divs) - 1):
1656 ThisBlockx = (divs[i] + divs[i + 1]) / 2
1657 ThisBlockw = divs[i + 1] - divs[i] - SetGrtOff
1659 AllBlocks.append([ThisBlockx, ShelfBtm, ThisBlockw, SetBH, ShelfThk, ShelfOffsets])
1661 ShelfBtm += SetBH + SetGrtOff # moving up to next row...
1663 # Add blocks to create "steps".
1664 # Does not account for openings (crosses gaps - which is a good thing)
1665 if stepMod:
1666 SetGrtOff = settings['g'] / 2 # half grout for block size modifier
1668 # Vary block width by wall block variations.
1669 SetWidVar = settings['wv']
1670 SetWidMin = settings['wm']
1672 StepXMod = stepSpecs['t'] # width of step/tread, also sets basic block size.
1673 StepZMod = stepSpecs['v']
1675 StepLft = stepSpecs['x']
1676 StepRt = stepSpecs['x'] + stepSpecs['w']
1677 StepBtm = stepSpecs['z'] + StepZMod / 2 # Start offset for centered blocks
1678 StepWide = stepSpecs['w']
1679 StepTop = StepBtm + stepSpecs['h']
1680 StepThk = stepSpecs['d'] * 2 # use double-depth due to offsets to position at cursor.
1682 # Use "corners" to adjust steps so not centered on depth.
1683 # Facing steps, at cursor (middle of wall blocks)
1684 # - this way no gaps between steps and wall face due to wall block depth.
1685 # Also, will work fine as stand-alone if not used with wall (try block depth 0 and see what happens).
1686 wallDepth = settings['d'] / 2
1687 if stepBack: # place blocks on backside of wall
1688 StepOffsets = [
1689 [0, StepThk / 2, 0], [0, wallDepth, 0],
1690 [0, StepThk / 2, 0], [0, wallDepth, 0],
1691 [0, StepThk / 2, 0], [0, wallDepth, 0],
1692 [0, StepThk / 2, 0], [0, wallDepth, 0]
1694 else:
1695 StepOffsets = [
1696 [0, -wallDepth, 0], [0, -StepThk / 2, 0],
1697 [0, -wallDepth, 0], [0, -StepThk / 2, 0],
1698 [0, -wallDepth, 0], [0, -StepThk / 2, 0],
1699 [0, -wallDepth, 0], [0, -StepThk / 2, 0]
1702 # Add steps for each "step row" in area (neg width is interesting but prevented)
1703 while StepBtm < StepTop and StepWide > 0:
1705 # Make blocks for each step row - based on rowOb::fillblocks
1706 # Does not vary grout.
1708 if stepOnly: # "cantilevered steps"
1709 if stepLeft:
1710 stepStart = StepRt - StepXMod
1711 else:
1712 stepStart = StepLft
1714 AllBlocks.append([stepStart, StepBtm, StepXMod, StepZMod, StepThk, StepOffsets])
1715 else:
1716 divs = fill(StepLft, StepRt, StepXMod, SetWidMin, SetWidVar)
1718 # loop through the row divisions, adding blocks for each one
1719 for i in range(len(divs) - 1):
1720 ThisBlockx = (divs[i] + divs[i + 1]) / 2
1721 ThisBlockw = divs[i + 1] - divs[i] - SetGrtOff
1723 AllBlocks.append([ThisBlockx, StepBtm, ThisBlockw, StepZMod, StepThk, StepOffsets])
1725 StepBtm += StepZMod + SetGrtOff # moving up to next row...
1726 StepWide -= StepXMod # reduce step width
1728 # adjust side limit depending on direction of steps
1729 if stepLeft:
1730 StepRt -= StepXMod # move in from right
1731 else:
1732 StepLft += StepXMod # move in from left
1734 # Copy all the blocks out of the rows
1735 for row in rows:
1736 AllBlocks += row.BlocksEdge
1737 AllBlocks += row.BlocksNorm
1739 # This loop makes individual blocks for each block specified in the plan
1740 for block in AllBlocks:
1741 x, z, w, h, d, corners = block
1742 if radialized:
1743 if slope:
1744 r1 = dims['t'] * sin(z * PI / (dims['t'] * 2))
1745 else:
1746 r1 = z
1747 else:
1748 r1 = 1
1750 geom = MakeABlock([x - w / 2, x + w / 2, z - h / 2, z + h / 2, -d / 2, d / 2],
1751 settings['sdv'], len(vlist),
1752 corners, None, settings['b'] + rndd() * settings['bv'], r1)
1753 vlist += geom[0]
1754 flist += geom[1]
1756 # This loop makes Arches for every opening specified in the plan.
1757 for hole in Aplan[1]:
1758 # lower arch stones
1759 if hole.vl > 0 and hole.rtl > (settings['g'] + settings['hm']): # make lower arch blocks
1760 archGeneration(hole, vlist, flist, -1)
1762 # top arch stones
1763 if hole.v > 0 and hole.rt > (settings['g'] + settings['hm']): # make upper arch blocks
1764 archGeneration(hole, vlist, flist, 1)
1766 # Warp all the points for domed stonework
1767 if slope:
1768 for i, vert in enumerate(vlist):
1769 vlist[i] = [vert[0], (dims['t'] + vert[1]) * cos(vert[2] * PI / (2 * dims['t'])),
1770 (dims['t'] + vert[1]) * sin(vert[2] * PI / (2 * dims['t']))]
1772 # Warp all the points for radial stonework
1773 if radialized:
1774 for i, vert in enumerate(vlist):
1775 vlist[i] = [vert[2] * cos(vert[0]), vert[2] * sin(vert[0]), vert[1]]
1777 return vlist, flist
1780 # The main function
1781 def createWall(radial, curve, openings, mergeBlox, shelf, shelfSide,
1782 steps, stepDir, stepBare, stepSide):
1783 __doc__ = """\
1784 Call all the functions you need to make a wall, return the verts and faces.
1786 global radialized
1787 global slope
1788 global openingSpecs
1789 global bigBlock
1790 global shelfExt
1791 global stepMod
1792 global stepLeft
1793 global shelfBack
1794 global stepOnly
1795 global stepBack
1797 # set all the working variables from passed parameters
1799 radialized = radial
1800 slope = curve
1801 openingSpecs = openings
1802 bigBlock = mergeBlox
1803 shelfExt = shelf
1804 stepMod = steps
1805 stepLeft = stepDir
1806 shelfBack = shelfSide
1807 stepOnly = stepBare
1808 stepBack = stepSide
1810 asketch = sketch()
1811 aplan = plan(asketch, 0)
1813 return build(aplan)