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# ---------------------------------------------------------- 27import bpy 28import bmesh 29 30import time 31 32from bpy.types import Operator, PropertyGroup, Mesh, Panel 33from bpy.props import ( 34 FloatProperty, BoolProperty, IntProperty, StringProperty, 35 FloatVectorProperty, CollectionProperty, EnumProperty 36) 37from .bmesh_utils import BmeshEdit as bmed 38from mathutils import Vector, Matrix 39from mathutils.geometry import ( 40 interpolate_bezier 41 ) 42from math import sin, cos, pi, atan2 43from .archipack_manipulator import ( 44 Manipulable, archipack_manipulator, 45 GlPolygon, GlPolyline, 46 GlLine, GlText, FeedbackPanel 47 ) 48from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool 49from .archipack_2d import Line, Arc 50from .archipack_snap import snap_point 51from .archipack_keymaps import Keymaps 52 53import logging 54logger = logging.getLogger("archipack") 55 56 57class Wall(): 58 def __init__(self, wall_z, z, t, flip): 59 self.z = z 60 self.wall_z = wall_z 61 self.t = t 62 self.flip = flip 63 self.z_step = len(z) 64 65 def set_offset(self, offset, last=None): 66 """ 67 Offset line and compute intersection point 68 between segments 69 """ 70 self.line = self.make_offset(offset, last) 71 72 def get_z(self, t): 73 t0 = self.t[0] 74 z0 = self.z[0] 75 for i in range(1, self.z_step): 76 t1 = self.t[i] 77 z1 = self.z[i] 78 if t <= t1: 79 return z0 + (t - t0) / (t1 - t0) * (z1 - z0) 80 t0, z0 = t1, z1 81 return self.z[-1] 82 83 def make_faces(self, i, f, faces): 84 if i < self.n_step: 85 # 1 3 5 7 86 # 0 2 4 6 87 if self.flip: 88 faces.append((f + 2, f, f + 1, f + 3)) 89 else: 90 faces.append((f, f + 2, f + 3, f + 1)) 91 92 def p3d(self, verts, t): 93 x, y = self.lerp(t) 94 z = self.wall_z + self.get_z(t) 95 verts.append((x, y, 0)) 96 verts.append((x, y, z)) 97 98 def make_wall(self, i, verts, faces): 99 t = self.t_step[i] 100 f = len(verts) 101 self.p3d(verts, t) 102 self.make_faces(i, f, faces) 103 104 def make_hole(self, i, verts, z0): 105 t = self.t_step[i] 106 x, y = self.line.lerp(t) 107 verts.append((x, y, z0)) 108 109 def straight_wall(self, a0, length, wall_z, z, t): 110 r = self.straight(length).rotate(a0) 111 return StraightWall(r.p, r.v, wall_z, z, t, self.flip) 112 113 def curved_wall(self, a0, da, radius, wall_z, z, t): 114 n = self.normal(1).rotate(a0).scale(radius) 115 if da < 0: 116 n.v = -n.v 117 a0 = n.angle 118 c = n.p - n.v 119 return CurvedWall(c, radius, a0, da, wall_z, z, t, self.flip) 120 121 122class StraightWall(Wall, Line): 123 def __init__(self, p, v, wall_z, z, t, flip): 124 Line.__init__(self, p, v) 125 Wall.__init__(self, wall_z, z, t, flip) 126 127 def param_t(self, step_angle): 128 self.t_step = self.t 129 self.n_step = len(self.t) - 1 130 131 132class CurvedWall(Wall, Arc): 133 def __init__(self, c, radius, a0, da, wall_z, z, t, flip): 134 Arc.__init__(self, c, radius, a0, da) 135 Wall.__init__(self, wall_z, z, t, flip) 136 137 def param_t(self, step_angle): 138 t_step, n_step = self.steps_by_angle(step_angle) 139 self.t_step = list(sorted([i * t_step for i in range(1, n_step)] + self.t)) 140 self.n_step = len(self.t_step) - 1 141 142 143class WallGenerator(): 144 def __init__(self, parts): 145 self.last_type = 'NONE' 146 self.segs = [] 147 self.parts = parts 148 self.faces_type = 'NONE' 149 self.closed = False 150 151 def set_offset(self, offset): 152 last = None 153 for i, seg in enumerate(self.segs): 154 seg.set_offset(offset, last) 155 last = seg.line 156 157 if self.closed: 158 w = self.segs[-1] 159 if len(self.segs) > 1: 160 w.line = w.make_offset(offset, self.segs[-2].line) 161 162 p1 = self.segs[0].line.p1 163 self.segs[0].line = self.segs[0].make_offset(offset, w.line) 164 self.segs[0].line.p1 = p1 165 166 def add_part(self, part, wall_z, flip): 167 168 # TODO: 169 # refactor this part (height manipulators) 170 manip_index = [] 171 if len(self.segs) < 1: 172 s = None 173 z = [part.z[0]] 174 manip_index.append(0) 175 else: 176 s = self.segs[-1] 177 z = [s.z[-1]] 178 179 t_cur = 0 180 z_last = part.n_splits - 1 181 t = [0] 182 183 for i in range(part.n_splits): 184 t_try = t[-1] + part.t[i] 185 if t_try == t_cur: 186 continue 187 if t_try <= 1: 188 t_cur = t_try 189 t.append(t_cur) 190 z.append(part.z[i]) 191 manip_index.append(i) 192 else: 193 z_last = i 194 break 195 196 if t_cur < 1: 197 t.append(1) 198 manip_index.append(z_last) 199 z.append(part.z[z_last]) 200 201 # start a new wall 202 if s is None: 203 if part.type == 'S_WALL': 204 p = Vector((0, 0)) 205 v = part.length * Vector((cos(part.a0), sin(part.a0))) 206 s = StraightWall(p, v, wall_z, z, t, flip) 207 elif part.type == 'C_WALL': 208 c = -part.radius * Vector((cos(part.a0), sin(part.a0))) 209 s = CurvedWall(c, part.radius, part.a0, part.da, wall_z, z, t, flip) 210 else: 211 if part.type == 'S_WALL': 212 s = s.straight_wall(part.a0, part.length, wall_z, z, t) 213 elif part.type == 'C_WALL': 214 s = s.curved_wall(part.a0, part.da, part.radius, wall_z, z, t) 215 216 self.segs.append(s) 217 self.last_type = part.type 218 219 return manip_index 220 221 def close(self, closed): 222 # Make last segment implicit closing one 223 if closed: 224 part = self.parts[-1] 225 w = self.segs[-1] 226 dp = self.segs[0].p0 - self.segs[-1].p0 227 if "C_" in part.type: 228 dw = (w.p1 - w.p0) 229 w.r = part.radius / dw.length * dp.length 230 # angle pt - p0 - angle p0 p1 231 da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x) 232 a0 = w.a0 + da 233 if a0 > pi: 234 a0 -= 2 * pi 235 if a0 < -pi: 236 a0 += 2 * pi 237 w.a0 = a0 238 else: 239 w.v = dp 240 241 def locate_manipulators(self, side): 242 243 for i, wall in enumerate(self.segs): 244 245 manipulators = self.parts[i].manipulators 246 247 p0 = wall.p0.to_3d() 248 p1 = wall.p1.to_3d() 249 250 # angle from last to current segment 251 if i > 0: 252 253 if i < len(self.segs) - 1: 254 manipulators[0].type_key = 'ANGLE' 255 else: 256 manipulators[0].type_key = 'DUMB_ANGLE' 257 258 v0 = self.segs[i - 1].straight(-side, 1).v.to_3d() 259 v1 = wall.straight(side, 0).v.to_3d() 260 manipulators[0].set_pts([p0, v0, v1]) 261 262 if type(wall).__name__ == "StraightWall": 263 # segment length 264 manipulators[1].type_key = 'SIZE' 265 manipulators[1].prop1_name = "length" 266 manipulators[1].set_pts([p0, p1, (side, 0, 0)]) 267 else: 268 # segment radius + angle 269 # scale to fix overlap with drag 270 v0 = side * (wall.p0 - wall.c).to_3d() 271 v1 = side * (wall.p1 - wall.c).to_3d() 272 scale = 1.0 + (0.5 / v0.length) 273 manipulators[1].type_key = 'ARC_ANGLE_RADIUS' 274 manipulators[1].prop1_name = "da" 275 manipulators[1].prop2_name = "radius" 276 manipulators[1].set_pts([wall.c.to_3d(), scale * v0, scale * v1]) 277 278 # snap manipulator, don't change index ! 279 manipulators[2].set_pts([p0, p1, (1, 0, 0)]) 280 281 # dumb, segment index 282 z = Vector((0, 0, 0.75 * wall.wall_z)) 283 manipulators[3].set_pts([p0 + z, p1 + z, (1, 0, 0)]) 284 285 def make_wall(self, step_angle, flip, closed, verts, faces): 286 287 # swap manipulators so they always face outside 288 side = 1 289 if flip: 290 side = -1 291 292 # Make last segment implicit closing one 293 294 nb_segs = len(self.segs) - 1 295 if closed: 296 nb_segs += 1 297 298 for i, wall in enumerate(self.segs): 299 300 wall.param_t(step_angle) 301 if i < nb_segs: 302 for j in range(wall.n_step + 1): 303 wall.make_wall(j, verts, faces) 304 else: 305 # last segment 306 for j in range(wall.n_step): 307 continue 308 # print("%s" % (wall.n_step)) 309 # wall.make_wall(j, verts, faces) 310 311 self.locate_manipulators(side) 312 313 def rotate(self, idx_from, a): 314 """ 315 apply rotation to all following segs 316 """ 317 self.segs[idx_from].rotate(a) 318 ca = cos(a) 319 sa = sin(a) 320 rM = Matrix([ 321 [ca, -sa], 322 [sa, ca] 323 ]) 324 # rotation center 325 p0 = self.segs[idx_from].p0 326 for i in range(idx_from + 1, len(self.segs)): 327 seg = self.segs[i] 328 seg.rotate(a) 329 dp = rM @ (seg.p0 - p0) 330 seg.translate(dp) 331 332 def translate(self, idx_from, dp): 333 """ 334 apply translation to all following segs 335 """ 336 self.segs[idx_from].p1 += dp 337 for i in range(idx_from + 1, len(self.segs)): 338 self.segs[i].translate(dp) 339 340 def change_coordsys(self, fromTM, toTM): 341 """ 342 move shape fromTM into toTM coordsys 343 """ 344 dp = (toTM.inverted() @ fromTM.translation).to_2d() 345 da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d()) 346 ca = cos(da) 347 sa = sin(da) 348 rM = Matrix([ 349 [ca, -sa], 350 [sa, ca] 351 ]) 352 for s in self.segs: 353 tp = (rM @ s.p0) - s.p0 + dp 354 s.rotate(da) 355 s.translate(tp) 356 357 def draw(self, context): 358 for seg in self.segs: 359 seg.draw(context, render=False) 360 361 def debug(self, verts): 362 for wall in self.segs: 363 for i in range(33): 364 x, y = wall.lerp(i / 32) 365 verts.append((x, y, 0)) 366 367 def make_surface(self, o, verts, height): 368 bm = bmesh.new() 369 for v in verts: 370 bm.verts.new(v) 371 bm.verts.ensure_lookup_table() 372 for i in range(1, len(verts)): 373 bm.edges.new((bm.verts[i - 1], bm.verts[i])) 374 bm.edges.new((bm.verts[-1], bm.verts[0])) 375 bm.edges.ensure_lookup_table() 376 bmesh.ops.contextual_create(bm, geom=bm.edges) 377 geom = bm.faces[:] 378 bmesh.ops.solidify(bm, geom=geom, thickness=height) 379 bm.to_mesh(o.data) 380 bm.free() 381 382 def make_hole(self, context, hole_obj, d): 383 384 offset = -0.5 * (1 - d.x_offset) * d.width 385 386 z0 = 0.1 387 self.set_offset(offset) 388 389 nb_segs = len(self.segs) - 1 390 if d.closed: 391 nb_segs += 1 392 393 verts = [] 394 for i, wall in enumerate(self.segs): 395 wall.param_t(d.step_angle) 396 if i < nb_segs: 397 for j in range(wall.n_step + 1): 398 wall.make_hole(j, verts, -z0) 399 400 self.make_surface(hole_obj, verts, d.z + z0) 401 402 403def update(self, context): 404 self.update(context) 405 406 407def update_childs(self, context): 408 self.update(context, update_childs=True, manipulable_refresh=True) 409 410 411def update_manipulators(self, context): 412 self.update(context, manipulable_refresh=True) 413 414 415def update_t_part(self, context): 416 """ 417 Make this wall a T child of parent wall 418 orient child so y points inside wall and x follow wall segment 419 set child a0 according 420 """ 421 o = self.find_in_selection(context) 422 if o is not None: 423 424 # w is parent wall 425 w = context.scene.objects.get(self.t_part.strip()) 426 wd = archipack_wall2.datablock(w) 427 428 if wd is not None: 429 og = self.get_generator() 430 self.setup_childs(o, og) 431 432 bpy.ops.object.select_all(action="DESELECT") 433 434 # 5 cases here: 435 # 1 No parents at all 436 # 2 o has parent 437 # 3 w has parent 438 # 4 o and w share same parent already 439 # 5 o and w doesn't share parent 440 link_to_parent = False 441 442 # when both walls do have a reference point, we may delete one of them 443 to_delete = None 444 445 # select childs and make parent reference point active 446 if w.parent is None: 447 # Either link to o.parent or create new parent 448 link_to_parent = True 449 if o.parent is None: 450 # create a reference point and make it active 451 x, y, z = w.bound_box[0] 452 context.scene.cursor.location = w.matrix_world @ Vector((x, y, z)) 453 # fix issue #9 454 context.view_layer.objects.active = o 455 bpy.ops.archipack.reference_point() 456 o.select_set(state=True) 457 else: 458 context.view_layer.objects.active = o.parent 459 w.select_set(state=True) 460 else: 461 # w has parent 462 if o.parent is not w.parent: 463 link_to_parent = True 464 context.view_layer.objects.active = w.parent 465 o.select_set(state=True) 466 if o.parent is not None: 467 # store o.parent to delete it 468 to_delete = o.parent 469 for c in o.parent.children: 470 if c is not o: 471 c.hide_select = False 472 c.select_set(state=True) 473 474 parent = context.active_object 475 476 dmax = 2 * wd.width 477 478 wg = wd.get_generator() 479 480 otM = o.matrix_world 481 orM = Matrix([ 482 otM[0].to_2d(), 483 otM[1].to_2d() 484 ]) 485 486 wtM = w.matrix_world 487 wrM = Matrix([ 488 wtM[0].to_2d(), 489 wtM[1].to_2d() 490 ]) 491 492 # dir in absolute world coordsys 493 dir = orM @ og.segs[0].straight(1, 0).v 494 495 # pt in w coordsys 496 pos = otM.translation 497 pt = (wtM.inverted() @ pos).to_2d() 498 499 for wall_idx, wall in enumerate(wg.segs): 500 res, dist, t = wall.point_sur_segment(pt) 501 # outside is on the right side of the wall 502 # p1 503 # |-- x 504 # p0 505 506 # NOTE: 507 # rotation here is wrong when w has not parent while o has parent 508 509 if res and t > 0 and t < 1 and abs(dist) < dmax: 510 x = wrM @ wall.straight(1, t).v 511 y = wrM @ wall.normal(t).v.normalized() 512 self.parts[0].a0 = dir.angle_signed(x) 513 o.matrix_world = Matrix([ 514 [x.x, -y.x, 0, pos.x], 515 [x.y, -y.y, 0, pos.y], 516 [0, 0, 1, pos.z], 517 [0, 0, 0, 1] 518 ]) 519 break 520 521 if link_to_parent and bpy.ops.archipack.parent_to_reference.poll(): 522 bpy.ops.archipack.parent_to_reference('INVOKE_DEFAULT') 523 524 # update generator to take new rotation in account 525 # use this to relocate windows on wall after reparenting 526 g = self.get_generator() 527 self.relocate_childs(context, o, g) 528 529 # hide holes from select 530 for c in parent.children: 531 if "archipack_hybridhole" in c: 532 c.hide_select = True 533 534 # delete unneeded reference point 535 if to_delete is not None: 536 bpy.ops.object.select_all(action="DESELECT") 537 to_delete.select_set(state=True) 538 context.view_layer.objects.active = to_delete 539 if bpy.ops.object.delete.poll(): 540 bpy.ops.object.delete(use_global=False) 541 542 elif self.t_part != "": 543 self.t_part = "" 544 545 self.restore_context(context) 546 547 548def set_splits(self, value): 549 if self.n_splits != value: 550 self.auto_update = False 551 self._set_t(value) 552 self.auto_update = True 553 self.n_splits = value 554 return None 555 556 557def get_splits(self): 558 return self.n_splits 559 560 561def update_type(self, context): 562 563 d = self.find_datablock_in_selection(context) 564 565 if d is not None and d.auto_update: 566 567 d.auto_update = False 568 idx = 0 569 for i, part in enumerate(d.parts): 570 if part == self: 571 idx = i 572 break 573 a0 = 0 574 if idx > 0: 575 g = d.get_generator() 576 w0 = g.segs[idx - 1] 577 a0 = w0.straight(1).angle 578 if "C_" in self.type: 579 w = w0.straight_wall(self.a0, self.length, d.z, self.z, self.t) 580 else: 581 w = w0.curved_wall(self.a0, self.da, self.radius, d.z, self.z, self.t) 582 else: 583 if "C_" in self.type: 584 p = Vector((0, 0)) 585 v = self.length * Vector((cos(self.a0), sin(self.a0))) 586 w = StraightWall(p, v, d.z, self.z, self.t, d.flip) 587 a0 = pi / 2 588 else: 589 c = -self.radius * Vector((cos(self.a0), sin(self.a0))) 590 w = CurvedWall(c, self.radius, self.a0, pi, d.z, self.z, self.t, d.flip) 591 592 # w0 - w - w1 593 if d.closed and idx == d.n_parts: 594 dp = - w.p0 595 else: 596 dp = w.p1 - w.p0 597 598 if "C_" in self.type: 599 self.radius = 0.5 * dp.length 600 self.da = pi 601 a0 = atan2(dp.y, dp.x) - pi / 2 - a0 602 else: 603 self.length = dp.length 604 a0 = atan2(dp.y, dp.x) - a0 605 606 if a0 > pi: 607 a0 -= 2 * pi 608 if a0 < -pi: 609 a0 += 2 * pi 610 self.a0 = a0 611 612 if idx + 1 < d.n_parts: 613 # adjust rotation of next part 614 part1 = d.parts[idx + 1] 615 if "C_" in self.type: 616 a0 = part1.a0 - pi / 2 617 else: 618 a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x) 619 620 if a0 > pi: 621 a0 -= 2 * pi 622 if a0 < -pi: 623 a0 += 2 * pi 624 part1.a0 = a0 625 626 d.auto_update = True 627 628 629class archipack_wall2_part(PropertyGroup): 630 type : EnumProperty( 631 items=( 632 ('S_WALL', 'Straight', '', 0), 633 ('C_WALL', 'Curved', '', 1) 634 ), 635 default='S_WALL', 636 update=update_type 637 ) 638 length : FloatProperty( 639 name="Length", 640 min=0.01, 641 default=2.0, 642 unit='LENGTH', subtype='DISTANCE', 643 update=update 644 ) 645 radius : FloatProperty( 646 name="Radius", 647 min=0.5, 648 default=0.7, 649 unit='LENGTH', subtype='DISTANCE', 650 update=update 651 ) 652 a0 : FloatProperty( 653 name="Start angle", 654 min=-pi, 655 max=pi, 656 default=pi / 2, 657 subtype='ANGLE', unit='ROTATION', 658 update=update 659 ) 660 da : FloatProperty( 661 name="Angle", 662 min=-pi, 663 max=pi, 664 default=pi / 2, 665 subtype='ANGLE', unit='ROTATION', 666 update=update 667 ) 668 z : FloatVectorProperty( 669 name="Height", 670 default=[ 671 0, 0, 0, 0, 0, 0, 0, 0, 672 0, 0, 0, 0, 0, 0, 0, 0, 673 0, 0, 0, 0, 0, 0, 0, 0, 674 0, 0, 0, 0, 0, 0, 0 675 ], 676 size=31, 677 update=update 678 ) 679 t : FloatVectorProperty( 680 name="Position", 681 min=0, 682 max=1, 683 default=[ 684 1, 1, 1, 1, 1, 1, 1, 1, 685 1, 1, 1, 1, 1, 1, 1, 1, 686 1, 1, 1, 1, 1, 1, 1, 1, 687 1, 1, 1, 1, 1, 1, 1 688 ], 689 size=31, 690 update=update 691 ) 692 splits : IntProperty( 693 name="Splits", 694 default=1, 695 min=1, 696 max=31, 697 get=get_splits, set=set_splits 698 ) 699 n_splits : IntProperty( 700 name="Splits", 701 default=1, 702 min=1, 703 max=31, 704 update=update 705 ) 706 auto_update : BoolProperty(default=True) 707 manipulators : CollectionProperty(type=archipack_manipulator) 708 # ui related 709 expand : BoolProperty(default=False) 710 711 def _set_t(self, splits): 712 t = 1 / splits 713 for i in range(splits): 714 self.t[i] = t 715 716 def find_datablock_in_selection(self, context): 717 """ 718 find witch selected object this instance belongs to 719 provide support for "copy to selected" 720 """ 721 selected = context.selected_objects[:] 722 for o in selected: 723 props = archipack_wall2.datablock(o) 724 if props: 725 for part in props.parts: 726 if part == self: 727 return props 728 return None 729 730 def update(self, context, manipulable_refresh=False): 731 if not self.auto_update: 732 return 733 props = self.find_datablock_in_selection(context) 734 if props is not None: 735 props.update(context, manipulable_refresh) 736 737 def draw(self, layout, context, index): 738 739 row = layout.row(align=True) 740 if self.expand: 741 row.prop(self, 'expand', icon="TRIA_DOWN", text="Part " + str(index + 1), emboss=False) 742 else: 743 row.prop(self, 'expand', icon="TRIA_RIGHT", text="Part " + str(index + 1), emboss=False) 744 745 row.prop(self, "type", text="") 746 747 if self.expand: 748 row = layout.row(align=True) 749 row.operator("archipack.wall2_insert", text="Split").index = index 750 row.operator("archipack.wall2_remove", text="Remove").index = index 751 if self.type == 'C_WALL': 752 row = layout.row() 753 row.prop(self, "radius") 754 row = layout.row() 755 row.prop(self, "da") 756 else: 757 row = layout.row() 758 row.prop(self, "length") 759 row = layout.row() 760 row.prop(self, "a0") 761 row = layout.row() 762 row.prop(self, "splits") 763 for split in range(self.n_splits): 764 row = layout.row() 765 row.prop(self, "z", text="alt", index=split) 766 row.prop(self, "t", text="pos", index=split) 767 768 769class archipack_wall2_child(PropertyGroup): 770 # Size Loc 771 # Delta Loc 772 manipulators : CollectionProperty(type=archipack_manipulator) 773 child_name : StringProperty() 774 wall_idx : IntProperty() 775 pos : FloatVectorProperty(subtype='XYZ') 776 flip : BoolProperty(default=False) 777 778 def get_child(self, context): 779 d = None 780 child = context.scene.objects.get(self.child_name.strip()) 781 if child is not None and child.data is not None: 782 cd = child.data 783 if 'archipack_window' in cd: 784 d = cd.archipack_window[0] 785 elif 'archipack_door' in cd: 786 d = cd.archipack_door[0] 787 return child, d 788 789 790class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): 791 parts : CollectionProperty(type=archipack_wall2_part) 792 n_parts : IntProperty( 793 name="Parts", 794 min=1, 795 max=1024, 796 default=1, update=update_manipulators 797 ) 798 step_angle : FloatProperty( 799 description="Curved parts segmentation", 800 name="Step angle", 801 min=1 / 180 * pi, 802 max=pi, 803 default=6 / 180 * pi, 804 subtype='ANGLE', unit='ROTATION', 805 update=update 806 ) 807 width : FloatProperty( 808 name="Width", 809 min=0.01, 810 default=0.2, 811 unit='LENGTH', subtype='DISTANCE', 812 update=update 813 ) 814 z : FloatProperty( 815 name='Height', 816 min=0.1, 817 default=2.7, precision=2, 818 unit='LENGTH', subtype='DISTANCE', 819 description='height', update=update, 820 ) 821 x_offset : FloatProperty( 822 name="Offset", 823 min=-1, max=1, 824 default=-1, precision=2, step=1, 825 update=update 826 ) 827 radius : FloatProperty( 828 name="Radius", 829 min=0.5, 830 default=0.7, 831 unit='LENGTH', subtype='DISTANCE', 832 update=update 833 ) 834 da : FloatProperty( 835 name="Angle", 836 min=-pi, 837 max=pi, 838 default=pi / 2, 839 subtype='ANGLE', unit='ROTATION', 840 update=update 841 ) 842 flip : BoolProperty( 843 name="Flip", 844 default=False, 845 update=update_childs 846 ) 847 closed : BoolProperty( 848 default=False, 849 name="Close", 850 update=update_manipulators 851 ) 852 auto_update : BoolProperty( 853 options={'SKIP_SAVE'}, 854 default=True, 855 update=update_manipulators 856 ) 857 realtime : BoolProperty( 858 options={'SKIP_SAVE'}, 859 default=True, 860 name="Real Time", 861 description="Relocate childs in realtime" 862 ) 863 # dumb manipulators to show sizes between childs 864 childs_manipulators : CollectionProperty(type=archipack_manipulator) 865 # store to manipulate windows and doors 866 childs : CollectionProperty(type=archipack_wall2_child) 867 t_part : StringProperty( 868 name="Parent wall", 869 description="This part will follow parent when set", 870 default="", 871 update=update_t_part 872 ) 873 874 def insert_part(self, context, o, where): 875 self.manipulable_disable(context) 876 self.auto_update = False 877 # the part we do split 878 part_0 = self.parts[where] 879 part_0.length /= 2 880 part_0.da /= 2 881 self.parts.add() 882 part_1 = self.parts[len(self.parts) - 1] 883 part_1.type = part_0.type 884 part_1.length = part_0.length 885 part_1.da = part_0.da 886 part_1.a0 = 0 887 # move after current one 888 self.parts.move(len(self.parts) - 1, where + 1) 889 self.n_parts += 1 890 # re-eval childs location 891 g = self.get_generator() 892 self.setup_childs(o, g) 893 894 self.setup_manipulators() 895 self.auto_update = True 896 897 def add_part(self, context, length): 898 self.manipulable_disable(context) 899 self.auto_update = False 900 p = self.parts.add() 901 p.length = length 902 self.parts.move(len(self.parts) - 1, self.n_parts) 903 self.n_parts += 1 904 self.setup_manipulators() 905 self.auto_update = True 906 return self.parts[self.n_parts - 1] 907 908 def remove_part(self, context, o, where): 909 self.manipulable_disable(context) 910 self.auto_update = False 911 # preserve shape 912 # using generator 913 if where > 0: 914 g = self.get_generator() 915 w = g.segs[where - 1] 916 w.p1 = g.segs[where].p1 917 918 if where + 1 < self.n_parts: 919 self.parts[where + 1].a0 = g.segs[where + 1].delta_angle(w) 920 921 part = self.parts[where - 1] 922 923 if "C_" in part.type: 924 part.radius = w.r 925 else: 926 part.length = w.length 927 928 if where > 1: 929 part.a0 = w.delta_angle(g.segs[where - 2]) 930 else: 931 part.a0 = w.straight(1, 0).angle 932 933 self.parts.remove(where) 934 self.n_parts -= 1 935 936 # re-eval child location 937 g = self.get_generator() 938 self.setup_childs(o, g) 939 940 # fix snap manipulators index 941 self.setup_manipulators() 942 self.auto_update = True 943 944 def get_generator(self): 945 # print("get_generator") 946 g = WallGenerator(self.parts) 947 for part in self.parts: 948 g.add_part(part, self.z, self.flip) 949 g.close(self.closed) 950 return g 951 952 def update_parts(self, o, update_childs=False): 953 # print("update_parts") 954 # remove rows 955 # NOTE: 956 # n_parts+1 957 # as last one is end point of last segment or closing one 958 row_change = False 959 for i in range(len(self.parts), self.n_parts + 1, -1): 960 row_change = True 961 self.parts.remove(i - 1) 962 963 # add rows 964 for i in range(len(self.parts), self.n_parts + 1): 965 row_change = True 966 self.parts.add() 967 968 self.setup_manipulators() 969 970 g = self.get_generator() 971 972 if o is not None and (row_change or update_childs): 973 self.setup_childs(o, g) 974 975 return g 976 977 def setup_manipulators(self): 978 979 if len(self.manipulators) == 0: 980 # make manipulators selectable 981 s = self.manipulators.add() 982 s.prop1_name = "width" 983 s = self.manipulators.add() 984 s.prop1_name = "n_parts" 985 s.type_key = 'COUNTER' 986 s = self.manipulators.add() 987 s.prop1_name = "z" 988 s.normal = (0, 1, 0) 989 990 if self.t_part != "" and len(self.manipulators) < 4: 991 s = self.manipulators.add() 992 s.prop1_name = "x" 993 s.type_key = 'DELTA_LOC' 994 995 for i in range(self.n_parts + 1): 996 p = self.parts[i] 997 n_manips = len(p.manipulators) 998 if n_manips < 1: 999 s = p.manipulators.add() 1000 s.type_key = "ANGLE" 1001 s.prop1_name = "a0" 1002 if n_manips < 2: 1003 s = p.manipulators.add() 1004 s.type_key = "SIZE" 1005 s.prop1_name = "length" 1006 if n_manips < 3: 1007 s = p.manipulators.add() 1008 s.type_key = 'WALL_SNAP' 1009 s.prop1_name = str(i) 1010 s.prop2_name = 'z' 1011 if n_manips < 4: 1012 s = p.manipulators.add() 1013 s.type_key = 'DUMB_STRING' 1014 s.prop1_name = str(i + 1) 1015 p.manipulators[2].prop1_name = str(i) 1016 p.manipulators[3].prop1_name = str(i + 1) 1017 1018 def interpolate_bezier(self, pts, wM, p0, p1, resolution): 1019 if resolution == 0: 1020 pts.append(wM @ p0.co.to_3d()) 1021 else: 1022 v = (p1.co - p0.co).normalized() 1023 d1 = (p0.handle_right - p0.co).normalized() 1024 d2 = (p1.co - p1.handle_left).normalized() 1025 if d1 == v and d2 == v: 1026 pts.append(wM @ p0.co.to_3d()) 1027 else: 1028 seg = interpolate_bezier(wM @ p0.co, 1029 wM @ p0.handle_right, 1030 wM @ p1.handle_left, 1031 wM @ p1.co, 1032 resolution + 1) 1033 for i in range(resolution): 1034 pts.append(seg[i].to_3d()) 1035 1036 def is_cw(self, pts): 1037 p0 = pts[0] 1038 d = 0 1039 for p in pts[1:]: 1040 d += (p.x * p0.y - p.y * p0.x) 1041 p0 = p 1042 return d > 0 1043 1044 def from_spline(self, wM, resolution, spline): 1045 pts = [] 1046 if spline.type == 'POLY': 1047 pts = [wM @ p.co.to_3d() for p in spline.points] 1048 if spline.use_cyclic_u: 1049 pts.append(pts[0]) 1050 elif spline.type == 'BEZIER': 1051 points = spline.bezier_points 1052 for i in range(1, len(points)): 1053 p0 = points[i - 1] 1054 p1 = points[i] 1055 self.interpolate_bezier(pts, wM, p0, p1, resolution) 1056 if spline.use_cyclic_u: 1057 p0 = points[-1] 1058 p1 = points[0] 1059 self.interpolate_bezier(pts, wM, p0, p1, resolution) 1060 pts.append(pts[0]) 1061 else: 1062 pts.append(wM @ points[-1].co) 1063 1064 if self.is_cw(pts): 1065 pts = list(reversed(pts)) 1066 1067 self.auto_update = False 1068 self.from_points(pts, spline.use_cyclic_u) 1069 self.auto_update = True 1070 1071 def from_points(self, pts, closed): 1072 1073 self.n_parts = len(pts) - 1 1074 1075 if closed: 1076 self.n_parts -= 1 1077 1078 self.update_parts(None) 1079 1080 p0 = pts.pop(0) 1081 a0 = 0 1082 for i, p1 in enumerate(pts): 1083 dp = p1 - p0 1084 da = atan2(dp.y, dp.x) - a0 1085 if da > pi: 1086 da -= 2 * pi 1087 if da < -pi: 1088 da += 2 * pi 1089 if i >= len(self.parts): 1090 print("Too many pts for parts") 1091 break 1092 p = self.parts[i] 1093 p.length = dp.to_2d().length 1094 p.dz = dp.z 1095 p.a0 = da 1096 a0 += da 1097 p0 = p1 1098 1099 self.closed = closed 1100 1101 def reverse(self, context, o): 1102 1103 g = self.get_generator() 1104 1105 self.auto_update = False 1106 1107 pts = [seg.p0.to_3d() for seg in g.segs] 1108 1109 if not self.closed: 1110 g.segs.pop() 1111 1112 g_segs = list(reversed(g.segs)) 1113 1114 last_seg = None 1115 1116 for i, seg in enumerate(g_segs): 1117 1118 s = seg.oposite 1119 if "Curved" in type(seg).__name__: 1120 self.parts[i].type = "C_WALL" 1121 self.parts[i].radius = s.r 1122 self.parts[i].da = s.da 1123 else: 1124 self.parts[i].type = "S_WALL" 1125 self.parts[i].length = s.length 1126 1127 self.parts[i].a0 = s.delta_angle(last_seg) 1128 1129 last_seg = s 1130 1131 if self.closed: 1132 pts.append(pts[0]) 1133 1134 pts = list(reversed(pts)) 1135 1136 # location wont change for closed walls 1137 if not self.closed: 1138 dp = pts[0] - pts[-1] 1139 # pre-translate as dp is in local coordsys 1140 o.matrix_world = o.matrix_world @ Matrix([ 1141 [1, 0, 0, dp.x], 1142 [0, 1, 0, dp.y], 1143 [0, 0, 1, 0], 1144 [0, 0, 0, 1], 1145 ]) 1146 1147 # self.from_points(pts, self.closed) 1148 1149 g = self.get_generator() 1150 1151 self.setup_childs(o, g) 1152 self.auto_update = True 1153 1154 # flip does trigger relocate and keep childs orientation 1155 self.flip = not self.flip 1156 1157 def update(self, context, manipulable_refresh=False, update_childs=False): 1158 1159 o = self.find_in_selection(context, self.auto_update) 1160 1161 if o is None: 1162 return 1163 1164 if manipulable_refresh: 1165 # prevent crash by removing all manipulators refs to datablock before changes 1166 self.manipulable_disable(context) 1167 1168 verts = [] 1169 faces = [] 1170 1171 g = self.update_parts(o, update_childs) 1172 # print("make_wall") 1173 g.make_wall(self.step_angle, self.flip, self.closed, verts, faces) 1174 1175 if self.closed: 1176 f = len(verts) 1177 if self.flip: 1178 faces.append((0, f - 2, f - 1, 1)) 1179 else: 1180 faces.append((f - 2, 0, 1, f - 1)) 1181 1182 # print("buildmesh") 1183 bmed.buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=True) 1184 1185 side = 1 1186 if self.flip: 1187 side = -1 1188 # Width 1189 offset = side * (0.5 * self.x_offset) * self.width 1190 self.manipulators[0].set_pts([ 1191 g.segs[0].sized_normal(0, offset + 0.5 * side * self.width).v.to_3d(), 1192 g.segs[0].sized_normal(0, offset - 0.5 * side * self.width).v.to_3d(), 1193 (-side, 0, 0) 1194 ]) 1195 1196 # Parts COUNTER 1197 self.manipulators[1].set_pts([g.segs[-2].lerp(1.1).to_3d(), 1198 g.segs[-2].lerp(1.1 + 0.5 / g.segs[-2].length).to_3d(), 1199 (-side, 0, 0) 1200 ]) 1201 1202 # Height 1203 self.manipulators[2].set_pts([ 1204 (0, 0, 0), 1205 (0, 0, self.z), 1206 (-1, 0, 0) 1207 ], normal=g.segs[0].straight(side, 0).v.to_3d()) 1208 1209 if self.t_part != "": 1210 t = 0.3 / g.segs[0].length 1211 self.manipulators[3].set_pts([ 1212 g.segs[0].sized_normal(t, 0.1).p1.to_3d(), 1213 g.segs[0].sized_normal(t, -0.1).p1.to_3d(), 1214 (1, 0, 0) 1215 ]) 1216 1217 if self.realtime: 1218 # update child location and size 1219 self.relocate_childs(context, o, g) 1220 # store gl points 1221 self.update_childs(context, o, g) 1222 else: 1223 bpy.ops.archipack.wall2_throttle_update(name=o.name) 1224 1225 modif = o.modifiers.get('Wall') 1226 if modif is None: 1227 modif = o.modifiers.new('Wall', 'SOLIDIFY') 1228 modif.use_quality_normals = True 1229 modif.use_even_offset = True 1230 modif.material_offset_rim = 2 1231 modif.material_offset = 1 1232 1233 modif.thickness = self.width 1234 modif.offset = self.x_offset 1235 1236 if manipulable_refresh: 1237 # print("manipulable_refresh=True") 1238 self.manipulable_refresh = True 1239 1240 self.restore_context(context) 1241 1242 # manipulable children objects like windows and doors 1243 def child_partition(self, array, begin, end): 1244 pivot = begin 1245 for i in range(begin + 1, end + 1): 1246 # wall idx 1247 if array[i][1] < array[begin][1]: 1248 pivot += 1 1249 array[i], array[pivot] = array[pivot], array[i] 1250 # param t on the wall 1251 elif array[i][1] == array[begin][1] and array[i][4] <= array[begin][4]: 1252 pivot += 1 1253 array[i], array[pivot] = array[pivot], array[i] 1254 array[pivot], array[begin] = array[begin], array[pivot] 1255 return pivot 1256 1257 def sort_child(self, array, begin=0, end=None): 1258 # print("sort_child") 1259 if end is None: 1260 end = len(array) - 1 1261 1262 def _quicksort(array, begin, end): 1263 if begin >= end: 1264 return 1265 pivot = self.child_partition(array, begin, end) 1266 _quicksort(array, begin, pivot - 1) 1267 _quicksort(array, pivot + 1, end) 1268 return _quicksort(array, begin, end) 1269 1270 def add_child(self, name, wall_idx, pos, flip): 1271 # print("add_child %s %s" % (name, wall_idx)) 1272 c = self.childs.add() 1273 c.child_name = name 1274 c.wall_idx = wall_idx 1275 c.pos = pos 1276 c.flip = flip 1277 m = c.manipulators.add() 1278 m.type_key = 'DELTA_LOC' 1279 m.prop1_name = "x" 1280 m = c.manipulators.add() 1281 m.type_key = 'SNAP_SIZE_LOC' 1282 m.prop1_name = "x" 1283 m.prop2_name = "x" 1284 1285 def setup_childs(self, o, g): 1286 """ 1287 Store childs 1288 create manipulators 1289 call after a boolean oop 1290 """ 1291 # tim = time.time() 1292 self.childs.clear() 1293 self.childs_manipulators.clear() 1294 if o.parent is None: 1295 return 1296 wall_with_childs = [0 for i in range(self.n_parts + 1)] 1297 relocate = [] 1298 dmax = 2 * self.width 1299 1300 wtM = o.matrix_world 1301 wrM = Matrix([ 1302 wtM[0].to_2d(), 1303 wtM[1].to_2d() 1304 ]) 1305 witM = wtM.inverted() 1306 1307 for child in o.parent.children: 1308 # filter allowed childs 1309 cd = child.data 1310 wd = archipack_wall2.datablock(child) 1311 if (child != o and cd is not None and ( 1312 'archipack_window' in cd or 1313 'archipack_door' in cd or ( 1314 wd is not None and 1315 o.name in wd.t_part 1316 ) 1317 )): 1318 1319 # setup on T linked walls 1320 if wd is not None: 1321 wg = wd.get_generator() 1322 wd.setup_childs(child, wg) 1323 1324 ctM = child.matrix_world 1325 crM = Matrix([ 1326 ctM[0].to_2d(), 1327 ctM[1].to_2d() 1328 ]) 1329 1330 # pt in w coordsys 1331 pos = ctM.translation 1332 pt = (witM @ pos).to_2d() 1333 1334 for wall_idx, wall in enumerate(g.segs): 1335 # may be optimized with a bound check 1336 res, dist, t = wall.point_sur_segment(pt) 1337 # outside is on the right side of the wall 1338 # p1 1339 # |-- x 1340 # p0 1341 if res and t > 0 and t < 1 and abs(dist) < dmax: 1342 # dir in world coordsys 1343 dir = wrM @ wall.sized_normal(t, 1).v 1344 wall_with_childs[wall_idx] = 1 1345 m = self.childs_manipulators.add() 1346 m.type_key = 'DUMB_SIZE' 1347 # always make window points outside 1348 if "archipack_window" in cd: 1349 flip = self.flip 1350 else: 1351 dir_y = crM @ Vector((0, -1)) 1352 # let door orient where user want 1353 flip = (dir_y - dir).length > 0.5 1354 # store z in wall space 1355 relocate.append(( 1356 child.name, 1357 wall_idx, 1358 (t * wall.length, dist, (witM @ pos).z), 1359 flip, 1360 t)) 1361 break 1362 1363 self.sort_child(relocate) 1364 for child in relocate: 1365 name, wall_idx, pos, flip, t = child 1366 self.add_child(name, wall_idx, pos, flip) 1367 1368 # add a dumb size from last child to end of wall segment 1369 for i in range(sum(wall_with_childs)): 1370 m = self.childs_manipulators.add() 1371 m.type_key = 'DUMB_SIZE' 1372 # print("setup_childs:%1.4f" % (time.time()-tim)) 1373 1374 def relocate_childs(self, context, o, g): 1375 """ 1376 Move and resize childs after wall edition 1377 """ 1378 # print("relocate_childs") 1379 # tim = time.time() 1380 w = -self.x_offset * self.width 1381 if self.flip: 1382 w = -w 1383 tM = o.matrix_world 1384 for child in self.childs: 1385 c, d = child.get_child(context) 1386 if c is None: 1387 continue 1388 t = child.pos.x / g.segs[child.wall_idx].length 1389 n = g.segs[child.wall_idx].sized_normal(t, 1) 1390 rx, ry = -n.v 1391 rx, ry = ry, -rx 1392 if child.flip: 1393 rx, ry = -rx, -ry 1394 1395 if d is not None: 1396 # print("change flip:%s width:%s" % (d.flip != child.flip, d.y != self.width)) 1397 if d.y != self.width or d.flip != child.flip: 1398 c.select_set(state=True) 1399 d.auto_update = False 1400 d.flip = child.flip 1401 d.y = self.width 1402 d.auto_update = True 1403 c.select_set(state=False) 1404 x, y = n.p - (0.5 * w * n.v) 1405 else: 1406 x, y = n.p - (child.pos.y * n.v) 1407 1408 context.view_layer.objects.active = o 1409 # preTranslate 1410 c.matrix_world = tM @ Matrix([ 1411 [rx, -ry, 0, x], 1412 [ry, rx, 0, y], 1413 [0, 0, 1, child.pos.z], 1414 [0, 0, 0, 1] 1415 ]) 1416 1417 # Update T linked wall's childs 1418 if archipack_wall2.filter(c): 1419 d = archipack_wall2.datablock(c) 1420 cg = d.get_generator() 1421 d.relocate_childs(context, c, cg) 1422 1423 # print("relocate_childs:%1.4f" % (time.time()-tim)) 1424 1425 def update_childs(self, context, o, g): 1426 """ 1427 setup gl points for childs 1428 """ 1429 # print("update_childs") 1430 1431 if o.parent is None: 1432 return 1433 1434 # swap manipulators so they always face outside 1435 manip_side = 1 1436 if self.flip: 1437 manip_side = -1 1438 1439 itM = o.matrix_world.inverted() 1440 m_idx = 0 1441 for wall_idx, wall in enumerate(g.segs): 1442 p0 = wall.lerp(0) 1443 wall_has_childs = False 1444 for child in self.childs: 1445 if child.wall_idx == wall_idx: 1446 c, d = child.get_child(context) 1447 if d is not None: 1448 # child is either a window or a door 1449 wall_has_childs = True 1450 dt = 0.5 * d.x / wall.length 1451 pt = (itM @ c.matrix_world.translation).to_2d() 1452 res, y, t = wall.point_sur_segment(pt) 1453 child.pos = (wall.length * t, y, child.pos.z) 1454 p1 = wall.lerp(t - dt) 1455 # dumb size between childs 1456 self.childs_manipulators[m_idx].set_pts([ 1457 (p0.x, p0.y, 0), 1458 (p1.x, p1.y, 0), 1459 (manip_side * 0.5, 0, 0)]) 1460 m_idx += 1 1461 x, y = 0.5 * d.x, -self.x_offset * 0.5 * d.y 1462 1463 if child.flip: 1464 side = -manip_side 1465 else: 1466 side = manip_side 1467 1468 # delta loc 1469 child.manipulators[0].set_pts([(-x, side * -y, 0), (x, side * -y, 0), (side, 0, 0)]) 1470 # loc size 1471 child.manipulators[1].set_pts([ 1472 (-x, side * -y, 0), 1473 (x, side * -y, 0), 1474 (0.5 * side, 0, 0)]) 1475 p0 = wall.lerp(t + dt) 1476 p1 = wall.lerp(1) 1477 if wall_has_childs: 1478 # dub size after all childs 1479 self.childs_manipulators[m_idx].set_pts([ 1480 (p0.x, p0.y, 0), 1481 (p1.x, p1.y, 0), 1482 (manip_side * 0.5, 0, 0)]) 1483 m_idx += 1 1484 1485 def manipulate_childs(self, context): 1486 """ 1487 setup child manipulators 1488 """ 1489 # print("manipulate_childs") 1490 n_parts = self.n_parts 1491 if self.closed: 1492 n_parts += 1 1493 1494 for wall_idx in range(n_parts): 1495 for child in self.childs: 1496 if child.wall_idx == wall_idx: 1497 c, d = child.get_child(context) 1498 if d is not None: 1499 # delta loc 1500 self.manip_stack.append(child.manipulators[0].setup(context, c, d, self.manipulate_callback)) 1501 # loc size 1502 self.manip_stack.append(child.manipulators[1].setup(context, c, d, self.manipulate_callback)) 1503 1504 def manipulate_callback(self, context, o=None, manipulator=None): 1505 found = False 1506 if o.parent is not None: 1507 for c in o.parent.children: 1508 if (archipack_wall2.datablock(c) == self): 1509 context.view_layer.objects.active = c 1510 found = True 1511 break 1512 if found: 1513 self.manipulable_manipulate(context, manipulator=manipulator) 1514 1515 def manipulable_manipulate(self, context, event=None, manipulator=None): 1516 type_name = type(manipulator).__name__ 1517 # print("manipulable_manipulate %s" % (type_name)) 1518 if type_name in [ 1519 'DeltaLocationManipulator', 1520 'SizeLocationManipulator', 1521 'SnapSizeLocationManipulator' 1522 ]: 1523 # update manipulators pos of childs 1524 o = context.active_object 1525 if o.parent is None: 1526 return 1527 g = self.get_generator() 1528 itM = o.matrix_world.inverted() @ o.parent.matrix_world 1529 for child in self.childs: 1530 c, d = child.get_child(context) 1531 if d is not None: 1532 wall = g.segs[child.wall_idx] 1533 pt = (itM @ c.location).to_2d() 1534 res, d, t = wall.point_sur_segment(pt) 1535 child.pos = (t * wall.length, d, child.pos.z) 1536 # update childs manipulators 1537 self.update_childs(context, o, g) 1538 1539 def manipulable_move_t_part(self, context, o=None, manipulator=None): 1540 """ 1541 Callback for t_parts childs 1542 """ 1543 type_name = type(manipulator).__name__ 1544 # print("manipulable_manipulate %s" % (type_name)) 1545 if type_name in [ 1546 'DeltaLocationManipulator' 1547 ]: 1548 # update manipulators pos of childs 1549 if archipack_wall2.datablock(o) != self: 1550 return 1551 g = self.get_generator() 1552 # update childs 1553 self.relocate_childs(context, o, g) 1554 1555 def manipulable_release(self, context): 1556 """ 1557 Override with action to do on mouse release 1558 eg: big update 1559 """ 1560 return 1561 1562 def manipulable_setup(self, context): 1563 # print("manipulable_setup") 1564 self.manipulable_disable(context) 1565 o = context.active_object 1566 1567 # setup childs manipulators 1568 self.manipulate_childs(context) 1569 n_parts = self.n_parts 1570 if self.closed: 1571 n_parts += 1 1572 1573 # update manipulators on version change 1574 self.setup_manipulators() 1575 1576 for i, part in enumerate(self.parts): 1577 1578 if i < n_parts: 1579 if i > 0: 1580 # start angle 1581 self.manip_stack.append(part.manipulators[0].setup(context, o, part)) 1582 1583 # length / radius + angle 1584 self.manip_stack.append(part.manipulators[1].setup(context, o, part)) 1585 # segment index 1586 self.manip_stack.append(part.manipulators[3].setup(context, o, self)) 1587 1588 # snap point 1589 self.manip_stack.append(part.manipulators[2].setup(context, o, self)) 1590 1591 # height as per segment will be here when done 1592 1593 # width + counter 1594 for m in self.manipulators: 1595 self.manip_stack.append(m.setup(context, o, self, self.manipulable_move_t_part)) 1596 1597 # dumb between childs 1598 for m in self.childs_manipulators: 1599 self.manip_stack.append(m.setup(context, o, self)) 1600 1601 def manipulable_exit(self, context): 1602 """ 1603 Override with action to do when modal exit 1604 """ 1605 return 1606 1607 def manipulable_invoke(self, context): 1608 """ 1609 call this in operator invoke() 1610 """ 1611 # print("manipulable_invoke") 1612 if self.manipulate_mode: 1613 self.manipulable_disable(context) 1614 return False 1615 1616 # self.manip_stack = [] 1617 o = context.active_object 1618 g = self.get_generator() 1619 # setup childs manipulators 1620 self.setup_childs(o, g) 1621 # store gl points 1622 self.update_childs(context, o, g) 1623 # don't do anything .. 1624 # self.manipulable_release(context) 1625 # self.manipulate_mode = True 1626 self.manipulable_setup(context) 1627 self.manipulate_mode = True 1628 1629 self._manipulable_invoke(context) 1630 1631 return True 1632 1633 def find_roof(self, context, o, g): 1634 tM = o.matrix_world 1635 up = Vector((0, 0, 1)) 1636 for seg in g.segs: 1637 p = tM @ seg.p0.to_3d() 1638 p.z = 0.01 1639 # prevent self intersect 1640 o.hide_viewport = True 1641 res, pos, normal, face_index, r, matrix_world = context.scene.ray_cast( 1642 depsgraph=context.view_layer.depsgraph, 1643 origin=p, 1644 direction=up) 1645 1646 o.hide_viewport = False 1647 # print("res:%s" % res) 1648 if res and r.data is not None and "archipack_roof" in r.data: 1649 return r, r.data.archipack_roof[0] 1650 1651 return None, None 1652 1653 1654# Update throttle (hack) 1655# use 2 globals to store a timer and state of update_action 1656# Use to update floor boolean on edit 1657update_timer = None 1658update_timer_updating = False 1659throttle_delay = 0.5 1660throttle_start = 0 1661 1662 1663class ARCHIPACK_OT_wall2_throttle_update(Operator): 1664 bl_idname = "archipack.wall2_throttle_update" 1665 bl_label = "Update childs with a delay" 1666 1667 name : StringProperty() 1668 1669 def modal(self, context, event): 1670 global update_timer_updating 1671 if event.type == 'TIMER' and not update_timer_updating: 1672 # can't rely on TIMER event as another timer may run 1673 if time.time() - throttle_start > throttle_delay: 1674 update_timer_updating = True 1675 o = context.scene.objects.get(self.name.strip()) 1676 if o is not None: 1677 m = o.modifiers.get("AutoBoolean") 1678 if m is not None: 1679 o.hide_viewport = False 1680 # o.display_type = 'TEXTURED' 1681 # m.show_viewport = True 1682 1683 return self.cancel(context) 1684 return {'PASS_THROUGH'} 1685 1686 def execute(self, context): 1687 global update_timer 1688 global update_timer_updating 1689 global throttle_delay 1690 global throttle_start 1691 if update_timer is not None: 1692 context.window_manager.event_timer_remove(update_timer) 1693 if update_timer_updating: 1694 return {'CANCELLED'} 1695 # reset update_timer so it only occurs once 0.1s after last action 1696 throttle_start = time.time() 1697 update_timer = context.window_manager.event_timer_add(throttle_delay, context.window) 1698 return {'CANCELLED'} 1699 throttle_start = time.time() 1700 update_timer_updating = False 1701 context.window_manager.modal_handler_add(self) 1702 update_timer = context.window_manager.event_timer_add(throttle_delay, context.window) 1703 return {'RUNNING_MODAL'} 1704 1705 def cancel(self, context): 1706 global update_timer 1707 context.window_manager.event_timer_remove(update_timer) 1708 update_timer = None 1709 return {'CANCELLED'} 1710 1711 1712class ARCHIPACK_PT_wall2(Panel): 1713 bl_idname = "ARCHIPACK_PT_wall2" 1714 bl_label = "Wall" 1715 bl_space_type = 'VIEW_3D' 1716 bl_region_type = 'UI' 1717 bl_category = 'Archipack' 1718 1719 def draw(self, context): 1720 prop = archipack_wall2.datablock(context.object) 1721 if prop is None: 1722 return 1723 layout = self.layout 1724 row = layout.row(align=True) 1725 row.operator("archipack.wall2_manipulate", icon='VIEW_PAN') 1726 # row = layout.row(align=True) 1727 # row.prop(prop, 'realtime') 1728 box = layout.box() 1729 box.prop(prop, 'n_parts') 1730 box.prop(prop, 'step_angle') 1731 box.prop(prop, 'width') 1732 box.prop(prop, 'z') 1733 box.prop(prop, 'flip') 1734 box.prop(prop, 'x_offset') 1735 row = layout.row() 1736 row.prop(prop, "closed") 1737 row = layout.row() 1738 row.prop_search(prop, "t_part", context.scene, "objects", text="T link", icon='OBJECT_DATAMODE') 1739 layout.operator("archipack.wall2_reverse", icon='FILE_REFRESH') 1740 row = layout.row(align=True) 1741 row.operator("archipack.wall2_fit_roof") 1742 # row.operator("archipack.wall2_fit_roof", text="Inside").inside = True 1743 n_parts = prop.n_parts 1744 if prop.closed: 1745 n_parts += 1 1746 for i, part in enumerate(prop.parts): 1747 if i < n_parts: 1748 box = layout.box() 1749 part.draw(box, context, i) 1750 1751 @classmethod 1752 def poll(cls, context): 1753 return archipack_wall2.filter(context.active_object) 1754 1755 1756# ------------------------------------------------------------------ 1757# Define operator class to create object 1758# ------------------------------------------------------------------ 1759 1760 1761class ARCHIPACK_OT_wall2(ArchipackCreateTool, Operator): 1762 bl_idname = "archipack.wall2" 1763 bl_label = "Wall" 1764 bl_description = "Create a Wall" 1765 bl_category = 'Archipack' 1766 bl_options = {'REGISTER', 'UNDO'} 1767 1768 def create(self, context): 1769 m = bpy.data.meshes.new("Wall") 1770 o = bpy.data.objects.new("Wall", m) 1771 d = m.archipack_wall2.add() 1772 d.manipulable_selectable = True 1773 self.link_object_to_scene(context, o) 1774 o.select_set(state=True) 1775 # around 12 degree 1776 m.auto_smooth_angle = 0.20944 1777 context.view_layer.objects.active = o 1778 self.load_preset(d) 1779 self.add_material(o) 1780 return o 1781 1782 def execute(self, context): 1783 if context.mode == "OBJECT": 1784 bpy.ops.object.select_all(action="DESELECT") 1785 o = self.create(context) 1786 o.location = bpy.context.scene.cursor.location 1787 o.select_set(state=True) 1788 context.view_layer.objects.active = o 1789 self.manipulate() 1790 return {'FINISHED'} 1791 else: 1792 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 1793 return {'CANCELLED'} 1794 1795 1796class ARCHIPACK_OT_wall2_from_curve(Operator): 1797 bl_idname = "archipack.wall2_from_curve" 1798 bl_label = "Wall curve" 1799 bl_description = "Create a wall from a curve" 1800 bl_category = 'Archipack' 1801 bl_options = {'REGISTER', 'UNDO'} 1802 1803 auto_manipulate : BoolProperty(default=True) 1804 1805 @classmethod 1806 def poll(self, context): 1807 return context.active_object is not None and context.active_object.type == 'CURVE' 1808 1809 def create(self, context): 1810 curve = context.active_object 1811 for spline in curve.data.splines: 1812 bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate) 1813 o = context.view_layer.objects.active 1814 d = archipack_wall2.datablock(o) 1815 d.from_spline(curve.matrix_world, 12, spline) 1816 if spline.type == 'POLY': 1817 pt = spline.points[0].co 1818 elif spline.type == 'BEZIER': 1819 pt = spline.bezier_points[0].co 1820 else: 1821 pt = Vector((0, 0, 0)) 1822 # pretranslate 1823 o.matrix_world = curve.matrix_world @ Matrix([ 1824 [1, 0, 0, pt.x], 1825 [0, 1, 0, pt.y], 1826 [0, 0, 1, pt.z], 1827 [0, 0, 0, 1] 1828 ]) 1829 return o 1830 1831 # ----------------------------------------------------- 1832 # Execute 1833 # ----------------------------------------------------- 1834 def execute(self, context): 1835 if context.mode == "OBJECT": 1836 bpy.ops.object.select_all(action="DESELECT") 1837 o = self.create(context) 1838 if o is not None: 1839 o.select_set(state=True) 1840 context.view_layer.objects.active = o 1841 return {'FINISHED'} 1842 else: 1843 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 1844 return {'CANCELLED'} 1845 1846 1847class ARCHIPACK_OT_wall2_from_slab(Operator): 1848 bl_idname = "archipack.wall2_from_slab" 1849 bl_label = "->Wall" 1850 bl_description = "Create a wall from a slab" 1851 bl_category = 'Archipack' 1852 bl_options = {'REGISTER', 'UNDO'} 1853 1854 auto_manipulate : BoolProperty(default=True) 1855 1856 @classmethod 1857 def poll(self, context): 1858 o = context.active_object 1859 return o is not None and o.data is not None and 'archipack_slab' in o.data 1860 1861 def create(self, context): 1862 slab = context.active_object 1863 wd = slab.data.archipack_slab[0] 1864 bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate) 1865 o = context.view_layer.objects.active 1866 d = archipack_wall2.datablock(o) 1867 d.auto_update = False 1868 d.parts.clear() 1869 d.n_parts = wd.n_parts - 1 1870 d.closed = True 1871 for part in wd.parts: 1872 p = d.parts.add() 1873 if "S_" in part.type: 1874 p.type = "S_WALL" 1875 else: 1876 p.type = "C_WALL" 1877 p.length = part.length 1878 p.radius = part.radius 1879 p.da = part.da 1880 p.a0 = part.a0 1881 o.select_set(state=True) 1882 context.view_layer.objects.active = o 1883 d.auto_update = True 1884 # pretranslate 1885 o.matrix_world = slab.matrix_world.copy() 1886 1887 bpy.ops.object.select_all(action='DESELECT') 1888 # parenting childs to wall reference point 1889 if o.parent is None: 1890 x, y, z = o.bound_box[0] 1891 context.scene.cursor.location = o.matrix_world @ Vector((x, y, z)) 1892 # fix issue #9 1893 context.view_layer.objects.active = o 1894 bpy.ops.archipack.reference_point() 1895 else: 1896 o.parent.select_set(state=True) 1897 context.view_layer.objects.active = o.parent 1898 o.select_set(state=True) 1899 slab.select_set(state=True) 1900 bpy.ops.archipack.parent_to_reference() 1901 o.parent.select_set(state=False) 1902 return o 1903 1904 # ----------------------------------------------------- 1905 # Execute 1906 # ----------------------------------------------------- 1907 def execute(self, context): 1908 if context.mode == "OBJECT": 1909 bpy.ops.object.select_all(action="DESELECT") 1910 o = self.create(context) 1911 o.select_set(state=True) 1912 context.view_layer.objects.active = o 1913 return {'FINISHED'} 1914 else: 1915 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 1916 return {'CANCELLED'} 1917 1918 1919class ARCHIPACK_OT_wall2_fit_roof(Operator): 1920 bl_idname = "archipack.wall2_fit_roof" 1921 bl_label = "Fit roof" 1922 bl_description = "Fit roof" 1923 bl_category = 'Archipack' 1924 bl_options = {'REGISTER', 'UNDO'} 1925 1926 inside : BoolProperty(default=False) 1927 1928 @classmethod 1929 def poll(self, context): 1930 return archipack_wall2.filter(context.active_object) 1931 1932 def execute(self, context): 1933 o = context.active_object 1934 d = archipack_wall2.datablock(o) 1935 g = d.get_generator() 1936 r, rd = d.find_roof(context, o, g) 1937 if rd is not None: 1938 d.setup_childs(o, g) 1939 rd.make_wall_fit(context, r, o, self.inside) 1940 return {'FINISHED'} 1941 1942# ------------------------------------------------------------------ 1943# Define operator class to draw a wall 1944# ------------------------------------------------------------------ 1945 1946 1947class ARCHIPACK_OT_wall2_draw(ArchipackDrawTool, Operator): 1948 bl_idname = "archipack.wall2_draw" 1949 bl_label = "Draw a Wall" 1950 bl_description = "Create a wall by drawing its baseline in 3D view" 1951 bl_category = 'Archipack' 1952 1953 o = None 1954 state = 'RUNNING' 1955 flag_create = False 1956 flag_next = False 1957 wall_part1 = None 1958 wall_line1 = None 1959 line = None 1960 label = None 1961 feedback = None 1962 takeloc = Vector((0, 0, 0)) 1963 sel = [] 1964 act = None 1965 1966 # constraint to other wall and make a T child 1967 parent = None 1968 takemat = None 1969 1970 @classmethod 1971 def poll(cls, context): 1972 return True 1973 1974 def draw_callback(self, _self, context): 1975 self.feedback.draw(context) 1976 1977 def sp_draw(self, sp, context): 1978 z = 2.7 1979 if self.state == 'CREATE': 1980 p0 = self.takeloc 1981 else: 1982 p0 = sp.takeloc 1983 1984 p1 = sp.placeloc 1985 delta = p1 - p0 1986 # print("sp_draw state:%s delta:%s p0:%s p1:%s" % (self.state, delta.length, p0, p1)) 1987 if delta.length == 0: 1988 return 1989 self.wall_part1.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))]) 1990 self.wall_line1.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))]) 1991 self.wall_part1.draw(context) 1992 self.wall_line1.draw(context) 1993 self.line.p = p0 1994 self.line.v = delta 1995 self.label.set_pos(context, self.line.length, self.line.lerp(0.5), self.line.v, normal=Vector((0, 0, 1))) 1996 self.label.draw(context) 1997 self.line.draw(context) 1998 1999 def sp_callback(self, context, event, state, sp): 2000 logger.debug("ARCHIPACK_OT_wall2_draw.sp_callback event %s %s state:%s", event.type, event.value, state) 2001 2002 if state == 'SUCCESS': 2003 2004 if self.state == 'CREATE': 2005 takeloc = self.takeloc 2006 delta = sp.placeloc - self.takeloc 2007 else: 2008 takeloc = sp.takeloc 2009 delta = sp.delta 2010 2011 old = context.object 2012 if self.o is None: 2013 bpy.ops.archipack.wall2(auto_manipulate=False) 2014 o = context.object 2015 o.location = takeloc 2016 self.o = o 2017 d = archipack_wall2.datablock(o) 2018 2019 part = d.parts[0] 2020 part.length = delta.length 2021 else: 2022 o = self.o 2023 # select and make active 2024 o.select_set(state=True) 2025 context.view_layer.objects.active = o 2026 d = archipack_wall2.datablock(o) 2027 # Check for end close to start and close when applicable 2028 dp = sp.placeloc - o.location 2029 if dp.length < 0.01: 2030 d.closed = True 2031 self.state = 'CANCEL' 2032 return 2033 2034 part = d.add_part(context, delta.length) 2035 2036 # print("self.o :%s" % o.name) 2037 rM = o.matrix_world.inverted().to_3x3() 2038 g = d.get_generator() 2039 w = g.segs[-2] 2040 dp = rM @ delta 2041 da = atan2(dp.y, dp.x) - w.straight(1).angle 2042 a0 = part.a0 + da 2043 if a0 > pi: 2044 a0 -= 2 * pi 2045 if a0 < -pi: 2046 a0 += 2 * pi 2047 part.a0 = a0 2048 d.update(context) 2049 2050 old.select_set(state=True) 2051 context.view_layer.objects.active = old 2052 self.flag_next = True 2053 context.area.tag_redraw() 2054 # print("feedback.on:%s" % self.feedback.on) 2055 2056 self.state = state 2057 2058 def sp_init(self, context, event, state, sp): 2059 # print("sp_init event %s %s %s" % (event.type, event.value, state)) 2060 if state == 'SUCCESS': 2061 # point placed, check if a wall was under mouse 2062 res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event) 2063 if res: 2064 d = archipack_wall2.datablock(wall) 2065 if event.ctrl: 2066 # user snap, use direction as constraint 2067 tM.translation = sp.placeloc.copy() 2068 else: 2069 # without snap, use wall's bottom 2070 tM.translation -= y.normalized() * (0.5 * d.width) 2071 self.takeloc = tM.translation 2072 self.parent = wall.name 2073 self.takemat = tM 2074 else: 2075 self.takeloc = sp.placeloc.copy() 2076 2077 self.state = 'RUNNING' 2078 # print("feedback.on:%s" % self.feedback.on) 2079 elif state == 'CANCEL': 2080 self.state = state 2081 return 2082 2083 def ensure_ccw(self): 2084 """ 2085 Wall to slab expect wall vertex order to be ccw 2086 so reverse order here when needed 2087 """ 2088 d = archipack_wall2.datablock(self.o) 2089 g = d.get_generator(axis=False) 2090 pts = [seg.p0 for seg in g.segs] 2091 2092 if d.closed: 2093 pts.append(pts[0]) 2094 2095 if d.is_cw(pts): 2096 d.x_offset = 1 2097 pts = list(reversed(pts)) 2098 self.o.location += pts[0] - pts[-1] 2099 2100 d.from_points(pts, d.closed) 2101 2102 def modal(self, context, event): 2103 2104 context.area.tag_redraw() 2105 if event.type in {'NONE', 'TIMER', 'TIMER_REPORT', 'EVT_TWEAK_L', 'WINDOW_DEACTIVATE'}: 2106 return {'PASS_THROUGH'} 2107 2108 if self.keymap.check(event, self.keymap.delete): 2109 self.feedback.disable() 2110 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 2111 self.o = None 2112 return {'FINISHED', 'PASS_THROUGH'} 2113 2114 if self.state == 'STARTING' and event.type not in {'ESC', 'RIGHTMOUSE'}: 2115 # wait for takeloc being visible when button is over horizon 2116 takeloc = self.mouse_to_plane(context, event) 2117 if takeloc is not None: 2118 logger.debug("ARCHIPACK_OT_wall2_draw.modal(STARTING) location:%s", takeloc) 2119 snap_point(takeloc=takeloc, 2120 callback=self.sp_init, 2121 constraint_axis=(True, True, False), 2122 release_confirm=True) 2123 return {'RUNNING_MODAL'} 2124 2125 elif self.state == 'RUNNING': 2126 # print("RUNNING") 2127 logger.debug("ARCHIPACK_OT_wall2_draw.modal(RUNNING) location:%s", self.takeloc) 2128 self.state = 'CREATE' 2129 snap_point(takeloc=self.takeloc, 2130 draw=self.sp_draw, 2131 takemat=self.takemat, 2132 transform_orientation=context.scene.transform_orientation_slots[0].type, 2133 callback=self.sp_callback, 2134 constraint_axis=(True, True, False), 2135 release_confirm=self.max_style_draw_tool) 2136 return {'RUNNING_MODAL'} 2137 2138 elif self.state != 'CANCEL' and event.type in {'C', 'c'}: 2139 2140 logger.debug("ARCHIPACK_OT_wall2_draw.modal(%s) C pressed", self.state) 2141 self.feedback.disable() 2142 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 2143 2144 o = self.o 2145 # select and make active 2146 o.select_set(state=True) 2147 context.view_layer.objects.active = o 2148 2149 d = archipack_wall2.datablock(o) 2150 d.closed = True 2151 2152 if bpy.ops.archipack.manipulate.poll(): 2153 bpy.ops.archipack.manipulate('INVOKE_DEFAULT') 2154 2155 return {'FINISHED'} 2156 2157 elif self.state != 'CANCEL' and event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: 2158 2159 # print('LEFTMOUSE %s' % (event.value)) 2160 self.feedback.instructions(context, "Draw a wall", "Click & Drag to add a segment", [ 2161 ('ENTER', 'Add part'), 2162 ('BACK_SPACE', 'Remove part'), 2163 ('CTRL', 'Snap'), 2164 ('C', 'Close wall and exit'), 2165 ('MMBTN', 'Constraint to axis'), 2166 ('X Y', 'Constraint to axis'), 2167 ('RIGHTCLICK or ESC', 'exit') 2168 ]) 2169 2170 # press with max mode release with blender mode 2171 if self.max_style_draw_tool: 2172 evt_value = 'PRESS' 2173 else: 2174 evt_value = 'RELEASE' 2175 2176 if event.value == evt_value: 2177 2178 if self.flag_next: 2179 self.flag_next = False 2180 o = self.o 2181 2182 # select and make active 2183 o.select_set(state=True) 2184 context.view_layer.objects.active = o 2185 2186 d = archipack_wall2.datablock(o) 2187 g = d.get_generator() 2188 p0 = g.segs[-2].p0 2189 p1 = g.segs[-2].p1 2190 dp = p1 - p0 2191 takemat = o.matrix_world @ Matrix([ 2192 [dp.x, dp.y, 0, p1.x], 2193 [dp.y, -dp.x, 0, p1.y], 2194 [0, 0, 1, 0], 2195 [0, 0, 0, 1] 2196 ]) 2197 takeloc = o.matrix_world @ p1.to_3d() 2198 o.select_set(state=False) 2199 else: 2200 takemat = None 2201 takeloc = self.mouse_to_plane(context, event) 2202 2203 if takeloc is not None: 2204 logger.debug("ARCHIPACK_OT_wall2_draw.modal(CREATE) location:%s", takeloc) 2205 2206 snap_point(takeloc=takeloc, 2207 takemat=takemat, 2208 draw=self.sp_draw, 2209 callback=self.sp_callback, 2210 constraint_axis=(True, True, False), 2211 release_confirm=self.max_style_draw_tool) 2212 2213 return {'RUNNING_MODAL'} 2214 2215 if self.keymap.check(event, self.keymap.undo) or ( 2216 event.type in {'BACK_SPACE'} and event.value == 'RELEASE' 2217 ): 2218 if self.o is not None: 2219 o = self.o 2220 2221 # select and make active 2222 o.select_set(state=True) 2223 context.view_layer.objects.active = o 2224 d = archipack_wall2.datablock(o) 2225 if d.n_parts > 1: 2226 d.n_parts -= 1 2227 return {'RUNNING_MODAL'} 2228 2229 if self.state == 'CANCEL' or (event.type in {'ESC', 'RIGHTMOUSE'} and 2230 event.value == 'RELEASE'): 2231 2232 self.feedback.disable() 2233 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') 2234 logger.debug("ARCHIPACK_OT_wall2_draw.modal(CANCEL) %s", event.type) 2235 if self.o is None: 2236 for o in self.sel: 2237 o.select_set(state=True) 2238 # select and make active 2239 if self.act is not None: 2240 self.act.select_set(state=True) 2241 context.view_layer.objects.active = self.act 2242 2243 else: 2244 o = self.o 2245 o.select_set(state=True) 2246 context.view_layer.objects.active = o 2247 2248 # remove last segment with blender mode 2249 d = archipack_wall2.datablock(o) 2250 if not self.max_style_draw_tool: 2251 if not d.closed and d.n_parts > 1: 2252 d.n_parts -= 1 2253 o.select_set(state=True) 2254 context.view_layer.objects.active = o 2255 # make T child 2256 if self.parent is not None: 2257 d.t_part = self.parent 2258 2259 if bpy.ops.archipack.manipulate.poll(): 2260 bpy.ops.archipack.manipulate('INVOKE_DEFAULT') 2261 2262 return {'FINISHED'} 2263 2264 return {'PASS_THROUGH'} 2265 2266 def invoke(self, context, event): 2267 2268 if context.mode == "OBJECT": 2269 prefs = context.preferences.addons[__name__.split('.')[0]].preferences 2270 self.max_style_draw_tool = prefs.max_style_draw_tool 2271 self.keymap = Keymaps(context) 2272 self.wall_part1 = GlPolygon((0.5, 0, 0, 0.2)) 2273 self.wall_line1 = GlPolyline((0.5, 0, 0, 0.8)) 2274 self.line = GlLine() 2275 self.label = GlText() 2276 self.feedback = FeedbackPanel() 2277 self.feedback.instructions(context, "Draw a wall", "Click & Drag to start", [ 2278 ('CTRL', 'Snap'), 2279 ('MMBTN', 'Constraint to axis'), 2280 ('X Y', 'Constraint to axis'), 2281 ('SHIFT+CTRL+TAB', 'Switch snap mode'), 2282 ('RIGHTCLICK or ESC', 'exit without change') 2283 ]) 2284 self.feedback.enable() 2285 args = (self, context) 2286 2287 self.sel = context.selected_objects[:] 2288 self.act = context.active_object 2289 bpy.ops.object.select_all(action="DESELECT") 2290 2291 self.state = 'STARTING' 2292 2293 self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL') 2294 context.window_manager.modal_handler_add(self) 2295 return {'RUNNING_MODAL'} 2296 else: 2297 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 2298 return {'CANCELLED'} 2299 2300 2301# ------------------------------------------------------------------ 2302# Define operator class to manage parts 2303# ------------------------------------------------------------------ 2304 2305 2306class ARCHIPACK_OT_wall2_insert(Operator): 2307 bl_idname = "archipack.wall2_insert" 2308 bl_label = "Insert" 2309 bl_description = "Insert part" 2310 bl_category = 'Archipack' 2311 bl_options = {'REGISTER', 'UNDO'} 2312 index : IntProperty(default=0) 2313 2314 def execute(self, context): 2315 if context.mode == "OBJECT": 2316 o = context.active_object 2317 d = archipack_wall2.datablock(o) 2318 if d is None: 2319 return {'CANCELLED'} 2320 d.insert_part(context, o, self.index) 2321 return {'FINISHED'} 2322 else: 2323 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 2324 return {'CANCELLED'} 2325 2326 2327class ARCHIPACK_OT_wall2_remove(Operator): 2328 bl_idname = "archipack.wall2_remove" 2329 bl_label = "Remove" 2330 bl_description = "Remove part" 2331 bl_category = 'Archipack' 2332 bl_options = {'REGISTER', 'UNDO'} 2333 index : IntProperty(default=0) 2334 2335 def execute(self, context): 2336 if context.mode == "OBJECT": 2337 o = context.active_object 2338 d = archipack_wall2.datablock(o) 2339 if d is None: 2340 return {'CANCELLED'} 2341 d.remove_part(context, o, self.index) 2342 return {'FINISHED'} 2343 else: 2344 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 2345 return {'CANCELLED'} 2346 2347 2348class ARCHIPACK_OT_wall2_reverse(Operator): 2349 bl_idname = "archipack.wall2_reverse" 2350 bl_label = "Reverse" 2351 bl_description = "Reverse parts order" 2352 bl_category = 'Archipack' 2353 bl_options = {'REGISTER', 'UNDO'} 2354 2355 def execute(self, context): 2356 if context.mode == "OBJECT": 2357 o = context.active_object 2358 d = archipack_wall2.datablock(o) 2359 if d is None: 2360 return {'CANCELLED'} 2361 d.reverse(context, o) 2362 return {'FINISHED'} 2363 else: 2364 self.report({'WARNING'}, "Archipack: Option only valid in Object mode") 2365 return {'CANCELLED'} 2366 2367 2368# ------------------------------------------------------------------ 2369# Define operator class to manipulate object 2370# ------------------------------------------------------------------ 2371 2372 2373class ARCHIPACK_OT_wall2_manipulate(Operator): 2374 bl_idname = "archipack.wall2_manipulate" 2375 bl_label = "Manipulate" 2376 bl_description = "Manipulate" 2377 bl_options = {'REGISTER', 'UNDO'} 2378 2379 @classmethod 2380 def poll(self, context): 2381 return archipack_wall2.filter(context.active_object) 2382 2383 def invoke(self, context, event): 2384 d = archipack_wall2.datablock(context.active_object) 2385 d.manipulable_invoke(context) 2386 return {'FINISHED'} 2387 2388 def execute(self, context): 2389 """ 2390 For use in boolean ops 2391 """ 2392 if archipack_wall2.filter(context.active_object): 2393 o = context.active_object 2394 d = archipack_wall2.datablock(o) 2395 g = d.get_generator() 2396 d.setup_childs(o, g) 2397 d.update_childs(context, o, g) 2398 d.update(context) 2399 o.select_set(state=True) 2400 context.view_layer.objects.active = o 2401 return {'FINISHED'} 2402 2403 2404def register(): 2405 bpy.utils.register_class(archipack_wall2_part) 2406 bpy.utils.register_class(archipack_wall2_child) 2407 bpy.utils.register_class(archipack_wall2) 2408 Mesh.archipack_wall2 = CollectionProperty(type=archipack_wall2) 2409 bpy.utils.register_class(ARCHIPACK_PT_wall2) 2410 bpy.utils.register_class(ARCHIPACK_OT_wall2) 2411 bpy.utils.register_class(ARCHIPACK_OT_wall2_draw) 2412 bpy.utils.register_class(ARCHIPACK_OT_wall2_insert) 2413 bpy.utils.register_class(ARCHIPACK_OT_wall2_remove) 2414 bpy.utils.register_class(ARCHIPACK_OT_wall2_reverse) 2415 bpy.utils.register_class(ARCHIPACK_OT_wall2_manipulate) 2416 bpy.utils.register_class(ARCHIPACK_OT_wall2_from_curve) 2417 bpy.utils.register_class(ARCHIPACK_OT_wall2_from_slab) 2418 bpy.utils.register_class(ARCHIPACK_OT_wall2_throttle_update) 2419 bpy.utils.register_class(ARCHIPACK_OT_wall2_fit_roof) 2420 2421 2422def unregister(): 2423 bpy.utils.unregister_class(archipack_wall2_part) 2424 bpy.utils.unregister_class(archipack_wall2_child) 2425 bpy.utils.unregister_class(archipack_wall2) 2426 del Mesh.archipack_wall2 2427 bpy.utils.unregister_class(ARCHIPACK_PT_wall2) 2428 bpy.utils.unregister_class(ARCHIPACK_OT_wall2) 2429 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_draw) 2430 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_insert) 2431 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_remove) 2432 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_reverse) 2433 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_manipulate) 2434 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_curve) 2435 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_slab) 2436 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_throttle_update) 2437 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_fit_roof) 2438