1# -*- coding:utf-8 -*- 2 3# ##### BEGIN GPL LICENSE BLOCK ##### 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 2 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software Foundation, 17# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA. 18# 19# ##### END GPL LICENSE BLOCK ##### 20 21# <pep8 compliant> 22 23# ---------------------------------------------------------- 24# Author: Stephen Leger (s-leger) 25# 26# ---------------------------------------------------------- 27# noinspection PyUnresolvedReferences 28import bpy 29import time 30# noinspection PyUnresolvedReferences 31from bpy.types import Operator, PropertyGroup, Mesh, Panel 32from bpy.props import ( 33 FloatProperty, BoolProperty, IntProperty, 34 StringProperty, EnumProperty, 35 CollectionProperty 36 ) 37from .bmesh_utils import BmeshEdit as bmed 38from random import randint 39import bmesh 40from mathutils import Vector, Matrix 41from math import sin, cos, pi, atan2, sqrt, tan 42from .archipack_manipulator import Manipulable, archipack_manipulator 43from .archipack_2d import Line, Arc 44from .archipack_preset import ArchipackPreset, PresetMenuOperator 45from .archipack_object import ArchipackCreateTool, ArchipackObject 46from .archipack_cutter import ( 47 CutAblePolygon, CutAbleGenerator, 48 ArchipackCutter, 49 ArchipackCutterPart 50 ) 51 52 53class Roof(): 54 55 def __init__(self): 56 self.angle_0 = 0 57 self.v0_idx = 0 58 self.v1_idx = 0 59 self.constraint_type = None 60 self.slope_left = 1 61 self.slope_right = 1 62 self.width_left = 1 63 self.width_right = 1 64 self.auto_left = 'AUTO' 65 self.auto_right = 'AUTO' 66 self.type = 'SIDE' 67 # force hip or valley 68 self.enforce_part = 'AUTO' 69 self.triangular_end = False 70 # seg is part of hole 71 self.is_hole = False 72 73 def copy_params(self, s): 74 s.angle_0 = self.angle_0 75 s.v0_idx = self.v0_idx 76 s.v1_idx = self.v1_idx 77 s.constraint_type = self.constraint_type 78 s.slope_left = self.slope_left 79 s.slope_right = self.slope_right 80 s.width_left = self.width_left 81 s.width_right = self.width_right 82 s.auto_left = self.auto_left 83 s.auto_right = self.auto_right 84 s.type = self.type 85 s.enforce_part = self.enforce_part 86 s.triangular_end = self.triangular_end 87 # segment is part of hole / slice 88 s.is_hole = self.is_hole 89 90 @property 91 def copy(self): 92 s = StraightRoof(self.p.copy(), self.v.copy()) 93 self.copy_params(s) 94 return s 95 96 def straight(self, length, t=1): 97 s = self.copy 98 s.p = self.lerp(t) 99 s.v = self.v.normalized() * length 100 return s 101 102 def set_offset(self, offset, last=None): 103 """ 104 Offset line and compute intersection point 105 between segments 106 """ 107 self.line = self.make_offset(offset, last) 108 109 def offset(self, offset): 110 o = self.copy 111 o.p += offset * self.cross_z.normalized() 112 return o 113 114 @property 115 def oposite(self): 116 o = self.copy 117 o.p += o.v 118 o.v = -o.v 119 return o 120 121 @property 122 def t_diff(self): 123 return self.t_end - self.t_start 124 125 def straight_roof(self, a0, length): 126 s = self.straight(length).rotate(a0) 127 r = StraightRoof(s.p, s.v) 128 r.angle_0 = a0 129 return r 130 131 def curved_roof(self, a0, da, radius): 132 n = self.normal(1).rotate(a0).scale(radius) 133 if da < 0: 134 n.v = -n.v 135 c = n.p - n.v 136 r = CurvedRoof(c, radius, n.angle, da) 137 r.angle_0 = a0 138 return r 139 140 141class StraightRoof(Roof, Line): 142 def __str__(self): 143 return "p0:{} p1:{}".format(self.p0, self.p1) 144 145 def __init__(self, p, v): 146 Line.__init__(self, p, v) 147 Roof.__init__(self) 148 149 150class CurvedRoof(Roof, Arc): 151 def __str__(self): 152 return "t_start:{} t_end:{} dist:{}".format(self.t_start, self.t_end, self.dist) 153 154 def __init__(self, c, radius, a0, da): 155 Arc.__init__(self, c, radius, a0, da) 156 Roof.__init__(self) 157 158 159class RoofSegment(): 160 """ 161 Roof part with 2 polygons 162 and "axis" StraightRoof segment 163 """ 164 def __init__(self, seg, left, right): 165 self.seg = seg 166 self.left = left 167 self.right = right 168 self.a0 = 0 169 self.reversed = False 170 171 172class RoofAxisNode(): 173 """ 174 Connection between parts 175 for radial analysis 176 """ 177 def __init__(self): 178 # axis segments 179 self.segs = [] 180 self.root = None 181 self.center = 0 182 # store count of horizontal segs 183 self.n_horizontal = 0 184 # store count of slopes segs 185 self.n_slope = 0 186 187 @property 188 def count(self): 189 return len(self.segs) 190 191 @property 192 def last(self): 193 """ 194 last segments in this node 195 """ 196 return self.segs[-1] 197 198 def left(self, index): 199 if index + 1 >= self.count: 200 return self.segs[0] 201 return self.segs[index + 1] 202 203 def right(self, index): 204 return self.segs[index - 1] 205 206 def add(self, a0, reversed, seg, left, right): 207 208 if seg.constraint_type == 'HORIZONTAL': 209 self.n_horizontal += 1 210 elif seg.constraint_type == 'SLOPE': 211 self.n_slope += 1 212 213 s = RoofSegment(seg, left, right) 214 s.a0 = a0 215 s.reversed = reversed 216 if reversed: 217 self.root = s 218 self.segs.append(s) 219 220 def update_center(self): 221 for i, s in enumerate(self.segs): 222 if s is self.root: 223 self.center = i 224 return 225 226 # sort tree segments by angle 227 def partition(self, array, begin, end): 228 pivot = begin 229 for i in range(begin + 1, end + 1): 230 if array[i].a0 < array[begin].a0: 231 pivot += 1 232 array[i], array[pivot] = array[pivot], array[i] 233 array[pivot], array[begin] = array[begin], array[pivot] 234 return pivot 235 236 def sort(self): 237 def _quicksort(array, begin, end): 238 if begin >= end: 239 return 240 pivot = self.partition(array, begin, end) 241 _quicksort(array, begin, pivot - 1) 242 _quicksort(array, pivot + 1, end) 243 244 end = len(self.segs) - 1 245 _quicksort(self.segs, 0, end) 246 247 # index of root in segs array 248 self.update_center() 249 250 251class RoofPolygon(CutAblePolygon): 252 """ 253 ccw roof pitch boundary 254 closed by explicit segment 255 handle triangular shape with zero axis length 256 257 mov <_________________ 258 | /\ 259 | | rot 260 | | left last <> next 261 \/_____axis_______>| 262 263 node <_____axis________ next 264 | /\ 265 | | rot 266 | | right last <> next 267 mov \/________________>| 268 side angle 269 """ 270 def __init__(self, axis, side, fake_axis=None): 271 """ 272 Create a default rectangle 273 axis from node to next 274 slope float -z for 1 in side direction 275 side in ['LEFT', 'RIGHT'] in axis direction 276 277 NOTE: 278 when axis length is null (eg: triangular shape) 279 use "fake_axis" with a 1 length to handle 280 distance from segment 281 """ 282 if side == 'LEFT': 283 # slope 284 self.slope = axis.slope_left 285 # width 286 self.width = axis.width_left 287 # constraint width 288 self.auto_mode = axis.auto_left 289 else: 290 # slope 291 self.slope = axis.slope_right 292 # width 293 self.width = axis.width_right 294 # constraint width 295 self.auto_mode = axis.auto_right 296 297 self.side = side 298 # backward deps 299 self.backward = False 300 # pointers to neighbors along axis 301 self.last = None 302 self.next = None 303 self.other_side = None 304 305 # axis segment 306 if side == 'RIGHT': 307 self.axis = axis.oposite 308 else: 309 self.axis = axis 310 311 self.fake_axis = None 312 313 # _axis is either a fake one or real one 314 # to prevent further check 315 if fake_axis is None: 316 self._axis = self.axis 317 self.fake_axis = self.axis 318 self.next_cross = axis 319 self.last_cross = axis 320 else: 321 if side == 'RIGHT': 322 self.fake_axis = fake_axis.oposite 323 else: 324 self.fake_axis = fake_axis 325 self._axis = self.fake_axis 326 327 # unit vector perpendicular to axis 328 # looking at outside part 329 v = self.fake_axis.sized_normal(0, -1) 330 self.cross = v 331 self.next_cross = v 332 self.last_cross = v 333 334 self.convex = True 335 # segments from axis end in ccw order 336 # closed by explicit segment 337 self.segs = [] 338 # holes 339 self.holes = [] 340 341 # Triangular ends 342 self.node_tri = False 343 self.next_tri = False 344 self.is_tri = False 345 346 # sizes 347 self.tmin = 0 348 self.tmax = 1 349 self.dt = 1 350 self.ysize = 0 351 self.xsize = 0 352 self.vx = Vector() 353 self.vy = Vector() 354 self.vz = Vector() 355 356 def move_node(self, p): 357 """ 358 Move slope point in node side 359 """ 360 if self.side == 'LEFT': 361 self.segs[-1].p0 = p 362 self.segs[2].p1 = p 363 else: 364 self.segs[2].p0 = p 365 self.segs[1].p1 = p 366 367 def move_next(self, p): 368 """ 369 Move slope point in next side 370 """ 371 if self.side == 'LEFT': 372 self.segs[2].p0 = p 373 self.segs[1].p1 = p 374 else: 375 self.segs[-1].p0 = p 376 self.segs[2].p1 = p 377 378 def node_link(self, da): 379 angle_90 = round(pi / 2, 4) 380 if self.side == 'LEFT': 381 idx = -1 382 else: 383 idx = 1 384 da = abs(round(da, 4)) 385 type = "LINK" 386 if da < angle_90: 387 type += "_VALLEY" 388 elif da > angle_90: 389 type += "_HIP" 390 self.segs[idx].type = type 391 392 def next_link(self, da): 393 angle_90 = round(pi / 2, 4) 394 if self.side == 'LEFT': 395 idx = 1 396 else: 397 idx = -1 398 da = abs(round(da, 4)) 399 type = "LINK" 400 if da < angle_90: 401 type += "_VALLEY" 402 elif da > angle_90: 403 type += "_HIP" 404 self.segs[idx].type = type 405 406 def bind(self, last, ccw=False): 407 """ 408 always in axis real direction 409 """ 410 # backward dependency relative to axis 411 if last.backward: 412 self.backward = self.side == last.side 413 414 if self.side == last.side: 415 last.next_cross = self.cross 416 else: 417 last.last_cross = self.cross 418 419 self.last_cross = last.cross 420 421 # axis of last / next segments 422 if self.backward: 423 self.next = last 424 last.last = self 425 else: 426 self.last = last 427 last.next = self 428 429 # width auto 430 if self.auto_mode == 'AUTO': 431 self.width = last.width 432 self.slope = last.slope 433 elif self.auto_mode == 'WIDTH' and self.width != 0: 434 self.slope = last.slope * last.width / self.width 435 elif self.auto_mode == 'SLOPE' and self.slope != 0: 436 self.width = last.width * last.slope / self.slope 437 438 self.make_segments() 439 last.make_segments() 440 441 res, p, t = self.segs[2].intersect(last.segs[2]) 442 443 if res: 444 # dont move anything when no intersection found 445 # aka when delta angle == 0 446 self.move_node(p) 447 if self.side != last.side: 448 last.move_node(p) 449 else: 450 last.move_next(p) 451 452 # Free mode 453 # move border 454 # and find intersections 455 # with sides 456 if self.auto_mode == 'ALL': 457 s0 = self._axis.offset(-self.width) 458 res, p0, t = self.segs[1].intersect(s0) 459 if res: 460 self.segs[2].p0 = p0 461 self.segs[1].p1 = p0 462 res, p1, t = self.segs[-1].intersect(s0) 463 if res: 464 self.segs[2].p1 = p1 465 self.segs[-1].p0 = p1 466 467 # /\ 468 # | angle 469 # |____> 470 # 471 # v1 node -> next 472 if self.side == 'LEFT': 473 v1 = self._axis.v 474 else: 475 v1 = -self._axis.v 476 477 if last.side == self.side: 478 # contiguous, v0 node <- next 479 480 # half angle between segments 481 if self.side == 'LEFT': 482 v0 = -last._axis.v 483 else: 484 v0 = last._axis.v 485 da = v0.angle_signed(v1) 486 if ccw: 487 if da < 0: 488 da = 2 * pi + da 489 elif da > 0: 490 da = da - 2 * pi 491 last.next_link(0.5 * da) 492 493 else: 494 # alternate v0 node -> next 495 # half angle between segments 496 if last.side == 'LEFT': 497 v0 = last._axis.v 498 else: 499 v0 = -last._axis.v 500 da = v0.angle_signed(v1) 501 # angle always ccw 502 if ccw: 503 if da < 0: 504 da = 2 * pi + da 505 elif da > 0: 506 da = da - 2 * pi 507 last.node_link(0.5 * da) 508 509 self.node_link(-0.5 * da) 510 511 def next_seg(self, index): 512 idx = self.get_index(index + 1) 513 return self.segs[idx] 514 515 def last_seg(self, index): 516 return self.segs[index - 1] 517 518 def make_segments(self): 519 if len(self.segs) < 1: 520 s0 = self._axis 521 w = self.width 522 s1 = s0.straight(w, 1).rotate(pi / 2) 523 s1.type = 'SIDE' 524 s3 = s0.straight(w, 0).rotate(pi / 2).oposite 525 s3.type = 'SIDE' 526 s2 = StraightRoof(s1.p1, s3.p0 - s1.p1) 527 s2.type = 'BOTTOM' 528 self.segs = [s0, s1, s2, s3] 529 530 def move_side(self, pt): 531 """ 532 offset side to point 533 """ 534 s2 = self.segs[2] 535 d0, t = self.distance(s2.p0) 536 d1, t = self.distance(pt) 537 # adjust width and slope according 538 self.width = d1 539 self.slope = self.slope * d0 / d1 540 self.segs[2] = s2.offset(d1 - d0) 541 542 def propagate_backward(self, pt): 543 """ 544 Propagate slope, keep 2d angle of slope 545 Move first point and border 546 keep border parallel 547 adjust slope 548 and next shape 549 """ 550 # distance of p 551 # offset side to point 552 self.move_side(pt) 553 554 # move verts on node side 555 self.move_next(pt) 556 557 if self.side == 'LEFT': 558 # move verts on next side 559 res, p, t = self.segs[-1].intersect(self.segs[2]) 560 else: 561 # move verts on next side 562 res, p, t = self.segs[1].intersect(self.segs[2]) 563 564 if res: 565 self.move_node(p) 566 567 if self.next is not None and self.next.auto_mode in {'AUTO'}: 568 self.next.propagate_backward(p) 569 570 def propagate_forward(self, pt): 571 """ 572 Propagate slope, keep 2d angle of slope 573 Move first point and border 574 keep border parallel 575 adjust slope 576 and next shape 577 """ 578 # offset side to point 579 self.move_side(pt) 580 581 # move verts on node side 582 self.move_node(pt) 583 if self.side == 'LEFT': 584 # move verts on next side 585 res, p, t = self.segs[1].intersect(self.segs[2]) 586 else: 587 # move verts on next side 588 res, p, t = self.segs[-1].intersect(self.segs[2]) 589 590 if res: 591 self.move_next(p) 592 if self.next is not None and self.next.auto_mode in {'AUTO'}: 593 self.next.propagate_forward(p) 594 595 def rotate_next_slope(self, a0): 596 """ 597 Rotate next slope part 598 """ 599 if self.side == 'LEFT': 600 s0 = self.segs[1].rotate(a0) 601 s1 = self.segs[2] 602 res, p, t = s1.intersect(s0) 603 else: 604 s0 = self.segs[2] 605 s1 = self.segs[-1] 606 res, p, t = s1.oposite.rotate(-a0).intersect(s0) 607 608 if res: 609 s1.p0 = p 610 s0.p1 = p 611 612 if self.next is not None: 613 if self.next.auto_mode == 'ALL': 614 return 615 if self.next.backward: 616 self.next.propagate_backward(p) 617 else: 618 self.next.propagate_forward(p) 619 620 def rotate_node_slope(self, a0): 621 """ 622 Rotate node slope part 623 """ 624 if self.side == 'LEFT': 625 s0 = self.segs[2] 626 s1 = self.segs[-1] 627 res, p, t = s1.oposite.rotate(-a0).intersect(s0) 628 else: 629 s0 = self.segs[1].rotate(a0) 630 s1 = self.segs[2] 631 res, p, t = s1.intersect(s0) 632 633 if res: 634 s1.p0 = p 635 s0.p1 = p 636 637 if self.next is not None: 638 if self.next.auto_mode == 'ALL': 639 return 640 if self.next.backward: 641 self.next.propagate_backward(p) 642 else: 643 self.next.propagate_forward(p) 644 645 def distance(self, pt): 646 """ 647 distance from axis 648 always use fake_axis here to 649 allow axis being cut and 650 still work 651 """ 652 res, d, t = self.fake_axis.point_sur_segment(pt) 653 return d, t 654 655 def altitude(self, pt): 656 d, t = self.distance(pt) 657 return -d * self.slope 658 659 def uv(self, pt): 660 d, t = self.distance(pt) 661 return ((t - self.tmin) * self.xsize, d) 662 663 def intersect(self, seg): 664 """ 665 compute intersections of a segment with boundaries 666 segment must start on axis 667 return segments inside 668 """ 669 it = [] 670 for s in self.segs: 671 res, p, t, u = seg.intersect_ext(s) 672 if res: 673 it.append((t, p)) 674 return it 675 676 def merge(self, other): 677 678 raise NotImplementedError 679 680 def draw(self, context, z, verts, edges): 681 f = len(verts) 682 # 683 # 0_______1 684 # |_______| 685 # 3 2 686 verts.extend([(s.p0.x, s.p0.y, z + self.altitude(s.p0)) for s in self.segs]) 687 n_segs = len(self.segs) - 1 688 edges.extend([[f + i, f + i + 1] for i in range(n_segs)]) 689 edges.append([f + n_segs, f]) 690 """ 691 f = len(verts) 692 verts.extend([(s.p1.x, s.p1.y, z + self.altitude(s.p1)) for s in self.segs]) 693 n_segs = len(self.segs) - 1 694 edges.extend([[f + i, f + i + 1] for i in range(n_segs)]) 695 edges.append([f + n_segs, f]) 696 """ 697 # holes 698 for hole in self.holes: 699 f = len(verts) 700 # 701 # 0_______1 702 # |_______| 703 # 3 2 704 verts.extend([(s.p0.x, s.p0.y, z + self.altitude(s.p0)) for s in hole.segs]) 705 n_segs = len(hole.segs) - 1 706 edges.extend([[f + i, f + i + 1] for i in range(n_segs)]) 707 edges.append([f + n_segs, f]) 708 709 # axis 710 """ 711 f = len(verts) 712 verts.extend([self.axis.p0.to_3d(), self.axis.p1.to_3d()]) 713 edges.append([f, f + 1]) 714 715 # cross 716 f = len(verts) 717 verts.extend([self.axis.lerp(0.5).to_3d(), (self.axis.lerp(0.5) + self.cross.v).to_3d()]) 718 edges.append([f, f + 1]) 719 """ 720 721 # relationships arrows 722 if self.next or self.last: 723 w = 0.2 724 s0 = self._axis.offset(-0.5 * self.ysize) 725 p0 = s0.lerp(0.4).to_3d() 726 p0.z = z 727 p1 = s0.lerp(0.6).to_3d() 728 p1.z = z 729 if self.side == 'RIGHT': 730 p0, p1 = p1, p0 731 if self.backward: 732 p0, p1 = p1, p0 733 s1 = s0.sized_normal(0.5, w) 734 s2 = s0.sized_normal(0.5, -w) 735 f = len(verts) 736 p2 = s1.p1.to_3d() 737 p2.z = z 738 p3 = s2.p1.to_3d() 739 p3.z = z 740 verts.extend([p1, p0, p2, p3]) 741 edges.extend([[f + 1, f], [f + 2, f], [f + 3, f]]) 742 743 def as_string(self): 744 """ 745 Print strips relationships 746 """ 747 if self.backward: 748 dir = "/\\" 749 print("%s next" % (dir)) 750 else: 751 dir = "\\/" 752 print("%s node" % (dir)) 753 print("%s %s" % (dir, self.side)) 754 if self.backward: 755 print("%s node" % (dir)) 756 else: 757 print("%s next" % (dir)) 758 if self.next: 759 print("_________") 760 self.next.as_string() 761 else: 762 print("#########") 763 764 def limits(self): 765 dist = [] 766 param_t = [] 767 for s in self.segs: 768 res, d, t = self.fake_axis.point_sur_segment(s.p0) 769 param_t.append(t) 770 dist.append(d) 771 772 if len(param_t) > 0: 773 self.tmin = min(param_t) 774 self.tmax = max(param_t) 775 else: 776 self.tmin = 0 777 self.tmax = 1 778 779 self.dt = self.tmax - self.tmin 780 781 if len(dist) > 0: 782 self.ysize = max(dist) 783 else: 784 self.ysize = 0 785 786 self.xsize = self.fake_axis.length * self.dt 787 # vectors components of part matrix 788 # where x is is axis direction 789 # y down 790 # z up 791 vx = -self.fake_axis.v.normalized().to_3d() 792 vy = Vector((-vx.y, vx.x, self.slope)).normalized() 793 self.vx = vx 794 self.vy = vy 795 self.vz = vx.cross(vy) 796 797 798""" 799import bmesh 800m = C.object.data 801[(round(v.co.x, 3), round(v.co.y, 3), round(v.co.z, 3)) for v in m.vertices] 802[tuple(p.vertices) for p in m.polygons] 803 804uvs = [] 805bpy.ops.object.mode_set(mode='EDIT') 806bm = bmesh.from_edit_mesh(m) 807[tuple(i.index for i in edge.verts) for edge in bm.edges] 808 809layer = bm.loops.layers.uv.verify() 810for i, face in enumerate(bm.faces): 811 uv = [] 812 for j, loop in enumerate(face.loops): 813 co = loop[layer].uv 814 uv.append((round(co.x, 2), round(co.y, 2))) 815 uvs.append(uv) 816uvs 817""" 818 819 820class RoofGenerator(CutAbleGenerator): 821 822 def __init__(self, d, origin=Vector((0, 0, 0))): 823 self.parts = d.parts 824 self.segs = [] 825 self.nodes = [] 826 self.pans = [] 827 self.length = 0 828 self.origin = origin.to_2d() 829 self.z = origin.z 830 self.width_right = d.width_right 831 self.width_left = d.width_left 832 self.slope_left = d.slope_left 833 self.slope_right = d.slope_right 834 self.user_defined_tile = None 835 self.user_defined_uvs = None 836 self.user_defined_mat = None 837 self.is_t_child = d.t_parent != "" 838 839 def add_part(self, part): 840 841 if len(self.segs) < 1 or part.bound_idx < 1: 842 s = None 843 else: 844 s = self.segs[part.bound_idx - 1] 845 846 a0 = part.a0 847 848 if part.constraint_type == 'SLOPE' and a0 == 0: 849 a0 = 90 850 851 # start a new roof 852 if s is None: 853 v = part.length * Vector((cos(a0), sin(a0))) 854 s = StraightRoof(self.origin, v) 855 else: 856 s = s.straight_roof(a0, part.length) 857 858 # parent segment (root) index is v0_idx - 1 859 s.v0_idx = min(len(self.segs), part.bound_idx) 860 861 s.constraint_type = part.constraint_type 862 863 if part.constraint_type == 'SLOPE': 864 s.enforce_part = part.enforce_part 865 else: 866 s.enforce_part = 'AUTO' 867 868 s.angle_0 = a0 869 s.take_precedence = part.take_precedence 870 s.auto_right = part.auto_right 871 s.auto_left = part.auto_left 872 s.width_left = part.width_left 873 s.width_right = part.width_right 874 s.slope_left = part.slope_left 875 s.slope_right = part.slope_right 876 s.type = 'AXIS' 877 s.triangular_end = part.triangular_end 878 self.segs.append(s) 879 880 def locate_manipulators(self): 881 """ 882 883 """ 884 for i, f in enumerate(self.segs): 885 886 manipulators = self.parts[i].manipulators 887 p0 = f.p0.to_3d() 888 p0.z = self.z 889 p1 = f.p1.to_3d() 890 p1.z = self.z 891 # angle from last to current segment 892 if i > 0: 893 894 manipulators[0].type_key = 'ANGLE' 895 v0 = self.segs[f.v0_idx - 1].straight(-1, 1).v.to_3d() 896 v1 = f.straight(1, 0).v.to_3d() 897 manipulators[0].set_pts([p0, v0, v1]) 898 899 # segment length 900 manipulators[1].type_key = 'SIZE' 901 manipulators[1].prop1_name = "length" 902 manipulators[1].set_pts([p0, p1, (1.0, 0, 0)]) 903 904 # dumb segment id 905 manipulators[2].set_pts([p0, p1, (1, 0, 0)]) 906 907 p0 = f.lerp(0.5).to_3d() 908 p0.z = self.z 909 # size left 910 p1 = f.sized_normal(0.5, -self.parts[i].width_left).p1.to_3d() 911 p1.z = self.z 912 manipulators[3].set_pts([p0, p1, (1, 0, 0)]) 913 914 # size right 915 p1 = f.sized_normal(0.5, self.parts[i].width_right).p1.to_3d() 916 p1.z = self.z 917 manipulators[4].set_pts([p0, p1, (-1, 0, 0)]) 918 919 # slope left 920 n0 = f.sized_normal(0.5, -1) 921 p0 = n0.p1.to_3d() 922 p0.z = self.z 923 p1 = p0.copy() 924 p1.z = self.z - self.parts[i].slope_left 925 manipulators[5].set_pts([p0, p1, (-1, 0, 0)], normal=n0.v.to_3d()) 926 927 # slope right 928 n0 = f.sized_normal(0.5, 1) 929 p0 = n0.p1.to_3d() 930 p0.z = self.z 931 p1 = p0.copy() 932 p1.z = self.z - self.parts[i].slope_right 933 manipulators[6].set_pts([p0, p1, (1, 0, 0)], normal=n0.v.to_3d()) 934 935 def seg_partition(self, array, begin, end): 936 """ 937 sort tree segments by angle 938 """ 939 pivot = begin 940 for i in range(begin + 1, end + 1): 941 if array[i].a0 < array[begin].a0: 942 pivot += 1 943 array[i], array[pivot] = array[pivot], array[i] 944 array[pivot], array[begin] = array[begin], array[pivot] 945 return pivot 946 947 def sort_seg(self, array, begin=0, end=None): 948 # print("sort_child") 949 if end is None: 950 end = len(array) - 1 951 952 def _quicksort(array, begin, end): 953 if begin >= end: 954 return 955 pivot = self.seg_partition(array, begin, end) 956 _quicksort(array, begin, pivot - 1) 957 _quicksort(array, pivot + 1, end) 958 return _quicksort(array, begin, end) 959 960 def make_roof(self, context): 961 """ 962 Init data structure for possibly multi branched nodes 963 nodes : radial relationships 964 pans : quad strip linear relationships 965 """ 966 967 pans = [] 968 969 # node are connected segments 970 # node 971 # (segment idx) 972 # (angle from root part > 0 right) 973 # (reversed) a seg connected by p1 974 # "root" of node 975 nodes = [RoofAxisNode() for s in range(len(self.segs) + 1)] 976 977 # Init width on seg 0 978 s0 = self.segs[0] 979 if self.parts[0].auto_left in {'AUTO', 'SLOPE'}: 980 s0.width_left = self.width_left 981 if self.parts[0].auto_right in {'AUTO', 'SLOPE'}: 982 s0.width_right = self.width_right 983 if self.parts[0].auto_left in {'AUTO', 'WIDTH'}: 984 s0.slope_left = self.slope_left 985 if self.parts[0].auto_left in {'AUTO', 'WIDTH'}: 986 s0.slope_right = self.slope_right 987 988 # make nodes with HORIZONTAL constraints 989 for idx, s in enumerate(self.segs): 990 s.v1_idx = idx + 1 991 if s.constraint_type == 'HORIZONTAL': 992 left = RoofPolygon(s, 'LEFT') 993 right = RoofPolygon(s, 'RIGHT') 994 left.other_side = right 995 right.other_side = left 996 rs = RoofSegment(s, left, right) 997 pans.append(rs) 998 nodes[s.v0_idx].add(s.angle_0, False, s, left, right) 999 nodes[s.v1_idx].add(-pi, True, s, left, right) 1000 1001 # set first node root 1002 # so regular sort does work 1003 nodes[0].root = nodes[0].segs[0] 1004 self.nodes = nodes 1005 # Propagate slope and width 1006 # on node basis along axis 1007 # bi-direction Radial around node 1008 # from left and right to center 1009 # contiguous -> same 1010 # T: and (x % 2 == 1) 1011 # First one take precedence over others 1012 # others inherit from side 1013 # 1014 # l / rb l = left 1015 # 3 r = right 1016 # l _1_ / b = backward 1017 # r \ 1018 # 2 1019 # r\ l 1020 # 1021 # X: right one or left one l (x % 2 == 0) 1022 # inherits from side 1023 # 1024 # l 3 lb l = left 1025 # l__1_|_2_l r = right 1026 # r | r b = backward -> propagate in reverse axis direction 1027 # r 4 rb 1028 # 1029 # for idx, node in enumerate(nodes): 1030 # print("idx:%s node:%s" % (idx, node.root)) 1031 1032 for idx, node in enumerate(nodes): 1033 1034 node.sort() 1035 1036 nb_segs = node.count 1037 1038 if node.root is None: 1039 continue 1040 1041 left = node.root.left 1042 right = node.root.right 1043 1044 # basic one single node 1045 if nb_segs < 2: 1046 left.make_segments() 1047 right.make_segments() 1048 continue 1049 1050 # get "root" slope and width 1051 l_bind = left 1052 r_bind = right 1053 1054 # simple case: 2 contiguous segments 1055 if nb_segs == 2: 1056 s = node.last 1057 s.right.bind(r_bind, ccw=False) 1058 s.left.bind(l_bind, ccw=True) 1059 continue 1060 1061 # More than 2 segments, uneven distribution 1062 if nb_segs % 2 == 1: 1063 # find which child does take precedence 1064 # first one on rootline (arbitrary) 1065 center = (nb_segs - 1) / 2 1066 else: 1067 # even distribution 1068 center = nb_segs / 2 1069 1070 # user defined precedence if any 1071 for i, s in enumerate(node.segs): 1072 if s.seg.take_precedence: 1073 center = i 1074 break 1075 1076 # bind right side to center 1077 for i, s in enumerate(node.segs): 1078 # skip axis 1079 if i > 0: 1080 if i < center: 1081 # right contiguous with last 1082 s.right.bind(r_bind, ccw=False) 1083 1084 # next bind to left 1085 r_bind = s.left 1086 1087 # left backward, not bound 1088 # so setup width and slope 1089 if s.left.auto_mode in {'AUTO', 'WIDTH'}: 1090 s.left.slope = right.slope 1091 if s.left.auto_mode in {'AUTO', 'SLOPE'}: 1092 s.left.width = right.width 1093 s.left.backward = True 1094 else: 1095 # right bound to last 1096 s.right.bind(r_bind, ccw=False) 1097 break 1098 1099 # bind left side to center 1100 for i, s in enumerate(reversed(node.segs)): 1101 # skip axis 1102 if i < nb_segs - center - 1: 1103 # left contiguous with last 1104 s.left.bind(l_bind, ccw=True) 1105 # next bind to right 1106 l_bind = s.right 1107 # right backward, not bound 1108 # so setup width and slope 1109 if s.right.auto_mode in {'AUTO', 'WIDTH'}: 1110 s.right.slope = left.slope 1111 if s.right.auto_mode in {'AUTO', 'SLOPE'}: 1112 s.right.width = left.width 1113 s.right.backward = True 1114 else: 1115 # right bound to last 1116 s.left.bind(l_bind, ccw=True) 1117 break 1118 1119 # slope constraints allowed between segments 1120 # multiple (up to 2) on start and end 1121 # single between others 1122 # 1123 # 2 slope 2 slope 2 slope 1124 # | | | 1125 # |______section_1___|___section_2_____| 1126 # | | | 1127 # | | | 1128 # multiple single multiple 1129 1130 # add slopes constraints to nodes 1131 for i, s in enumerate(self.segs): 1132 if s.constraint_type == 'SLOPE': 1133 nodes[s.v0_idx].add(s.angle_0, False, s, None, None) 1134 1135 # sort nodes, remove duplicate slopes between 1136 # horizontal, keeping only first one 1137 for idx, node in enumerate(nodes): 1138 to_remove = [] 1139 node.sort() 1140 # remove dup between all 1141 # but start / end nodes 1142 if node.n_horizontal > 1: 1143 last = None 1144 for i, s in enumerate(node.segs): 1145 if s.seg.constraint_type == last: 1146 if s.seg.constraint_type == 'SLOPE': 1147 to_remove.append(i) 1148 last = s.seg.constraint_type 1149 for i in reversed(to_remove): 1150 node.segs.pop(i) 1151 node.update_center() 1152 1153 for idx, node in enumerate(nodes): 1154 1155 # a node may contain many slopes 1156 # 2 * (part starting from node - 1) 1157 # 1158 # s0 1159 # root 0 |_______ 1160 # | 1161 # s1 1162 # 1163 # s1 1164 # root _______| 1165 # | 1166 # s0 1167 # 1168 # s3 3 s2 1169 # l \l|r/ l 1170 # root ___\|/___ 2 1171 # r /|\ r 1172 # /r|l\ 1173 # s0 1 s1 1174 # 1175 # s2 s1=slope 1176 # |r / 1177 # | / l 1178 # |/____s 1179 # 1180 # root to first child -> equal side 1181 # any other childs -> oposite sides 1182 1183 if node.n_horizontal == 1: 1184 # slopes at start or end of segment 1185 # segment slope is not affected 1186 if node.n_slope > 0: 1187 # node has user def slope 1188 s = node.root 1189 s0 = node.left(node.center) 1190 a0 = s0.seg.delta_angle(s.seg) 1191 if node.root.reversed: 1192 # slope at end of segment 1193 # first one is right or left 1194 if a0 < 0: 1195 # right side 1196 res, p, t = s0.seg.intersect(s.right.segs[2]) 1197 s.right.segs[-1].p0 = p 1198 s.right.segs[2].p1 = p 1199 else: 1200 # left side 1201 res, p, t = s0.seg.intersect(s.left.segs[2]) 1202 s.left.segs[1].p1 = p 1203 s.left.segs[2].p0 = p 1204 if node.n_slope > 1: 1205 # last one must be left 1206 s1 = node.right(node.center) 1207 a1 = s1.seg.delta_angle(s.seg) 1208 # both slopes on same side: 1209 # skip this one 1210 if a0 > 0 and a1 < 0: 1211 # right side 1212 res, p, t = s1.seg.intersect(s.right.segs[2]) 1213 s.right.segs[-1].p0 = p 1214 s.right.segs[2].p1 = p 1215 if a0 < 0 and a1 > 0: 1216 # left side 1217 res, p, t = s1.seg.intersect(s.left.segs[2]) 1218 s.left.segs[1].p1 = p 1219 s.left.segs[2].p0 = p 1220 1221 else: 1222 # slope at start of segment 1223 if a0 < 0: 1224 # right side 1225 res, p, t = s0.seg.intersect(s.right.segs[2]) 1226 s.right.segs[1].p1 = p 1227 s.right.segs[2].p0 = p 1228 else: 1229 # left side 1230 res, p, t = s0.seg.intersect(s.left.segs[2]) 1231 s.left.segs[-1].p0 = p 1232 s.left.segs[2].p1 = p 1233 if node.n_slope > 1: 1234 # last one must be right 1235 s1 = node.right(node.center) 1236 a1 = s1.seg.delta_angle(s.seg) 1237 # both slopes on same side: 1238 # skip this one 1239 if a0 > 0 and a1 < 0: 1240 # right side 1241 res, p, t = s1.seg.intersect(s.right.segs[2]) 1242 s.right.segs[1].p1 = p 1243 s.right.segs[2].p0 = p 1244 if a0 < 0 and a1 > 0: 1245 # left side 1246 res, p, t = s1.seg.intersect(s.left.segs[2]) 1247 s.left.segs[-1].p0 = p 1248 s.left.segs[2].p1 = p 1249 1250 else: 1251 # slopes between segments 1252 # does change next segment slope 1253 for i, s0 in enumerate(node.segs): 1254 s1 = node.left(i) 1255 s2 = node.left(i + 1) 1256 1257 if s1.seg.constraint_type == 'SLOPE': 1258 1259 # 3 cases: 1260 # s0 is root contiguous -> sides are same 1261 # s2 is root contiguous -> sides are same 1262 # back to back -> sides are not same 1263 1264 if s0.reversed: 1265 # contiguous right / right 1266 # 2 cases 1267 # right is backward 1268 # right is forward 1269 if s2.right.backward: 1270 # s0 depends on s2 1271 main = s2.right 1272 v = main.segs[1].v 1273 else: 1274 # s2 depends on s0 1275 main = s0.right 1276 v = -main.segs[-1].v 1277 res, p, t = s1.seg.intersect(main.segs[2]) 1278 if res: 1279 # slope vector 1280 dp = p - s1.seg.p0 1281 a0 = dp.angle_signed(v) 1282 if s2.right.backward: 1283 main.rotate_node_slope(a0) 1284 else: 1285 main.rotate_next_slope(-a0) 1286 elif s2.reversed: 1287 # contiguous left / left 1288 # 2 cases 1289 # left is backward 1290 # left is forward 1291 if s0.left.backward: 1292 # s0 depends on s2 1293 main = s0.left 1294 v = -main.segs[-1].v 1295 else: 1296 # s2 depends on s0 1297 main = s2.left 1298 v = main.segs[1].v 1299 res, p, t = s1.seg.intersect(main.segs[2]) 1300 if res: 1301 # slope vector 1302 dp = p - s1.seg.p0 1303 a0 = dp.angle_signed(v) 1304 if s0.left.backward: 1305 main.rotate_node_slope(-a0) 1306 else: 1307 main.rotate_next_slope(a0) 1308 else: 1309 # back left / right 1310 # 2 cases 1311 # left is backward 1312 # left is forward 1313 if s0.left.backward: 1314 # s2 depends on s0 1315 main = s0.left 1316 v = -main.segs[-1].v 1317 else: 1318 # s0 depends on s2 1319 main = s2.right 1320 v = main.segs[1].v 1321 1322 res, p, t = s1.seg.intersect(main.segs[2]) 1323 if res: 1324 # slope vector 1325 dp = p - s1.seg.p0 1326 a0 = dp.angle_signed(v) 1327 if s0.left.backward: 1328 main.rotate_node_slope(-a0) 1329 else: 1330 main.rotate_node_slope(a0) 1331 1332 self.pans = [] 1333 1334 # triangular ends 1335 for node in self.nodes: 1336 if node.root is None: 1337 continue 1338 if node.n_horizontal == 1 and node.root.seg.triangular_end: 1339 if node.root.reversed: 1340 # Next side (segment end) 1341 left = node.root.left 1342 right = node.root.right 1343 left.next_tri = True 1344 right.next_tri = True 1345 1346 s0 = left.segs[1] 1347 s1 = left.segs[2] 1348 s2 = right.segs[-1] 1349 s3 = right.segs[2] 1350 p0 = s1.lerp(-left.width / s1.length) 1351 p1 = s0.p0 1352 p2 = s3.lerp(1 + right.width / s3.length) 1353 1354 # compute slope from points 1355 p3 = p0.to_3d() 1356 p3.z = -left.width * left.slope 1357 p4 = p1.to_3d() 1358 p5 = p2.to_3d() 1359 p5.z = -right.width * right.slope 1360 n = (p3 - p4).normalized().cross((p5 - p4).normalized()) 1361 v = n.cross(Vector((0, 0, 1))) 1362 dz = n.cross(v) 1363 1364 # compute axis 1365 s = StraightRoof(p1, v) 1366 res, d0, t = s.point_sur_segment(p0) 1367 res, d1, t = s.point_sur_segment(p2) 1368 p = RoofPolygon(s, 'RIGHT') 1369 p.make_segments() 1370 p.slope = -dz.z / dz.to_2d().length 1371 p.is_tri = True 1372 1373 p.cross = StraightRoof(p1, (p2 - p0)).sized_normal(0, -1) 1374 p.next_cross = left.cross 1375 p.last_cross = right.cross 1376 right.next_cross = p.cross 1377 left.next_cross = p.cross 1378 1379 # remove axis seg of tri 1380 p.segs[-1].p0 = p0 1381 p.segs[-1].p1 = p1 1382 p.segs[2].p0 = p2 1383 p.segs[2].p1 = p0 1384 p.segs[1].p1 = p2 1385 p.segs[1].p0 = p1 1386 p.segs[1].type = 'LINK_HIP' 1387 p.segs[-1].type = 'LINK_HIP' 1388 p.segs.pop(0) 1389 # adjust left and side borders 1390 s0.p1 = p0 1391 s1.p0 = p0 1392 s2.p0 = p2 1393 s3.p1 = p2 1394 s0.type = 'LINK_HIP' 1395 s2.type = 'LINK_HIP' 1396 self.pans.append(p) 1397 1398 elif not self.is_t_child: 1399 # no triangular part with t_child 1400 # on "node" parent roof side 1401 left = node.root.left 1402 right = node.root.right 1403 left.node_tri = True 1404 right.node_tri = True 1405 s0 = right.segs[1] 1406 s1 = right.segs[2] 1407 s2 = left.segs[-1] 1408 s3 = left.segs[2] 1409 p0 = s1.lerp(-right.width / s1.length) 1410 p1 = s0.p0 1411 p2 = s3.lerp(1 + left.width / s3.length) 1412 1413 # compute axis and slope from points 1414 p3 = p0.to_3d() 1415 p3.z = -right.width * right.slope 1416 p4 = p1.to_3d() 1417 p5 = p2.to_3d() 1418 p5.z = -left.width * left.slope 1419 n = (p3 - p4).normalized().cross((p5 - p4).normalized()) 1420 v = n.cross(Vector((0, 0, 1))) 1421 dz = n.cross(v) 1422 1423 s = StraightRoof(p1, v) 1424 p = RoofPolygon(s, 'RIGHT') 1425 p.make_segments() 1426 p.slope = -dz.z / dz.to_2d().length 1427 p.is_tri = True 1428 1429 p.cross = StraightRoof(p1, (p2 - p0)).sized_normal(0, -1) 1430 p.next_cross = right.cross 1431 p.last_cross = left.cross 1432 right.last_cross = p.cross 1433 left.last_cross = p.cross 1434 1435 # remove axis seg of tri 1436 p.segs[-1].p0 = p0 1437 p.segs[-1].p1 = p1 1438 p.segs[2].p0 = p2 1439 p.segs[2].p1 = p0 1440 p.segs[1].p1 = p2 1441 p.segs[1].p0 = p1 1442 p.segs[1].type = 'LINK_HIP' 1443 p.segs[-1].type = 'LINK_HIP' 1444 p.segs.pop(0) 1445 # adjust left and side borders 1446 s0.p1 = p0 1447 s1.p0 = p0 1448 s2.p0 = p2 1449 s3.p1 = p2 1450 s0.type = 'LINK_HIP' 1451 s2.type = 'LINK_HIP' 1452 self.pans.append(p) 1453 1454 # make flat array 1455 for pan in pans: 1456 self.pans.extend([pan.left, pan.right]) 1457 1458 # merge contiguous with 0 angle diff 1459 to_remove = [] 1460 for i, pan in enumerate(self.pans): 1461 if pan.backward: 1462 next = pan.last 1463 if next is not None: 1464 # same side only can merge 1465 if next.side == pan.side: 1466 if round(next._axis.delta_angle(pan._axis), 4) == 0: 1467 to_remove.append(i) 1468 next.next = pan.next 1469 next.last_cross = pan.last_cross 1470 next.node_tri = pan.node_tri 1471 1472 next.slope = pan.slope 1473 if pan.side == 'RIGHT': 1474 if next.backward: 1475 next._axis.p1 = pan._axis.p1 1476 next.segs[1] = pan.segs[1] 1477 next.segs[2].p0 = pan.segs[2].p0 1478 else: 1479 next._axis.p0 = pan._axis.p0 1480 next.segs[-1] = pan.segs[-1] 1481 next.segs[2].p1 = pan.segs[2].p1 1482 else: 1483 if next.backward: 1484 next._axis.p0 = pan._axis.p0 1485 next.segs[-1] = pan.segs[-1] 1486 next.segs[2].p1 = pan.segs[2].p1 1487 else: 1488 next._axis.p1 = pan._axis.p1 1489 next.segs[1] = pan.segs[1] 1490 next.segs[2].p0 = pan.segs[2].p0 1491 else: 1492 next = pan.next 1493 if next is not None: 1494 # same side only can merge 1495 if next.side == pan.side: 1496 if round(next._axis.delta_angle(pan._axis), 4) == 0: 1497 to_remove.append(i) 1498 next.last = pan.last 1499 next.last_cross = pan.last_cross 1500 next.node_tri = pan.node_tri 1501 1502 next.slope = pan.slope 1503 if pan.side == 'LEFT': 1504 if next.backward: 1505 next._axis.p1 = pan._axis.p1 1506 next.segs[1] = pan.segs[1] 1507 next.segs[2].p0 = pan.segs[2].p0 1508 else: 1509 next._axis.p0 = pan._axis.p0 1510 next.segs[-1] = pan.segs[-1] 1511 next.segs[2].p1 = pan.segs[2].p1 1512 else: 1513 if next.backward: 1514 next._axis.p0 = pan._axis.p0 1515 next.segs[-1] = pan.segs[-1] 1516 next.segs[2].p1 = pan.segs[2].p1 1517 else: 1518 next._axis.p1 = pan._axis.p1 1519 next.segs[1] = pan.segs[1] 1520 next.segs[2].p0 = pan.segs[2].p0 1521 1522 for i in reversed(to_remove): 1523 self.pans.pop(i) 1524 1525 # compute limits 1526 for pan in self.pans: 1527 pan.limits() 1528 1529 """ 1530 for pan in self.pans: 1531 if pan.last is None: 1532 pan.as_string() 1533 """ 1534 return 1535 1536 def lambris(self, context, o, d): 1537 1538 idmat = 0 1539 lambris_height = 0.02 1540 alt = self.z - lambris_height 1541 for pan in self.pans: 1542 1543 verts = [] 1544 faces = [] 1545 matids = [] 1546 uvs = [] 1547 1548 f = len(verts) 1549 verts.extend([(s.p0.x, s.p0.y, alt + pan.altitude(s.p0)) for s in pan.segs]) 1550 uvs.append([pan.uv(s.p0) for s in pan.segs]) 1551 n_segs = len(pan.segs) 1552 face = [f + i for i in range(n_segs)] 1553 faces.append(face) 1554 matids.append(idmat) 1555 1556 bm = bmed.buildmesh( 1557 context, o, verts, faces, matids=matids, uvs=uvs, 1558 weld=False, clean=False, auto_smooth=True, temporary=True) 1559 1560 self.cut_holes(bm, pan) 1561 1562 bmesh.ops.dissolve_limit(bm, 1563 angle_limit=0.01, 1564 use_dissolve_boundaries=False, 1565 verts=bm.verts, 1566 edges=bm.edges, 1567 delimit={'MATERIAL'}) 1568 1569 geom = bm.faces[:] 1570 verts = bm.verts[:] 1571 bmesh.ops.solidify(bm, geom=geom, thickness=0.0001) 1572 bmesh.ops.translate(bm, vec=Vector((0, 0, lambris_height)), space=o.matrix_world, verts=verts) 1573 1574 # merge with object 1575 bmed.bmesh_join(context, o, [bm], normal_update=True) 1576 1577 bpy.ops.object.mode_set(mode='OBJECT') 1578 1579 def couverture(self, context, o, d): 1580 1581 idmat = 7 1582 rand = 3 1583 ttl = len(self.pans) 1584 if ttl < 1: 1585 return 1586 1587 sx, sy, sz = d.tile_size_x, d.tile_size_y, d.tile_size_z 1588 1589 """ 1590 /* Bevel offset_type slot values */ 1591 enum { 1592 BEVEL_AMT_OFFSET, 1593 BEVEL_AMT_WIDTH, 1594 BEVEL_AMT_DEPTH, 1595 BEVEL_AMT_PERCENT 1596 }; 1597 """ 1598 offset_type = 'PERCENT' 1599 1600 if d.tile_offset > 0: 1601 offset = - d.tile_offset / 100 1602 else: 1603 offset = 0 1604 1605 if d.tile_model == 'BRAAS2': 1606 t_pts = [Vector(p) for p in [ 1607 (0.06, -1.0, 1.0), (0.19, -1.0, 0.5), (0.31, -1.0, 0.5), (0.44, -1.0, 1.0), 1608 (0.56, -1.0, 1.0), (0.69, -1.0, 0.5), (0.81, -1.0, 0.5), (0.94, -1.0, 1.0), 1609 (0.06, 0.0, 0.5), (0.19, 0.0, 0.0), (0.31, 0.0, 0.0), (0.44, 0.0, 0.5), 1610 (0.56, 0.0, 0.5), (0.69, 0.0, 0.0), (0.81, 0.0, 0.0), (0.94, 0.0, 0.5), 1611 (-0.0, -1.0, 1.0), (-0.0, 0.0, 0.5), (1.0, -1.0, 1.0), (1.0, 0.0, 0.5)]] 1612 t_faces = [ 1613 (16, 0, 8, 17), (0, 1, 9, 8), (1, 2, 10, 9), (2, 3, 11, 10), 1614 (3, 4, 12, 11), (4, 5, 13, 12), (5, 6, 14, 13), (6, 7, 15, 14), (7, 18, 19, 15)] 1615 elif d.tile_model == 'BRAAS1': 1616 t_pts = [Vector(p) for p in [ 1617 (0.1, -1.0, 1.0), (0.2, -1.0, 0.5), (0.6, -1.0, 0.5), (0.7, -1.0, 1.0), 1618 (0.1, 0.0, 0.5), (0.2, 0.0, 0.0), (0.6, 0.0, 0.0), (0.7, 0.0, 0.5), 1619 (-0.0, -1.0, 1.0), (-0.0, 0.0, 0.5), (1.0, -1.0, 1.0), (1.0, 0.0, 0.5)]] 1620 t_faces = [(8, 0, 4, 9), (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (3, 10, 11, 7)] 1621 elif d.tile_model == 'ETERNIT': 1622 t_pts = [Vector(p) for p in [ 1623 (0.11, -1.0, 1.0), (0.9, -1.0, 1.0), (0.0, -0.79, 0.79), 1624 (1.0, -0.79, 0.79), (0.0, 2.0, -2.0), (1.0, 2.0, -2.0)]] 1625 t_faces = [(0, 1, 3, 5, 4, 2)] 1626 elif d.tile_model == 'ONDULEE': 1627 t_pts = [Vector(p) for p in [ 1628 (0.0, -1.0, 0.1), (0.05, -1.0, 1.0), (0.1, -1.0, 0.1), 1629 (0.15, -1.0, 1.0), (0.2, -1.0, 0.1), (0.25, -1.0, 1.0), 1630 (0.3, -1.0, 0.1), (0.35, -1.0, 1.0), (0.4, -1.0, 0.1), 1631 (0.45, -1.0, 1.0), (0.5, -1.0, 0.1), (0.55, -1.0, 1.0), 1632 (0.6, -1.0, 0.1), (0.65, -1.0, 1.0), (0.7, -1.0, 0.1), 1633 (0.75, -1.0, 1.0), (0.8, -1.0, 0.1), (0.85, -1.0, 1.0), 1634 (0.9, -1.0, 0.1), (0.95, -1.0, 1.0), (1.0, -1.0, 0.1), 1635 (0.0, 0.0, 0.0), (0.05, 0.0, 0.9), (0.1, 0.0, 0.0), 1636 (0.15, 0.0, 0.9), (0.2, 0.0, 0.0), (0.25, 0.0, 0.9), 1637 (0.3, 0.0, 0.0), (0.35, 0.0, 0.9), (0.4, 0.0, 0.0), 1638 (0.45, 0.0, 0.9), (0.5, 0.0, 0.0), (0.55, 0.0, 0.9), 1639 (0.6, 0.0, 0.0), (0.65, 0.0, 0.9), (0.7, 0.0, 0.0), 1640 (0.75, 0.0, 0.9), (0.8, 0.0, 0.0), (0.85, 0.0, 0.9), 1641 (0.9, 0.0, 0.0), (0.95, 0.0, 0.9), (1.0, 0.0, 0.0)]] 1642 t_faces = [ 1643 (0, 1, 22, 21), (1, 2, 23, 22), (2, 3, 24, 23), 1644 (3, 4, 25, 24), (4, 5, 26, 25), (5, 6, 27, 26), 1645 (6, 7, 28, 27), (7, 8, 29, 28), (8, 9, 30, 29), 1646 (9, 10, 31, 30), (10, 11, 32, 31), (11, 12, 33, 32), 1647 (12, 13, 34, 33), (13, 14, 35, 34), (14, 15, 36, 35), 1648 (15, 16, 37, 36), (16, 17, 38, 37), (17, 18, 39, 38), 1649 (18, 19, 40, 39), (19, 20, 41, 40)] 1650 elif d.tile_model == 'METAL': 1651 t_pts = [Vector(p) for p in [ 1652 (0.0, -1.0, 0.0), (0.99, -1.0, 0.0), (1.0, -1.0, 0.0), 1653 (0.0, 0.0, 0.0), (0.99, 0.0, 0.0), (1.0, 0.0, 0.0), 1654 (0.99, -1.0, 1.0), (1.0, -1.0, 1.0), (1.0, 0.0, 1.0), (0.99, 0.0, 1.0)]] 1655 t_faces = [(0, 1, 4, 3), (7, 2, 5, 8), (1, 6, 9, 4), (6, 7, 8, 9)] 1656 elif d.tile_model == 'LAUZE': 1657 t_pts = [Vector(p) for p in [ 1658 (0.75, -0.8, 0.8), (0.5, -1.0, 1.0), (0.25, -0.8, 0.8), 1659 (0.0, -0.5, 0.5), (1.0, -0.5, 0.5), (0.0, 0.5, -0.5), (1.0, 0.5, -0.5)]] 1660 t_faces = [(1, 0, 4, 6, 5, 3, 2)] 1661 elif d.tile_model == 'PLACEHOLDER': 1662 t_pts = [Vector(p) for p in [(0.0, -1.0, 1.0), (1.0, -1.0, 1.0), (0.0, 0.0, 0.0), (1.0, 0.0, 0.0)]] 1663 t_faces = [(0, 1, 3, 2)] 1664 elif d.tile_model == 'ROMAN': 1665 t_pts = [Vector(p) for p in [ 1666 (0.18, 0.0, 0.3), (0.24, 0.0, 0.58), (0.76, 0.0, 0.58), 1667 (0.82, 0.0, 0.3), (0.05, -1.0, 0.5), (0.14, -1.0, 0.8), 1668 (0.86, -1.0, 0.8), (0.95, -1.0, 0.5), (0.45, 0.0, 0.5), 1669 (0.36, 0.0, 0.2), (-0.36, 0.0, 0.2), (-0.45, -0.0, 0.5), 1670 (0.32, -1.0, 0.7), (0.26, -1.0, 0.42), (-0.26, -1.0, 0.42), 1671 (-0.32, -1.0, 0.7), (0.5, 0.0, 0.74), (0.5, -1.0, 1.0), 1672 (-0.0, -1.0, 0.26), (-0.0, 0.0, 0.0)] 1673 ] 1674 t_faces = [ 1675 (0, 4, 5, 1), (16, 17, 6, 2), (2, 6, 7, 3), 1676 (13, 12, 8, 9), (18, 13, 9, 19), (15, 14, 10, 11), 1677 (14, 18, 19, 10), (1, 5, 17, 16) 1678 ] 1679 elif d.tile_model == 'ROUND': 1680 t_pts = [Vector(p) for p in [ 1681 (0.0, -0.5, 0.5), (1.0, -0.5, 0.5), (0.0, 0.0, 0.0), 1682 (1.0, 0.0, 0.0), (0.93, -0.71, 0.71), (0.78, -0.88, 0.88), 1683 (0.39, -0.97, 0.97), (0.61, -0.97, 0.97), (0.07, -0.71, 0.71), 1684 (0.22, -0.88, 0.88)] 1685 ] 1686 t_faces = [(6, 7, 5, 4, 1, 3, 2, 0, 8, 9)] 1687 else: 1688 return 1689 1690 n_faces = len(t_faces) 1691 t_uvs = [[(t_pts[i].x, t_pts[i].y) for i in f] for f in t_faces] 1692 1693 dx, dy = d.tile_space_x, d.tile_space_y 1694 1695 step = 100 / ttl 1696 1697 # if d.quick_edit: 1698 # context.scene.archipack_progress_text = "Build tiles:" 1699 1700 for i, pan in enumerate(self.pans): 1701 1702 seg = pan.fake_axis 1703 # compute base matrix top left of face 1704 vx = pan.vx 1705 vy = pan.vy 1706 vz = pan.vz 1707 1708 x0, y0 = seg.lerp(pan.tmax) 1709 z0 = self.z + d.tile_altitude 1710 ysize_2d = (d.tile_border + pan.ysize) 1711 space_x = pan.xsize + 2 * d.tile_side 1712 space_y = ysize_2d * sqrt(1 + pan.slope * pan.slope) 1713 n_x = 1 + int(space_x / dx) 1714 n_y = 1 + int(space_y / dy) 1715 1716 if d.tile_fit_x: 1717 dx = space_x / n_x 1718 1719 if d.tile_fit_y: 1720 dy = space_y / n_y 1721 1722 if d.tile_alternate: 1723 n_y += 1 1724 1725 tM = Matrix([ 1726 [vx.x, vy.x, vz.x, x0], 1727 [vx.y, vy.y, vz.y, y0], 1728 [vx.z, vy.z, vz.z, z0], 1729 [0, 0, 0, 1] 1730 ]) 1731 1732 verts = [] 1733 faces = [] 1734 matids = [] 1735 uvs = [] 1736 1737 # steps for this pan 1738 substep = step / n_y 1739 # print("step:%s sub:%s" % (step, substep)) 1740 1741 for k in range(n_y): 1742 1743 progress = step * i + substep * k 1744 # print("progress %s" % (progress)) 1745 # if d.quick_edit: 1746 # context.scene.archipack_progress = progress 1747 1748 y = k * dy 1749 1750 x0 = offset * dx - d.tile_side 1751 nx = n_x 1752 1753 if d.tile_alternate and k % 2 == 1: 1754 x0 -= 0.5 * dx 1755 nx += 1 1756 1757 if d.tile_offset > 0: 1758 nx += 1 1759 1760 for j in range(nx): 1761 x = x0 + j * dx 1762 lM = tM @ Matrix([ 1763 [sx, 0, 0, x], 1764 [0, sy, 0, -y], 1765 [0, 0, sz, 0], 1766 [0, 0, 0, 1] 1767 ]) 1768 1769 v = len(verts) 1770 1771 verts.extend([lM @ p for p in t_pts]) 1772 faces.extend([tuple(i + v for i in f) for f in t_faces]) 1773 mid = randint(idmat, idmat + rand) 1774 t_mats = [mid for i in range(n_faces)] 1775 matids.extend(t_mats) 1776 uvs.extend(t_uvs) 1777 1778 # build temp bmesh and bissect 1779 bm = bmed.buildmesh( 1780 context, o, verts, faces, matids=matids, uvs=uvs, 1781 weld=False, clean=False, auto_smooth=True, temporary=True) 1782 1783 # clean outer on convex parts 1784 # pan.convex = False 1785 remove = pan.convex 1786 1787 for s in pan.segs: 1788 # seg without length lead to invalid normal 1789 if s.length > 0: 1790 if s.type == 'AXIS': 1791 self.bissect(bm, s.p1.to_3d(), s.cross_z.to_3d(), clear_outer=remove) 1792 elif s.type == 'BOTTOM': 1793 s0 = s.offset(d.tile_border) 1794 dz = pan.altitude(s0.p0) 1795 vx = s0.v.to_3d() 1796 vx.z = pan.altitude(s0.p1) - dz 1797 vy = vz.cross(vx.normalized()) 1798 x, y = s0.p0 1799 z = z0 + dz 1800 self.bissect(bm, Vector((x, y, z)), -vy, clear_outer=remove) 1801 elif s.type == 'SIDE': 1802 p0 = s.p0 + s.cross_z.normalized() * d.tile_side 1803 self.bissect(bm, p0.to_3d(), s.cross_z.to_3d(), clear_outer=remove) 1804 elif s.type == 'LINK_VALLEY': 1805 p0 = s.p0 - s.cross_z.normalized() * d.tile_couloir 1806 self.bissect(bm, p0.to_3d(), s.cross_z.to_3d(), clear_outer=remove) 1807 elif s.type in {'LINK_HIP', 'LINK'}: 1808 self.bissect(bm, s.p0.to_3d(), s.cross_z.to_3d(), clear_outer=remove) 1809 1810 # when not convex, select and remove outer parts 1811 if not pan.convex: 1812 """ 1813 /* del "context" slot values, used for operator too */ 1814 enum { 1815 DEL_VERTS = 1, 1816 DEL_EDGES, 1817 DEL_ONLYFACES, 1818 DEL_EDGESFACES, 1819 DEL_FACES, 1820 /* A version of 'DEL_FACES' that keeps edges on face boundaries, 1821 * allowing the surrounding edge-loop to be kept from removed face regions. */ 1822 DEL_FACES_KEEP_BOUNDARY, 1823 DEL_ONLYTAGGED 1824 }; 1825 """ 1826 # Build boundary including borders and bottom offsets 1827 new_s = None 1828 segs = [] 1829 for s in pan.segs: 1830 if s.length > 0: 1831 if s.type == 'LINK_VALLEY': 1832 offset = -d.tile_couloir 1833 elif s.type == 'BOTTOM': 1834 offset = d.tile_border 1835 elif s.type == 'SIDE': 1836 offset = d.tile_side 1837 else: 1838 offset = 0 1839 new_s = s.make_offset(offset, new_s) 1840 segs.append(new_s) 1841 1842 if len(segs) > 0: 1843 # last / first intersection 1844 res, p, t = segs[0].intersect(segs[-1]) 1845 if res: 1846 segs[0].p0 = p 1847 segs[-1].p1 = p 1848 f_geom = [f for f in bm.faces if not pan.inside(f.calc_center_median().to_2d(), segs)] 1849 if len(f_geom) > 0: 1850 bmesh.ops.delete(bm, geom=f_geom, context="FACES") 1851 1852 self.cut_holes(bm, pan) 1853 1854 bmesh.ops.dissolve_limit(bm, 1855 angle_limit=0.01, 1856 use_dissolve_boundaries=False, 1857 verts=bm.verts[:], 1858 edges=bm.edges[:], 1859 delimit={'MATERIAL'}) 1860 1861 if d.tile_bevel: 1862 geom = bm.verts[:] 1863 geom.extend(bm.edges[:]) 1864 bmesh.ops.bevel(bm, 1865 geom=geom, 1866 offset=d.tile_bevel_amt, 1867 offset_type=offset_type, 1868 segments=d.tile_bevel_segs, 1869 profile=0.5, 1870 # vertex_only=False, 1871 clamp_overlap=True, 1872 material=-1) 1873 1874 if d.tile_solidify: 1875 geom = bm.faces[:] 1876 verts = bm.verts[:] 1877 bmesh.ops.solidify(bm, geom=geom, thickness=0.0001) 1878 bmesh.ops.translate(bm, vec=vz * d.tile_height, space=o.matrix_world, verts=verts) 1879 1880 # merge with object 1881 bmed.bmesh_join(context, o, [bm], normal_update=True) 1882 bpy.ops.object.mode_set(mode='OBJECT') 1883 1884 # if d.quick_edit: 1885 # context.scene.archipack_progress = -1 1886 1887 def _bargeboard(self, s, i, boundary, pan, 1888 width, height, altitude, offset, idmat, 1889 verts, faces, edges, matids, uvs): 1890 1891 f = len(verts) 1892 1893 s0 = s.offset(offset - width) 1894 s1 = s.offset(offset) 1895 1896 p0 = s0.p0 1897 p1 = s1.p0 1898 p2 = s0.p1 1899 p3 = s1.p1 1900 1901 s2 = boundary.last_seg(i) 1902 s3 = boundary.next_seg(i) 1903 1904 if s2.type == 'SIDE': 1905 # intersect last seg offset 1906 s4 = s2.offset(offset - width) 1907 s5 = s2.offset(offset) 1908 res, p, t = s4.intersect(s0) 1909 if res: 1910 p0 = p 1911 res, p, t = s5.intersect(s1) 1912 if res: 1913 p1 = p 1914 1915 elif s2.type == 'AXIS' or 'LINK' in s2.type: 1916 # intersect axis or link seg 1917 res, p, t = s2.intersect(s0) 1918 if res: 1919 p0 = p 1920 res, p, t = s2.intersect(s1) 1921 if res: 1922 p1 = p 1923 1924 if s3.type == 'SIDE': 1925 # intersect next seg offset 1926 s4 = s3.offset(offset - width) 1927 s5 = s3.offset(offset) 1928 res, p, t = s4.intersect(s0) 1929 if res: 1930 p2 = p 1931 res, p, t = s5.intersect(s1) 1932 if res: 1933 p3 = p 1934 1935 elif s3.type == 'AXIS' or 'LINK' in s3.type: 1936 # intersect axis or link seg 1937 res, p, t = s3.intersect(s0) 1938 if res: 1939 p2 = p 1940 res, p, t = s3.intersect(s1) 1941 if res: 1942 p3 = p 1943 1944 x0, y0 = p0 1945 x1, y1 = p1 1946 x2, y2 = p3 1947 x3, y3 = p2 1948 1949 z0 = self.z + altitude + pan.altitude(p0) 1950 z1 = self.z + altitude + pan.altitude(p1) 1951 z2 = self.z + altitude + pan.altitude(p3) 1952 z3 = self.z + altitude + pan.altitude(p2) 1953 1954 verts.extend([ 1955 (x0, y0, z0), 1956 (x1, y1, z1), 1957 (x2, y2, z2), 1958 (x3, y3, z3), 1959 ]) 1960 z0 -= height 1961 z1 -= height 1962 z2 -= height 1963 z3 -= height 1964 verts.extend([ 1965 (x0, y0, z0), 1966 (x1, y1, z1), 1967 (x2, y2, z2), 1968 (x3, y3, z3), 1969 ]) 1970 1971 faces.extend([ 1972 # top 1973 (f, f + 1, f + 2, f + 3), 1974 # sides 1975 (f, f + 4, f + 5, f + 1), 1976 (f + 1, f + 5, f + 6, f + 2), 1977 (f + 2, f + 6, f + 7, f + 3), 1978 (f + 3, f + 7, f + 4, f), 1979 # bottom 1980 (f + 4, f + 7, f + 6, f + 5) 1981 ]) 1982 edges.append([f, f + 3]) 1983 edges.append([f + 1, f + 2]) 1984 edges.append([f + 4, f + 7]) 1985 edges.append([f + 5, f + 6]) 1986 1987 matids.extend([idmat, idmat, idmat, idmat, idmat, idmat]) 1988 uvs.extend([ 1989 [(0, 0), (0, 1), (1, 1), (1, 0)], 1990 [(0, 0), (0, 1), (1, 1), (1, 0)], 1991 [(0, 0), (0, 1), (1, 1), (1, 0)], 1992 [(0, 0), (0, 1), (1, 1), (1, 0)], 1993 [(0, 0), (0, 1), (1, 1), (1, 0)], 1994 [(0, 0), (0, 1), (1, 1), (1, 0)] 1995 ]) 1996 1997 def bargeboard(self, d, verts, faces, edges, matids, uvs): 1998 1999 ##################### 2000 # Vire-vents 2001 ##################### 2002 2003 idmat = 1 2004 for pan in self.pans: 2005 2006 for hole in pan.holes: 2007 for i, s in enumerate(hole.segs): 2008 if s.type == 'SIDE': 2009 self._bargeboard(s, 2010 i, 2011 hole, pan, 2012 d.bargeboard_width, 2013 d.bargeboard_height, 2014 d.bargeboard_altitude, 2015 d.bargeboard_offset, 2016 idmat, 2017 verts, 2018 faces, 2019 edges, 2020 matids, 2021 uvs) 2022 2023 for i, s in enumerate(pan.segs): 2024 if s.type == 'SIDE': 2025 self._bargeboard(s, 2026 i, 2027 pan, pan, 2028 d.bargeboard_width, 2029 d.bargeboard_height, 2030 d.bargeboard_altitude, 2031 d.bargeboard_offset, 2032 idmat, 2033 verts, 2034 faces, 2035 edges, 2036 matids, 2037 uvs) 2038 2039 def _fascia(self, s, i, boundary, pan, tri_0, tri_1, 2040 width, height, altitude, offset, idmat, 2041 verts, faces, edges, matids, uvs): 2042 2043 f = len(verts) 2044 s0 = s.offset(offset) 2045 s1 = s.offset(offset + width) 2046 2047 s2 = boundary.last_seg(i) 2048 s3 = boundary.next_seg(i) 2049 s4 = s2 2050 s5 = s3 2051 2052 p0 = s0.p0 2053 p1 = s1.p0 2054 p2 = s0.p1 2055 p3 = s1.p1 2056 2057 # find last neighbor depending on type 2058 if s2.type == 'AXIS' or 'LINK' in s2.type: 2059 # apply only on boundaries 2060 if not s.is_hole: 2061 # use last axis 2062 if pan.side == 'LEFT': 2063 s6 = pan.next_cross 2064 else: 2065 s6 = pan.last_cross 2066 if tri_0: 2067 s2 = s.copy 2068 else: 2069 s2 = s2.oposite 2070 s2.v = (s.sized_normal(0, 1).v + s6.v).normalized() 2071 s4 = s2 2072 2073 elif s2.type == 'SIDE': 2074 s2 = s.copy 2075 s2.type = 'SIDE' 2076 s2.v = s.sized_normal(0, 1).v 2077 s4 = s2 2078 else: 2079 s2 = s2.offset(offset) 2080 s4 = s2.offset(offset + width) 2081 2082 # find next neighbor depending on type 2083 if s3.type == 'AXIS' or 'LINK' in s3.type: 2084 if not s.is_hole: 2085 # use last axis 2086 if pan.side == 'LEFT': 2087 s6 = pan.last_cross 2088 else: 2089 s6 = pan.next_cross 2090 if tri_1: 2091 s3 = s.oposite 2092 else: 2093 s3 = s3.copy 2094 s3.v = (s.sized_normal(0, 1).v + s6.v).normalized() 2095 s5 = s3 2096 elif s3.type == 'SIDE': 2097 # when next is side, use perpendicular 2098 s3 = s.oposite 2099 s3.type = 'SIDE' 2100 s3.v = s.sized_normal(0, 1).v 2101 s5 = s3 2102 else: 2103 s3 = s3.offset(offset) 2104 s5 = s3.offset(offset + width) 2105 2106 # units vectors and scale 2107 # is unit normal on sides 2108 # print("s.p:%s, s.v:%s s1.p::%s s1.v::%s" % (s.p, s.v, s1.p, s1.v)) 2109 res, p, t = s0.intersect(s2) 2110 if res: 2111 p0 = p 2112 res, p, t = s0.intersect(s3) 2113 if res: 2114 p1 = p 2115 res, p, t = s1.intersect(s4) 2116 if res: 2117 p2 = p 2118 res, p, t = s1.intersect(s5) 2119 if res: 2120 p3 = p 2121 2122 x0, y0 = p0 2123 x1, y1 = p2 2124 x2, y2 = p3 2125 x3, y3 = p1 2126 2127 z0 = self.z + altitude + pan.altitude(p0) 2128 z1 = self.z + altitude + pan.altitude(p2) 2129 z2 = self.z + altitude + pan.altitude(p3) 2130 z3 = self.z + altitude + pan.altitude(p1) 2131 2132 verts.extend([ 2133 (x0, y0, z0), 2134 (x1, y1, z1), 2135 (x2, y2, z2), 2136 (x3, y3, z3), 2137 ]) 2138 2139 z0 -= height 2140 z1 -= height 2141 z2 -= height 2142 z3 -= height 2143 verts.extend([ 2144 (x0, y0, z0), 2145 (x1, y1, z1), 2146 (x2, y2, z2), 2147 (x3, y3, z3), 2148 ]) 2149 2150 faces.extend([ 2151 # top 2152 (f, f + 1, f + 2, f + 3), 2153 # sides 2154 (f, f + 4, f + 5, f + 1), 2155 (f + 1, f + 5, f + 6, f + 2), 2156 (f + 2, f + 6, f + 7, f + 3), 2157 (f + 3, f + 7, f + 4, f), 2158 # bottom 2159 (f + 4, f + 7, f + 6, f + 5) 2160 ]) 2161 edges.append([f, f + 3]) 2162 edges.append([f + 1, f + 2]) 2163 edges.append([f + 4, f + 7]) 2164 edges.append([f + 5, f + 6]) 2165 matids.extend([idmat, idmat, idmat, idmat, idmat, idmat]) 2166 uvs.extend([ 2167 [(0, 0), (0, 1), (1, 1), (1, 0)], 2168 [(0, 0), (0, 1), (1, 1), (1, 0)], 2169 [(0, 0), (0, 1), (1, 1), (1, 0)], 2170 [(0, 0), (0, 1), (1, 1), (1, 0)], 2171 [(0, 0), (0, 1), (1, 1), (1, 0)], 2172 [(0, 0), (0, 1), (1, 1), (1, 0)] 2173 ]) 2174 2175 def fascia(self, d, verts, faces, edges, matids, uvs): 2176 2177 ##################### 2178 # Larmiers 2179 ##################### 2180 2181 idmat = 2 2182 for pan in self.pans: 2183 2184 for hole in pan.holes: 2185 for i, s in enumerate(hole.segs): 2186 if s.type == 'BOTTOM': 2187 self._fascia(s, 2188 i, 2189 hole, pan, 2190 False, False, 2191 d.fascia_width, 2192 d.fascia_height, 2193 d.fascia_altitude, 2194 d.fascia_offset, 2195 idmat, 2196 verts, 2197 faces, 2198 edges, 2199 matids, 2200 uvs) 2201 2202 for i, s in enumerate(pan.segs): 2203 if s.type == 'BOTTOM': 2204 2205 tri_0 = pan.node_tri 2206 tri_1 = pan.next_tri 2207 2208 # triangular ends apply on boundary only 2209 # unless cut, boundary is parallel to axis 2210 # except for triangular ends 2211 if pan.side == 'LEFT': 2212 tri_0, tri_1 = tri_1, tri_0 2213 2214 self._fascia(s, 2215 i, 2216 pan, pan, 2217 tri_0, tri_1, 2218 d.fascia_width, 2219 d.fascia_height, 2220 d.fascia_altitude, 2221 d.fascia_offset, 2222 idmat, 2223 verts, 2224 faces, 2225 edges, 2226 matids, 2227 uvs) 2228 2229 continue 2230 2231 f = len(verts) 2232 s0 = s.offset(d.fascia_width) 2233 2234 s1 = pan.last_seg(i) 2235 s2 = pan.next_seg(i) 2236 2237 # triangular ends apply on boundary only 2238 # unless cut, boundary is parallel to axis 2239 # except for triangular ends 2240 2241 tri_0 = (pan.node_tri and not s.is_hole) or pan.is_tri 2242 tri_1 = (pan.next_tri and not s.is_hole) or pan.is_tri 2243 2244 if pan.side == 'LEFT': 2245 tri_0, tri_1 = tri_1, tri_0 2246 2247 # tiangular use bottom segment direction 2248 # find last neighbor depending on type 2249 if s1.type == 'AXIS' or 'LINK' in s1.type: 2250 # apply only on boundaries 2251 if not s.is_hole: 2252 # use last axis 2253 if pan.side == 'LEFT': 2254 s3 = pan.next_cross 2255 else: 2256 s3 = pan.last_cross 2257 if tri_0: 2258 s1 = s.copy 2259 else: 2260 s1 = s1.oposite 2261 s1.v = (s.sized_normal(0, 1).v + s3.v).normalized() 2262 elif s1.type == 'SIDE': 2263 s1 = s.copy 2264 s1.type = 'SIDE' 2265 s1.v = s.sized_normal(0, 1).v 2266 else: 2267 s1 = s1.offset(d.fascia_width) 2268 2269 # find next neighbor depending on type 2270 if s2.type == 'AXIS' or 'LINK' in s2.type: 2271 if not s.is_hole: 2272 # use last axis 2273 if pan.side == 'LEFT': 2274 s3 = pan.last_cross 2275 else: 2276 s3 = pan.next_cross 2277 if tri_1: 2278 s2 = s.oposite 2279 else: 2280 s2 = s2.copy 2281 s2.v = (s.sized_normal(0, 1).v + s3.v).normalized() 2282 elif s2.type == 'SIDE': 2283 s2 = s.oposite 2284 s2.type = 'SIDE' 2285 s2.v = s.sized_normal(0, 1).v 2286 else: 2287 2288 s2 = s2.offset(d.fascia_width) 2289 2290 # units vectors and scale 2291 # is unit normal on sides 2292 # print("s.p:%s, s.v:%s s1.p::%s s1.v::%s" % (s.p, s.v, s1.p, s1.v)) 2293 res, p0, t = s0.intersect(s1) 2294 res, p1, t = s0.intersect(s2) 2295 2296 x0, y0 = s.p0 2297 x1, y1 = p0 2298 x2, y2 = p1 2299 x3, y3 = s.p1 2300 z0 = self.z + d.fascia_altitude + pan.altitude(s.p0) 2301 z1 = self.z + d.fascia_altitude + pan.altitude(s.p1) 2302 verts.extend([ 2303 (x0, y0, z0), 2304 (x1, y1, z0), 2305 (x2, y2, z1), 2306 (x3, y3, z1), 2307 ]) 2308 z0 -= d.fascia_height 2309 z1 -= d.fascia_height 2310 verts.extend([ 2311 (x0, y0, z0), 2312 (x1, y1, z0), 2313 (x2, y2, z1), 2314 (x3, y3, z1), 2315 ]) 2316 2317 faces.extend([ 2318 # top 2319 (f, f + 1, f + 2, f + 3), 2320 # sides 2321 (f, f + 4, f + 5, f + 1), 2322 (f + 1, f + 5, f + 6, f + 2), 2323 (f + 2, f + 6, f + 7, f + 3), 2324 (f + 3, f + 7, f + 4, f), 2325 # bottom 2326 (f + 4, f + 7, f + 6, f + 5) 2327 ]) 2328 edges.append([f, f + 3]) 2329 edges.append([f + 1, f + 2]) 2330 edges.append([f + 4, f + 7]) 2331 edges.append([f + 5, f + 6]) 2332 matids.extend([idmat, idmat, idmat, idmat, idmat, idmat]) 2333 uvs.extend([ 2334 [(0, 0), (0, 1), (1, 1), (1, 0)], 2335 [(0, 0), (0, 1), (1, 1), (1, 0)], 2336 [(0, 0), (0, 1), (1, 1), (1, 0)], 2337 [(0, 0), (0, 1), (1, 1), (1, 0)], 2338 [(0, 0), (0, 1), (1, 1), (1, 0)], 2339 [(0, 0), (0, 1), (1, 1), (1, 0)] 2340 ]) 2341 2342 def gutter(self, d, verts, faces, edges, matids, uvs): 2343 2344 ##################### 2345 # Chenaux 2346 ##################### 2347 2348 idmat = 5 2349 2350 # caps at start and end 2351 if d.gutter_segs % 2 == 1: 2352 n_faces = int((d.gutter_segs - 1) / 2) 2353 else: 2354 n_faces = int((d.gutter_segs / 2) - 1) 2355 2356 df = 2 * d.gutter_segs + 1 2357 2358 for pan in self.pans: 2359 for i, s in enumerate(pan.segs): 2360 2361 if s.type == 'BOTTOM': 2362 f = len(verts) 2363 2364 s0 = s.offset(d.gutter_dist + d.gutter_width) 2365 2366 s1 = pan.last_seg(i) 2367 s2 = pan.next_seg(i) 2368 2369 p0 = s0.p0 2370 p1 = s0.p1 2371 2372 tri_0 = pan.node_tri or pan.is_tri 2373 tri_1 = pan.next_tri or pan.is_tri 2374 2375 if pan.side == 'LEFT': 2376 tri_0, tri_1 = tri_1, tri_0 2377 2378 f = len(verts) 2379 2380 # tiangular use segment direction 2381 # find last neighbor depending on type 2382 if s1.type == 'AXIS' or 'LINK' in s1.type: 2383 # apply only on boundaries 2384 if not s.is_hole: 2385 # use last axis 2386 if pan.side == 'LEFT': 2387 s3 = pan.next_cross 2388 else: 2389 s3 = pan.last_cross 2390 if tri_0: 2391 s1 = s.copy 2392 else: 2393 s1 = s1.oposite 2394 s1.v = (s.sized_normal(0, 1).v + s3.v).normalized() 2395 elif s1.type == 'SIDE': 2396 s1 = s.copy 2397 s1.type = 'SIDE' 2398 s1.v = s.sized_normal(0, 1).v 2399 else: 2400 s1 = s1.offset(d.gutter_dist + d.gutter_width) 2401 2402 # find next neighbor depending on type 2403 if s2.type == 'AXIS' or 'LINK' in s2.type: 2404 if not s.is_hole: 2405 # use last axis 2406 if pan.side == 'LEFT': 2407 s3 = pan.last_cross 2408 else: 2409 s3 = pan.next_cross 2410 if tri_1: 2411 s2 = s.oposite 2412 else: 2413 s2 = s2.copy 2414 s2.v = (s.sized_normal(0, 1).v + s3.v).normalized() 2415 elif s2.type == 'SIDE': 2416 s2 = s.oposite 2417 s2.type = 'SIDE' 2418 s2.v = s.sized_normal(0, 1).v 2419 else: 2420 s2 = s2.offset(d.gutter_dist + d.gutter_width) 2421 2422 # units vectors and scale 2423 # is unit normal on sides 2424 # print("s.p:%s, s.v:%s s1.p::%s s1.v::%s" % (s.p, s.v, s1.p, s1.v)) 2425 res, p, t = s0.intersect(s1) 2426 if res: 2427 p0 = p 2428 res, p, t = s0.intersect(s2) 2429 if res: 2430 p1 = p 2431 """ 2432 f = len(verts) 2433 verts.extend([s1.p0.to_3d(), s1.p1.to_3d()]) 2434 edges.append([f, f + 1]) 2435 2436 f = len(verts) 2437 verts.extend([s2.p0.to_3d(), s2.p1.to_3d()]) 2438 edges.append([f, f + 1]) 2439 continue 2440 """ 2441 2442 v0 = p0 - s.p0 2443 v1 = p1 - s.p1 2444 2445 scale_0 = v0.length / (d.gutter_dist + d.gutter_width) 2446 scale_1 = v1.length / (d.gutter_dist + d.gutter_width) 2447 2448 s3 = Line(s.p0, v0.normalized()) 2449 s4 = Line(s.p1, v1.normalized()) 2450 2451 zt = self.z + d.fascia_altitude + pan.altitude(s3.p0) 2452 z0 = self.z + d.gutter_alt + pan.altitude(s3.p0) 2453 z1 = z0 - 0.5 * d.gutter_width 2454 z2 = z1 - 0.5 * d.gutter_width 2455 z3 = z1 - 0.5 * d.gutter_boudin 2456 dz0 = z2 - z1 2457 dz1 = z3 - z1 2458 2459 tt = scale_0 * d.fascia_width 2460 t0 = scale_0 * d.gutter_dist 2461 t1 = t0 + scale_0 * (0.5 * d.gutter_width) 2462 t2 = t1 + scale_0 * (0.5 * d.gutter_width) 2463 t3 = t2 + scale_0 * (0.5 * d.gutter_boudin) 2464 2465 # bord tablette 2466 xt, yt = s3.lerp(tt) 2467 2468 # bord 2469 x0, y0 = s3.lerp(t0) 2470 # axe chenaux 2471 x1, y1 = s3.lerp(t1) 2472 # bord boudin interieur 2473 x2, y2 = s3.lerp(t2) 2474 # axe boudin 2475 x3, y3 = s3.lerp(t3) 2476 2477 dx = x0 - x1 2478 dy = y0 - y1 2479 2480 verts.append((xt, yt, zt)) 2481 # chenaux 2482 da = pi / d.gutter_segs 2483 for i in range(d.gutter_segs): 2484 sa = sin(i * da) 2485 ca = cos(i * da) 2486 verts.append((x1 + dx * ca, y1 + dy * ca, z1 + dz0 * sa)) 2487 2488 dx = x2 - x3 2489 dy = y2 - y3 2490 2491 # boudin 2492 da = -pi / (0.75 * d.gutter_segs) 2493 for i in range(d.gutter_segs): 2494 sa = sin(i * da) 2495 ca = cos(i * da) 2496 verts.append((x3 + dx * ca, y3 + dy * ca, z1 + dz1 * sa)) 2497 2498 zt = self.z + d.fascia_altitude + pan.altitude(s4.p0) 2499 z0 = self.z + d.gutter_alt + pan.altitude(s4.p0) 2500 z1 = z0 - 0.5 * d.gutter_width 2501 z2 = z1 - 0.5 * d.gutter_width 2502 z3 = z1 - 0.5 * d.gutter_boudin 2503 dz0 = z2 - z1 2504 dz1 = z3 - z1 2505 tt = scale_1 * d.fascia_width 2506 t0 = scale_1 * d.gutter_dist 2507 t1 = t0 + scale_1 * (0.5 * d.gutter_width) 2508 t2 = t1 + scale_1 * (0.5 * d.gutter_width) 2509 t3 = t2 + scale_1 * (0.5 * d.gutter_boudin) 2510 2511 # bord tablette 2512 xt, yt = s4.lerp(tt) 2513 2514 # bord 2515 x0, y0 = s4.lerp(t0) 2516 # axe chenaux 2517 x1, y1 = s4.lerp(t1) 2518 # bord boudin interieur 2519 x2, y2 = s4.lerp(t2) 2520 # axe boudin 2521 x3, y3 = s4.lerp(t3) 2522 2523 dx = x0 - x1 2524 dy = y0 - y1 2525 2526 # tablette 2527 verts.append((xt, yt, zt)) 2528 faces.append((f + df, f, f + 1, f + df + 1)) 2529 uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)]) 2530 matids.append(idmat) 2531 2532 # chenaux 2533 da = pi / d.gutter_segs 2534 for i in range(d.gutter_segs): 2535 sa = sin(i * da) 2536 ca = cos(i * da) 2537 verts.append((x1 + dx * ca, y1 + dy * ca, z1 + dz0 * sa)) 2538 2539 dx = x2 - x3 2540 dy = y2 - y3 2541 2542 # boudin 2543 da = -pi / (0.75 * d.gutter_segs) 2544 for i in range(d.gutter_segs): 2545 sa = sin(i * da) 2546 ca = cos(i * da) 2547 verts.append((x3 + dx * ca, y3 + dy * ca, z1 + dz1 * sa)) 2548 2549 df = 2 * d.gutter_segs + 1 2550 2551 for i in range(1, 2 * d.gutter_segs): 2552 j = i + f 2553 faces.append((j, j + df, j + df + 1, j + 1)) 2554 uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)]) 2555 matids.append(idmat) 2556 2557 """ 2558 segs = 6 2559 2560 n_faces = segs / 2 - 1 2561 2562 0 6 2563 1 5 2564 2 4 2565 3 2566 """ 2567 # close start 2568 if s1.type == 'SIDE': 2569 2570 if d.gutter_segs % 2 == 0: 2571 faces.append((f + n_faces + 3, f + n_faces + 1, f + n_faces + 2)) 2572 uvs.append([(0, 0), (1, 0), (0.5, -0.5)]) 2573 matids.append(idmat) 2574 2575 for i in range(n_faces): 2576 2577 j = i + f + 1 2578 k = f + d.gutter_segs - i 2579 faces.append((j + 1, k, k + 1, j)) 2580 uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)]) 2581 matids.append(idmat) 2582 2583 # close end 2584 if s2.type == 'SIDE': 2585 2586 f += 2 * d.gutter_segs + 1 2587 2588 if d.gutter_segs % 2 == 0: 2589 faces.append((f + n_faces + 1, f + n_faces + 3, f + n_faces + 2)) 2590 uvs.append([(0, 0), (1, 0), (0.5, -0.5)]) 2591 matids.append(idmat) 2592 2593 for i in range(n_faces): 2594 2595 j = i + f + 1 2596 k = f + d.gutter_segs - i 2597 faces.append((j, k + 1, k, j + 1)) 2598 uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)]) 2599 matids.append(idmat) 2600 2601 def beam_primary(self, d, verts, faces, edges, matids, uvs): 2602 2603 idmat = 3 2604 2605 for pan in self.pans: 2606 for i, s in enumerate(pan.segs): 2607 2608 if s.type == 'AXIS': 2609 2610 #################### 2611 # Poutre Faitiere 2612 #################### 2613 2614 """ 2615 1___________________2 left 2616 0|___________________|3 axis 2617 |___________________| right 2618 5 4 2619 """ 2620 f = len(verts) 2621 2622 s2 = s.offset(-0.5 * d.beam_width) 2623 2624 # offset from roof border 2625 s0 = pan.last_seg(i) 2626 s1 = pan.next_seg(i) 2627 t0 = 0 2628 t1 = 1 2629 2630 s0_tri = pan.next_tri 2631 s1_tri = pan.node_tri 2632 2633 if pan.side == 'LEFT': 2634 s0_tri, s1_tri = s1_tri, s0_tri 2635 2636 if s0.type == 'SIDE' and s.length > 0: 2637 s0 = s0.offset(d.beam_offset) 2638 t0 = -d.beam_offset / s.length 2639 2640 if s0_tri: 2641 p0 = s2.p0 2642 t0 = 0 2643 else: 2644 res, p0, t = s2.intersect(s0) 2645 if not res: 2646 continue 2647 2648 if s1.type == 'SIDE' and s.length > 0: 2649 s1 = s1.offset(d.beam_offset) 2650 t1 = 1 + d.beam_offset / s.length 2651 2652 if s1_tri: 2653 t1 = 1 2654 p1 = s2.p1 2655 else: 2656 res, p1, t = s2.intersect(s1) 2657 if not res: 2658 continue 2659 2660 x0, y0 = p0 2661 x1, y1 = s.lerp(t0) 2662 x2, y2 = p1 2663 x3, y3 = s.lerp(t1) 2664 z0 = self.z + d.beam_alt + pan.altitude(p0) 2665 z1 = z0 - d.beam_height 2666 z2 = self.z + d.beam_alt + pan.altitude(p1) 2667 z3 = z2 - d.beam_height 2668 verts.extend([ 2669 (x0, y0, z0), 2670 (x1, y1, z0), 2671 (x2, y2, z2), 2672 (x3, y3, z2), 2673 (x0, y0, z1), 2674 (x1, y1, z1), 2675 (x2, y2, z3), 2676 (x3, y3, z3), 2677 ]) 2678 if s0_tri or s0.type == 'SIDE': 2679 faces.append((f + 4, f + 5, f + 1, f)) 2680 uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)]) 2681 matids.append(idmat) 2682 if s1_tri or s1.type == 'SIDE': 2683 faces.append((f + 2, f + 3, f + 7, f + 6)) 2684 uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)]) 2685 matids.append(idmat) 2686 2687 faces.extend([ 2688 # internal side 2689 # (f + 1, f + 5, f + 7, f + 3), 2690 # external side 2691 (f + 2, f + 6, f + 4, f), 2692 # top 2693 (f, f + 1, f + 3, f + 2), 2694 # bottom 2695 (f + 5, f + 4, f + 6, f + 7) 2696 ]) 2697 matids.extend([ 2698 idmat, idmat, idmat 2699 ]) 2700 uvs.extend([ 2701 [(0, 0), (0, 1), (1, 1), (1, 0)], 2702 [(0, 0), (0, 1), (1, 1), (1, 0)], 2703 [(0, 0), (0, 1), (1, 1), (1, 0)] 2704 ]) 2705 2706 def rafter(self, context, o, d): 2707 2708 idmat = 4 2709 2710 # Rafters / Chevrons 2711 start = max(0.001 + 0.5 * d.rafter_width, d.rafter_start) 2712 2713 holes_offset = -d.rafter_width 2714 2715 # build temp bmesh and bissect 2716 for pan in self.pans: 2717 tmin, tmax, ysize = pan.tmin, pan.tmax, pan.ysize 2718 2719 # print("tmin:%s tmax:%s ysize:%s" % (tmin, tmax, ysize)) 2720 2721 f = 0 2722 2723 verts = [] 2724 faces = [] 2725 matids = [] 2726 uvs = [] 2727 alt = d.rafter_alt 2728 seg = pan.fake_axis 2729 2730 t0 = tmin + (start - 0.5 * d.rafter_width) / seg.length 2731 t1 = tmin + (start + 0.5 * d.rafter_width) / seg.length 2732 2733 tx = start / seg.length 2734 dt = d.rafter_spacing / seg.length 2735 2736 n_items = max(1, round((tmax - tmin) / dt, 0)) 2737 2738 dt = ((tmax - tmin) - 2 * tx) / n_items 2739 2740 for j in range(int(n_items) + 1): 2741 n0 = seg.sized_normal(t1 + j * dt, - ysize) 2742 n1 = seg.sized_normal(t0 + j * dt, - ysize) 2743 f = len(verts) 2744 2745 z0 = self.z + alt + pan.altitude(n0.p0) 2746 x0, y0 = n0.p0 2747 z1 = self.z + alt + pan.altitude(n0.p1) 2748 x1, y1 = n0.p1 2749 z2 = self.z + alt + pan.altitude(n1.p0) 2750 x2, y2 = n1.p0 2751 z3 = self.z + alt + pan.altitude(n1.p1) 2752 x3, y3 = n1.p1 2753 2754 verts.extend([ 2755 (x0, y0, z0), 2756 (x1, y1, z1), 2757 (x2, y2, z2), 2758 (x3, y3, z3) 2759 ]) 2760 2761 faces.append((f + 1, f, f + 2, f + 3)) 2762 matids.append(idmat) 2763 uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)]) 2764 2765 bm = bmed.buildmesh( 2766 context, o, verts, faces, matids=matids, uvs=uvs, 2767 weld=False, clean=False, auto_smooth=True, temporary=True) 2768 2769 self.cut_boundary(bm, pan) 2770 self.cut_holes(bm, pan, offset={'DEFAULT': holes_offset}) 2771 2772 bmesh.ops.dissolve_limit(bm, 2773 angle_limit=0.01, 2774 use_dissolve_boundaries=False, 2775 verts=bm.verts, 2776 edges=bm.edges, 2777 delimit={'MATERIAL'}) 2778 2779 geom = bm.faces[:] 2780 verts = bm.verts[:] 2781 bmesh.ops.solidify(bm, geom=geom, thickness=0.0001) 2782 bmesh.ops.translate(bm, vec=Vector((0, 0, -d.rafter_height)), space=o.matrix_world, verts=verts) 2783 # uvs for sides 2784 uvs = [(0, 0), (1, 0), (1, 1), (0, 1)] 2785 layer = bm.loops.layers.uv.verify() 2786 for i, face in enumerate(bm.faces): 2787 if len(face.loops) == 4: 2788 for j, loop in enumerate(face.loops): 2789 loop[layer].uv = uvs[j] 2790 2791 # merge with object 2792 bmed.bmesh_join(context, o, [bm], normal_update=True) 2793 2794 bpy.ops.object.mode_set(mode='OBJECT') 2795 2796 def hips(self, d, verts, faces, edges, matids, uvs): 2797 2798 idmat_valley = 5 2799 idmat = 6 2800 idmat_poutre = 4 2801 2802 sx, sy, sz = d.hip_size_x, d.hip_size_y, d.hip_size_z 2803 2804 if d.hip_model == 'ROUND': 2805 2806 # round hips 2807 t_pts = [Vector((sx * x, sy * y, sz * z)) for x, y, z in [ 2808 (-0.5, 0.34, 0.08), (-0.5, 0.32, 0.19), (0.5, -0.4, -0.5), 2809 (0.5, 0.4, -0.5), (-0.5, 0.26, 0.28), (-0.5, 0.16, 0.34), 2810 (-0.5, 0.05, 0.37), (-0.5, -0.05, 0.37), (-0.5, -0.16, 0.34), 2811 (-0.5, -0.26, 0.28), (-0.5, -0.32, 0.19), (-0.5, -0.34, 0.08), 2812 (-0.5, -0.25, -0.5), (-0.5, 0.25, -0.5), (0.5, -0.08, 0.5), 2813 (0.5, -0.5, 0.08), (0.5, -0.24, 0.47), (0.5, -0.38, 0.38), 2814 (0.5, -0.47, 0.24), (0.5, 0.5, 0.08), (0.5, 0.08, 0.5), 2815 (0.5, 0.47, 0.24), (0.5, 0.38, 0.38), (0.5, 0.24, 0.47) 2816 ]] 2817 t_faces = [ 2818 (23, 22, 4, 5), (3, 19, 21, 22, 23, 20, 14, 16, 17, 18, 15, 2), (14, 20, 6, 7), 2819 (18, 17, 9, 10), (15, 18, 10, 11), (21, 19, 0, 1), (17, 16, 8, 9), 2820 (13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 1, 0), (19, 3, 13, 0), (20, 23, 5, 6), (22, 21, 1, 4), 2821 (3, 2, 12, 13), (2, 15, 11, 12), (16, 14, 7, 8) 2822 ] 2823 t_uvs = [ 2824 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2825 [(0.5, 1.0), (0.75, 0.93), (0.93, 0.75), 2826 (1.0, 0.5), (0.93, 0.25), (0.75, 0.07), 2827 (0.5, 0.0), (0.25, 0.07), (0.07, 0.25), 2828 (0.0, 0.5), (0.07, 0.75), (0.25, 0.93)], 2829 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2830 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2831 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2832 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2833 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2834 [(0.5, 1.0), (0.75, 0.93), (0.93, 0.75), 2835 (1.0, 0.5), (0.93, 0.25), (0.75, 0.07), 2836 (0.5, 0.0), (0.25, 0.07), (0.07, 0.25), 2837 (0.0, 0.5), (0.07, 0.75), (0.25, 0.93)], 2838 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2839 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2840 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2841 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2842 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2843 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)] 2844 ] 2845 # affect vertex with slope 2846 t_left = [] 2847 t_right = [] 2848 2849 elif d.hip_model == 'ETERNIT': 2850 2851 # square hips "eternit like" 2852 t_pts = [Vector((sx * x, sy * y, sz * z)) for x, y, z in [ 2853 (0.5, 0.5, 0.0), (-0.5, 0.5, -0.5), (0.5, -0.5, 0.0), 2854 (-0.5, -0.5, -0.5), (0.5, 0.0, 0.0), (-0.5, -0.0, -0.5), 2855 (0.5, 0.0, 0.5), (0.5, -0.5, 0.5), (-0.5, -0.5, 0.0), 2856 (-0.5, -0.0, 0.0), (0.5, 0.5, 0.5), (-0.5, 0.5, 0.0)] 2857 ] 2858 t_faces = [ 2859 (4, 2, 3, 5), (0, 4, 5, 1), (6, 9, 8, 7), 2860 (10, 11, 9, 6), (0, 10, 6, 4), (5, 9, 11, 1), 2861 (2, 7, 8, 3), (1, 11, 10, 0), (4, 6, 7, 2), (3, 8, 9, 5) 2862 ] 2863 t_uvs = [ 2864 [(0.0, 0.5), (0.0, 1.0), (1.0, 1.0), (1.0, 0.5)], [(0.0, 0.0), (0.0, 0.5), (1.0, 0.5), (1.0, 0.0)], 2865 [(0.0, 0.5), (1.0, 0.5), (1.0, 1.0), (0.0, 1.0)], [(0.0, 0.0), (1.0, 0.0), (1.0, 0.5), (0.0, 0.5)], 2866 [(0.0, 0.5), (0.0, 1.0), (0.5, 1.0), (0.5, 0.5)], [(0.5, 0.5), (0.5, 1.0), (0.0, 1.0), (0.0, 0.5)], 2867 [(0.0, 0.5), (0.0, 1.0), (1.0, 1.0), (1.0, 0.5)], [(0.0, 0.5), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.5)], 2868 [(0.5, 0.5), (0.5, 1.0), (1.0, 1.0), (1.0, 0.5)], [(0.0, 0.5), (0.0, 1.0), (-0.5, 1.0), (-0.5, 0.5)] 2869 ] 2870 t_left = [2, 3, 7, 8] 2871 t_right = [0, 1, 10, 11] 2872 2873 elif d.hip_model == 'FLAT': 2874 # square hips "eternit like" 2875 t_pts = [Vector((sx * x, sy * y, sz * z)) for x, y, z in [ 2876 (-0.5, -0.4, 0.0), (-0.5, -0.4, 0.5), (-0.5, 0.4, 0.0), 2877 (-0.5, 0.4, 0.5), (0.5, -0.5, 0.5), (0.5, -0.5, 1.0), 2878 (0.5, 0.5, 0.5), (0.5, 0.5, 1.0), (-0.5, 0.33, 0.0), 2879 (-0.5, -0.33, 0.0), (0.5, -0.33, 0.5), (0.5, 0.33, 0.5), 2880 (-0.5, 0.33, -0.5), (-0.5, -0.33, -0.5), (0.5, -0.33, -0.5), 2881 (0.5, 0.33, -0.5)] 2882 ] 2883 t_faces = [ 2884 (0, 1, 3, 2, 8, 9), (2, 3, 7, 6), (6, 7, 5, 4, 10, 11), 2885 (4, 5, 1, 0), (9, 10, 4, 0), (7, 3, 1, 5), 2886 (2, 6, 11, 8), (9, 8, 12, 13), (12, 15, 14, 13), 2887 (8, 11, 15, 12), (10, 9, 13, 14), (11, 10, 14, 15)] 2888 t_uvs = [ 2889 [(0.5, 1.0), (0.93, 0.75), (0.93, 0.25), (0.5, 0.0), (0.07, 0.25), (0.07, 0.75)], 2890 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2891 [(0.5, 1.0), (0.93, 0.75), (0.93, 0.25), (0.5, 0.0), (0.07, 0.25), (0.07, 0.75)], 2892 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2893 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2894 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2895 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2896 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2897 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2898 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2899 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], 2900 [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)] 2901 ] 2902 t_left = [] 2903 t_right = [] 2904 2905 t_idmats = [idmat for f in t_faces] 2906 2907 for pan in self.pans: 2908 for i, s in enumerate(pan.segs): 2909 if ('LINK' in s.type and 2910 d.beam_sec_enable): 2911 ############## 2912 # beam inside 2913 ############## 2914 f = len(verts) 2915 2916 s0 = s.offset(-0.5 * d.beam_sec_width) 2917 2918 s2 = pan.last_seg(i) 2919 s3 = pan.next_seg(i) 2920 p0 = s0.p0 2921 p1 = s0.p1 2922 t0 = 0 2923 t1 = 1 2924 res, p, t = s0.intersect(s2) 2925 if res: 2926 t0 = t 2927 p0 = p 2928 res, p, t = s0.intersect(s3) 2929 if res: 2930 t1 = t 2931 p1 = p 2932 2933 p0 = s.lerp(t0) 2934 p1 = s.lerp(t1) 2935 2936 x0, y0 = s0.lerp(t0) 2937 x1, y1 = s.p0 2938 2939 z0 = self.z + d.beam_sec_alt + pan.altitude(p0) 2940 z1 = z0 - d.beam_sec_height 2941 z2 = self.z + d.beam_sec_alt + pan.altitude(s.p0) 2942 z3 = z2 - d.beam_sec_height 2943 2944 verts.extend([ 2945 (x0, y0, z0), 2946 (x0, y0, z1), 2947 (x1, y1, z2), 2948 (x1, y1, z3) 2949 ]) 2950 2951 x2, y2 = s0.lerp(t1) 2952 x3, y3 = s.p1 2953 2954 z0 = self.z + d.beam_sec_alt + pan.altitude(p1) 2955 z1 = z0 - d.beam_sec_height 2956 z2 = self.z + d.beam_sec_alt + pan.altitude(s.p1) 2957 z3 = z2 - d.beam_sec_height 2958 2959 verts.extend([ 2960 (x2, y2, z0), 2961 (x2, y2, z1), 2962 (x3, y3, z2), 2963 (x3, y3, z3) 2964 ]) 2965 2966 faces.extend([ 2967 (f, f + 4, f + 5, f + 1), 2968 (f + 1, f + 5, f + 7, f + 3), 2969 (f + 2, f + 3, f + 7, f + 6), 2970 (f + 2, f + 6, f + 4, f), 2971 (f, f + 1, f + 3, f + 2), 2972 (f + 5, f + 4, f + 6, f + 7) 2973 ]) 2974 matids.extend([ 2975 idmat_poutre, idmat_poutre, idmat_poutre, 2976 idmat_poutre, idmat_poutre, idmat_poutre 2977 ]) 2978 uvs.extend([ 2979 [(0, 0), (1, 0), (1, 1), (0, 1)], 2980 [(0, 0), (1, 0), (1, 1), (0, 1)], 2981 [(0, 0), (1, 0), (1, 1), (0, 1)], 2982 [(0, 0), (1, 0), (1, 1), (0, 1)], 2983 [(0, 0), (1, 0), (1, 1), (0, 1)], 2984 [(0, 0), (1, 0), (1, 1), (0, 1)] 2985 ]) 2986 2987 if s.type == 'LINK_HIP': 2988 2989 # TODO: 2990 # Slice borders properly 2991 2992 if d.hip_enable: 2993 2994 s0 = pan.last_seg(i) 2995 s1 = pan.next_seg(i) 2996 s2 = s 2997 p0 = s0.p1 2998 p1 = s1.p0 2999 z0 = pan.altitude(p0) 3000 z1 = pan.altitude(p1) 3001 3002 # s0 is top seg 3003 if z1 > z0: 3004 p0, p1 = p1, p0 3005 z0, z1 = z1, z0 3006 s2 = s2.oposite 3007 dz = pan.altitude(s2.sized_normal(0, 1).p1) - z0 3008 3009 if dz < 0: 3010 s1 = s1.offset(d.tile_border) 3011 # vx from p0 to p1 3012 x, y = p1 - p0 3013 v = Vector((x, y, z1 - z0)) 3014 vx = v.normalized() 3015 vy = vx.cross(Vector((0, 0, 1))) 3016 vz = vy.cross(vx) 3017 3018 x0, y0 = p0 + d.hip_alt * vz.to_2d() 3019 z2 = z0 + self.z + d.hip_alt * vz.z 3020 tM = Matrix([ 3021 [vx.x, vy.x, vz.x, x0], 3022 [vx.y, vy.y, vz.y, y0], 3023 [vx.z, vy.z, vz.z, z2], 3024 [0, 0, 0, 1] 3025 ]) 3026 space_x = v.length - d.tile_border 3027 n_x = 1 + int(space_x / d.hip_space_x) 3028 dx = space_x / n_x 3029 x0 = 0.5 * dx 3030 3031 t_verts = [p for p in t_pts] 3032 3033 # apply slope 3034 3035 for i in t_left: 3036 t_verts[i] = t_verts[i].copy() 3037 t_verts[i].z -= dz * t_verts[i].y 3038 for i in t_right: 3039 t_verts[i] = t_verts[i].copy() 3040 t_verts[i].z += dz * t_verts[i].y 3041 3042 for k in range(n_x): 3043 lM = tM @ Matrix([ 3044 [1, 0, 0, x0 + k * dx], 3045 [0, -1, 0, 0], 3046 [0, 0, 1, 0], 3047 [0, 0, 0, 1] 3048 ]) 3049 f = len(verts) 3050 3051 verts.extend([lM @ p for p in t_verts]) 3052 faces.extend([tuple(i + f for i in p) for p in t_faces]) 3053 matids.extend(t_idmats) 3054 uvs.extend(t_uvs) 3055 3056 elif s.type == 'LINK_VALLEY': 3057 if d.valley_enable: 3058 f = len(verts) 3059 s0 = s.offset(-2 * d.tile_couloir) 3060 s1 = pan.last_seg(i) 3061 s2 = pan.next_seg(i) 3062 p0 = s0.p0 3063 p1 = s0.p1 3064 res, p, t = s0.intersect(s1) 3065 if res: 3066 p0 = p 3067 res, p, t = s0.intersect(s2) 3068 if res: 3069 p1 = p 3070 alt = self.z + d.valley_altitude 3071 x0, y0 = s1.p1 3072 x1, y1 = p0 3073 x2, y2 = p1 3074 x3, y3 = s2.p0 3075 z0 = alt + pan.altitude(s1.p1) 3076 z1 = alt + pan.altitude(p0) 3077 z2 = alt + pan.altitude(p1) 3078 z3 = alt + pan.altitude(s2.p0) 3079 3080 verts.extend([ 3081 (x0, y0, z0), 3082 (x1, y1, z1), 3083 (x2, y2, z2), 3084 (x3, y3, z3), 3085 ]) 3086 faces.extend([ 3087 (f, f + 3, f + 2, f + 1) 3088 ]) 3089 matids.extend([ 3090 idmat_valley 3091 ]) 3092 uvs.extend([ 3093 [(0, 0), (1, 0), (1, 1), (0, 1)] 3094 ]) 3095 3096 elif s.type == 'AXIS' and d.hip_enable and pan.side == 'LEFT': 3097 3098 tmin = 0 3099 tmax = 1 3100 s0 = pan.last_seg(i) 3101 if s0.type == 'SIDE' and s.length > 0: 3102 tmin = 0 - d.tile_side / s.length 3103 s1 = pan.next_seg(i) 3104 3105 if s1.type == 'SIDE' and s.length > 0: 3106 tmax = 1 + d.tile_side / s.length 3107 3108 # print("tmin:%s tmax:%s" % (tmin, tmax)) 3109 #################### 3110 # Faitiere 3111 #################### 3112 3113 f = len(verts) 3114 s_len = (tmax - tmin) * s.length 3115 n_obj = 1 + int(s_len / d.hip_space_x) 3116 dx = s_len / n_obj 3117 x0 = 0.5 * dx 3118 v = s.v.normalized() 3119 p0 = s.lerp(tmin) 3120 tM = Matrix([ 3121 [v.x, v.y, 0, p0.x], 3122 [v.y, -v.x, 0, p0.y], 3123 [0, 0, 1, self.z + d.hip_alt], 3124 [0, 0, 0, 1] 3125 ]) 3126 t_verts = [p.copy() for p in t_pts] 3127 3128 # apply slope 3129 for i in t_left: 3130 t_verts[i].z += t_verts[i].y * (pan.other_side.slope - d.tile_size_z / d.tile_size_y) 3131 for i in t_right: 3132 t_verts[i].z -= t_verts[i].y * (pan.slope - d.tile_size_z / d.tile_size_y) 3133 3134 for k in range(n_obj): 3135 lM = tM @ Matrix([ 3136 [1, 0, 0, x0 + k * dx], 3137 [0, -1, 0, 0], 3138 [0, 0, 1, 0], 3139 [0, 0, 0, 1] 3140 ]) 3141 v = len(verts) 3142 verts.extend([lM @ p for p in t_verts]) 3143 faces.extend([tuple(i + v for i in f) for f in t_faces]) 3144 matids.extend(t_idmats) 3145 uvs.extend(t_uvs) 3146 3147 def make_hole(self, context, hole_obj, o, d, update_parent=False): 3148 """ 3149 Hole for t child on parent 3150 create / update a RoofCutter on parent 3151 assume context object is child roof 3152 with parent set 3153 """ 3154 # print("Make hole :%s hole_obj:%s" % (o.name, hole_obj)) 3155 if o.parent is None: 3156 return 3157 # root is a RoofSegment 3158 root = self.nodes[0].root 3159 r_pan = root.right 3160 l_pan = root.left 3161 3162 # merge : 3163 # 5 ____________ 4 3164 # / | 3165 # / left | 3166 # /_____axis_____| 3 <- kill axis and this one 3167 # 0\ | 3168 # \ right | 3169 # 1 \____________| 2 3170 # 3171 # degenerate case: 3172 # 3173 # /| 3174 # / | 3175 # \ | 3176 # \| 3177 # 3178 3179 segs = [] 3180 last = len(r_pan.segs) - 1 3181 for i, seg in enumerate(r_pan.segs): 3182 # r_pan start parent roof side 3183 if i == last: 3184 to_merge = seg.copy 3185 elif seg.type != 'AXIS': 3186 segs.append(seg.copy) 3187 3188 for i, seg in enumerate(l_pan.segs): 3189 # l_pan end parent roof side 3190 if i == 1: 3191 # 0 is axis 3192 to_merge.p1 = seg.p1 3193 segs.append(to_merge) 3194 elif seg.type != 'AXIS': 3195 segs.append(seg.copy) 3196 3197 # if there is side offset: 3198 # create an arrow 3199 # 3200 # 4 s4 3201 # /| 3202 # / |___s1_______ 3203 # / p3 | p2 s3 3204 # 0\ p0___s0_______| p1 3205 # \ | 3206 # 1 \| 3207 s0 = root.left._axis.offset( 3208 max(0.001, 3209 min( 3210 root.right.ysize - 0.001, 3211 root.right.ysize - d.hole_offset_right 3212 ) 3213 )) 3214 s1 = root.left._axis.offset( 3215 -max(0.001, 3216 min( 3217 root.left.ysize - 0.001, 3218 root.left.ysize - d.hole_offset_left 3219 ) 3220 )) 3221 3222 s3 = segs[2].offset( 3223 -min(root.left.xsize - 0.001, d.hole_offset_front) 3224 ) 3225 s4 = segs[0].copy 3226 p1 = s4.p1 3227 s4.p1 = segs[-1].p0 3228 s4.p0 = p1 3229 res, p0, t = s4.intersect(s0) 3230 res, p1, t = s0.intersect(s3) 3231 res, p2, t = s1.intersect(s3) 3232 res, p3, t = s4.intersect(s1) 3233 pts = [] 3234 # pts in cw order for 'DIFFERENCE' mode 3235 pts.extend([segs[-1].p1, segs[-1].p0]) 3236 if (segs[-1].p0 - p3).length > 0.001: 3237 pts.append(p3) 3238 pts.extend([p2, p1]) 3239 if (segs[0].p1 - p0).length > 0.001: 3240 pts.append(p0) 3241 pts.extend([segs[0].p1, segs[0].p0]) 3242 3243 pts = [p.to_3d() for p in pts] 3244 3245 if hole_obj is None: 3246 context.view_layer.objects.active = o.parent 3247 bpy.ops.archipack.roof_cutter(parent=d.t_parent, auto_manipulate=False) 3248 hole_obj = context.active_object 3249 else: 3250 context.view_layer.objects.active = hole_obj 3251 3252 hole_obj.select_set(state=True) 3253 if d.parts[0].a0 < 0: 3254 y = -d.t_dist_y 3255 else: 3256 y = d.t_dist_y 3257 3258 hole_obj.matrix_world = o.matrix_world @ Matrix([ 3259 [1, 0, 0, 0], 3260 [0, 1, 0, y], 3261 [0, 0, 1, 0], 3262 [0, 0, 0, 1] 3263 ]) 3264 3265 hd = archipack_roof_cutter.datablock(hole_obj) 3266 hd.boundary = o.name 3267 hd.update_points(context, hole_obj, pts, update_parent=update_parent) 3268 hole_obj.select_set(state=False) 3269 3270 context.view_layer.objects.active = o 3271 3272 def change_coordsys(self, fromTM, toTM): 3273 """ 3274 move shape fromTM into toTM coordsys 3275 """ 3276 dp = (toTM.inverted() @ fromTM.translation).to_2d() 3277 da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d()) 3278 ca = cos(da) 3279 sa = sin(da) 3280 rM = Matrix([ 3281 [ca, -sa], 3282 [sa, ca] 3283 ]) 3284 for s in self.segs: 3285 tp = (rM @ s.p0) - s.p0 + dp 3286 s.rotate(da) 3287 s.translate(tp) 3288 3289 def t_partition(self, array, begin, end): 3290 pivot = begin 3291 for i in range(begin + 1, end + 1): 3292 # wall idx 3293 if array[i][0] < array[begin][0]: 3294 pivot += 1 3295 array[i], array[pivot] = array[pivot], array[i] 3296 array[pivot], array[begin] = array[begin], array[pivot] 3297 return pivot 3298 3299 def sort_t(self, array, begin=0, end=None): 3300 # print("sort_child") 3301 if end is None: 3302 end = len(array) - 1 3303 3304 def _quicksort(array, begin, end): 3305 if begin >= end: 3306 return 3307 pivot = self.t_partition(array, begin, end) 3308 _quicksort(array, begin, pivot - 1) 3309 _quicksort(array, pivot + 1, end) 3310 return _quicksort(array, begin, end) 3311 3312 def make_wall_fit(self, context, o, wall, inside): 3313 wd = wall.data.archipack_wall2[0] 3314 wg = wd.get_generator() 3315 z0 = self.z - wd.z 3316 3317 # wg in roof coordsys 3318 wg.change_coordsys(wall.matrix_world, o.matrix_world) 3319 3320 if inside: 3321 # fit inside 3322 offset = -0.5 * (1 - wd.x_offset) * wd.width 3323 else: 3324 # fit outside 3325 offset = 0 3326 3327 wg.set_offset(offset) 3328 3329 wall_t = [[] for w in wg.segs] 3330 3331 for pan in self.pans: 3332 # walls segment 3333 for widx, wseg in enumerate(wg.segs): 3334 3335 ls = wseg.line.length 3336 3337 for seg in pan.segs: 3338 # intersect with a roof segment 3339 # any linked or axis intersection here 3340 # will be dup as they are between 2 roof parts 3341 res, p, t, v = wseg.line.intersect_ext(seg) 3342 if res: 3343 z = z0 + pan.altitude(p) 3344 wall_t[widx].append((t, z, t * ls)) 3345 3346 # lie under roof 3347 if type(wseg).__name__ == "CurvedWall": 3348 for step in range(12): 3349 t = step / 12 3350 p = wseg.line.lerp(t) 3351 if pan.inside(p): 3352 z = z0 + pan.altitude(p) 3353 wall_t[widx].append((t, z, t * ls)) 3354 else: 3355 if pan.inside(wseg.line.p0): 3356 z = z0 + pan.altitude(wseg.line.p0) 3357 wall_t[widx].append((0, z, 0)) 3358 3359 old = context.active_object 3360 old_sel = wall.select_get() 3361 wall.select_set(state=True) 3362 context.view_layer.objects.active = wall 3363 3364 wd.auto_update = False 3365 # setup splits count and first split to 0 3366 for widx, seg in enumerate(wall_t): 3367 self.sort_t(seg) 3368 # print("seg: %s" % seg) 3369 for s in seg: 3370 t, z, d = s 3371 wd.parts[widx].n_splits = len(seg) + 1 3372 wd.parts[widx].z[0] = 0 3373 wd.parts[widx].t[0] = 0 3374 break 3375 3376 # add splits, skip dups 3377 for widx, seg in enumerate(wall_t): 3378 t0 = 0 3379 last_d = -1 3380 sid = 1 3381 for s in seg: 3382 t, z, d = s 3383 if t == 0: 3384 # add at end of last segment 3385 if widx > 0: 3386 lid = wd.parts[widx - 1].n_splits - 1 3387 wd.parts[widx - 1].z[lid] = z 3388 wd.parts[widx - 1].t[lid] = 1 3389 else: 3390 wd.parts[widx].z[0] = z 3391 wd.parts[widx].t[0] = t 3392 sid = 1 3393 else: 3394 if d - last_d < 0.001: 3395 wd.parts[widx].n_splits -= 1 3396 continue 3397 wd.parts[widx].z[sid] = z 3398 wd.parts[widx].t[sid] = t - t0 3399 t0 = t 3400 sid += 1 3401 last_d = d 3402 3403 if wd.closed: 3404 last = wd.parts[wd.n_parts].n_splits - 1 3405 wd.parts[wd.n_parts].z[last] = wd.parts[0].z[0] 3406 wd.parts[wd.n_parts].t[last] = 1.0 3407 3408 wd.auto_update = True 3409 """ 3410 for s in self.segs: 3411 s.as_curve(context) 3412 for s in wg.segs: 3413 s.as_curve(context) 3414 """ 3415 wall.select_set(state=old_sel) 3416 context.view_layer.objects.active = old 3417 3418 def boundary(self, context, o): 3419 """ 3420 either external or holes cuts 3421 """ 3422 to_remove = [] 3423 for b in o.children: 3424 d = archipack_roof_cutter.datablock(b) 3425 if d is not None: 3426 g = d.ensure_direction() 3427 g.change_coordsys(b.matrix_world, o.matrix_world) 3428 for i, pan in enumerate(self.pans): 3429 keep = pan.slice(g) 3430 if not keep: 3431 if i not in to_remove: 3432 to_remove.append(i) 3433 pan.limits() 3434 to_remove.sort() 3435 for i in reversed(to_remove): 3436 self.pans.pop(i) 3437 3438 def draft(self, context, verts, edges): 3439 for pan in self.pans: 3440 pan.draw(context, self.z, verts, edges) 3441 3442 for s in self.segs: 3443 if s.constraint_type == 'SLOPE': 3444 f = len(verts) 3445 p0 = s.p0.to_3d() 3446 p0.z = self.z 3447 p1 = s.p1.to_3d() 3448 p1.z = self.z 3449 verts.extend([p0, p1]) 3450 edges.append([f, f + 1]) 3451 3452 3453def update(self, context): 3454 self.update(context) 3455 3456 3457def update_manipulators(self, context): 3458 self.update(context, manipulable_refresh=True) 3459 3460 3461def update_path(self, context): 3462 self.update_path(context) 3463 3464 3465def update_parent(self, context): 3466 3467 # update part a0 3468 o = context.active_object 3469 p, d = self.find_parent(context) 3470 3471 if d is not None: 3472 3473 o.parent = p 3474 3475 # trigger object update 3476 # hole creation and parent's update 3477 3478 self.parts[0].a0 = pi / 2 3479 3480 elif self.t_parent != "": 3481 self.t_parent = "" 3482 3483 3484def update_cutter(self, context): 3485 self.update(context, update_hole=True) 3486 3487 3488def update_childs(self, context): 3489 self.update(context, update_childs=True, update_hole=True) 3490 3491 3492def update_components(self, context): 3493 self.update(context, update_parent=False, update_hole=False) 3494 3495 3496class ArchipackSegment(): 3497 length : FloatProperty( 3498 name="Length", 3499 min=0.01, 3500 max=1000.0, 3501 default=4.0, 3502 update=update 3503 ) 3504 a0 : FloatProperty( 3505 name="Angle", 3506 min=-2 * pi, 3507 max=2 * pi, 3508 default=0, 3509 subtype='ANGLE', unit='ROTATION', 3510 update=update_cutter 3511 ) 3512 manipulators : CollectionProperty(type=archipack_manipulator) 3513 3514 3515class ArchipackLines(): 3516 n_parts : IntProperty( 3517 name="Parts", 3518 min=1, 3519 default=1, update=update_manipulators 3520 ) 3521 # UI layout related 3522 parts_expand : BoolProperty( 3523 default=False 3524 ) 3525 3526 def draw(self, layout, context): 3527 box = layout.box() 3528 row = box.row() 3529 if self.parts_expand: 3530 row.prop(self, 'parts_expand', icon="TRIA_DOWN", text="Parts", emboss=False) 3531 box.prop(self, 'n_parts') 3532 for i, part in enumerate(self.parts): 3533 part.draw(layout, context, i) 3534 else: 3535 row.prop(self, 'parts_expand', icon="TRIA_RIGHT", text="Parts", emboss=False) 3536 3537 def update_parts(self): 3538 # print("update_parts") 3539 # remove rows 3540 # NOTE: 3541 # n_parts+1 3542 # as last one is end point of last segment or closing one 3543 for i in range(len(self.parts), self.n_parts + 1, -1): 3544 self.parts.remove(i - 1) 3545 3546 # add rows 3547 for i in range(len(self.parts), self.n_parts + 1): 3548 self.parts.add() 3549 3550 self.setup_manipulators() 3551 3552 def setup_parts_manipulators(self): 3553 for i in range(self.n_parts + 1): 3554 p = self.parts[i] 3555 n_manips = len(p.manipulators) 3556 if n_manips < 1: 3557 s = p.manipulators.add() 3558 s.type_key = "ANGLE" 3559 s.prop1_name = "a0" 3560 if n_manips < 2: 3561 s = p.manipulators.add() 3562 s.type_key = "SIZE" 3563 s.prop1_name = "length" 3564 if n_manips < 3: 3565 s = p.manipulators.add() 3566 s.type_key = 'WALL_SNAP' 3567 s.prop1_name = str(i) 3568 s.prop2_name = 'z' 3569 if n_manips < 4: 3570 s = p.manipulators.add() 3571 s.type_key = 'DUMB_STRING' 3572 s.prop1_name = str(i + 1) 3573 if n_manips < 5: 3574 s = p.manipulators.add() 3575 s.type_key = "SIZE" 3576 s.prop1_name = "offset" 3577 p.manipulators[2].prop1_name = str(i) 3578 p.manipulators[3].prop1_name = str(i + 1) 3579 3580 3581class archipack_roof_segment(ArchipackSegment, PropertyGroup): 3582 3583 bound_idx : IntProperty( 3584 name="Link to", 3585 default=0, 3586 min=0, 3587 update=update_manipulators 3588 ) 3589 width_left : FloatProperty( 3590 name="L Width", 3591 min=0.01, 3592 default=3.0, 3593 update=update_cutter 3594 ) 3595 width_right : FloatProperty( 3596 name="R Width", 3597 min=0.01, 3598 default=3.0, 3599 update=update_cutter 3600 ) 3601 slope_left : FloatProperty( 3602 name="L slope", 3603 min=0.0, 3604 default=0.3, 3605 update=update_cutter 3606 ) 3607 slope_right : FloatProperty( 3608 name="R slope", 3609 min=0.0, 3610 default=0.3, 3611 update=update_cutter 3612 ) 3613 auto_left : EnumProperty( 3614 description="Left mode", 3615 name="Left", 3616 items=( 3617 ('AUTO', 'Auto', '', 0), 3618 ('WIDTH', 'Width', '', 1), 3619 ('SLOPE', 'Slope', '', 2), 3620 ('ALL', 'All', '', 3), 3621 ), 3622 default="AUTO", 3623 update=update_manipulators 3624 ) 3625 auto_right : EnumProperty( 3626 description="Right mode", 3627 name="Right", 3628 items=( 3629 ('AUTO', 'Auto', '', 0), 3630 ('WIDTH', 'Width', '', 1), 3631 ('SLOPE', 'Slope', '', 2), 3632 ('ALL', 'All', '', 3), 3633 ), 3634 default="AUTO", 3635 update=update_manipulators 3636 ) 3637 triangular_end : BoolProperty( 3638 name="Triangular end", 3639 default=False, 3640 update=update 3641 ) 3642 take_precedence : BoolProperty( 3643 name="Take precedence", 3644 description="On T segment take width precedence", 3645 default=False, 3646 update=update 3647 ) 3648 3649 constraint_type : EnumProperty( 3650 items=( 3651 ('HORIZONTAL', 'Horizontal', '', 0), 3652 ('SLOPE', 'Slope', '', 1) 3653 ), 3654 default='HORIZONTAL', 3655 update=update_manipulators 3656 ) 3657 3658 enforce_part : EnumProperty( 3659 name="Enforce part", 3660 items=( 3661 ('AUTO', 'Auto', '', 0), 3662 ('VALLEY', 'Valley', '', 1), 3663 ('HIP', 'Hip', '', 2) 3664 ), 3665 default='AUTO', 3666 update=update 3667 ) 3668 3669 def find_in_selection(self, context): 3670 """ 3671 find witch selected object this instance belongs to 3672 provide support for "copy to selected" 3673 """ 3674 selected = context.selected_objects[:] 3675 for o in selected: 3676 d = archipack_roof.datablock(o) 3677 if d: 3678 for part in d.parts: 3679 if part == self: 3680 return d 3681 return None 3682 3683 def draw(self, layout, context, index): 3684 box = layout.box() 3685 if index > 0: 3686 box.prop(self, "constraint_type", text=str(index + 1)) 3687 if self.constraint_type == 'SLOPE': 3688 box.prop(self, "enforce_part", text="") 3689 else: 3690 box.label(text="Part 1:") 3691 box.prop(self, "length") 3692 box.prop(self, "a0") 3693 3694 if index > 0: 3695 box.prop(self, 'bound_idx') 3696 if self.constraint_type == 'HORIZONTAL': 3697 box.prop(self, "triangular_end") 3698 row = box.row(align=True) 3699 row.prop(self, "auto_left", text="") 3700 row.prop(self, "auto_right", text="") 3701 if self.auto_left in {'ALL', 'WIDTH'}: 3702 box.prop(self, "width_left") 3703 if self.auto_left in {'ALL', 'SLOPE'}: 3704 box.prop(self, "slope_left") 3705 if self.auto_right in {'ALL', 'WIDTH'}: 3706 box.prop(self, "width_right") 3707 if self.auto_right in {'ALL', 'SLOPE'}: 3708 box.prop(self, "slope_right") 3709 elif self.constraint_type == 'HORIZONTAL': 3710 box.prop(self, "triangular_end") 3711 3712 def update(self, context, manipulable_refresh=False, update_hole=False): 3713 props = self.find_in_selection(context) 3714 if props is not None: 3715 props.update(context, 3716 manipulable_refresh, 3717 update_parent=True, 3718 update_hole=True, 3719 update_childs=True) 3720 3721 3722class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup): 3723 parts : CollectionProperty(type=archipack_roof_segment) 3724 z : FloatProperty( 3725 name="Altitude", 3726 default=3, precision=2, step=1, 3727 unit='LENGTH', subtype='DISTANCE', 3728 update=update_childs 3729 ) 3730 slope_left : FloatProperty( 3731 name="L slope", 3732 default=0.5, precision=2, step=1, 3733 update=update_childs 3734 ) 3735 slope_right : FloatProperty( 3736 name="R slope", 3737 default=0.5, precision=2, step=1, 3738 update=update_childs 3739 ) 3740 width_left : FloatProperty( 3741 name="L width", 3742 default=3, precision=2, step=1, 3743 unit='LENGTH', subtype='DISTANCE', 3744 update=update_cutter 3745 ) 3746 width_right : FloatProperty( 3747 name="R width", 3748 default=3, precision=2, step=1, 3749 unit='LENGTH', subtype='DISTANCE', 3750 update=update_cutter 3751 ) 3752 draft : BoolProperty( 3753 options={'SKIP_SAVE'}, 3754 name="Draft mode", 3755 default=False, 3756 update=update_manipulators 3757 ) 3758 auto_update : BoolProperty( 3759 options={'SKIP_SAVE'}, 3760 default=True, 3761 update=update_manipulators 3762 ) 3763 quick_edit : BoolProperty( 3764 options={'SKIP_SAVE'}, 3765 name="Quick Edit", 3766 default=False 3767 ) 3768 3769 tile_enable : BoolProperty( 3770 name="Enable", 3771 default=True, 3772 update=update_components 3773 ) 3774 tile_solidify : BoolProperty( 3775 name="Solidify", 3776 default=True, 3777 update=update_components 3778 ) 3779 tile_height : FloatProperty( 3780 name="Height", 3781 description="Amount for solidify", 3782 min=0, 3783 default=0.02, 3784 unit='LENGTH', subtype='DISTANCE', 3785 update=update_components 3786 ) 3787 tile_bevel : BoolProperty( 3788 name="Bevel", 3789 default=False, 3790 update=update_components 3791 ) 3792 tile_bevel_amt : FloatProperty( 3793 name="Amount", 3794 description="Amount for bevel", 3795 min=0, 3796 default=0.02, 3797 unit='LENGTH', subtype='DISTANCE', 3798 update=update_components 3799 ) 3800 tile_bevel_segs : IntProperty( 3801 name="Segs", 3802 description="Bevel Segs", 3803 min=1, 3804 default=2, 3805 update=update_components 3806 ) 3807 tile_alternate : BoolProperty( 3808 name="Alternate", 3809 default=False, 3810 update=update_components 3811 ) 3812 tile_offset : FloatProperty( 3813 name="Offset", 3814 description="Offset from start", 3815 min=0, 3816 max=100, 3817 subtype="PERCENTAGE", 3818 update=update_components 3819 ) 3820 tile_altitude : FloatProperty( 3821 name="Altitude", 3822 description="Altitude from roof", 3823 default=0.1, 3824 unit='LENGTH', subtype='DISTANCE', 3825 update=update_components 3826 ) 3827 tile_size_x : FloatProperty( 3828 name="Width", 3829 description="Size of tiles on x axis", 3830 min=0.01, 3831 default=0.2, 3832 unit='LENGTH', subtype='DISTANCE', 3833 update=update_components 3834 ) 3835 tile_size_y : FloatProperty( 3836 name="Length", 3837 description="Size of tiles on y axis", 3838 min=0.01, 3839 default=0.3, 3840 unit='LENGTH', subtype='DISTANCE', 3841 update=update_components 3842 ) 3843 tile_size_z : FloatProperty( 3844 name="Thickness", 3845 description="Size of tiles on z axis", 3846 min=0.0, 3847 default=0.02, 3848 unit='LENGTH', subtype='DISTANCE', 3849 update=update_components 3850 ) 3851 tile_space_x : FloatProperty( 3852 name="Width", 3853 description="Space between tiles on x axis", 3854 min=0.01, 3855 default=0.2, 3856 unit='LENGTH', subtype='DISTANCE', 3857 update=update_components 3858 ) 3859 tile_space_y : FloatProperty( 3860 name="Length", 3861 description="Space between tiles on y axis", 3862 min=0.01, 3863 default=0.3, 3864 unit='LENGTH', subtype='DISTANCE', 3865 update=update_components 3866 ) 3867 tile_fit_x : BoolProperty( 3868 name="Fit x", 3869 description="Fit roof on x axis", 3870 default=True, 3871 update=update_components 3872 ) 3873 tile_fit_y : BoolProperty( 3874 name="Fit y", 3875 description="Fit roof on y axis", 3876 default=True, 3877 update=update_components 3878 ) 3879 tile_expand : BoolProperty( 3880 options={'SKIP_SAVE'}, 3881 name="Tiles", 3882 description="Expand tiles panel", 3883 default=False 3884 ) 3885 tile_model : EnumProperty( 3886 name="Model", 3887 items=( 3888 ('BRAAS1', 'Braas 1', '', 0), 3889 ('BRAAS2', 'Braas 2', '', 1), 3890 ('ETERNIT', 'Eternit', '', 2), 3891 ('LAUZE', 'Lauze', '', 3), 3892 ('ROMAN', 'Roman', '', 4), 3893 ('ROUND', 'Round', '', 5), 3894 ('PLACEHOLDER', 'Square', '', 6), 3895 ('ONDULEE', 'Ondule', '', 7), 3896 ('METAL', 'Metal', '', 8), 3897 # ('USER', 'User defined', '', 7) 3898 ), 3899 default="BRAAS2", 3900 update=update_components 3901 ) 3902 tile_side : FloatProperty( 3903 name="Side", 3904 description="Space on side", 3905 default=0, 3906 unit='LENGTH', subtype='DISTANCE', 3907 update=update_components 3908 ) 3909 tile_couloir : FloatProperty( 3910 name="Valley", 3911 description="Space between tiles on valley", 3912 min=0, 3913 default=0.05, 3914 unit='LENGTH', subtype='DISTANCE', 3915 update=update_components 3916 ) 3917 tile_border : FloatProperty( 3918 name="Bottom", 3919 description="Tiles offset from bottom", 3920 default=0, 3921 unit='LENGTH', subtype='DISTANCE', 3922 update=update_components 3923 ) 3924 3925 gutter_expand : BoolProperty( 3926 options={'SKIP_SAVE'}, 3927 name="Gutter", 3928 description="Expand gutter panel", 3929 default=False 3930 ) 3931 gutter_enable : BoolProperty( 3932 name="Enable", 3933 default=True, 3934 update=update_components 3935 ) 3936 gutter_alt : FloatProperty( 3937 name="Altitude", 3938 description="altitude", 3939 default=0, 3940 unit='LENGTH', subtype='DISTANCE', 3941 update=update_components 3942 ) 3943 gutter_width : FloatProperty( 3944 name="Width", 3945 description="Width", 3946 min=0.01, 3947 default=0.15, 3948 unit='LENGTH', subtype='DISTANCE', 3949 update=update_components 3950 ) 3951 gutter_dist : FloatProperty( 3952 name="Spacing", 3953 description="Spacing", 3954 min=0, 3955 default=0.05, 3956 unit='LENGTH', subtype='DISTANCE', 3957 update=update_components 3958 ) 3959 gutter_boudin : FloatProperty( 3960 name="Small width", 3961 description="Small width", 3962 min=0, 3963 default=0.015, 3964 unit='LENGTH', subtype='DISTANCE', 3965 update=update_components 3966 ) 3967 gutter_segs : IntProperty( 3968 default=6, 3969 min=1, 3970 name="Segs", 3971 update=update_components 3972 ) 3973 3974 beam_expand : BoolProperty( 3975 options={'SKIP_SAVE'}, 3976 name="Beam", 3977 description="Expand beam panel", 3978 default=False 3979 ) 3980 beam_enable : BoolProperty( 3981 name="Ridge pole", 3982 default=True, 3983 update=update_components 3984 ) 3985 beam_width : FloatProperty( 3986 name="Width", 3987 description="Width", 3988 min=0.01, 3989 default=0.2, 3990 unit='LENGTH', subtype='DISTANCE', 3991 update=update_components 3992 ) 3993 beam_height : FloatProperty( 3994 name="Height", 3995 description="Height", 3996 min=0.01, 3997 default=0.35, 3998 unit='LENGTH', subtype='DISTANCE', 3999 update=update_components 4000 ) 4001 beam_offset : FloatProperty( 4002 name="Offset", 4003 description="Distance from roof border", 4004 default=0.02, 4005 unit='LENGTH', subtype='DISTANCE', 4006 update=update_components 4007 ) 4008 beam_alt : FloatProperty( 4009 name="Altitude", 4010 description="Altitude from roof", 4011 default=-0.15, 4012 unit='LENGTH', subtype='DISTANCE', 4013 update=update_components 4014 ) 4015 beam_sec_enable : BoolProperty( 4016 name="Hip rafter", 4017 default=True, 4018 update=update_components 4019 ) 4020 beam_sec_width : FloatProperty( 4021 name="Width", 4022 description="Width", 4023 min=0.01, 4024 default=0.15, 4025 unit='LENGTH', subtype='DISTANCE', 4026 update=update_components 4027 ) 4028 beam_sec_height : FloatProperty( 4029 name="Height", 4030 description="Height", 4031 min=0.01, 4032 default=0.2, 4033 unit='LENGTH', subtype='DISTANCE', 4034 update=update_components 4035 ) 4036 beam_sec_alt : FloatProperty( 4037 name="Altitude", 4038 description="Distance from roof", 4039 default=-0.1, 4040 unit='LENGTH', subtype='DISTANCE', 4041 update=update_components 4042 ) 4043 rafter_enable : BoolProperty( 4044 name="Rafter", 4045 default=True, 4046 update=update_components 4047 ) 4048 rafter_width : FloatProperty( 4049 name="Width", 4050 description="Width", 4051 min=0.01, 4052 default=0.1, 4053 unit='LENGTH', subtype='DISTANCE', 4054 update=update_components 4055 ) 4056 rafter_height : FloatProperty( 4057 name="Height", 4058 description="Height", 4059 min=0.01, 4060 default=0.2, 4061 unit='LENGTH', subtype='DISTANCE', 4062 update=update_components 4063 ) 4064 rafter_spacing : FloatProperty( 4065 name="Spacing", 4066 description="Spacing", 4067 min=0.1, 4068 default=0.7, 4069 unit='LENGTH', subtype='DISTANCE', 4070 update=update_components 4071 ) 4072 rafter_start : FloatProperty( 4073 name="Offset", 4074 description="Spacing from roof border", 4075 min=0, 4076 default=0.1, 4077 unit='LENGTH', subtype='DISTANCE', 4078 update=update_components 4079 ) 4080 rafter_alt : FloatProperty( 4081 name="Altitude", 4082 description="Altitude from roof", 4083 max=-0.0001, 4084 default=-0.001, 4085 unit='LENGTH', subtype='DISTANCE', 4086 update=update_components 4087 ) 4088 4089 hip_enable : BoolProperty( 4090 name="Enable", 4091 default=True, 4092 update=update_components 4093 ) 4094 hip_expand : BoolProperty( 4095 options={'SKIP_SAVE'}, 4096 name="Hips", 4097 description="Expand hips panel", 4098 default=False 4099 ) 4100 hip_alt : FloatProperty( 4101 name="Altitude", 4102 description="Hip altitude from roof", 4103 default=0.1, 4104 unit='LENGTH', subtype='DISTANCE', 4105 update=update_components 4106 ) 4107 hip_space_x : FloatProperty( 4108 name="Spacing", 4109 description="Space between hips", 4110 min=0.01, 4111 default=0.4, 4112 unit='LENGTH', subtype='DISTANCE', 4113 update=update_components 4114 ) 4115 hip_size_x : FloatProperty( 4116 name="Length", 4117 description="Length of hip", 4118 min=0.01, 4119 default=0.4, 4120 unit='LENGTH', subtype='DISTANCE', 4121 update=update_components 4122 ) 4123 hip_size_y : FloatProperty( 4124 name="Width", 4125 description="Width of hip", 4126 min=0.01, 4127 default=0.15, 4128 unit='LENGTH', subtype='DISTANCE', 4129 update=update_components 4130 ) 4131 hip_size_z : FloatProperty( 4132 name="Height", 4133 description="Height of hip", 4134 min=0.0, 4135 default=0.15, 4136 unit='LENGTH', subtype='DISTANCE', 4137 update=update_components 4138 ) 4139 hip_model : EnumProperty( 4140 name="Model", 4141 items=( 4142 ('ROUND', 'Round', '', 0), 4143 ('ETERNIT', 'Eternit', '', 1), 4144 ('FLAT', 'Flat', '', 2) 4145 ), 4146 default="ROUND", 4147 update=update_components 4148 ) 4149 valley_altitude : FloatProperty( 4150 name="Altitude", 4151 description="Valley altitude from roof", 4152 default=0.1, 4153 unit='LENGTH', subtype='DISTANCE', 4154 update=update_components 4155 ) 4156 valley_enable : BoolProperty( 4157 name="Valley", 4158 default=True, 4159 update=update_components 4160 ) 4161 4162 fascia_enable : BoolProperty( 4163 name="Enable", 4164 description="Enable Fascia", 4165 default=True, 4166 update=update_components 4167 ) 4168 fascia_expand : BoolProperty( 4169 options={'SKIP_SAVE'}, 4170 name="Fascia", 4171 description="Expand fascia panel", 4172 default=False 4173 ) 4174 fascia_height : FloatProperty( 4175 name="Height", 4176 description="Height", 4177 min=0.01, 4178 default=0.3, 4179 unit='LENGTH', subtype='DISTANCE', 4180 update=update_components 4181 ) 4182 fascia_width : FloatProperty( 4183 name="Width", 4184 description="Width", 4185 min=0.01, 4186 default=0.02, 4187 unit='LENGTH', subtype='DISTANCE', 4188 update=update_components 4189 ) 4190 fascia_offset : FloatProperty( 4191 name="Offset", 4192 description="Offset from roof border", 4193 default=0, 4194 unit='LENGTH', subtype='DISTANCE', 4195 update=update_components 4196 ) 4197 fascia_altitude : FloatProperty( 4198 name="Altitude", 4199 description="Fascia altitude from roof", 4200 default=0.1, 4201 unit='LENGTH', subtype='DISTANCE', 4202 update=update_components 4203 ) 4204 4205 bargeboard_enable : BoolProperty( 4206 name="Enable", 4207 description="Enable Bargeboard", 4208 default=True, 4209 update=update_components 4210 ) 4211 bargeboard_expand : BoolProperty( 4212 options={'SKIP_SAVE'}, 4213 name="Bargeboard", 4214 description="Expand Bargeboard panel", 4215 default=False 4216 ) 4217 bargeboard_height : FloatProperty( 4218 name="Height", 4219 description="Height", 4220 min=0.01, 4221 default=0.3, 4222 unit='LENGTH', subtype='DISTANCE', 4223 update=update_components 4224 ) 4225 bargeboard_width : FloatProperty( 4226 name="Width", 4227 description="Width", 4228 min=0.01, 4229 default=0.02, 4230 unit='LENGTH', subtype='DISTANCE', 4231 update=update_components 4232 ) 4233 bargeboard_offset : FloatProperty( 4234 name="Offset", 4235 description="Offset from roof border", 4236 default=0.001, 4237 unit='LENGTH', subtype='DISTANCE', 4238 update=update_components 4239 ) 4240 bargeboard_altitude : FloatProperty( 4241 name="Altitude", 4242 description="Fascia altitude from roof", 4243 default=0.1, 4244 unit='LENGTH', subtype='DISTANCE', 4245 update=update_components 4246 ) 4247 4248 t_parent : StringProperty( 4249 name="Parent", 4250 default="", 4251 update=update_parent 4252 ) 4253 t_part : IntProperty( 4254 name="Part", 4255 description="Parent part index", 4256 default=0, 4257 min=0, 4258 update=update_cutter 4259 ) 4260 t_dist_x : FloatProperty( 4261 name="Dist x", 4262 description="Location on axis ", 4263 default=0, 4264 update=update_cutter 4265 ) 4266 t_dist_y : FloatProperty( 4267 name="Dist y", 4268 description="Lateral distance from axis", 4269 min=0.0001, 4270 default=0.0001, 4271 update=update_cutter 4272 ) 4273 z_parent: FloatProperty( 4274 description="Delta z of t child for grand childs", 4275 default=0 4276 ) 4277 hole_offset_left : FloatProperty( 4278 name="Left", 4279 description="Left distance from border", 4280 min=0, 4281 default=0, 4282 update=update_cutter 4283 ) 4284 hole_offset_right : FloatProperty( 4285 name="Right", 4286 description="Right distance from border", 4287 min=0, 4288 default=0, 4289 update=update_cutter 4290 ) 4291 hole_offset_front : FloatProperty( 4292 name="Front", 4293 description="Front distance from border", 4294 default=0, 4295 update=update_cutter 4296 ) 4297 4298 def make_wall_fit(self, context, o, wall, inside=False): 4299 origin = Vector((0, 0, self.z)) 4300 g = self.get_generator(origin) 4301 g.make_roof(context) 4302 g.make_wall_fit(context, o, wall, inside) 4303 4304 def update_parts(self): 4305 # NOTE: 4306 # n_parts+1 4307 # as last one is end point of last segment or closing one 4308 for i in range(len(self.parts), self.n_parts, -1): 4309 self.parts.remove(i - 1) 4310 4311 # add rows 4312 for i in range(len(self.parts), self.n_parts): 4313 bound_idx = len(self.parts) 4314 self.parts.add() 4315 self.parts[-1].bound_idx = bound_idx 4316 4317 self.setup_manipulators() 4318 4319 def setup_manipulators(self): 4320 if len(self.manipulators) < 1: 4321 s = self.manipulators.add() 4322 s.type_key = "SIZE" 4323 s.prop1_name = "z" 4324 s.normal = (0, 1, 0) 4325 if len(self.manipulators) < 2: 4326 s = self.manipulators.add() 4327 s.type_key = "SIZE" 4328 s.prop1_name = "width_left" 4329 if len(self.manipulators) < 3: 4330 s = self.manipulators.add() 4331 s.type_key = "SIZE" 4332 s.prop1_name = "width_right" 4333 4334 for i in range(self.n_parts): 4335 p = self.parts[i] 4336 n_manips = len(p.manipulators) 4337 if n_manips < 1: 4338 s = p.manipulators.add() 4339 s.type_key = "ANGLE" 4340 s.prop1_name = "a0" 4341 if n_manips < 2: 4342 s = p.manipulators.add() 4343 s.type_key = "SIZE" 4344 s.prop1_name = "length" 4345 if n_manips < 3: 4346 s = p.manipulators.add() 4347 s.type_key = 'DUMB_STRING' 4348 s.prop1_name = str(i + 1) 4349 p.manipulators[2].prop1_name = str(i + 1) 4350 if n_manips < 4: 4351 s = p.manipulators.add() 4352 s.type_key = 'SIZE' 4353 s.prop1_name = "width_left" 4354 if n_manips < 5: 4355 s = p.manipulators.add() 4356 s.type_key = 'SIZE' 4357 s.prop1_name = "width_right" 4358 if n_manips < 6: 4359 s = p.manipulators.add() 4360 s.type_key = 'SIZE' 4361 s.prop1_name = "slope_left" 4362 if n_manips < 7: 4363 s = p.manipulators.add() 4364 s.type_key = 'SIZE' 4365 s.prop1_name = "slope_right" 4366 4367 def get_generator(self, origin=Vector((0, 0, 0))): 4368 g = RoofGenerator(self, origin) 4369 4370 # TODO: sort part by bound idx so deps always find parent 4371 4372 for i, part in enumerate(self.parts): 4373 # skip part if bound_idx > parent 4374 # so deps always see parent 4375 if part.bound_idx <= i: 4376 g.add_part(part) 4377 g.locate_manipulators() 4378 return g 4379 4380 def make_surface(self, o, verts, edges): 4381 bm = bmesh.new() 4382 for v in verts: 4383 bm.verts.new(v) 4384 bm.verts.ensure_lookup_table() 4385 for ed in edges: 4386 bm.edges.new((bm.verts[ed[0]], bm.verts[ed[1]])) 4387 bm.edges.ensure_lookup_table() 4388 # bmesh.ops.contextual_create(bm, geom=bm.edges) 4389 bm.to_mesh(o.data) 4390 bm.free() 4391 4392 def find_parent(self, context): 4393 o = context.scene.objects.get(self.t_parent.strip()) 4394 return o, archipack_roof.datablock(o) 4395 4396 def intersection_angle(self, t_slope, t_width, p_slope, angle): 4397 # 2d intersection angle between two roofs parts 4398 dy = abs(t_slope * t_width / p_slope) 4399 ca = cos(angle) 4400 ta = tan(angle) 4401 if ta == 0: 4402 w0 = 0 4403 else: 4404 w0 = dy * ta 4405 if ca == 0: 4406 w1 = 0 4407 else: 4408 w1 = t_width / ca 4409 dx = w1 - w0 4410 return atan2(dy, dx) 4411 4412 def relocate_child(self, context, o, g, child): 4413 4414 d = archipack_roof.datablock(child) 4415 4416 if d is not None and d.t_part - 1 < len(g.segs): 4417 # print("relocate_child(%s)" % (child.name)) 4418 4419 seg = g.segs[d.t_part] 4420 # adjust T part matrix_world from parent 4421 # T part origin located on parent axis 4422 # with y in parent direction 4423 t = (d.t_dist_x / seg.length) 4424 x, y, z = seg.lerp(t).to_3d() 4425 dy = -seg.v.normalized() 4426 child.matrix_world = o.matrix_world @ Matrix([ 4427 [dy.x, -dy.y, 0, x], 4428 [dy.y, dy.x, 0, y], 4429 [0, 0, 1, z], 4430 [0, 0, 0, 1] 4431 ]) 4432 4433 def relocate_childs(self, context, o, g): 4434 for child in o.children: 4435 d = archipack_roof.datablock(child) 4436 if d is not None and d.t_parent == o.name: 4437 self.relocate_child(context, o, g, child) 4438 4439 def update_childs(self, context, o, g): 4440 for child in o.children: 4441 d = archipack_roof.datablock(child) 4442 if d is not None and d.t_parent == o.name: 4443 # print("upate_childs(%s)" % (child.name)) 4444 child.select_set(state=True) 4445 context.view_layer.objects.active = child 4446 # regenerate hole 4447 d.update(context, update_hole=True, update_parent=False) 4448 child.select_set(state=False) 4449 o.select_set(state=True) 4450 context.view_layer.objects.active = o 4451 4452 def update(self, 4453 context, 4454 manipulable_refresh=False, 4455 update_childs=False, 4456 update_parent=True, 4457 update_hole=False, 4458 force_update=False): 4459 """ 4460 update_hole: on t_child must update parent 4461 update_childs: force childs update 4462 force_update: skip throttle 4463 """ 4464 # print("update") 4465 o = self.find_in_selection(context, self.auto_update) 4466 4467 if o is None: 4468 return 4469 4470 # clean up manipulators before any data model change 4471 if manipulable_refresh: 4472 self.manipulable_disable(context) 4473 4474 self.update_parts() 4475 4476 verts, edges, faces, matids, uvs = [], [], [], [], [] 4477 4478 y = 0 4479 z = self.z 4480 p, d = self.find_parent(context) 4481 g = None 4482 4483 # t childs: use parent to relocate 4484 # setup slopes into generator 4485 if d is not None: 4486 pg = d.get_generator() 4487 pg.make_roof(context) 4488 4489 if self.t_part - 1 < len(pg.segs): 4490 4491 seg = pg.nodes[self.t_part].root 4492 4493 d.relocate_child(context, p, pg, o) 4494 4495 a0 = self.parts[0].a0 4496 a_axis = a0 - pi / 2 4497 a_offset = 0 4498 s_left = self.slope_left 4499 w_left = -self.width_left 4500 s_right = self.slope_right 4501 w_right = self.width_right 4502 if a0 > 0: 4503 # a_axis est mesure depuis la perpendiculaire à l'axe 4504 slope = seg.right.slope 4505 y = self.t_dist_y 4506 else: 4507 a_offset = pi 4508 slope = seg.left.slope 4509 y = -self.t_dist_y 4510 s_left, s_right = s_right, s_left 4511 w_left, w_right = -w_right, -w_left 4512 4513 if slope == 0: 4514 slope = 0.0001 4515 4516 # print("slope: %s" % (slope)) 4517 4518 z = d.z_parent + d.z - self.t_dist_y * slope 4519 self.z_parent = z - self.z 4520 # a_right from axis cross z 4521 4522 b_right = self.intersection_angle( 4523 s_left, 4524 w_left, 4525 slope, 4526 a_axis) 4527 4528 a_right = b_right + a_offset 4529 4530 b_left = self.intersection_angle( 4531 s_right, 4532 w_right, 4533 slope, 4534 a_axis) 4535 4536 a_left = b_left + a_offset 4537 4538 g = self.get_generator(origin=Vector((0, y, z))) 4539 4540 # override by user defined slope if any 4541 make_right = True 4542 make_left = True 4543 for s in g.segs: 4544 if (s.constraint_type == 'SLOPE' and 4545 s.v0_idx == 0): 4546 da = g.segs[0].v.angle_signed(s.v) 4547 if da > 0: 4548 make_left = False 4549 else: 4550 make_right = False 4551 4552 if make_left: 4553 # Add 'SLOPE' constraints for segment 0 4554 v = Vector((cos(a_left), sin(a_left))) 4555 s = StraightRoof(g.origin, v) 4556 s.v0_idx = 0 4557 s.constraint_type = 'SLOPE' 4558 # s.enforce_part = 'VALLEY' 4559 s.angle_0 = a_left 4560 s.take_precedence = False 4561 g.segs.append(s) 4562 4563 if make_right: 4564 v = Vector((cos(a_right), sin(a_right))) 4565 s = StraightRoof(g.origin, v) 4566 s.v0_idx = 0 4567 s.constraint_type = 'SLOPE' 4568 # s.enforce_part = 'VALLEY' 4569 s.angle_0 = a_right 4570 s.take_precedence = False 4571 g.segs.append(s) 4572 4573 if g is None: 4574 g = self.get_generator(origin=Vector((0, y, z))) 4575 4576 # setup per segment manipulators 4577 if len(g.segs) > 0: 4578 f = g.segs[0] 4579 # z 4580 n = f.straight(-1, 0).v.to_3d() 4581 self.manipulators[0].set_pts([(0, 0, 0), (0, 0, self.z), (1, 0, 0)], normal=n) 4582 # left width 4583 n = f.sized_normal(0, -self.width_left) 4584 self.manipulators[1].set_pts([n.p0.to_3d(), n.p1.to_3d(), (-1, 0, 0)]) 4585 # right width 4586 n = f.sized_normal(0, self.width_right) 4587 self.manipulators[2].set_pts([n.p0.to_3d(), n.p1.to_3d(), (1, 0, 0)]) 4588 4589 g.make_roof(context) 4590 4591 # update childs here so parent may use 4592 # new holes when parent shape does change 4593 if update_childs: 4594 self.update_childs(context, o, g) 4595 4596 # on t_child 4597 if d is not None and update_hole: 4598 hole_obj = self.find_hole(context, o) 4599 g.make_hole(context, hole_obj, o, self, update_parent) 4600 # print("make_hole") 4601 4602 # add cutters 4603 g.boundary(context, o) 4604 4605 if self.draft: 4606 4607 g.draft(context, verts, edges) 4608 g.gutter(self, verts, faces, edges, matids, uvs) 4609 self.make_surface(o, verts, edges) 4610 4611 else: 4612 4613 if self.bargeboard_enable: 4614 g.bargeboard(self, verts, faces, edges, matids, uvs) 4615 4616 if self.fascia_enable: 4617 g.fascia(self, verts, faces, edges, matids, uvs) 4618 4619 if self.beam_enable: 4620 g.beam_primary(self, verts, faces, edges, matids, uvs) 4621 4622 g.hips(self, verts, faces, edges, matids, uvs) 4623 4624 if self.gutter_enable: 4625 g.gutter(self, verts, faces, edges, matids, uvs) 4626 4627 bmed.buildmesh( 4628 4629 context, o, verts, faces, matids=matids, uvs=uvs, 4630 weld=False, clean=False, auto_smooth=True, temporary=False) 4631 4632 # bpy.ops.object.mode_set(mode='EDIT') 4633 g.lambris(context, o, self) 4634 # print("lambris") 4635 4636 if self.rafter_enable: 4637 # bpy.ops.object.mode_set(mode='EDIT') 4638 g.rafter(context, o, self) 4639 # print("rafter") 4640 4641 if self.quick_edit and not force_update: 4642 if self.tile_enable: 4643 bpy.ops.archipack.roof_throttle_update(name=o.name) 4644 else: 4645 # throttle here 4646 if self.tile_enable: 4647 g.couverture(context, o, self) 4648 # print("couverture") 4649 4650 # enable manipulators rebuild 4651 if manipulable_refresh: 4652 self.manipulable_refresh = True 4653 # print("rafter") 4654 # restore context 4655 self.restore_context(context) 4656 # print("restore context") 4657 4658 def find_hole(self, context, o): 4659 p, d = self.find_parent(context) 4660 if d is not None: 4661 for child in p.children: 4662 cd = archipack_roof_cutter.datablock(child) 4663 if cd is not None and cd.boundary == o.name: 4664 return child 4665 return None 4666 4667 def manipulable_setup(self, context): 4668 """ 4669 NOTE: 4670 this one assume context.active_object is the instance this 4671 data belongs to, failing to do so will result in wrong 4672 manipulators set on active object 4673 """ 4674 self.manipulable_disable(context) 4675 4676 o = context.active_object 4677 4678 self.setup_manipulators() 4679 4680 for i, part in enumerate(self.parts): 4681 4682 if i > 0: 4683 # start angle 4684 self.manip_stack.append(part.manipulators[0].setup(context, o, part)) 4685 4686 if part.constraint_type == 'HORIZONTAL': 4687 # length / radius + angle 4688 self.manip_stack.append(part.manipulators[1].setup(context, o, part)) 4689 4690 # index 4691 self.manip_stack.append(part.manipulators[2].setup(context, o, self)) 4692 4693 # size left 4694 if part.auto_left in {'WIDTH', 'ALL'}: 4695 self.manip_stack.append(part.manipulators[3].setup(context, o, part)) 4696 # size right 4697 if part.auto_right in {'WIDTH', 'ALL'}: 4698 self.manip_stack.append(part.manipulators[4].setup(context, o, part)) 4699 # slope left 4700 if part.auto_left in {'SLOPE', 'ALL'}: 4701 self.manip_stack.append(part.manipulators[5].setup(context, o, part)) 4702 # slope right 4703 if part.auto_right in {'SLOPE', 'ALL'}: 4704 self.manip_stack.append(part.manipulators[6].setup(context, o, part)) 4705 4706 for m in self.manipulators: 4707 self.manip_stack.append(m.setup(context, o, self)) 4708 4709 def draw(self, layout, context): 4710 box = layout.box() 4711 row = box.row() 4712 if self.parts_expand: 4713 row.prop(self, 'parts_expand', icon="TRIA_DOWN", text="Parts", emboss=False) 4714 box.prop(self, 'n_parts') 4715 # box.prop(self, 'closed') 4716 for i, part in enumerate(self.parts): 4717 part.draw(layout, context, i) 4718 else: 4719 row.prop(self, 'parts_expand', icon="TRIA_RIGHT", text="Parts", emboss=False) 4720 4721 4722def update_hole(self, context): 4723 # update parent's roof only when manipulated 4724 self.update(context, update_parent=True) 4725 4726 4727def update_operation(self, context): 4728 self.reverse(context, make_ccw=(self.operation == 'INTERSECTION')) 4729 4730 4731class archipack_roof_cutter_segment(ArchipackCutterPart, PropertyGroup): 4732 manipulators : CollectionProperty(type=archipack_manipulator) 4733 type : EnumProperty( 4734 name="Type", 4735 items=( 4736 ('SIDE', 'Side', 'Side with bargeboard', 0), 4737 ('BOTTOM', 'Bottom', 'Bottom with gutter', 1), 4738 ('LINK', 'Side link', 'Side without decoration', 2), 4739 ('AXIS', 'Top', 'Top part with hip and beam', 3) 4740 # ('LINK_VALLEY', 'Side valley', 'Side with valley', 3), 4741 # ('LINK_HIP', 'Side hip', 'Side with hip', 4) 4742 ), 4743 default='SIDE', 4744 update=update_hole 4745 ) 4746 4747 def find_in_selection(self, context): 4748 selected = context.selected_objects[:] 4749 for o in selected: 4750 d = archipack_roof_cutter.datablock(o) 4751 if d: 4752 for part in d.parts: 4753 if part == self: 4754 return d 4755 return None 4756 4757 4758class archipack_roof_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup): 4759 # boundary 4760 parts : CollectionProperty(type=archipack_roof_cutter_segment) 4761 boundary : StringProperty( 4762 default="", 4763 name="Boundary", 4764 description="Boundary of t child to cut parent" 4765 ) 4766 4767 def update_points(self, context, o, pts, update_parent=False): 4768 """ 4769 Create boundary from roof 4770 """ 4771 self.auto_update = False 4772 self.manipulable_disable(context) 4773 self.from_points(pts) 4774 self.manipulable_refresh = True 4775 self.auto_update = True 4776 if update_parent: 4777 self.update_parent(context, o) 4778 # print("update_points") 4779 4780 def update_parent(self, context, o): 4781 4782 d = archipack_roof.datablock(o.parent) 4783 if d is not None: 4784 o.parent.select_set(state=True) 4785 context.view_layer.objects.active = o.parent 4786 d.update(context, update_childs=False, update_hole=False) 4787 o.parent.select_set(state=False) 4788 context.view_layer.objects.active = o 4789 # print("update_parent") 4790 4791 4792class ARCHIPACK_PT_roof_cutter(Panel): 4793 bl_idname = "ARCHIPACK_PT_roof_cutter" 4794 bl_label = "Roof Cutter" 4795 bl_space_type = 'VIEW_3D' 4796 bl_region_type = 'UI' 4797 bl_category = 'Archipack' 4798 4799 @classmethod 4800 def poll(cls, context): 4801 return archipack_roof_cutter.filter(context.active_object) 4802 4803 def draw(self, context): 4804 prop = archipack_roof_cutter.datablock(context.active_object) 4805 if prop is None: 4806 return 4807 layout = self.layout 4808 scene = context.scene 4809 box = layout.box() 4810 if prop.boundary != "": 4811 box.label(text="Auto Cutter:") 4812 box.label(text=prop.boundary) 4813 else: 4814 box.operator('archipack.roof_cutter_manipulate', icon='VIEW_PAN') 4815 box.prop(prop, 'operation', text="") 4816 box = layout.box() 4817 box.label(text="From curve") 4818 box.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE') 4819 if prop.user_defined_path != "": 4820 box.prop(prop, 'user_defined_resolution') 4821 # box.prop(prop, 'x_offset') 4822 # box.prop(prop, 'angle_limit') 4823 """ 4824 box.prop_search(prop, "boundary", scene, "objects", text="", icon='OUTLINER_OB_CURVE') 4825 """ 4826 prop.draw(layout, context) 4827 4828 4829class ARCHIPACK_PT_roof(Panel): 4830 bl_idname = "ARCHIPACK_PT_roof" 4831 bl_label = "Roof" 4832 bl_space_type = 'VIEW_3D' 4833 bl_region_type = 'UI' 4834 bl_category = 'Archipack' 4835 4836 @classmethod 4837 def poll(cls, context): 4838 return archipack_roof.filter(context.active_object) 4839 4840 def draw(self, context): 4841 o = context.active_object 4842 prop = archipack_roof.datablock(o) 4843 if prop is None: 4844 return 4845 scene = context.scene 4846 layout = self.layout 4847 row = layout.row(align=True) 4848 row.operator('archipack.roof_manipulate', icon='VIEW_PAN') 4849 4850 box = layout.box() 4851 row = box.row(align=True) 4852 row.operator("archipack.roof_preset_menu", text=bpy.types.ARCHIPACK_OT_roof_preset_menu.bl_label) 4853 row.operator("archipack.roof_preset", text="", icon='ADD') 4854 row.operator("archipack.roof_preset", text="", icon='REMOVE').remove_active = True 4855 box = layout.box() 4856 box.prop_search(prop, "t_parent", scene, "objects", text="Parent", icon='OBJECT_DATA') 4857 layout.operator('archipack.roof_cutter').parent = o.name 4858 p, d = prop.find_parent(context) 4859 if d is not None: 4860 box.prop(prop, 't_part') 4861 box.prop(prop, 't_dist_x') 4862 box.prop(prop, 't_dist_y') 4863 box.label(text="Hole") 4864 box.prop(prop, 'hole_offset_front') 4865 box.prop(prop, 'hole_offset_left') 4866 box.prop(prop, 'hole_offset_right') 4867 box = layout.box() 4868 box.prop(prop, 'quick_edit', icon="MOD_MULTIRES") 4869 box.prop(prop, 'draft') 4870 if d is None: 4871 box.prop(prop, 'z') 4872 box.prop(prop, 'slope_left') 4873 box.prop(prop, 'slope_right') 4874 box.prop(prop, 'width_left') 4875 box.prop(prop, 'width_right') 4876 # parts 4877 prop.draw(layout, context) 4878 # tiles 4879 box = layout.box() 4880 row = box.row(align=True) 4881 if prop.tile_expand: 4882 row.prop(prop, 'tile_expand', icon="TRIA_DOWN", text="Covering", emboss=False) 4883 else: 4884 row.prop(prop, 'tile_expand', icon="TRIA_RIGHT", text="Covering", emboss=False) 4885 row.prop(prop, 'tile_enable') 4886 if prop.tile_expand: 4887 box.prop(prop, 'tile_model', text="") 4888 4889 box.prop(prop, 'tile_solidify', icon='MOD_SOLIDIFY') 4890 if prop.tile_solidify: 4891 box.prop(prop, 'tile_height') 4892 box.separator() 4893 box.prop(prop, 'tile_bevel', icon='MOD_BEVEL') 4894 if prop.tile_bevel: 4895 box.prop(prop, 'tile_bevel_amt') 4896 box.prop(prop, 'tile_bevel_segs') 4897 box.separator() 4898 box.label(text="Tile size") 4899 box.prop(prop, 'tile_size_x') 4900 box.prop(prop, 'tile_size_y') 4901 box.prop(prop, 'tile_size_z') 4902 box.prop(prop, 'tile_altitude') 4903 4904 box.separator() 4905 box.label(text="Distribution") 4906 box.prop(prop, 'tile_alternate', icon='NLA') 4907 row = box.row(align=True) 4908 row.prop(prop, 'tile_fit_x', icon='ALIGN') 4909 row.prop(prop, 'tile_fit_y', icon='ALIGN') 4910 box.prop(prop, 'tile_offset') 4911 4912 box.label(text="Spacing") 4913 box.prop(prop, 'tile_space_x') 4914 box.prop(prop, 'tile_space_y') 4915 4916 box.separator() # hip 4917 box.label(text="Borders") 4918 box.prop(prop, 'tile_side') 4919 box.prop(prop, 'tile_couloir') 4920 box.prop(prop, 'tile_border') 4921 4922 box = layout.box() 4923 row = box.row(align=True) 4924 if prop.hip_expand: 4925 row.prop(prop, 'hip_expand', icon="TRIA_DOWN", text="Hip", emboss=False) 4926 else: 4927 row.prop(prop, 'hip_expand', icon="TRIA_RIGHT", text="Hip", emboss=False) 4928 row.prop(prop, 'hip_enable') 4929 if prop.hip_expand: 4930 box.prop(prop, 'hip_model', text="") 4931 box.prop(prop, 'hip_size_x') 4932 box.prop(prop, 'hip_size_y') 4933 box.prop(prop, 'hip_size_z') 4934 box.prop(prop, 'hip_alt') 4935 box.prop(prop, 'hip_space_x') 4936 box.separator() 4937 box.prop(prop, 'valley_enable') 4938 box.prop(prop, 'valley_altitude') 4939 4940 box = layout.box() 4941 row = box.row(align=True) 4942 4943 if prop.beam_expand: 4944 row.prop(prop, 'beam_expand', icon="TRIA_DOWN", text="Beam", emboss=False) 4945 else: 4946 row.prop(prop, 'beam_expand', icon="TRIA_RIGHT", text="Beam", emboss=False) 4947 if prop.beam_expand: 4948 box.prop(prop, 'beam_enable') 4949 if prop.beam_enable: 4950 box.prop(prop, 'beam_width') 4951 box.prop(prop, 'beam_height') 4952 box.prop(prop, 'beam_offset') 4953 box.prop(prop, 'beam_alt') 4954 box.separator() 4955 box.prop(prop, 'beam_sec_enable') 4956 if prop.beam_sec_enable: 4957 box.prop(prop, 'beam_sec_width') 4958 box.prop(prop, 'beam_sec_height') 4959 box.prop(prop, 'beam_sec_alt') 4960 box.separator() 4961 box.prop(prop, 'rafter_enable') 4962 if prop.rafter_enable: 4963 box.prop(prop, 'rafter_height') 4964 box.prop(prop, 'rafter_width') 4965 box.prop(prop, 'rafter_spacing') 4966 box.prop(prop, 'rafter_start') 4967 box.prop(prop, 'rafter_alt') 4968 4969 box = layout.box() 4970 row = box.row(align=True) 4971 if prop.gutter_expand: 4972 row.prop(prop, 'gutter_expand', icon="TRIA_DOWN", text="Gutter", emboss=False) 4973 else: 4974 row.prop(prop, 'gutter_expand', icon="TRIA_RIGHT", text="Gutter", emboss=False) 4975 row.prop(prop, 'gutter_enable') 4976 if prop.gutter_expand: 4977 box.prop(prop, 'gutter_alt') 4978 box.prop(prop, 'gutter_width') 4979 box.prop(prop, 'gutter_dist') 4980 box.prop(prop, 'gutter_boudin') 4981 box.prop(prop, 'gutter_segs') 4982 4983 box = layout.box() 4984 row = box.row(align=True) 4985 if prop.fascia_expand: 4986 row.prop(prop, 'fascia_expand', icon="TRIA_DOWN", text="Fascia", emboss=False) 4987 else: 4988 row.prop(prop, 'fascia_expand', icon="TRIA_RIGHT", text="Fascia", emboss=False) 4989 row.prop(prop, 'fascia_enable') 4990 if prop.fascia_expand: 4991 box.prop(prop, 'fascia_altitude') 4992 box.prop(prop, 'fascia_width') 4993 box.prop(prop, 'fascia_height') 4994 box.prop(prop, 'fascia_offset') 4995 4996 box = layout.box() 4997 row = box.row(align=True) 4998 if prop.bargeboard_expand: 4999 row.prop(prop, 'bargeboard_expand', icon="TRIA_DOWN", text="Bargeboard", emboss=False) 5000 else: 5001 row.prop(prop, 'bargeboard_expand', icon="TRIA_RIGHT", text="Bargeboard", emboss=False) 5002 row.prop(prop, 'bargeboard_enable') 5003 if prop.bargeboard_expand: 5004 box.prop(prop, 'bargeboard_altitude') 5005 box.prop(prop, 'bargeboard_width') 5006 box.prop(prop, 'bargeboard_height') 5007 box.prop(prop, 'bargeboard_offset') 5008 5009 """ 5010 box = layout.box() 5011 row.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE') 5012 box.prop(prop, 'user_defined_resolution') 5013 box.prop(prop, 'angle_limit') 5014 """ 5015 5016 5017# ------------------------------------------------------------------ 5018# Define operator class to create object 5019# ------------------------------------------------------------------ 5020 5021 5022class ARCHIPACK_OT_roof(ArchipackCreateTool, Operator): 5023 bl_idname = "archipack.roof" 5024 bl_label = "Roof" 5025 bl_description = "Roof" 5026 bl_category = 'Archipack' 5027 bl_options = {'REGISTER', 'UNDO'} 5028 5029 def create(self, context): 5030 m = bpy.data.meshes.new("Roof") 5031 o = bpy.data.objects.new("Roof", m) 5032 d = m.archipack_roof.add() 5033 # make manipulators selectable 5034 d.manipulable_selectable = True 5035 self.link_object_to_scene(context, o) 5036 o.select_set(state=True) 5037 context.view_layer.objects.active = o 5038 self.add_material(o) 5039 5040 # disable progress bar when 5041 # background render thumbs 5042 if not self.auto_manipulate: 5043 d.quick_edit = False 5044 5045 self.load_preset(d) 5046 return o 5047 5048 # ----------------------------------------------------- 5049 # Execute 5050 # ----------------------------------------------------- 5051 def execute(self, context): 5052 if context.mode == "OBJECT": 5053 bpy.ops.object.select_all(action="DESELECT") 5054 o = self.create(context) 5055 o.location = context.scene.cursor.location 5056 o.select_set(state=True) 5057 context.view_layer.objects.active = o 5058 self.manipulate() 5059 return {'FINISHED'} 5060 else: 5061 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 5062 return {'CANCELLED'} 5063 5064 5065class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator): 5066 bl_idname = "archipack.roof_cutter" 5067 bl_label = "Roof Cutter" 5068 bl_description = "Roof Cutter" 5069 bl_category = 'Archipack' 5070 bl_options = {'REGISTER', 'UNDO'} 5071 5072 parent : StringProperty("") 5073 5074 def create(self, context): 5075 m = bpy.data.meshes.new("Roof Cutter") 5076 o = bpy.data.objects.new("Roof Cutter", m) 5077 d = m.archipack_roof_cutter.add() 5078 parent = context.scene.objects.get(self.parent.strip()) 5079 if parent is not None: 5080 o.parent = parent 5081 bbox = parent.bound_box 5082 angle_90 = pi / 2 5083 x0, y0, z = bbox[0] 5084 x1, y1, z = bbox[6] 5085 x = 0.2 * (x1 - x0) 5086 y = 0.2 * (y1 - y0) 5087 o.matrix_world = parent.matrix_world @ Matrix([ 5088 [1, 0, 0, -3 * x], 5089 [0, 1, 0, 0], 5090 [0, 0, 1, 0], 5091 [0, 0, 0, 1] 5092 ]) 5093 p = d.parts.add() 5094 p.a0 = - angle_90 5095 p.length = y 5096 p = d.parts.add() 5097 p.a0 = angle_90 5098 p.length = x 5099 p = d.parts.add() 5100 p.a0 = angle_90 5101 p.length = y 5102 d.n_parts = 3 5103 # d.close = True 5104 pd = archipack_roof.datablock(parent) 5105 pd.boundary = o.name 5106 else: 5107 o.location = context.scene.cursor.location 5108 # make manipulators selectable 5109 d.manipulable_selectable = True 5110 self.link_object_to_scene(context, o) 5111 o.select_set(state=True) 5112 context.view_layer.objects.active = o 5113 self.add_material(o) 5114 self.load_preset(d) 5115 update_operation(d, context) 5116 return o 5117 5118 # ----------------------------------------------------- 5119 # Execute 5120 # ----------------------------------------------------- 5121 def execute(self, context): 5122 if context.mode == "OBJECT": 5123 bpy.ops.object.select_all(action="DESELECT") 5124 o = self.create(context) 5125 o.select_set(state=True) 5126 context.view_layer.objects.active = o 5127 self.manipulate() 5128 return {'FINISHED'} 5129 else: 5130 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 5131 return {'CANCELLED'} 5132 5133 5134# ------------------------------------------------------------------ 5135# Define operator class to create object 5136# ------------------------------------------------------------------ 5137 5138 5139class ARCHIPACK_OT_roof_from_curve(ArchipackCreateTool, Operator): 5140 bl_idname = "archipack.roof_from_curve" 5141 bl_label = "Roof curve" 5142 bl_description = "Create a roof from a curve" 5143 bl_category = 'Archipack' 5144 bl_options = {'REGISTER', 'UNDO'} 5145 5146 auto_manipulate : BoolProperty(default=True) 5147 5148 @classmethod 5149 def poll(self, context): 5150 return context.active_object is not None and context.active_object.type == 'CURVE' 5151 5152 def draw(self, context): 5153 layout = self.layout 5154 row = layout.row() 5155 row.label(text="Use Properties panel (N) to define parms", icon='INFO') 5156 5157 def create(self, context): 5158 curve = context.active_object 5159 m = bpy.data.meshes.new("Roof") 5160 o = bpy.data.objects.new("Roof", m) 5161 d = m.archipack_roof.add() 5162 # make manipulators selectable 5163 d.manipulable_selectable = True 5164 d.user_defined_path = curve.name 5165 self.link_object_to_scene(context, o) 5166 o.select_set(state=True) 5167 context.view_layer.objects.active = o 5168 d.update_path(context) 5169 5170 spline = curve.data.splines[0] 5171 if spline.type == 'POLY': 5172 pt = spline.points[0].co 5173 elif spline.type == 'BEZIER': 5174 pt = spline.bezier_points[0].co 5175 else: 5176 pt = Vector((0, 0, 0)) 5177 # pretranslate 5178 o.matrix_world = curve.matrix_world @ Matrix([ 5179 [1, 0, 0, pt.x], 5180 [0, 1, 0, pt.y], 5181 [0, 0, 1, pt.z], 5182 [0, 0, 0, 1] 5183 ]) 5184 o.select_set(state=True) 5185 context.view_layer.objects.active = o 5186 return o 5187 5188 # ----------------------------------------------------- 5189 # Execute 5190 # ----------------------------------------------------- 5191 def execute(self, context): 5192 if context.mode == "OBJECT": 5193 bpy.ops.object.select_all(action="DESELECT") 5194 self.create(context) 5195 if self.auto_manipulate: 5196 bpy.ops.archipack.roof_manipulate('INVOKE_DEFAULT') 5197 return {'FINISHED'} 5198 else: 5199 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 5200 return {'CANCELLED'} 5201 5202 5203# ------------------------------------------------------------------ 5204# Define operator class to manipulate object 5205# ------------------------------------------------------------------ 5206 5207 5208class ARCHIPACK_OT_roof_manipulate(Operator): 5209 bl_idname = "archipack.roof_manipulate" 5210 bl_label = "Manipulate" 5211 bl_description = "Manipulate" 5212 bl_options = {'REGISTER', 'UNDO'} 5213 5214 @classmethod 5215 def poll(self, context): 5216 return archipack_roof.filter(context.active_object) 5217 5218 def invoke(self, context, event): 5219 d = archipack_roof.datablock(context.active_object) 5220 d.manipulable_invoke(context) 5221 return {'FINISHED'} 5222 5223 5224class ARCHIPACK_OT_roof_cutter_manipulate(Operator): 5225 bl_idname = "archipack.roof_cutter_manipulate" 5226 bl_label = "Manipulate" 5227 bl_description = "Manipulate" 5228 bl_options = {'REGISTER', 'UNDO'} 5229 5230 @classmethod 5231 def poll(self, context): 5232 return archipack_roof_cutter.filter(context.active_object) 5233 5234 def invoke(self, context, event): 5235 d = archipack_roof_cutter.datablock(context.active_object) 5236 d.manipulable_invoke(context) 5237 return {'FINISHED'} 5238 5239 5240# Update throttle 5241class ArchipackThrottleHandler(): 5242 """ 5243 One modal runs for each object at time 5244 when call for 2nd one 5245 update timer so first one wait more 5246 and kill 2nd one 5247 """ 5248 def __init__(self, context, delay): 5249 self._timer = None 5250 self.start = 0 5251 self.update_state = False 5252 self.delay = delay 5253 5254 def start_timer(self, context): 5255 self.start = time.time() 5256 self._timer = context.window_manager.event_timer_add(self.delay, window=context.window) 5257 5258 def stop_timer(self, context): 5259 if self._timer is not None: 5260 context.window_manager.event_timer_remove(self._timer) 5261 self._timer = None 5262 5263 def execute(self, context): 5264 """ 5265 refresh timer on execute 5266 return 5267 True if modal should run 5268 False on complete 5269 """ 5270 if self._timer is None: 5271 self.update_state = False 5272 self.start_timer(context) 5273 return True 5274 5275 # already a timer running 5276 self.stop_timer(context) 5277 5278 # prevent race conditions when already in update mode 5279 if self.is_updating: 5280 return False 5281 5282 self.start_timer(context) 5283 return False 5284 5285 def modal(self, context, event): 5286 if event.type == 'TIMER' and not self.is_updating: 5287 if time.time() - self.start > self.delay: 5288 self.update_state = True 5289 self.stop_timer(context) 5290 return True 5291 return False 5292 5293 @property 5294 def is_updating(self): 5295 return self.update_state 5296 5297 5298throttle_handlers = {} 5299throttle_delay = 1 5300 5301 5302class ARCHIPACK_OT_roof_throttle_update(Operator): 5303 bl_idname = "archipack.roof_throttle_update" 5304 bl_label = "Update childs with a delay" 5305 5306 name : StringProperty() 5307 5308 def kill_handler(self, context, name): 5309 if name in throttle_handlers.keys(): 5310 throttle_handlers[name].stop_timer(context) 5311 del throttle_handlers[self.name] 5312 5313 def get_handler(self, context, delay): 5314 global throttle_handlers 5315 if self.name not in throttle_handlers.keys(): 5316 throttle_handlers[self.name] = ArchipackThrottleHandler(context, delay) 5317 return throttle_handlers[self.name] 5318 5319 def modal(self, context, event): 5320 global throttle_handlers 5321 if self.name in throttle_handlers.keys(): 5322 if throttle_handlers[self.name].modal(context, event): 5323 act = context.active_object 5324 o = context.scene.objects.get(self.name.strip()) 5325 # print("delay update of %s" % (self.name)) 5326 if o is not None: 5327 selected = o.select_get() 5328 o.select_set(state=True) 5329 context.view_layer.objects.active = o 5330 d = o.data.archipack_roof[0] 5331 d.update(context, 5332 force_update=True, 5333 update_parent=False) 5334 # skip_parent_update=self.skip_parent_update) 5335 o.select_set(state=selected) 5336 context.view_layer.objects.active = act 5337 del throttle_handlers[self.name] 5338 return {'FINISHED'} 5339 else: 5340 return {'PASS_THROUGH'} 5341 else: 5342 return {'FINISHED'} 5343 5344 def execute(self, context): 5345 global throttle_delay 5346 handler = self.get_handler(context, throttle_delay) 5347 if handler.execute(context): 5348 context.window_manager.modal_handler_add(self) 5349 return {'RUNNING_MODAL'} 5350 return {'FINISHED'} 5351 5352 5353# ------------------------------------------------------------------ 5354# Define operator class to load / save presets 5355# ------------------------------------------------------------------ 5356 5357 5358class ARCHIPACK_OT_roof_preset_menu(PresetMenuOperator, Operator): 5359 bl_description = "Show Roof presets" 5360 bl_idname = "archipack.roof_preset_menu" 5361 bl_label = "Roof Styles" 5362 preset_subdir = "archipack_roof" 5363 5364 5365class ARCHIPACK_OT_roof_preset(ArchipackPreset, Operator): 5366 """Add a Roof Styles""" 5367 bl_idname = "archipack.roof_preset" 5368 bl_label = "Add Roof Style" 5369 preset_menu = "ARCHIPACK_OT_roof_preset_menu" 5370 5371 @property 5372 def blacklist(self): 5373 return ['n_parts', 'parts', 'manipulators', 'user_defined_path', 'quick_edit', 'draft'] 5374 5375 5376def register(): 5377 # bpy.utils.register_class(archipack_roof_material) 5378 bpy.utils.register_class(archipack_roof_cutter_segment) 5379 bpy.utils.register_class(archipack_roof_cutter) 5380 bpy.utils.register_class(ARCHIPACK_PT_roof_cutter) 5381 bpy.utils.register_class(ARCHIPACK_OT_roof_cutter) 5382 bpy.utils.register_class(ARCHIPACK_OT_roof_cutter_manipulate) 5383 Mesh.archipack_roof_cutter = CollectionProperty(type=archipack_roof_cutter) 5384 bpy.utils.register_class(archipack_roof_segment) 5385 bpy.utils.register_class(archipack_roof) 5386 Mesh.archipack_roof = CollectionProperty(type=archipack_roof) 5387 bpy.utils.register_class(ARCHIPACK_OT_roof_preset_menu) 5388 bpy.utils.register_class(ARCHIPACK_PT_roof) 5389 bpy.utils.register_class(ARCHIPACK_OT_roof) 5390 bpy.utils.register_class(ARCHIPACK_OT_roof_preset) 5391 bpy.utils.register_class(ARCHIPACK_OT_roof_manipulate) 5392 bpy.utils.register_class(ARCHIPACK_OT_roof_from_curve) 5393 bpy.utils.register_class(ARCHIPACK_OT_roof_throttle_update) 5394 5395 5396def unregister(): 5397 # bpy.utils.unregister_class(archipack_roof_material) 5398 bpy.utils.unregister_class(archipack_roof_cutter_segment) 5399 bpy.utils.unregister_class(archipack_roof_cutter) 5400 bpy.utils.unregister_class(ARCHIPACK_PT_roof_cutter) 5401 bpy.utils.unregister_class(ARCHIPACK_OT_roof_cutter) 5402 bpy.utils.unregister_class(ARCHIPACK_OT_roof_cutter_manipulate) 5403 del Mesh.archipack_roof_cutter 5404 bpy.utils.unregister_class(archipack_roof_segment) 5405 bpy.utils.unregister_class(archipack_roof) 5406 del Mesh.archipack_roof 5407 bpy.utils.unregister_class(ARCHIPACK_OT_roof_preset_menu) 5408 bpy.utils.unregister_class(ARCHIPACK_PT_roof) 5409 bpy.utils.unregister_class(ARCHIPACK_OT_roof) 5410 bpy.utils.unregister_class(ARCHIPACK_OT_roof_preset) 5411 bpy.utils.unregister_class(ARCHIPACK_OT_roof_manipulate) 5412 bpy.utils.unregister_class(ARCHIPACK_OT_roof_from_curve) 5413 bpy.utils.unregister_class(ARCHIPACK_OT_roof_throttle_update) 5414