Cleanup: trailing space
[blender-addons.git] / add_mesh_extra_objects / Blocks.py
blob928ed1d6e589e6a17d19bd685756481490b98fba
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Authors: dudecon, jambay
5 # Module notes:
7 # Grout needs to be implemented.
8 # consider removing wedge crit for small "c" and "cl" values
9 # wrap around for openings on radial stonework?
10 # auto-clip wall edge to SMALL for radial and domes.
11 # unregister doesn't release all references.
12 # repeat for opening doesn't distribute evenly when radialized - see wrap around
13 # note above.
14 # if opening width == indent*2 the edge blocks fail (row of blocks cross opening).
15 # if openings overlap fills inverse with blocks - see h/v slots.
16 # Negative grout width creates a pair of phantom blocks, separated by grout
17 # width, inside the edges.
18 # if block width variance is 0, and edging is on, right edge blocks create a "vertical seam"
21 import bpy
22 from random import random
23 from math import (
24 fmod, sqrt,
25 sin, cos, atan,
26 pi as PI,
29 # Set to True to enable debug_prints
30 DEBUG = False
32 # A few constants
33 SMALL = 0.000000000001
34 # for values that must be != 0; see UI options/variables - sort of a bug to be fixed
35 NOTZERO = 0.01
37 # Global variables
39 # General masonry Settings
40 # ------------------------
41 settings = {
42 'w': 1.2, 'wv': 0.3, 'h': .6, 'hv': 0.3, 'd': 0.3, 'dv': 0.1,
43 'g': 0.1, 'gv': 0.07, 'gd': 0.01, 'gdv': 0.0, 'b': 0, 'bv': 0,
44 'f': 0.0, 'fv': 0.0, 't': 0.0, 'sdv': 0.1, 'hwt': 0.5, 'aln': 0,
45 'wm': 0.8, 'hm': 0.3, 'dm': 0.1,
46 'woff': 0.0, 'woffv': 0.0, 'eoff': 0.3, 'eoffv': 0.0, 'rwhl': 1,
47 'hb': 0, 'ht': 0, 'ge': 0, 'physics': 0
49 """
50 settings DOCUMENTATION:
51 'w':width 'wv':widthVariation
52 'h':height 'hv':heightVariation
53 'd':depth 'dv':depthVariation
54 'g':grout 'gv':groutVariation 'gd':groutDepth 'gdv':groutDepthVariation
55 'b':bevel 'bv':bevelVariation
56 'f':flawSize 'fv':flawSizeVariation 'ff':flawFraction
57 't':taper
58 'sdv':subdivision(distance or angle)
59 'hwt':row height effect on block widths in the row (0=no effect,
60 1=1:1 relationship, negative values allowed, 0.5 works well)
61 'aln':alignment(0=none, 1=rows w/features, 2=features w/rows)
62 (currently unused)
63 'wm':width minimum 'hm':height minimum 'dm':depth minimum
64 'woff':row start offset(fraction of width)
65 'woffv':width offset variation(fraction of width)
66 'eoff':edge offset 'eoffv':edge offset variation
67 'rwhl':row height lock(1 is all blocks in row have same height)
68 'hb':bottom row height 'ht': top row height 'ge': grout the edges
69 'physics': set up for physics
70 """
72 # dims = area of wall (face)
73 # ------------------------
74 dims = {
75 's': 0, 'e': PI * 3 / 2, 'b': 0.1, 't': 12.3
76 } # radial
77 """
78 dims DOCUMENTATION:
79 's':start x or theta 'e':end x or theta 'b':bottom z or r 't':top z or r
80 'w' = e-s and h = t-b; calculated to optimize for various operations/usages
81 dims = {'s':-12, 'e':15, 'w':27, 'b':-15., 't':15., 'h':30}
82 dims = {'s':-bayDim/2, 'e':bayDim/2, 'b':-5., 't':10.} # bay settings?
83 """
85 # ------------------------
86 radialized = 0 # Radiating from one point - round/disc; instead of square
87 slope = 0 # Warp/slope; curved over like a vaulted tunnel
89 # 'bigblock': merge adjacent blocks into single large blocks
90 bigBlock = 0 # Merge blocks
93 # Gaps in blocks for various apertures
94 # ------------------------
95 # openingSpecs = []
96 openingSpecs = [
97 {'w': 0.5, 'h': 0.5, 'x': 0.8, 'z': 2.7, 'rp': 1, 'b': 0.0,
98 'v': 0, 'vl': 0, 't': 0, 'tl': 0}
101 openingSpecs DOCUMENTATION:
102 'w': opening width, 'h': opening height,
103 'x': horizontal position, 'z': vertical position,
104 'rp': make multiple openings, with a spacing of x,
105 'b': bevel the opening, inside only, like an arrow slit.
106 'v': height of the top arch, 'vl':height of the bottom arch,
107 't': thickness of the top arch, 'tl': thickness of the bottom arch
110 # Add blocks to make platforms
111 # ------------------------
112 shelfExt = 0
114 shelfSpecs = {
115 'w': 0.5, 'h': 0.5, 'd': 0.3, 'x': 0.8, 'z': 2.7
118 shelfSpecs DOCUMENTATION:
119 'w': block width, 'h': block height, 'd': block depth (shelf size; offset from wall)
120 'x': horizontal start position, 'z': vertical start position
123 # Add blocks to make steps
124 # ------------------------
125 stepMod = 0
127 stepSpecs = {
128 'x': 0.0, 'z': -10, 'w': 10.0, 'h': 10.0,
129 'v': 0.7, 't': 1.0, 'd': 1.0
132 stepSpecs DOCUMENTATION:
133 'x': horizontal start position, 'z': vertical start position,
134 'w': step area width, 'h': step area height,
135 'v': riser height, 't': tread width, 'd': block depth (step size; offset from wall)
137 stepLeft = 0
138 shelfBack = 0
139 stepOnly = 0
140 stepBack = 0
143 # switchable prints
144 def debug_prints(func="", text="Message", var=None):
145 global DEBUG
146 if DEBUG:
147 print("\n[{}]\nmessage: {}".format(func, text))
148 if var:
149 print("Error: ", var)
152 # pass variables just like for the regular prints
153 def debug_print_vars(*args, **kwargs):
154 global DEBUG
155 if DEBUG:
156 print(*args, **kwargs)
159 # easier way to get to the random function
160 def rnd():
161 return random()
164 # random number from -0.5 to 0.5
165 def rndc():
166 return (random() - 0.5)
169 # random number from -1.0 to 1.0
170 def rndd():
171 return (random() - 0.5) * 2.0
174 # Opening Test suite
175 # opening test function
177 def test(TestN=13):
178 dims = {'s': -29., 'e': 29., 'b': -6., 't': TestN * 7.5}
179 openingSpecs = []
180 for i in range(TestN):
181 x = (random() - 0.5) * 6
182 z = i * 7.5
183 v = .2 + i * (3. / TestN)
184 vl = 3.2 - i * (3. / TestN)
185 t = 0.3 + random()
186 tl = 0.3 + random()
187 rn = random() * 2
188 openingSpecs += [{'w': 3.1 + rn, 'h': 0.3 + rn, 'x': float(x),
189 'z': float(z), 'rp': 0, 'b': 0.,
190 'v': float(v), 'vl': float(vl),
191 't': float(t), 'tl': float(tl)}]
192 return dims, openingSpecs
195 # dims, openingSpecs = test(15)
198 # For filling a linear space with divisions
199 def fill(left, right, avedst, mindst=0.0, dev=0.0, pad=(0.0, 0.0), num=0,
200 center=0):
201 __doc__ = """\
202 Fills a linear range with points and returns an ordered list of those points
203 including the end points.
205 left: the lower boundary
206 right: the upper boundary
207 avedst: the average distance between points
208 mindst: the minimum distance between points
209 dev: the maximum random deviation from avedst
210 pad: tends to move the points near the bounds right (positive) or
211 left (negative).
212 element 0 pads the lower bounds, element 1 pads the upper bounds
213 num: substitutes a numerical limit for the right limit. fill will then make
214 a num+1 element list
215 center: flag to center the elements in the range, 0 == disabled
218 poslist = [left]
219 curpos = left + pad[0]
221 # Set offset by average spacing, then add blocks (fall through);
222 # if not at right edge.
223 if center:
224 curpos += ((right - left - mindst * 2) % avedst) / 2 + mindst
225 if curpos - poslist[-1] < mindst:
226 curpos = poslist[-1] + mindst + rnd() * dev / 2
228 # clip to right edge.
229 if (right - curpos < mindst) or (right - curpos < mindst - pad[1]):
230 poslist.append(right)
231 return poslist
233 else:
234 poslist.append(curpos)
236 # unused... for now.
237 if num:
238 idx = len(poslist)
240 while idx < num + 1:
241 curpos += avedst + rndd() * dev
242 if curpos - poslist[-1] < mindst:
243 curpos = poslist[-1] + mindst + rnd() * dev / 2
244 poslist.append(curpos)
245 idx += 1
247 return poslist
249 # make block edges
250 else:
251 while True: # loop for blocks
252 curpos += avedst + rndd() * dev
253 if curpos - poslist[-1] < mindst:
254 curpos = poslist[-1] + mindst + rnd() * dev / 2
255 # close off edges at limit
256 if (right - curpos < mindst) or (right - curpos < mindst - pad[1]):
257 poslist.append(right)
258 return poslist
259 else:
260 poslist.append(curpos)
263 # For generating block geometry
264 def MakeABlock(bounds, segsize, vll=0, Offsets=None, FaceExclude=[],
265 bevel=0, xBevScl=1):
266 __doc__ = """\
267 MakeABlock returns lists of points and faces to be made into a square
268 cornered block, subdivided along the length, with optional bevels.
269 bounds: a list of boundary positions:
270 0:left, 1:right, 2:bottom, 3:top, 4:back, 5:front
271 segsize: the maximum size before lengthwise subdivision occurs
272 vll: the number of vertexes already in the mesh. len(mesh.verts) should
273 give this number.
274 Offsets: list of coordinate delta values.
275 Offsets are lists, [x,y,z] in
277 0:left_bottom_back,
278 1:left_bottom_front,
279 2:left_top_back,
280 3:left_top_front,
281 4:right_bottom_back,
282 5:right_bottom_front,
283 6:right_top_back,
284 7:right_top_front,
286 FaceExclude: list of faces to exclude from the faces list. see bounds above for indices
287 xBevScl: how much to divide the end (+- x axis) bevel dimensions. Set to current average
288 radius to compensate for angular distortion on curved blocks
291 slices = fill(bounds[0], bounds[1], segsize, segsize, center=1)
292 points = []
293 faces = []
295 if Offsets is None:
296 points.append([slices[0], bounds[4], bounds[2]])
297 points.append([slices[0], bounds[5], bounds[2]])
298 points.append([slices[0], bounds[5], bounds[3]])
299 points.append([slices[0], bounds[4], bounds[3]])
301 for x in slices[1:-1]:
302 points.append([x, bounds[4], bounds[2]])
303 points.append([x, bounds[5], bounds[2]])
304 points.append([x, bounds[5], bounds[3]])
305 points.append([x, bounds[4], bounds[3]])
307 points.append([slices[-1], bounds[4], bounds[2]])
308 points.append([slices[-1], bounds[5], bounds[2]])
309 points.append([slices[-1], bounds[5], bounds[3]])
310 points.append([slices[-1], bounds[4], bounds[3]])
312 else:
313 points.append([slices[0] + Offsets[0][0], bounds[4] + Offsets[0][1], bounds[2] + Offsets[0][2]])
314 points.append([slices[0] + Offsets[1][0], bounds[5] + Offsets[1][1], bounds[2] + Offsets[1][2]])
315 points.append([slices[0] + Offsets[3][0], bounds[5] + Offsets[3][1], bounds[3] + Offsets[3][2]])
316 points.append([slices[0] + Offsets[2][0], bounds[4] + Offsets[2][1], bounds[3] + Offsets[2][2]])
318 for x in slices[1: -1]:
319 xwt = (x - bounds[0]) / (bounds[1] - bounds[0])
320 points.append([x + Offsets[0][0] * (1 - xwt) + Offsets[4][0] * xwt,
321 bounds[4] + Offsets[0][1] * (1 - xwt) + Offsets[4][1] * xwt,
322 bounds[2] + Offsets[0][2] * (1 - xwt) + Offsets[4][2] * xwt])
323 points.append([x + Offsets[1][0] * (1 - xwt) + Offsets[5][0] * xwt,
324 bounds[5] + Offsets[1][1] * (1 - xwt) + Offsets[5][1] * xwt,
325 bounds[2] + Offsets[1][2] * (1 - xwt) + Offsets[5][2] * xwt])
326 points.append([x + Offsets[3][0] * (1 - xwt) + Offsets[7][0] * xwt,
327 bounds[5] + Offsets[3][1] * (1 - xwt) + Offsets[7][1] * xwt,
328 bounds[3] + Offsets[3][2] * (1 - xwt) + Offsets[7][2] * xwt])
329 points.append([x + Offsets[2][0] * (1 - xwt) + Offsets[6][0] * xwt,
330 bounds[4] + Offsets[2][1] * (1 - xwt) + Offsets[6][1] * xwt,
331 bounds[3] + Offsets[2][2] * (1 - xwt) + Offsets[6][2] * xwt])
333 points.append([slices[-1] + Offsets[4][0], bounds[4] + Offsets[4][1], bounds[2] + Offsets[4][2]])
334 points.append([slices[-1] + Offsets[5][0], bounds[5] + Offsets[5][1], bounds[2] + Offsets[5][2]])
335 points.append([slices[-1] + Offsets[7][0], bounds[5] + Offsets[7][1], bounds[3] + Offsets[7][2]])
336 points.append([slices[-1] + Offsets[6][0], bounds[4] + Offsets[6][1], bounds[3] + Offsets[6][2]])
338 faces.append([vll, vll + 3, vll + 2, vll + 1])
340 for x in range(len(slices) - 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 + 1, vll + 5, vll + 4])
346 vll += 1
347 faces.append([vll, vll - 3, vll + 1, vll + 4])
348 vll += 1
350 faces.append([vll, vll + 1, vll + 2, vll + 3])
352 return points, faces
355 # For generating Keystone Geometry
357 def MakeAKeystone(xpos, width, zpos, ztop, zbtm, thick, bevel, vll=0, FaceExclude=[], xBevScl=1):
358 __doc__ = """\
359 MakeAKeystone returns lists of points and faces to be made into a
360 square cornered keystone, with optional bevels.
361 xpos: x position of the centerline
362 width: x width of the keystone at the widest point (discounting bevels)
363 zpos: z position of the widest point
364 ztop: distance from zpos to the top
365 zbtm: distance from zpos to the bottom
366 thick: thickness
367 bevel: the amount to raise the back vertex to account for arch beveling
368 vll: the number of vertexes already in the mesh. len(mesh.verts) should give this number
369 faceExclude: list of faces to exclude from the faces list.
370 0:left, 1:right, 2:bottom, 3:top, 4:back, 5:front
371 xBevScl: how much to divide the end (+- x axis) bevel dimensions.
372 Set to current average radius to compensate for angular distortion on curved blocks
375 points = []
376 faces = []
377 faceinclude = [1 for x in range(6)]
378 for x in FaceExclude:
379 faceinclude[x] = 0
380 Top = zpos + ztop
381 Btm = zpos - zbtm
382 Wid = width / 2.0
383 Thk = thick / 2.0
385 # The front top point
386 points.append([xpos, Thk, Top])
387 # The front left point
388 points.append([xpos - Wid, Thk, zpos])
389 # The front bottom point
390 points.append([xpos, Thk, Btm])
391 # The front right point
392 points.append([xpos + Wid, Thk, zpos])
394 MirrorPoints = []
395 for i in points:
396 MirrorPoints.append([i[0], -i[1], i[2]])
397 points += MirrorPoints
398 points[6][2] += bevel
400 faces.append([3, 2, 1, 0])
401 faces.append([4, 5, 6, 7])
402 faces.append([4, 7, 3, 0])
403 faces.append([5, 4, 0, 1])
404 faces.append([6, 5, 1, 2])
405 faces.append([7, 6, 2, 3])
406 # Offset the vertex numbers by the number of vertices already in the list
407 for i in range(len(faces)):
408 for j in range(len(faces[i])):
409 faces[i][j] += vll
411 return points, faces
414 # for finding line/circle intercepts
416 def circ(offs=0., r=1.):
417 __doc__ = """\
418 offs is the distance perpendicular to the line to the center of the circle
419 r is the radius of the circle
420 circ returns the distance parallel to the line to the center of the circle at the intercept.
422 offs = abs(offs)
423 if offs > r:
424 return None
425 elif offs == r:
426 return 0.
427 else:
428 return sqrt(r ** 2 - offs ** 2)
431 # class openings in the wall
433 class opening:
434 __doc__ = """\
435 This is the class for holding the data for the openings in the wall.
436 It has methods for returning the edges of the opening for any given position value,
437 as well as bevel settings and top and bottom positions.
438 It stores the 'style' of the opening, and all other pertinent information.
440 # x = 0. # x position of the opening
441 # z = 0. # x position of the opening
442 # w = 0. # width of the opening
443 # h = 0. # height of the opening
444 r = 0 # top radius of the arch (derived from 'v')
445 rl = 0 # lower radius of the arch (derived from 'vl')
446 rt = 0 # top arch thickness
447 rtl = 0 # lower arch thickness
448 ts = 0 # Opening side thickness, if greater than average width, replaces it.
449 c = 0 # top arch corner position (for low arches), distance from the top of the straight sides
450 cl = 0 # lower arch corner position (for low arches), distance from the top of the straight sides
451 # form = 0 # arch type (unused for now)
452 # b = 0. # back face bevel distance, like an arrow slit
453 v = 0. # top arch height
454 vl = 0. # lower arch height
455 # variable "s" is used for "side" in the "edge" function.
456 # it is a signed int, multiplied by the width to get + or - of the center
458 def btm(self):
459 if self.vl <= self.w / 2:
460 return self.z - self.h / 2 - self.vl - self.rtl
461 else:
462 return self.z - sqrt((self.rl + self.rtl) ** 2 - (self.rl - self.w / 2) ** 2) - self.h / 2
464 def top(self):
465 if self.v <= self.w / 2:
466 return self.z + self.h / 2 + self.v + self.rt
467 else:
468 return sqrt((self.r + self.rt) ** 2 - (self.r - self.w / 2) ** 2) + self.z + self.h / 2
470 # crits returns the critical split points, or discontinuities, used for making rows
471 def crits(self):
472 critlist = []
473 if self.vl > 0: # for lower arch
474 # add the top point if it is pointed
475 # if self.vl >= self.w/2.: critlist.append(self.btm())
476 if self.vl < self.w / 2.: # else: for low arches, with wedge blocks under them
477 # critlist.append(self.btm())
478 critlist.append(self.z - self.h / 2 - self.cl)
480 if self.h > 0: # if it has a height, append points at the top and bottom of the main square section
481 critlist += [self.z - self.h / 2, self.z + self.h / 2]
482 else: # otherwise, append just one in the center
483 critlist.append(self.z)
485 if self.v > 0: # for the upper arch
486 if self.v < self.w / 2: # add the splits for the upper wedge blocks, if needed
487 critlist.append(self.z + self.h / 2 + self.c)
488 # critlist.append(self.top())
489 # otherwise just add the top point, if it is pointed
490 # else: critlist.append(self.top())
492 return critlist
494 # get the side position of the opening.
495 # ht is the z position; s is the side: 1 for right, -1 for left
496 # if the height passed is above or below the opening, return None
497 def edgeS(self, ht, s):
499 # set the row radius: 1 for standard wall (flat)
500 if radialized:
501 if slope:
502 r1 = abs(dims['t'] * sin(ht * PI / (dims['t'] * 2)))
503 else:
504 r1 = abs(ht)
505 else:
506 r1 = 1
508 # Go through all the options, and return the correct value
509 if ht < self.btm(): # too low
510 return None
511 elif ht > self.top(): # too high
512 return None
514 # Check for circ returning None - prevent TypeError (script failure) with float.
515 # in this range, pass the lower arch info
516 elif ht <= self.z - self.h / 2 - self.cl:
517 if self.vl > self.w / 2:
518 circVal = circ(ht - self.z + self.h / 2, self.rl + self.rtl)
519 if circVal is None:
520 return None
521 else:
522 return self.x + s * (self.w / 2. - self.rl + circVal) / r1
523 else:
524 circVal = circ(ht - self.z + self.h / 2 + self.vl - self.rl, self.rl + self.rtl)
525 if circVal is None:
526 return None
527 else:
528 return self.x + s * circVal / r1
530 # in this range, pass the top arch info
531 elif ht >= self.z + self.h / 2 + self.c:
532 if self.v > self.w / 2:
533 circVal = circ(ht - self.z - self.h / 2, self.r + self.rt)
534 if circVal is None:
535 return None
536 else:
537 return self.x + s * (self.w / 2. - self.r + circVal) / r1
538 else:
539 circVal = circ(ht - (self.z + self.h / 2 + self.v - self.r), self.r + self.rt)
540 if circVal is None:
541 return None
542 else:
543 return self.x + s * circVal / r1
545 # in this range pass the lower corner edge info
546 elif ht <= self.z - self.h / 2:
547 d = sqrt(self.rtl ** 2 - self.cl ** 2)
548 if self.cl > self.rtl / sqrt(2.):
549 return self.x + s * (self.w / 2 + (self.z - self.h / 2 - ht) * d / self.cl) / r1
550 else:
551 return self.x + s * (self.w / 2 + d) / r1
553 # in this range pass the upper corner edge info
554 elif ht >= self.z + self.h / 2:
555 d = sqrt(self.rt ** 2 - self.c ** 2)
556 if self.c > self.rt / sqrt(2.):
557 return self.x + s * (self.w / 2 + (ht - self.z - self.h / 2) * d / self.c) / r1
558 else:
559 return self.x + s * (self.w / 2 + d) / r1
561 # in this range, pass the middle info (straight sides)
562 else:
563 return self.x + s * self.w / 2 / r1
565 # get the top or bottom of the opening
566 # ht is the x position; s is the side: 1 for top, -1 for bottom
567 def edgeV(self, ht, s):
569 dist = abs(self.x - ht)
571 def radialAdjust(dist, sideVal):
572 # take the distance and adjust for radial geometry, return dist
573 if radialized:
574 if slope:
575 dist = dist * abs(dims['t'] * sin(sideVal * PI / (dims['t'] * 2)))
576 else:
577 dist = dist * sideVal
578 return dist
580 if s > 0: # and (dist <= self.edgeS(self.z + self.h / 2 + self.c, 1) - self.x): # check top down
581 # hack for radialized masonry, import approx Z instead of self.top()
582 dist = radialAdjust(dist, self.top())
584 # no arch on top, flat
585 if not self.r:
586 return self.z + self.h / 2
588 # pointed arch on top
589 elif self.v > self.w / 2:
590 circVal = circ(dist - self.w / 2 + self.r, self.r + self.rt)
591 if circVal is None:
592 return None
593 else:
594 return self.z + self.h / 2 + circVal
596 # domed arch on top
597 else:
598 circVal = circ(dist, self.r + self.rt)
599 if circVal is None:
600 return None
601 else:
602 return self.z + self.h / 2 + self.v - self.r + circVal
604 else: # and (dist <= self.edgeS(self.z - self.h / 2 - self.cl, 1) - self.x): # check bottom up
605 # hack for radialized masonry, import approx Z instead of self.top()
606 dist = radialAdjust(dist, self.btm())
608 # no arch on bottom
609 if not self.rl:
610 return self.z - self.h / 2
612 # pointed arch on bottom
613 elif self.vl > self.w / 2:
614 circVal = circ(dist - self.w / 2 + self.rl, self.rl + self.rtl)
615 if circVal is None:
616 return None
617 else:
618 return self.z - self.h / 2 - circVal
620 # old conditional? if (dist-self.w / 2 + self.rl) <= (self.rl + self.rtl):
621 # domed arch on bottom
622 else:
623 circVal = circ(dist, self.rl + self.rtl) # dist-self.w / 2 + self.rl
624 if circVal is None:
625 return None
626 else:
627 return self.z - self.h / 2 - self.vl + self.rl - circVal
629 # and this never happens - but, leave it as failsafe :)
630 debug_prints(func="opening.EdgeV",
631 text="Got all the way out of the edgeV! Not good!")
632 debug_print_vars("opening x = ", self.x, ", opening z = ", self.z)
634 return 0.0
636 def edgeBev(self, ht):
637 if ht > (self.z + self.h / 2):
638 return 0.0
639 if ht < (self.z - self.h / 2):
640 return 0.0
641 if radialized:
642 if slope:
643 r1 = abs(dims['t'] * sin(ht * PI / (dims['t'] * 2)))
644 else:
645 r1 = abs(ht)
646 else:
647 r1 = 1
648 bevel = self.b / r1
649 return bevel
651 def __init__(self, xpos, zpos, width, height, archHeight=0, archThk=0,
652 archHeightLower=0, archThkLower=0, bevel=0, edgeThk=0):
653 self.x = float(xpos)
654 self.z = float(zpos)
655 self.w = float(width)
656 self.h = float(height)
657 self.rt = archThk
658 self.rtl = archThkLower
659 self.v = archHeight
660 self.vl = archHeightLower
661 if self.w <= 0:
662 self.w = SMALL
664 # find the upper arch radius
665 if archHeight >= width / 2:
666 # just one arch, low long
667 self.r = (self.v ** 2) / self.w + self.w / 4
668 elif archHeight <= 0:
669 # No arches
670 self.r = 0
671 self.v = 0
672 else:
673 # Two arches
674 self.r = (self.w ** 2) / (8 * self.v) + self.v / 2.
675 self.c = self.rt * cos(atan(self.w / (2 * (self.r - self.v))))
677 # find the lower arch radius
678 if archHeightLower >= width / 2:
679 self.rl = (self.vl ** 2) / self.w + self.w / 4
680 elif archHeightLower <= 0:
681 self.rl = 0
682 self.vl = 0
683 else:
684 self.rl = (self.w ** 2) / (8 * self.vl) + self.vl / 2.
685 self.cl = self.rtl * cos(atan(self.w / (2 * (self.rl - self.vl))))
687 # self.form = something?
688 self.b = float(bevel)
689 self.ts = edgeThk
692 # class for the whole wall boundaries; a sub-class of "opening"
693 class openingInvert(opening):
694 # this is supposed to switch the sides of the opening
695 # so the wall will properly enclose the whole wall.
697 def edgeS(self, ht, s):
698 return opening.edgeS(self, ht, -s)
700 def edgeV(self, ht, s):
701 return opening.edgeV(self, ht, -s)
704 # class rows in the wall
706 class rowOb:
707 __doc__ = """\
708 This is the class for holding the data for individual rows of blocks.
709 each row is required to have some edge blocks, and can also have
710 intermediate sections of "normal" blocks.
712 radius = 1
713 EdgeOffset = 0.
715 def FillBlocks(self):
716 # Set the radius variable, in the case of radial geometry
717 if radialized:
718 if slope:
719 self.radius = dims['t'] * (sin(self.z * PI / (dims['t'] * 2)))
720 else:
721 self.radius = self.z
723 # initialize internal variables from global settings
725 SetH = settings['h']
726 SetHwt = settings['hwt']
727 SetWid = settings['w']
728 SetWidMin = settings['wm']
729 SetWidVar = settings['wv']
730 SetGrt = settings['g']
731 SetGrtVar = settings['gv']
732 SetRowHeightLink = settings['rwhl']
733 SetDepth = settings['d']
734 SetDepthVar = settings['dv']
736 # height weight, used for making shorter rows have narrower blocks, and vice-versa
737 hwt = ((self.h / SetH - 1) * SetHwt + 1)
739 # set variables for persistent values: loop optimization, readability, single ref for changes.
741 avgDist = hwt * SetWid / self.radius
742 minDist = SetWidMin / self.radius
743 deviation = hwt * SetWidVar / self.radius
744 grtOffset = SetGrt / (2 * self.radius)
746 # init loop variables that may change...
748 grt = (SetGrt + rndc() * SetGrtVar) / (self.radius)
749 ThisBlockHeight = self.h + rndc() * (1 - SetRowHeightLink) * SetGrtVar
750 ThisBlockDepth = rndd() * SetDepthVar + SetDepth
752 for segment in self.RowSegments:
753 divs = fill(segment[0] + grtOffset, segment[1] - grtOffset, avgDist, minDist, deviation)
755 # loop through the divisions, adding blocks for each one
756 for i in range(len(divs) - 1):
757 ThisBlockx = (divs[i] + divs[i + 1]) / 2
758 ThisBlockw = divs[i + 1] - divs[i] - grt
760 self.BlocksNorm.append([ThisBlockx, self.z, ThisBlockw, ThisBlockHeight, ThisBlockDepth, None])
762 if SetDepthVar: # vary depth
763 ThisBlockDepth = rndd() * SetDepthVar + SetDepth
765 if SetGrtVar: # vary grout
766 grt = (SetGrt + rndc() * SetGrtVar) / (self.radius)
767 ThisBlockHeight = self.h + rndc() * (1 - SetRowHeightLink) * SetGrtVar
769 def __init__(self, centerheight, rowheight, edgeoffset=0.):
770 self.z = float(centerheight)
771 self.h = float(rowheight)
772 self.EdgeOffset = float(edgeoffset)
774 # THIS INITIALIZATION IS IMPORTANT! OTHERWISE ALL OBJECTS WILL HAVE THE SAME LISTS!
775 self.BlocksEdge = []
776 self.RowSegments = []
777 self.BlocksNorm = []
780 def arch(ra, rt, x, z, archStart, archEnd, bevel, bevAngle, vll):
781 __doc__ = """\
782 Makes a list of faces and vertexes for arches.
783 ra: the radius of the arch, to the center of the bricks
784 rt: the thickness of the arch
785 x: x center location of the circular arc, as if the arch opening were centered on x = 0
786 z: z center location of the arch
787 anglebeg: start angle of the arch, in radians, from vertical?
788 angleend: end angle of the arch, in radians, from vertical?
789 bevel: how much to bevel the inside of the arch.
790 vll: how long is the vertex list already?
792 avlist = []
793 aflist = []
795 # initialize internal variables for global settings
796 SetGrt = settings['g']
797 SetGrtVar = settings['gv']
798 SetDepth = settings['d']
799 SetDepthVar = settings['dv']
801 # Init loop variables
803 def bevelEdgeOffset(offsets, bevel, side):
805 Take the block offsets and modify it for the correct bevel.
807 offsets = the offset list. See MakeABlock
808 bevel = how much to offset the edge
809 side = -1 for left (right side), 1 for right (left side)
811 left = (0, 2, 3)
812 right = (4, 6, 7)
813 if side == 1:
814 pointsToAffect = right
815 else:
816 pointsToAffect = left
817 for num in pointsToAffect:
818 offsets[num] = offsets[num][:]
819 offsets[num][0] += -bevel * side
821 ArchInner = ra - rt / 2
822 ArchOuter = ra + rt / 2 - SetGrt + rndc() * SetGrtVar
824 DepthBack = - SetDepth / 2 - rndc() * SetDepthVar
825 DepthFront = SetDepth / 2 + rndc() * SetDepthVar
827 if radialized:
828 subdivision = settings['sdv']
829 else:
830 subdivision = 0.12
832 grt = (SetGrt + rndc() * SetGrtVar) / (2 * ra) # init grout offset for loop
833 # set up the offsets, it will be the same for every block
834 offsets = ([[0] * 2 + [bevel]] + [[0] * 3] * 3) * 2
836 # make the divisions in the "length" of the arch
837 divs = fill(archStart, archEnd, settings['w'] / ra, settings['wm'] / ra, settings['wv'] / ra)
839 for i in range(len(divs) - 1):
840 if i == 0:
841 ThisOffset = offsets[:]
842 bevelEdgeOffset(ThisOffset, bevAngle, - 1)
843 elif i == len(divs) - 2:
844 ThisOffset = offsets[:]
845 bevelEdgeOffset(ThisOffset, bevAngle, 1)
846 else:
847 ThisOffset = offsets
849 geom = MakeABlock(
850 [divs[i] + grt, divs[i + 1] - grt, ArchInner, ArchOuter, DepthBack, DepthFront],
851 subdivision, len(avlist) + vll, ThisOffset, [], None, ra
854 avlist += geom[0]
855 aflist += geom[1]
857 if SetDepthVar: # vary depth
858 DepthBack = -SetDepth / 2 - rndc() * SetDepthVar
859 DepthFront = SetDepth / 2 + rndc() * SetDepthVar
861 if SetGrtVar: # vary grout
862 grt = (settings['g'] + rndc() * SetGrtVar) / (2 * ra)
863 ArchOuter = ra + rt / 2 - SetGrt + rndc() * SetGrtVar
865 for i, vert in enumerate(avlist):
866 v0 = vert[2] * sin(vert[0]) + x
867 v1 = vert[1]
868 v2 = vert[2] * cos(vert[0]) + z
870 if radialized == 1:
871 if slope == 1:
872 r1 = dims['t'] * (sin(v2 * PI / (dims['t'] * 2)))
873 else:
874 r1 = v2
875 v0 = v0 / r1
877 avlist[i] = [v0, v1, v2]
879 return (avlist, aflist)
882 def sketch():
883 __doc__ = """ \
884 The 'sketch' function creates a list of openings from the general specifications passed to it.
885 It takes curved and domed walls into account, placing the openings at the appropriate angular locations
887 boundlist = []
888 for x in openingSpecs:
889 if x['rp']:
890 if radialized:
891 r1 = x['z']
892 else:
893 r1 = 1
895 if x['x'] > (x['w'] + settings['wm']):
896 spacing = x['x'] / r1
897 else:
898 spacing = (x['w'] + settings['wm']) / r1
900 minspacing = (x['w'] + settings['wm']) / r1
902 divs = fill(dims['s'], dims['e'], spacing, minspacing, center=1)
904 for posidx in range(len(divs) - 2):
905 boundlist.append(opening(divs[posidx + 1], x['z'], x['w'], x['h'],
906 x['v'], x['t'], x['vl'], x['tl'], x['b']))
907 else:
908 boundlist.append(opening(x['x'], x['z'], x['w'], x['h'], x['v'], x['t'], x['vl'], x['tl'], x['b']))
909 # check for overlapping edges?
911 return boundlist
914 def wedgeBlocks(row, opening, leftPos, rightPos, edgeBinary, r1):
915 __doc__ = """\
916 Makes wedge blocks for the left and right sides, depending
917 example:
918 wedgeBlocks(row, LeftWedgeEdge, LNerEdge, LEB, r1)
919 wedgeBlocks(row, RNerEdge, RightWedgeEdge, REB, r1)
921 wedgeEdges = fill(leftPos, rightPos, settings['w'] / r1, settings['wm'] / r1,
922 settings['wv'] / r1)
924 for i in range(len(wedgeEdges) - 1):
925 x = (wedgeEdges[i + 1] + wedgeEdges[i]) / 2
926 grt = (settings['g'] + rndd() * settings['gv']) / r1
927 w = wedgeEdges[i + 1] - wedgeEdges[i] - grt
929 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
931 # edgeV may return "None" - causing TypeError for math op.
932 # use 0 until wedgeBlocks operation worked out
933 edgeVal = opening.edgeV(x - w / 2, edgeBinary)
934 if edgeVal is None:
935 edgeVal = 0.0
937 LeftVertOffset = -(row.z - (row.h / 2) * edgeBinary - edgeVal)
939 # edgeV may return "None" - causing TypeError for math op.
940 # use 0 until wedgeBlocks operation worked out
941 edgeVal = opening.edgeV(x + w / 2, edgeBinary)
942 if edgeVal is None:
943 edgeVal = 0.0
945 RightVertOffset = -(row.z - (row.h / 2) * edgeBinary - edgeVal)
947 # Wedges are on top = off, blank, off, blank
948 # Wedges are on btm = blank, off, blank, off
949 ThisBlockOffsets = [[0, 0, LeftVertOffset]] * 2 + [[0] * 3] * 2 + [[0, 0, RightVertOffset]] * 2
951 # Insert or append "blank" for top or bottom wedges.
952 if edgeBinary == 1:
953 ThisBlockOffsets = ThisBlockOffsets + [[0] * 3] * 2
954 else:
955 ThisBlockOffsets = [[0] * 3] * 2 + ThisBlockOffsets
957 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, ThisBlockOffsets])
959 return None
962 def bevelBlockOffsets(offsets, bevel, side):
964 Take the block offsets and modify it for the correct bevel.
966 offsets = the offset list. See MakeABlock
967 bevel = how much to offset the edge
968 side = -1 for left (right side), 1 for right (left side)
970 if side == 1:
971 pointsToAffect = (0, 2) # right
972 else:
973 pointsToAffect = (4, 6) # left
974 for num in pointsToAffect:
975 offsets[num] = offsets[num][:]
976 offsets[num][0] += bevel * side
979 def rowProcessing(row, Thesketch, WallBoundaries):
980 __doc__ = """\
981 Take row and opening data and process a single row, adding edge and fill blocks to the row data.
983 # set end blocks
984 # check for openings, record top and bottom of row for right and left of each
985 # if both top and bottom intersect create blocks on each edge, appropriate to the size of the overlap
986 # if only one side intersects, run fill to get edge positions, but this should never happen
988 if radialized: # this checks for radial stonework, and sets the row radius if required
989 if slope:
990 r1 = abs(dims['t'] * sin(row.z * PI / (dims['t'] * 2)))
991 else:
992 r1 = abs(row.z)
993 else:
994 r1 = 1
996 # set the edge grout thickness, especially with radial stonework in mind
997 edgrt = settings['ge'] * (settings['g'] / 2 + rndc() * settings['gv']) / (2 * r1)
999 # Sets up a list of intersections of top of row with openings,
1000 # from left to right [left edge of opening, right edge of opening, etc...]
1001 # initially just the left and right edge of the wall
1002 edgetop = [[dims['s'] + row.EdgeOffset / r1 + edgrt, WallBoundaries],
1003 [dims['e'] + row.EdgeOffset / r1 - edgrt, WallBoundaries]]
1005 # Same as edgetop, but for the bottms of the rows
1006 edgebtm = [[dims['s'] + row.EdgeOffset / r1 + edgrt, WallBoundaries],
1007 [dims['e'] + row.EdgeOffset / r1 - edgrt, WallBoundaries]]
1009 # set up some useful values for the top and bottom of the rows.
1010 rowTop = row.z + row.h / 2
1011 rowBtm = row.z - row.h / 2
1013 for hole in Thesketch:
1014 # check the top and bottom of the row, looking at the opening from the right
1015 e = [hole.edgeS(rowTop, -1), hole.edgeS(rowBtm, -1)]
1017 # If either one hit the opening, make split points for the left side of the opening.
1018 if e[0] or e[1]:
1019 e += [hole.edgeS(rowTop, 1), hole.edgeS(rowBtm, 1)]
1021 # If one of them missed for some reason, set that value to
1022 # the middle of the opening.
1023 for i, pos in enumerate(e):
1024 if pos is None:
1025 e[i] = hole.x
1027 # add the intersects to the list of edge points
1028 edgetop.append([e[0], hole])
1029 edgetop.append([e[2], hole])
1030 edgebtm.append([e[1], hole])
1031 edgebtm.append([e[3], hole])
1033 # We want to make the walls in order, so sort the intersects.
1034 # This is where you would want to remove edge points that are out of order
1035 # so that you don't get the "oddity where overlapping openings
1036 # create blocks inversely" problem
1038 # Note: sort ended up comparing function pointers
1039 # if both Openings and Slots were enabled with Repeats in one of them
1040 try:
1041 edgetop.sort(key=lambda x: x[0])
1042 edgebtm.sort(key=lambda x: x[0])
1043 except Exception as ex:
1044 debug_prints(func="rowProcessing",
1045 text="Sorting has failed", var=ex)
1047 # these two loops trim the edges to the limits of the wall.
1048 # This way openings extending outside the wall don't enlarge the wall.
1049 while True:
1050 try:
1051 if ((edgetop[-1][0] > dims['e'] + row.EdgeOffset / r1) or
1052 (edgebtm[-1][0] > dims['e'] + row.EdgeOffset / r1)):
1053 edgetop[-2:] = []
1054 edgebtm[-2:] = []
1055 else:
1056 break
1057 except IndexError:
1058 break
1059 # still trimming the edges...
1060 while True:
1061 try:
1062 if ((edgetop[0][0] < dims['s'] + row.EdgeOffset / r1) or
1063 (edgebtm[0][0] < dims['s'] + row.EdgeOffset / r1)):
1064 edgetop[:2] = []
1065 edgebtm[:2] = []
1066 else:
1067 break
1068 except IndexError:
1069 break
1071 # make those edge blocks and rows! Wooo!
1072 # This loop goes through each section, (a pair of points in edgetop)
1073 # and places the edge blocks and inbetween normal block zones into the row object
1074 for OpnSplitNo in range(int(len(edgetop) / 2)):
1075 # left edge is edge<x>[2*OpnSplitNo], right edge edgex[2*OpnSplitNo+1]
1076 leftEdgeIndex = 2 * OpnSplitNo
1077 rightEdgeIndex = 2 * OpnSplitNo + 1
1079 # get the openings, to save time and confusion
1080 leftOpening = edgetop[leftEdgeIndex][1]
1081 rightOpening = edgetop[rightEdgeIndex][1]
1083 # find the difference between the edge top and bottom on both sides
1084 LTop = edgetop[leftEdgeIndex][0]
1085 LBtm = edgebtm[leftEdgeIndex][0]
1086 RTop = edgetop[rightEdgeIndex][0]
1087 RBtm = edgebtm[rightEdgeIndex][0]
1088 LDiff = LBtm - LTop
1089 RDiff = RTop - RBtm
1091 # which is further out on each side, top or bottom?
1092 if LDiff > 0:
1093 LNerEdge = LBtm # the nearer edge left
1094 LEB = 1 # Left Edge Boolean, set to 1 if furthest edge is top, -1 if it is bottom
1095 else:
1096 LNerEdge = LTop
1097 LEB = -1
1099 if RDiff > 0:
1100 RNerEdge = RBtm # the nearer edge right
1101 REB = 1 # Right Edge Boolean, set to 1 if furthest edge is top, -1 if it is bottom
1103 else:
1104 RNerEdge = RTop
1105 REB = -1 # Right Edge Boolean, set to 1 if furthest edge is top, -1 if it is bottom
1107 # The space between the closest edges of the openings in this section of the row
1108 InnerDiff = RNerEdge - LNerEdge
1109 # The mid point between the nearest edges
1110 InnerMid = (RNerEdge + LNerEdge) / 2
1112 # maximum distance to span with one block
1113 MaxWid = (settings['w'] + settings['wv']) / r1
1114 AveWid = settings['w']
1116 # check the left and right sides for wedge blocks
1117 # Check and run the left edge first
1118 # find the edge of the correct side, offset for minimum block height. The LEB decides top or bottom
1119 ZPositionCheck = row.z + (row.h / 2 - settings['hm']) * LEB
1121 # edgeS may return "None"
1122 LeftWedgeEdge = leftOpening.edgeS(ZPositionCheck, 1)
1124 if (abs(LDiff) > AveWid) or (not LeftWedgeEdge):
1125 # make wedge blocks
1126 if not LeftWedgeEdge:
1127 LeftWedgeEdge = leftOpening.x
1128 wedgeBlocks(row, leftOpening, LeftWedgeEdge, LNerEdge, LEB, r1)
1129 # set the near and far edge settings to vertical, so the other edge blocks don't interfere
1130 LTop, LBtm = LNerEdge, LNerEdge
1131 LDiff = 0
1133 # Now do the wedge blocks for the right, same drill... repeated code?
1134 # find the edge of the correct side, offset for minimum block height. The REB decides top or bottom
1135 ZPositionCheck = row.z + (row.h / 2 - settings['hm']) * REB
1137 # edgeS may return "None"
1138 RightWedgeEdge = rightOpening.edgeS(ZPositionCheck, -1)
1139 if (abs(RDiff) > AveWid) or (not RightWedgeEdge):
1140 # make wedge blocks
1141 if not RightWedgeEdge:
1142 RightWedgeEdge = rightOpening.x
1143 wedgeBlocks(row, rightOpening, RNerEdge, RightWedgeEdge, REB, r1)
1144 # set the near and far edge settings to vertical, so the other edge blocks don't interfere
1145 RTop, RBtm = RNerEdge, RNerEdge
1146 RDiff = 0
1148 # Check to see if the edges are close enough toegther to warrant a single block filling it
1149 if (InnerDiff < MaxWid):
1150 # if this is true, then this row is just one block!
1151 x = (LNerEdge + RNerEdge) / 2.
1152 w = InnerDiff
1153 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1154 BtmOff = LBtm - LNerEdge
1155 TopOff = LTop - LNerEdge
1156 ThisBlockOffsets = [[BtmOff, 0, 0]] * 2 + [[TopOff, 0, 0]] * 2
1157 BtmOff = RBtm - RNerEdge
1158 TopOff = RTop - RNerEdge
1159 ThisBlockOffsets += [[BtmOff, 0, 0]] * 2 + [[TopOff, 0, 0]] * 2
1160 bevel = leftOpening.edgeBev(rowTop)
1161 bevelBlockOffsets(ThisBlockOffsets, bevel, 1)
1162 bevel = rightOpening.edgeBev(rowTop)
1163 bevelBlockOffsets(ThisBlockOffsets, bevel, -1)
1164 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, ThisBlockOffsets])
1165 continue
1167 # it's not one block, must be two or more
1168 # set up the offsets for the left
1169 BtmOff = LBtm - LNerEdge
1170 TopOff = LTop - LNerEdge
1171 leftOffsets = [[BtmOff, 0, 0]] * 2 + [[TopOff, 0, 0]] * 2 + [[0] * 3] * 4
1172 bevelL = leftOpening.edgeBev(rowTop)
1173 bevelBlockOffsets(leftOffsets, bevelL, 1)
1174 # and now for the right
1175 BtmOff = RBtm - RNerEdge
1176 TopOff = RTop - RNerEdge
1177 rightOffsets = [[0] * 3] * 4 + [[BtmOff, 0, 0]] * 2 + [[TopOff, 0, 0]] * 2
1178 bevelR = rightOpening.edgeBev(rowTop)
1179 bevelBlockOffsets(rightOffsets, bevelR, -1)
1180 # check to see if it is only two blocks
1181 if (InnerDiff < MaxWid * 2):
1182 # this row is just two blocks! Left block, then right block
1183 # div is the x position of the dividing point between the two bricks
1184 div = InnerMid + (rndd() * settings['wv']) / r1
1185 # set the grout distance, since we need grout separation between the blocks
1186 grt = (settings['g'] + rndc() * settings['gv']) / r1
1187 # set the x position and width for the left block
1188 x = (div + LNerEdge) / 2 - grt / 4
1189 w = (div - LNerEdge) - grt / 2
1190 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1191 # For reference: EdgeBlocks = [[x, z, w, h, d, [corner offset matrix]], [etc.]]
1192 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, leftOffsets])
1193 # Initialize for the block on the right side
1194 x = (div + RNerEdge) / 2 + grt / 4
1195 w = (RNerEdge - div) - grt / 2
1196 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1197 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, rightOffsets])
1198 continue
1200 # program should only get here if there are more than two blocks in the row, and no wedge blocks
1201 # make Left edge block
1202 # set the grout
1203 grt = (settings['g'] + rndc() * settings['gv']) / r1
1204 # set the x position and width for the left block
1205 widOptions = [settings['w'], bevelL + settings['wm'], leftOpening.ts]
1206 baseWid = max(widOptions)
1207 w = (rndd() * settings['wv'] + baseWid + row. EdgeOffset)
1208 widOptions[0] = settings['wm']
1209 widOptions[2] = w
1210 w = max(widOptions) / r1 - grt
1211 x = w / 2 + LNerEdge + grt / 2
1212 BlockRowL = x + w / 2
1213 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1214 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, leftOffsets])
1216 # make Right edge block
1217 # set the grout
1218 grt = (settings['g'] + rndc() * settings['gv']) / r1
1219 # set the x position and width for the left block
1220 widOptions = [settings['w'], bevelR + settings['wm'], rightOpening.ts]
1221 baseWid = max(widOptions)
1222 w = (rndd() * settings['wv'] + baseWid + row.EdgeOffset)
1223 widOptions[0] = settings['wm']
1224 widOptions[2] = w
1225 w = max(widOptions) / r1 - grt
1226 x = RNerEdge - w / 2 - grt / 2
1227 BlockRowR = x - w / 2
1228 ThisBlockDepth = rndd() * settings['dv'] + settings['d']
1229 row.BlocksEdge.append([x, row.z, w, row.h, ThisBlockDepth, rightOffsets])
1231 row.RowSegments.append([BlockRowL, BlockRowR])
1232 return None
1235 def plan(Thesketch, oldrows=0):
1236 __doc__ = """\
1237 The 'plan' function takes the data generated by the sketch function and the global settings
1238 and creates a list of blocks.
1239 It passes out a list of row heights, edge positions, edge blocks, and rows of blocks.
1241 # if we were passed a list of rows already, use those; else make a list.
1242 if oldrows:
1243 rows = oldrows
1244 else:
1245 # rows holds the important information common to all rows
1246 # rows = [list of row objects]
1247 rows = []
1249 # splits are places where we NEED a row division, to accomidate openings
1250 # add a split for the bottom row
1251 splits = [dims['b'] + settings['hb']]
1253 # add a split for each critical point on each opening
1254 for hole in Thesketch:
1255 splits += hole.crits()
1257 # and, a split for the top row
1258 splits.append(dims['t'] - settings['ht'])
1259 splits.sort()
1261 # divs are the normal old row divisions, add them between the top and bottom split
1262 divs = fill(splits[0], splits[-1], settings['h'], settings['hm'] + settings['g'], settings['hv'])[1: -1]
1264 # remove the divisions that are too close to the splits, so we don't get tiny thin rows
1265 for i in range(len(divs) - 1, -1, -1):
1266 for j in range(len(splits)):
1267 diff = abs(divs[i] - splits[j])
1268 if diff < (settings['h'] - settings['hv'] + settings['g']):
1269 del(divs[i])
1270 break
1272 # now merge the divs and splits lists
1273 divs += splits
1275 # add bottom and/or top points, if bottom and/or top row heights are more than zero
1276 if settings['hb'] > 0:
1277 divs.insert(0, dims['b'])
1278 if settings['ht'] > 0:
1279 divs.append(dims['t'])
1281 divs.sort()
1283 # trim the rows to the bottom and top of the wall
1284 if divs[0] < dims['b']:
1285 divs[:1] = []
1286 if divs[-1] > dims['t']:
1287 divs[-1:] = []
1289 # now, make the data for each row
1290 # rows = [[center height,row height,edge offset],[etc.]]
1292 divCount = len(divs) - 1 # number of divs to check
1293 divCheck = 0 # current div entry
1295 while divCheck < divCount:
1296 RowZ = (divs[divCheck] + divs[divCheck + 1]) / 2
1297 RowHeight = divs[divCheck + 1] - divs[divCheck] - settings['g'] + rndc() * \
1298 settings['rwhl'] * settings['gv']
1299 EdgeOffset = settings['eoff'] * (fmod(divCheck, 2) - 0.5) + settings['eoffv'] * rndd()
1301 # if row height is too shallow: delete next div entry, decrement total, and recheck current entry.
1302 if RowHeight < settings['hm']:
1303 del(divs[divCheck + 1])
1304 divCount -= 1 # Adjust count for removed div entry.
1305 continue
1307 rows.append(rowOb(RowZ, RowHeight, EdgeOffset))
1309 divCheck += 1 # on to next div entry
1311 # set up a special opening object to handle the edges of the wall
1312 x = (dims['s'] + dims['e']) / 2
1313 z = (dims['t'] + dims['b']) / 2
1314 w = (dims['e'] - dims['s'])
1315 h = (dims['t'] - dims['b'])
1316 WallBoundaries = openingInvert(x, z, w, h)
1318 # Go over each row in the list, set up edge blocks and block sections
1319 for rownum in range(len(rows)):
1320 rowProcessing(rows[rownum], Thesketch, WallBoundaries)
1322 # now return the things everyone needs
1323 # return [rows,edgeBlocks,blockRows,Asketch]
1324 return [rows, Thesketch]
1327 def archGeneration(hole, vlist, flist, sideSign):
1328 __doc__ = """\
1329 Makes arches for the top and bottom, depending on sideSign
1330 example, Lower arch:
1331 archGeneration(hole, vlist, flist, -1)
1332 example, Upper arch:
1333 archGeneration(hole, vlist, flist, 1)
1334 hole is the opening object that the arch is for
1335 add the vertices to vlist
1336 add the faces to flist
1337 sideSign is + or - 1, for the top or bottom arch. Other values may cause errors.
1340 # working arrays for vectors and faces
1341 avlist = []
1342 aflist = []
1344 # Top (1) or bottom (-1)
1345 if sideSign == -1:
1346 r = hole.rl # radius of the arch
1347 rt = hole.rtl # thickness of the arch (stone height)
1348 v = hole.vl # height of the arch
1349 c = hole.cl
1350 else:
1351 r = hole.r # radius of the arch
1352 rt = hole.rt # thickness of the arch (stone height)
1353 v = hole.v # height of the arch
1354 c = hole.c
1356 ra = r + rt / 2 # average radius of the arch
1357 x = hole.x
1358 w = hole.w
1359 h = hole.h
1360 z = hole.z
1361 bev = hole.b
1362 sideSignInv = -sideSign
1364 if v > w / 2: # two arcs, to make a pointed arch
1365 # positioning
1366 zpos = z + (h / 2) * sideSign
1367 xoffset = r - w / 2
1368 # left side top, right side bottom
1369 # angles reference straight up, and are in radians
1370 bevRad = r + bev
1371 bevHt = sqrt(bevRad ** 2 - (bevRad - (w / 2 + bev)) ** 2)
1372 midHalfAngle = atan(v / (r - w / 2))
1373 midHalfAngleBevel = atan(bevHt / (r - w / 2))
1374 bevelAngle = midHalfAngle - midHalfAngleBevel
1375 anglebeg = (PI / 2) * (sideSignInv)
1376 angleend = (PI / 2) * (sideSignInv) + midHalfAngle
1378 avlist, aflist = arch(ra, rt, (xoffset) * (sideSign), zpos, anglebeg, angleend, bev, bevelAngle, len(vlist))
1380 for i, vert in enumerate(avlist):
1381 avlist[i] = [vert[0] + hole.x, vert[1], vert[2]]
1382 vlist += avlist
1383 flist += aflist
1385 # right side top, left side bottom
1387 # angles reference straight up, and are in radians
1388 anglebeg = (PI / 2) * (sideSign) - midHalfAngle
1389 angleend = (PI / 2) * (sideSign)
1391 avlist, aflist = arch(ra, rt, (xoffset) * (sideSignInv), zpos, anglebeg, angleend, bev, bevelAngle, len(vlist))
1393 for i, vert in enumerate(avlist):
1394 avlist[i] = [vert[0] + hole.x, vert[1], vert[2]]
1396 vlist += avlist
1397 flist += aflist
1399 # keystone
1400 Dpth = settings['d'] + rndc() * settings['dv']
1401 Grout = settings['g'] + rndc() * settings['gv']
1402 angleBevel = (PI / 2) * (sideSign) - midHalfAngle
1403 Wdth = (rt - Grout - bev) * 2 * sin(angleBevel) * sideSign # note, sin may be negative
1404 MidZ = ((sideSign) * (bevHt + h / 2.0) + z) + (rt - Grout - bev) \
1405 * cos(angleBevel) # note, cos may come out negative
1406 nearCorner = sideSign * (MidZ - z) - v - h / 2
1408 if sideSign == 1:
1409 TopHt = hole.top() - MidZ - Grout
1410 BtmHt = nearCorner
1411 else:
1412 BtmHt = - (hole.btm() - MidZ) - Grout
1413 TopHt = nearCorner
1415 # set the amount to bevel the keystone
1416 keystoneBevel = (bevHt - v) * sideSign
1417 if Wdth >= settings['hm']:
1418 avlist, aflist = MakeAKeystone(x, Wdth, MidZ, TopHt, BtmHt, Dpth, keystoneBevel, len(vlist))
1420 if radialized:
1421 for i, vert in enumerate(avlist):
1422 if slope:
1423 r1 = dims['t'] * sin(vert[2] * PI / (dims['t'] * 2))
1424 else:
1425 r1 = vert[2]
1426 avlist[i] = [((vert[0] - hole.x) / r1) + hole.x, vert[1], vert[2]]
1428 vlist += avlist
1429 flist += aflist
1430 # remove "debug note" once bevel is finalized.
1431 else:
1432 debug_prints(func="archGeneration",
1433 text="Keystone was too narrow - " + str(Wdth))
1435 else: # only one arc - curve not peak.
1436 # bottom (sideSign -1) arch has poorly sized blocks...
1438 zpos = z + (sideSign * (h / 2 + v - r)) # single arc positioning
1440 # angles reference straight up, and are in radians
1441 if sideSign == -1:
1442 angleOffset = PI
1443 else:
1444 angleOffset = 0.0
1446 if v < w / 2:
1447 halfangle = atan(w / (2 * (r - v)))
1449 anglebeg = angleOffset - halfangle
1450 angleend = angleOffset + halfangle
1451 else:
1452 anglebeg = angleOffset - PI / 2
1453 angleend = angleOffset + PI / 2
1455 avlist, aflist = arch(ra, rt, 0, zpos, anglebeg, angleend, bev, 0.0, len(vlist))
1457 for i, vert in enumerate(avlist):
1458 avlist[i] = [vert[0] + x, vert[1], vert[2]]
1460 vlist += avlist
1461 flist += aflist
1463 # Make the Side Stones
1464 grt = (settings['g'] + rndc() * settings['gv'])
1465 width = sqrt(rt ** 2 - c ** 2) - grt
1467 if c > settings['hm'] + grt and c < width + grt:
1468 if radialized:
1469 subdivision = settings['sdv'] * (zpos + (h / 2) * sideSign)
1470 else:
1471 subdivision = settings['sdv']
1473 # set the height of the block, it should be as high as the max corner position, minus grout
1474 height = c - grt * (0.5 + c / (width + grt))
1476 # the vertical offset for the short side of the block
1477 voff = sideSign * (settings['hm'] - height)
1478 xstart = w / 2
1479 zstart = z + sideSign * (h / 2 + grt / 2)
1480 woffset = width * (settings['hm'] + grt / 2) / (c - grt / 2)
1481 depth = rndd() * settings['dv'] + settings['d']
1483 if sideSign == 1:
1484 offsets = [[0] * 3] * 6 + [[0] * 2 + [voff]] * 2
1485 topSide = zstart + height
1486 btmSide = zstart
1487 else:
1488 offsets = [[0] * 3] * 4 + [[0] * 2 + [voff]] * 2 + [[0] * 3] * 2
1489 topSide = zstart
1490 btmSide = zstart - height
1491 # Do some stuff to incorporate bev here
1492 bevelBlockOffsets(offsets, bev, -1)
1494 avlist, aflist = MakeABlock(
1495 [x - xstart - width, x - xstart - woffset, btmSide, topSide,
1496 -depth / 2, depth / 2], subdivision, len(vlist),
1497 Offsets=offsets, xBevScl=1
1500 # top didn't use radialized in prev version;
1501 # just noting for clarity - may need to revise for "sideSign == 1"
1502 if radialized:
1503 for i, vert in enumerate(avlist):
1504 avlist[i] = [((vert[0] - x) / vert[2]) + x, vert[1], vert[2]]
1506 vlist += avlist
1507 flist += aflist
1509 # keep sizing same - neat arches = master masons :)
1510 # grt = (settings['g'] + rndc()*settings['gv'])
1511 # height = c - grt*(0.5 + c/(width + grt))
1512 # if grout varies may as well change width too... width = sqrt(rt**2 - c**2) - grt
1513 # voff = sideSign * (settings['hm'] - height)
1514 # woffset = width*(settings['hm'] + grt/2)/(c - grt/2)
1516 if sideSign == 1:
1517 offsets = [[0] * 3] * 2 + [[0] * 2 + [voff]] * 2 + [[0] * 3] * 4
1518 topSide = zstart + height
1519 btmSide = zstart
1520 else:
1521 offsets = [[0] * 2 + [voff]] * 2 + [[0] * 3] * 6
1522 topSide = zstart
1523 btmSide = zstart - height
1524 # Do some stuff to incorporate bev here
1525 bevelBlockOffsets(offsets, bev, 1)
1527 avlist, aflist = MakeABlock(
1528 [x + xstart + woffset, x + xstart + width, btmSide, topSide,
1529 -depth / 2, depth / 2], subdivision, len(vlist),
1530 Offsets=offsets, xBevScl=1
1533 # top didn't use radialized in prev version;
1534 # just noting for clarity - may need to revise for "sideSign == 1"
1535 if radialized:
1536 for i, vert in enumerate(avlist):
1537 avlist[i] = [((vert[0] - x) / vert[2]) + x, vert[1], vert[2]]
1539 vlist += avlist
1540 flist += aflist
1541 return None
1544 def build(Aplan):
1545 __doc__ = """\
1546 Build creates the geometry for the wall, based on the
1547 "Aplan" object from the "plan" function. If physics is
1548 enabled, then it make a number of individual blocks with
1549 physics interaction enabled. Otherwise it creates
1550 geometry for the blocks, arches, etc. of the wall.
1552 vlist = []
1553 flist = []
1554 rows = Aplan[0]
1556 # all the edge blocks, redacted
1557 # AllBlocks = [[x, z, w, h, d, [corner offset matrix]], [etc.]]
1559 # loop through each row, adding the normal old blocks
1560 for rowidx in range(len(rows)):
1561 rows[rowidx].FillBlocks()
1563 AllBlocks = []
1565 # If the wall is set to merge blocks, check all the blocks to see if you can merge any
1566 # seems to only merge vertical, should do horizontal too
1567 if bigBlock:
1568 for rowidx in range(len(rows) - 1):
1569 if radialized:
1570 if slope:
1571 r1 = dims['t'] * sin(abs(rows[rowidx].z) * PI / (dims['t'] * 2))
1572 else:
1573 r1 = abs(rows[rowidx].z)
1574 else:
1575 r1 = 1
1577 Tolerance = settings['g'] / r1
1578 idxThis = len(rows[rowidx].BlocksNorm[:]) - 1
1579 idxThat = len(rows[rowidx + 1].BlocksNorm[:]) - 1
1581 while True:
1582 # end loop when either array idx wraps
1583 if idxThis < 0 or idxThat < 0:
1584 break
1586 blockThis = rows[rowidx].BlocksNorm[idxThis]
1587 blockThat = rows[rowidx + 1].BlocksNorm[idxThat]
1589 # seems to only merge vertical, should do horizontal too...
1590 cx, cz, cw, ch, cd = blockThis[:5]
1591 ox, oz, ow, oh, od = blockThat[:5]
1593 if (abs(cw - ow) < Tolerance) and (abs(cx - ox) < Tolerance):
1594 if cw > ow:
1595 BlockW = ow
1596 else:
1597 BlockW = cw
1599 AllBlocks.append([(cx + ox) / 2, (cz + oz + (oh - ch) / 2) / 2,
1600 BlockW, abs(cz - oz) + (ch + oh) / 2, (cd + od) / 2, None])
1602 rows[rowidx].BlocksNorm.pop(idxThis)
1603 rows[rowidx + 1].BlocksNorm.pop(idxThat)
1604 idxThis -= 1
1605 idxThat -= 1
1607 elif cx > ox:
1608 idxThis -= 1
1609 else:
1610 idxThat -= 1
1612 # Add blocks to create a "shelf/platform".
1613 # Does not account for openings (crosses gaps - which is a good thing)
1614 if shelfExt:
1615 SetGrtOff = settings['g'] / 2 # half grout for block size modifier
1617 # Use wall block settings for shelf
1618 SetBW = settings['w']
1619 SetBWVar = settings['wv']
1620 SetBWMin = settings['wm']
1621 SetBH = settings['h']
1623 # Shelf area settings
1624 ShelfLft = shelfSpecs['x']
1625 ShelfBtm = shelfSpecs['z']
1626 ShelfEnd = ShelfLft + shelfSpecs['w']
1627 ShelfTop = ShelfBtm + shelfSpecs['h']
1628 ShelfThk = shelfSpecs['d'] * 2 # use double-depth due to offsets to position at cursor.
1630 # Use "corners" to adjust position so not centered on depth.
1631 # Facing shelf, at cursor (middle of wall blocks)
1632 # - this way no gaps between platform and wall face due to wall block depth.
1633 wallDepth = settings['d'] / 2 # offset by wall depth so step depth matches UI setting :)
1634 if shelfBack: # place blocks on backside of wall
1635 ShelfOffsets = [
1636 [0, ShelfThk / 2, 0], [0, wallDepth, 0],
1637 [0, ShelfThk / 2, 0], [0, wallDepth, 0],
1638 [0, ShelfThk / 2, 0], [0, wallDepth, 0],
1639 [0, ShelfThk / 2, 0], [0, wallDepth, 0]
1641 else:
1642 ShelfOffsets = [
1643 [0, -wallDepth, 0], [0, -ShelfThk / 2, 0],
1644 [0, -wallDepth, 0], [0, -ShelfThk / 2, 0],
1645 [0, -wallDepth, 0], [0, -ShelfThk / 2, 0],
1646 [0, -wallDepth, 0], [0, -ShelfThk / 2, 0]
1649 # Add blocks for each "shelf row" in area
1650 while ShelfBtm < ShelfTop:
1652 # Make blocks for each row - based on rowOb::fillblocks
1653 # Does not vary grout.
1654 divs = fill(ShelfLft, ShelfEnd, SetBW, SetBWMin, SetBWVar)
1656 # loop through the row divisions, adding blocks for each one
1657 for i in range(len(divs) - 1):
1658 ThisBlockx = (divs[i] + divs[i + 1]) / 2
1659 ThisBlockw = divs[i + 1] - divs[i] - SetGrtOff
1661 AllBlocks.append([ThisBlockx, ShelfBtm, ThisBlockw, SetBH, ShelfThk, ShelfOffsets])
1663 ShelfBtm += SetBH + SetGrtOff # moving up to next row...
1665 # Add blocks to create "steps".
1666 # Does not account for openings (crosses gaps - which is a good thing)
1667 if stepMod:
1668 SetGrtOff = settings['g'] / 2 # half grout for block size modifier
1670 # Vary block width by wall block variations.
1671 SetWidVar = settings['wv']
1672 SetWidMin = settings['wm']
1674 StepXMod = stepSpecs['t'] # width of step/tread, also sets basic block size.
1675 StepZMod = stepSpecs['v']
1677 StepLft = stepSpecs['x']
1678 StepRt = stepSpecs['x'] + stepSpecs['w']
1679 StepBtm = stepSpecs['z'] + StepZMod / 2 # Start offset for centered blocks
1680 StepWide = stepSpecs['w']
1681 StepTop = StepBtm + stepSpecs['h']
1682 StepThk = stepSpecs['d'] * 2 # use double-depth due to offsets to position at cursor.
1684 # Use "corners" to adjust steps so not centered on depth.
1685 # Facing steps, at cursor (middle of wall blocks)
1686 # - this way no gaps between steps and wall face due to wall block depth.
1687 # Also, will work fine as stand-alone if not used with wall (try block depth 0 and see what happens).
1688 wallDepth = settings['d'] / 2
1689 if stepBack: # place blocks on backside of wall
1690 StepOffsets = [
1691 [0, StepThk / 2, 0], [0, wallDepth, 0],
1692 [0, StepThk / 2, 0], [0, wallDepth, 0],
1693 [0, StepThk / 2, 0], [0, wallDepth, 0],
1694 [0, StepThk / 2, 0], [0, wallDepth, 0]
1696 else:
1697 StepOffsets = [
1698 [0, -wallDepth, 0], [0, -StepThk / 2, 0],
1699 [0, -wallDepth, 0], [0, -StepThk / 2, 0],
1700 [0, -wallDepth, 0], [0, -StepThk / 2, 0],
1701 [0, -wallDepth, 0], [0, -StepThk / 2, 0]
1704 # Add steps for each "step row" in area (neg width is interesting but prevented)
1705 while StepBtm < StepTop and StepWide > 0:
1707 # Make blocks for each step row - based on rowOb::fillblocks
1708 # Does not vary grout.
1710 if stepOnly: # "cantilevered steps"
1711 if stepLeft:
1712 stepStart = StepRt - StepXMod
1713 else:
1714 stepStart = StepLft
1716 AllBlocks.append([stepStart, StepBtm, StepXMod, StepZMod, StepThk, StepOffsets])
1717 else:
1718 divs = fill(StepLft, StepRt, StepXMod, SetWidMin, SetWidVar)
1720 # loop through the row divisions, adding blocks for each one
1721 for i in range(len(divs) - 1):
1722 ThisBlockx = (divs[i] + divs[i + 1]) / 2
1723 ThisBlockw = divs[i + 1] - divs[i] - SetGrtOff
1725 AllBlocks.append([ThisBlockx, StepBtm, ThisBlockw, StepZMod, StepThk, StepOffsets])
1727 StepBtm += StepZMod + SetGrtOff # moving up to next row...
1728 StepWide -= StepXMod # reduce step width
1730 # adjust side limit depending on direction of steps
1731 if stepLeft:
1732 StepRt -= StepXMod # move in from right
1733 else:
1734 StepLft += StepXMod # move in from left
1736 # Copy all the blocks out of the rows
1737 for row in rows:
1738 AllBlocks += row.BlocksEdge
1739 AllBlocks += row.BlocksNorm
1741 # This loop makes individual blocks for each block specified in the plan
1742 for block in AllBlocks:
1743 x, z, w, h, d, corners = block
1744 if radialized:
1745 if slope:
1746 r1 = dims['t'] * sin(z * PI / (dims['t'] * 2))
1747 else:
1748 r1 = z
1749 else:
1750 r1 = 1
1752 geom = MakeABlock([x - w / 2, x + w / 2, z - h / 2, z + h / 2, -d / 2, d / 2],
1753 settings['sdv'], len(vlist),
1754 corners, None, settings['b'] + rndd() * settings['bv'], r1)
1755 vlist += geom[0]
1756 flist += geom[1]
1758 # This loop makes Arches for every opening specified in the plan.
1759 for hole in Aplan[1]:
1760 # lower arch stones
1761 if hole.vl > 0 and hole.rtl > (settings['g'] + settings['hm']): # make lower arch blocks
1762 archGeneration(hole, vlist, flist, -1)
1764 # top arch stones
1765 if hole.v > 0 and hole.rt > (settings['g'] + settings['hm']): # make upper arch blocks
1766 archGeneration(hole, vlist, flist, 1)
1768 # Warp all the points for domed stonework
1769 if slope:
1770 for i, vert in enumerate(vlist):
1771 vlist[i] = [vert[0], (dims['t'] + vert[1]) * cos(vert[2] * PI / (2 * dims['t'])),
1772 (dims['t'] + vert[1]) * sin(vert[2] * PI / (2 * dims['t']))]
1774 # Warp all the points for radial stonework
1775 if radialized:
1776 for i, vert in enumerate(vlist):
1777 vlist[i] = [vert[2] * cos(vert[0]), vert[2] * sin(vert[0]), vert[1]]
1779 return vlist, flist
1782 # The main function
1783 def createWall(radial, curve, openings, mergeBlox, shelf, shelfSide,
1784 steps, stepDir, stepBare, stepSide):
1785 __doc__ = """\
1786 Call all the functions you need to make a wall, return the verts and faces.
1788 global radialized
1789 global slope
1790 global openingSpecs
1791 global bigBlock
1792 global shelfExt
1793 global stepMod
1794 global stepLeft
1795 global shelfBack
1796 global stepOnly
1797 global stepBack
1799 # set all the working variables from passed parameters
1801 radialized = radial
1802 slope = curve
1803 openingSpecs = openings
1804 bigBlock = mergeBlox
1805 shelfExt = shelf
1806 stepMod = steps
1807 stepLeft = stepDir
1808 shelfBack = shelfSide
1809 stepOnly = stepBare
1810 stepBack = stepSide
1812 asketch = sketch()
1813 aplan = plan(asketch, 0)
1815 return build(aplan)