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