sun_position: fix warning from deleted prop in User Preferences
[blender-addons.git] / archipack / archipack_roof.py
blob59757c4f74ba9bf169a826ca16714ff474d7011b
1 # -*- coding:utf-8 -*-
3 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
19 # ##### END GPL LICENSE BLOCK #####
21 # <pep8 compliant>
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
26 # ----------------------------------------------------------
27 # noinspection PyUnresolvedReferences
28 import bpy
29 import time
30 # noinspection PyUnresolvedReferences
31 from bpy.types import Operator, PropertyGroup, Mesh, Panel
32 from bpy.props import (
33 FloatProperty, BoolProperty, IntProperty,
34 StringProperty, EnumProperty,
35 CollectionProperty
37 from .bmesh_utils import BmeshEdit as bmed
38 from random import randint
39 import bmesh
40 from mathutils import Vector, Matrix
41 from math import sin, cos, pi, atan2, sqrt, tan
42 from .archipack_manipulator import Manipulable, archipack_manipulator
43 from .archipack_2d import Line, Arc
44 from .archipack_preset import ArchipackPreset, PresetMenuOperator
45 from .archipack_object import ArchipackCreateTool, ArchipackObject
46 from .archipack_cutter import (
47 CutAblePolygon, CutAbleGenerator,
48 ArchipackCutter,
49 ArchipackCutterPart
53 class Roof():
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
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
90 @property
91 def copy(self):
92 s = StraightRoof(self.p.copy(), self.v.copy())
93 self.copy_params(s)
94 return s
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
102 def set_offset(self, offset, last=None):
104 Offset line and compute intersection point
105 between segments
107 self.line = self.make_offset(offset, last)
109 def offset(self, offset):
110 o = self.copy
111 o.p += offset * self.cross_z.normalized()
112 return o
114 @property
115 def oposite(self):
116 o = self.copy
117 o.p += o.v
118 o.v = -o.v
119 return o
121 @property
122 def t_diff(self):
123 return self.t_end - self.t_start
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
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
141 class StraightRoof(Roof, Line):
142 def __str__(self):
143 return "p0:{} p1:{}".format(self.p0, self.p1)
145 def __init__(self, p, v):
146 Line.__init__(self, p, v)
147 Roof.__init__(self)
150 class CurvedRoof(Roof, Arc):
151 def __str__(self):
152 return "t_start:{} t_end:{} dist:{}".format(self.t_start, self.t_end, self.dist)
154 def __init__(self, c, radius, a0, da):
155 Arc.__init__(self, c, radius, a0, da)
156 Roof.__init__(self)
159 class RoofSegment():
161 Roof part with 2 polygons
162 and "axis" StraightRoof segment
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
172 class RoofAxisNode():
174 Connection between parts
175 for radial analysis
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
187 @property
188 def count(self):
189 return len(self.segs)
191 @property
192 def last(self):
194 last segments in this node
196 return self.segs[-1]
198 def left(self, index):
199 if index + 1 >= self.count:
200 return self.segs[0]
201 return self.segs[index + 1]
203 def right(self, index):
204 return self.segs[index - 1]
206 def add(self, a0, reversed, seg, left, right):
208 if seg.constraint_type == 'HORIZONTAL':
209 self.n_horizontal += 1
210 elif seg.constraint_type == 'SLOPE':
211 self.n_slope += 1
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)
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
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
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)
244 end = len(self.segs) - 1
245 _quicksort(self.segs, 0, end)
247 # index of root in segs array
248 self.update_center()
251 class RoofPolygon(CutAblePolygon):
253 ccw roof pitch boundary
254 closed by explicit segment
255 handle triangular shape with zero axis length
257 mov <_________________
258 | /\
259 | | rot
260 | | left last <> next
261 \/_____axis_______>|
263 node <_____axis________ next
264 | /\
265 | | rot
266 | | right last <> next
267 mov \/________________>|
268 side angle
270 def __init__(self, axis, side, fake_axis=None):
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
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
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
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
305 # axis segment
306 if side == 'RIGHT':
307 self.axis = axis.oposite
308 else:
309 self.axis = axis
311 self.fake_axis = None
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
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
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 = []
341 # Triangular ends
342 self.node_tri = False
343 self.next_tri = False
344 self.is_tri = False
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()
356 def move_node(self, p):
358 Move slope point in node side
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
367 def move_next(self, p):
369 Move slope point in next side
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
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
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
406 def bind(self, last, ccw=False):
408 always in axis real direction
410 # backward dependency relative to axis
411 if last.backward:
412 self.backward = self.side == last.side
414 if self.side == last.side:
415 last.next_cross = self.cross
416 else:
417 last.last_cross = self.cross
419 self.last_cross = last.cross
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
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
438 self.make_segments()
439 last.make_segments()
441 res, p, t = self.segs[2].intersect(last.segs[2])
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)
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
467 # /\
468 # | angle
469 # |____>
471 # v1 node -> next
472 if self.side == 'LEFT':
473 v1 = self._axis.v
474 else:
475 v1 = -self._axis.v
477 if last.side == self.side:
478 # contiguous, v0 node <- next
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)
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)
509 self.node_link(-0.5 * da)
511 def next_seg(self, index):
512 idx = self.get_index(index + 1)
513 return self.segs[idx]
515 def last_seg(self, index):
516 return self.segs[index - 1]
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]
530 def move_side(self, pt):
532 offset side to point
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)
542 def propagate_backward(self, pt):
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
550 # distance of p
551 # offset side to point
552 self.move_side(pt)
554 # move verts on node side
555 self.move_next(pt)
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])
564 if res:
565 self.move_node(p)
567 if self.next is not None and self.next.auto_mode in {'AUTO'}:
568 self.next.propagate_backward(p)
570 def propagate_forward(self, pt):
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
578 # offset side to point
579 self.move_side(pt)
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])
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)
595 def rotate_next_slope(self, a0):
597 Rotate next slope part
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)
608 if res:
609 s1.p0 = p
610 s0.p1 = p
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)
620 def rotate_node_slope(self, a0):
622 Rotate node slope part
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)
633 if res:
634 s1.p0 = p
635 s0.p1 = p
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)
645 def distance(self, pt):
647 distance from axis
648 always use fake_axis here to
649 allow axis being cut and
650 still work
652 res, d, t = self.fake_axis.point_sur_segment(pt)
653 return d, t
655 def altitude(self, pt):
656 d, t = self.distance(pt)
657 return -d * self.slope
659 def uv(self, pt):
660 d, t = self.distance(pt)
661 return ((t - self.tmin) * self.xsize, d)
663 def intersect(self, seg):
665 compute intersections of a segment with boundaries
666 segment must start on axis
667 return segments inside
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
676 def merge(self, other):
678 raise NotImplementedError
680 def draw(self, context, z, verts, edges):
681 f = len(verts)
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])
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])
697 # holes
698 for hole in self.holes:
699 f = len(verts)
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])
709 # axis
711 f = len(verts)
712 verts.extend([self.axis.p0.to_3d(), self.axis.p1.to_3d()])
713 edges.append([f, f + 1])
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])
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]])
743 def as_string(self):
745 Print strips relationships
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("#########")
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)
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
779 self.dt = self.tmax - self.tmin
781 if len(dist) > 0:
782 self.ysize = max(dist)
783 else:
784 self.ysize = 0
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)
799 import bmesh
800 m = 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]
804 uvs = []
805 bpy.ops.object.mode_set(mode='EDIT')
806 bm = bmesh.from_edit_mesh(m)
807 [tuple(i.index for i in edge.verts) for edge in bm.edges]
809 layer = bm.loops.layers.uv.verify()
810 for 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)
820 class RoofGenerator(CutAbleGenerator):
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 != ""
839 def add_part(self, part):
841 if len(self.segs) < 1 or part.bound_idx < 1:
842 s = None
843 else:
844 s = self.segs[part.bound_idx - 1]
846 a0 = part.a0
848 if part.constraint_type == 'SLOPE' and a0 == 0:
849 a0 = 90
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)
858 # parent segment (root) index is v0_idx - 1
859 s.v0_idx = min(len(self.segs), part.bound_idx)
861 s.constraint_type = part.constraint_type
863 if part.constraint_type == 'SLOPE':
864 s.enforce_part = part.enforce_part
865 else:
866 s.enforce_part = 'AUTO'
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)
880 def locate_manipulators(self):
884 for i, f in enumerate(self.segs):
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:
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])
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)])
904 # dumb segment id
905 manipulators[2].set_pts([p0, p1, (1, 0, 0)])
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)])
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)])
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())
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())
935 def seg_partition(self, array, begin, end):
937 sort tree segments by angle
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
947 def sort_seg(self, array, begin=0, end=None):
948 # print("sort_child")
949 if end is None:
950 end = len(array) - 1
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)
960 def make_roof(self, context):
962 Init data structure for possibly multi branched nodes
963 nodes : radial relationships
964 pans : quad strip linear relationships
967 pans = []
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)]
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
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)
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
1014 # l / rb l = left
1015 # 3 r = right
1016 # l _1_ / b = backward
1017 # r \
1019 # r\ l
1021 # X: right one or left one l (x % 2 == 0)
1022 # inherits from side
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
1029 # for idx, node in enumerate(nodes):
1030 # print("idx:%s node:%s" % (idx, node.root))
1032 for idx, node in enumerate(nodes):
1034 node.sort()
1036 nb_segs = node.count
1038 if node.root is None:
1039 continue
1041 left = node.root.left
1042 right = node.root.right
1044 # basic one single node
1045 if nb_segs < 2:
1046 left.make_segments()
1047 right.make_segments()
1048 continue
1050 # get "root" slope and width
1051 l_bind = left
1052 r_bind = right
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
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
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
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)
1084 # next bind to left
1085 r_bind = s.left
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
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
1119 # slope constraints allowed between segments
1120 # multiple (up to 2) on start and end
1121 # single between others
1123 # 2 slope 2 slope 2 slope
1124 # | | |
1125 # |______section_1___|___section_2_____|
1126 # | | |
1127 # | | |
1128 # multiple single multiple
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)
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()
1153 for idx, node in enumerate(nodes):
1155 # a node may contain many slopes
1156 # 2 * (part starting from node - 1)
1158 # s0
1159 # root 0 |_______
1161 # s1
1163 # s1
1164 # root _______|
1166 # s0
1168 # s3 3 s2
1169 # l \l|r/ l
1170 # root ___\|/___ 2
1171 # r /|\ r
1172 # /r|l\
1173 # s0 1 s1
1175 # s2 s1=slope
1176 # |r /
1177 # | / l
1178 # |/____s
1180 # root to first child -> equal side
1181 # any other childs -> oposite sides
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
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
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)
1257 if s1.seg.constraint_type == 'SLOPE':
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
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
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)
1332 self.pans = []
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
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)
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)
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
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
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)
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)
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)
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
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
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)
1454 # make flat array
1455 for pan in pans:
1456 self.pans.extend([pan.left, pan.right])
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
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
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
1522 for i in reversed(to_remove):
1523 self.pans.pop(i)
1525 # compute limits
1526 for pan in self.pans:
1527 pan.limits()
1530 for pan in self.pans:
1531 if pan.last is None:
1532 pan.as_string()
1534 return
1536 def lambris(self, context, o, d):
1538 idmat = 0
1539 lambris_height = 0.02
1540 alt = self.z - lambris_height
1541 for pan in self.pans:
1543 verts = []
1544 faces = []
1545 matids = []
1546 uvs = []
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)
1556 bm = bmed.buildmesh(
1557 context, o, verts, faces, matids=matids, uvs=uvs,
1558 weld=False, clean=False, auto_smooth=True, temporary=True)
1560 self.cut_holes(bm, pan)
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'})
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)
1574 # merge with object
1575 bmed.bmesh_join(context, o, [bm], normal_update=True)
1577 bpy.ops.object.mode_set(mode='OBJECT')
1579 def couverture(self, context, o, d):
1581 idmat = 7
1582 rand = 3
1583 ttl = len(self.pans)
1584 if ttl < 1:
1585 return
1587 sx, sy, sz = d.tile_size_x, d.tile_size_y, d.tile_size_z
1590 /* Bevel offset_type slot values */
1591 enum {
1592 BEVEL_AMT_OFFSET,
1593 BEVEL_AMT_WIDTH,
1594 BEVEL_AMT_DEPTH,
1595 BEVEL_AMT_PERCENT
1598 offset_type = 'PERCENT'
1600 if d.tile_offset > 0:
1601 offset = - d.tile_offset / 100
1602 else:
1603 offset = 0
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)]
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)
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)]
1686 t_faces = [(6, 7, 5, 4, 1, 3, 2, 0, 8, 9)]
1687 else:
1688 return
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]
1693 dx, dy = d.tile_space_x, d.tile_space_y
1695 step = 100 / ttl
1697 # if d.quick_edit:
1698 # context.scene.archipack_progress_text = "Build tiles:"
1700 for i, pan in enumerate(self.pans):
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
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)
1716 if d.tile_fit_x:
1717 dx = space_x / n_x
1719 if d.tile_fit_y:
1720 dy = space_y / n_y
1722 if d.tile_alternate:
1723 n_y += 1
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]
1732 verts = []
1733 faces = []
1734 matids = []
1735 uvs = []
1737 # steps for this pan
1738 substep = step / n_y
1739 # print("step:%s sub:%s" % (step, substep))
1741 for k in range(n_y):
1743 progress = step * i + substep * k
1744 # print("progress %s" % (progress))
1745 # if d.quick_edit:
1746 # context.scene.archipack_progress = progress
1748 y = k * dy
1750 x0 = offset * dx - d.tile_side
1751 nx = n_x
1753 if d.tile_alternate and k % 2 == 1:
1754 x0 -= 0.5 * dx
1755 nx += 1
1757 if d.tile_offset > 0:
1758 nx += 1
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]
1769 v = len(verts)
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)
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)
1783 # clean outer on convex parts
1784 # pan.convex = False
1785 remove = pan.convex
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)
1810 # when not convex, select and remove outer parts
1811 if not pan.convex:
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
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)
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")
1852 self.cut_holes(bm, pan)
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'})
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)
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)
1880 # merge with object
1881 bmed.bmesh_join(context, o, [bm], normal_update=True)
1882 bpy.ops.object.mode_set(mode='OBJECT')
1884 # if d.quick_edit:
1885 # context.scene.archipack_progress = -1
1887 def _bargeboard(self, s, i, boundary, pan,
1888 width, height, altitude, offset, idmat,
1889 verts, faces, edges, matids, uvs):
1891 f = len(verts)
1893 s0 = s.offset(offset - width)
1894 s1 = s.offset(offset)
1896 p0 = s0.p0
1897 p1 = s1.p0
1898 p2 = s0.p1
1899 p3 = s1.p1
1901 s2 = boundary.last_seg(i)
1902 s3 = boundary.next_seg(i)
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
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
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
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
1944 x0, y0 = p0
1945 x1, y1 = p1
1946 x2, y2 = p3
1947 x3, y3 = p2
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)
1954 verts.extend([
1955 (x0, y0, z0),
1956 (x1, y1, z1),
1957 (x2, y2, z2),
1958 (x3, y3, z3),
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),
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)
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])
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)]
1997 def bargeboard(self, d, verts, faces, edges, matids, uvs):
1999 #####################
2000 # Vire-vents
2001 #####################
2003 idmat = 1
2004 for pan in self.pans:
2006 for hole in pan.holes:
2007 for i, s in enumerate(hole.segs):
2008 if s.type == 'SIDE':
2009 self._bargeboard(s,
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)
2023 for i, s in enumerate(pan.segs):
2024 if s.type == 'SIDE':
2025 self._bargeboard(s,
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)
2039 def _fascia(self, s, i, boundary, pan, tri_0, tri_1,
2040 width, height, altitude, offset, idmat,
2041 verts, faces, edges, matids, uvs):
2043 f = len(verts)
2044 s0 = s.offset(offset)
2045 s1 = s.offset(offset + width)
2047 s2 = boundary.last_seg(i)
2048 s3 = boundary.next_seg(i)
2049 s4 = s2
2050 s5 = s3
2052 p0 = s0.p0
2053 p1 = s1.p0
2054 p2 = s0.p1
2055 p3 = s1.p1
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
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)
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)
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
2122 x0, y0 = p0
2123 x1, y1 = p2
2124 x2, y2 = p3
2125 x3, y3 = p1
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)
2132 verts.extend([
2133 (x0, y0, z0),
2134 (x1, y1, z1),
2135 (x2, y2, z2),
2136 (x3, y3, z3),
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),
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)
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)]
2175 def fascia(self, d, verts, faces, edges, matids, uvs):
2177 #####################
2178 # Larmiers
2179 #####################
2181 idmat = 2
2182 for pan in self.pans:
2184 for hole in pan.holes:
2185 for i, s in enumerate(hole.segs):
2186 if s.type == 'BOTTOM':
2187 self._fascia(s,
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)
2202 for i, s in enumerate(pan.segs):
2203 if s.type == 'BOTTOM':
2205 tri_0 = pan.node_tri
2206 tri_1 = pan.next_tri
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
2214 self._fascia(s,
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)
2229 continue
2231 f = len(verts)
2232 s0 = s.offset(d.fascia_width)
2234 s1 = pan.last_seg(i)
2235 s2 = pan.next_seg(i)
2237 # triangular ends apply on boundary only
2238 # unless cut, boundary is parallel to axis
2239 # except for triangular ends
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
2244 if pan.side == 'LEFT':
2245 tri_0, tri_1 = tri_1, tri_0
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)
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:
2288 s2 = s2.offset(d.fascia_width)
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)
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),
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),
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)
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)]
2342 def gutter(self, d, verts, faces, edges, matids, uvs):
2344 #####################
2345 # Chenaux
2346 #####################
2348 idmat = 5
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)
2356 df = 2 * d.gutter_segs + 1
2358 for pan in self.pans:
2359 for i, s in enumerate(pan.segs):
2361 if s.type == 'BOTTOM':
2362 f = len(verts)
2364 s0 = s.offset(d.gutter_dist + d.gutter_width)
2366 s1 = pan.last_seg(i)
2367 s2 = pan.next_seg(i)
2369 p0 = s0.p0
2370 p1 = s0.p1
2372 tri_0 = pan.node_tri or pan.is_tri
2373 tri_1 = pan.next_tri or pan.is_tri
2375 if pan.side == 'LEFT':
2376 tri_0, tri_1 = tri_1, tri_0
2378 f = len(verts)
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)
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)
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
2432 f = len(verts)
2433 verts.extend([s1.p0.to_3d(), s1.p1.to_3d()])
2434 edges.append([f, f + 1])
2436 f = len(verts)
2437 verts.extend([s2.p0.to_3d(), s2.p1.to_3d()])
2438 edges.append([f, f + 1])
2439 continue
2442 v0 = p0 - s.p0
2443 v1 = p1 - s.p1
2445 scale_0 = v0.length / (d.gutter_dist + d.gutter_width)
2446 scale_1 = v1.length / (d.gutter_dist + d.gutter_width)
2448 s3 = Line(s.p0, v0.normalized())
2449 s4 = Line(s.p1, v1.normalized())
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
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)
2465 # bord tablette
2466 xt, yt = s3.lerp(tt)
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)
2477 dx = x0 - x1
2478 dy = y0 - y1
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))
2488 dx = x2 - x3
2489 dy = y2 - y3
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))
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)
2511 # bord tablette
2512 xt, yt = s4.lerp(tt)
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)
2523 dx = x0 - x1
2524 dy = y0 - y1
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)
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))
2539 dx = x2 - x3
2540 dy = y2 - y3
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))
2549 df = 2 * d.gutter_segs + 1
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)
2558 segs = 6
2560 n_faces = segs / 2 - 1
2567 # close start
2568 if s1.type == 'SIDE':
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)
2575 for i in range(n_faces):
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)
2583 # close end
2584 if s2.type == 'SIDE':
2586 f += 2 * d.gutter_segs + 1
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)
2593 for i in range(n_faces):
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)
2601 def beam_primary(self, d, verts, faces, edges, matids, uvs):
2603 idmat = 3
2605 for pan in self.pans:
2606 for i, s in enumerate(pan.segs):
2608 if s.type == 'AXIS':
2610 ####################
2611 # Poutre Faitiere
2612 ####################
2615 1___________________2 left
2616 0|___________________|3 axis
2617 |___________________| right
2620 f = len(verts)
2622 s2 = s.offset(-0.5 * d.beam_width)
2624 # offset from roof border
2625 s0 = pan.last_seg(i)
2626 s1 = pan.next_seg(i)
2627 t0 = 0
2628 t1 = 1
2630 s0_tri = pan.next_tri
2631 s1_tri = pan.node_tri
2633 if pan.side == 'LEFT':
2634 s0_tri, s1_tri = s1_tri, s0_tri
2636 if s0.type == 'SIDE' and s.length > 0:
2637 s0 = s0.offset(d.beam_offset)
2638 t0 = -d.beam_offset / s.length
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
2648 if s1.type == 'SIDE' and s.length > 0:
2649 s1 = s1.offset(d.beam_offset)
2650 t1 = 1 + d.beam_offset / s.length
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
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),
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)
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)
2697 matids.extend([
2698 idmat, idmat, idmat
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)]
2706 def rafter(self, context, o, d):
2708 idmat = 4
2710 # Rafters / Chevrons
2711 start = max(0.001 + 0.5 * d.rafter_width, d.rafter_start)
2713 holes_offset = -d.rafter_width
2715 # build temp bmesh and bissect
2716 for pan in self.pans:
2717 tmin, tmax, ysize = pan.tmin, pan.tmax, pan.ysize
2719 # print("tmin:%s tmax:%s ysize:%s" % (tmin, tmax, ysize))
2721 f = 0
2723 verts = []
2724 faces = []
2725 matids = []
2726 uvs = []
2727 alt = d.rafter_alt
2728 seg = pan.fake_axis
2730 t0 = tmin + (start - 0.5 * d.rafter_width) / seg.length
2731 t1 = tmin + (start + 0.5 * d.rafter_width) / seg.length
2733 tx = start / seg.length
2734 dt = d.rafter_spacing / seg.length
2736 n_items = max(1, round((tmax - tmin) / dt, 0))
2738 dt = ((tmax - tmin) - 2 * tx) / n_items
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)
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
2754 verts.extend([
2755 (x0, y0, z0),
2756 (x1, y1, z1),
2757 (x2, y2, z2),
2758 (x3, y3, z3)
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)])
2765 bm = bmed.buildmesh(
2766 context, o, verts, faces, matids=matids, uvs=uvs,
2767 weld=False, clean=False, auto_smooth=True, temporary=True)
2769 self.cut_boundary(bm, pan)
2770 self.cut_holes(bm, pan, offset={'DEFAULT': holes_offset})
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'})
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]
2791 # merge with object
2792 bmed.bmesh_join(context, o, [bm], normal_update=True)
2794 bpy.ops.object.mode_set(mode='OBJECT')
2796 def hips(self, d, verts, faces, edges, matids, uvs):
2798 idmat_valley = 5
2799 idmat = 6
2800 idmat_poutre = 4
2802 sx, sy, sz = d.hip_size_x, d.hip_size_y, d.hip_size_z
2804 if d.hip_model == 'ROUND':
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)
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)
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)]
2845 # affect vertex with slope
2846 t_left = []
2847 t_right = []
2849 elif d.hip_model == 'ETERNIT':
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)]
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)
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)]
2870 t_left = [2, 3, 7, 8]
2871 t_right = [0, 1, 10, 11]
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)]
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)]
2902 t_left = []
2903 t_right = []
2905 t_idmats = [idmat for f in t_faces]
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)
2916 s0 = s.offset(-0.5 * d.beam_sec_width)
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
2933 p0 = s.lerp(t0)
2934 p1 = s.lerp(t1)
2936 x0, y0 = s0.lerp(t0)
2937 x1, y1 = s.p0
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
2944 verts.extend([
2945 (x0, y0, z0),
2946 (x0, y0, z1),
2947 (x1, y1, z2),
2948 (x1, y1, z3)
2951 x2, y2 = s0.lerp(t1)
2952 x3, y3 = s.p1
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
2959 verts.extend([
2960 (x2, y2, z0),
2961 (x2, y2, z1),
2962 (x3, y3, z2),
2963 (x3, y3, z3)
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)
2974 matids.extend([
2975 idmat_poutre, idmat_poutre, idmat_poutre,
2976 idmat_poutre, idmat_poutre, idmat_poutre
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)]
2987 if s.type == 'LINK_HIP':
2989 # TODO:
2990 # Slice borders properly
2992 if d.hip_enable:
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)
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
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)
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]
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
3031 t_verts = [p for p in t_pts]
3033 # apply slope
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
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]
3049 f = len(verts)
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)
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)
3080 verts.extend([
3081 (x0, y0, z0),
3082 (x1, y1, z1),
3083 (x2, y2, z2),
3084 (x3, y3, z3),
3086 faces.extend([
3087 (f, f + 3, f + 2, f + 1)
3089 matids.extend([
3090 idmat_valley
3092 uvs.extend([
3093 [(0, 0), (1, 0), (1, 1), (0, 1)]
3096 elif s.type == 'AXIS' and d.hip_enable and pan.side == 'LEFT':
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)
3105 if s1.type == 'SIDE' and s.length > 0:
3106 tmax = 1 + d.tile_side / s.length
3108 # print("tmin:%s tmax:%s" % (tmin, tmax))
3109 ####################
3110 # Faitiere
3111 ####################
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]
3126 t_verts = [p.copy() for p in t_pts]
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)
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]
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)
3147 def make_hole(self, context, hole_obj, o, d, update_parent=False):
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
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
3162 # merge :
3163 # 5 ____________ 4
3164 # / |
3165 # / left |
3166 # /_____axis_____| 3 <- kill axis and this one
3167 # 0\ |
3168 # \ right |
3169 # 1 \____________| 2
3171 # degenerate case:
3173 # /|
3174 # / |
3175 # \ |
3176 # \|
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)
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)
3197 # if there is side offset:
3198 # create an arrow
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
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
3222 s3 = segs[2].offset(
3223 -min(root.left.xsize - 0.001, d.hole_offset_front)
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])
3243 pts = [p.to_3d() for p in pts]
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
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
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]
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)
3270 context.view_layer.objects.active = o
3272 def change_coordsys(self, fromTM, toTM):
3274 move shape fromTM into toTM coordsys
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]
3284 for s in self.segs:
3285 tp = (rM @ s.p0) - s.p0 + dp
3286 s.rotate(da)
3287 s.translate(tp)
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
3299 def sort_t(self, array, begin=0, end=None):
3300 # print("sort_child")
3301 if end is None:
3302 end = len(array) - 1
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)
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
3317 # wg in roof coordsys
3318 wg.change_coordsys(wall.matrix_world, o.matrix_world)
3320 if inside:
3321 # fit inside
3322 offset = -0.5 * (1 - wd.x_offset) * wd.width
3323 else:
3324 # fit outside
3325 offset = 0
3327 wg.set_offset(offset)
3329 wall_t = [[] for w in wg.segs]
3331 for pan in self.pans:
3332 # walls segment
3333 for widx, wseg in enumerate(wg.segs):
3335 ls = wseg.line.length
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))
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))
3359 old = context.active_object
3360 old_sel = wall.select_get()
3361 wall.select_set(state=True)
3362 context.view_layer.objects.active = wall
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
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
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
3408 wd.auto_update = True
3410 for s in self.segs:
3411 s.as_curve(context)
3412 for s in wg.segs:
3413 s.as_curve(context)
3415 wall.select_set(state=old_sel)
3416 context.view_layer.objects.active = old
3418 def boundary(self, context, o):
3420 either external or holes cuts
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)
3438 def draft(self, context, verts, edges):
3439 for pan in self.pans:
3440 pan.draw(context, self.z, verts, edges)
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])
3453 def update(self, context):
3454 self.update(context)
3457 def update_manipulators(self, context):
3458 self.update(context, manipulable_refresh=True)
3461 def update_path(self, context):
3462 self.update_path(context)
3465 def update_parent(self, context):
3467 # update part a0
3468 o = context.active_object
3469 p, d = self.find_parent(context)
3471 if d is not None:
3473 o.parent = p
3475 # trigger object update
3476 # hole creation and parent's update
3478 self.parts[0].a0 = pi / 2
3480 elif self.t_parent != "":
3481 self.t_parent = ""
3484 def update_cutter(self, context):
3485 self.update(context, update_hole=True)
3488 def update_childs(self, context):
3489 self.update(context, update_childs=True, update_hole=True)
3492 def update_components(self, context):
3493 self.update(context, update_parent=False, update_hole=False)
3496 class ArchipackSegment():
3497 length : FloatProperty(
3498 name="Length",
3499 min=0.01,
3500 max=1000.0,
3501 default=4.0,
3502 update=update
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
3512 manipulators : CollectionProperty(type=archipack_manipulator)
3515 class ArchipackLines():
3516 n_parts : IntProperty(
3517 name="Parts",
3518 min=1,
3519 default=1, update=update_manipulators
3521 # UI layout related
3522 parts_expand : BoolProperty(
3523 default=False
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)
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)
3546 # add rows
3547 for i in range(len(self.parts), self.n_parts + 1):
3548 self.parts.add()
3550 self.setup_manipulators()
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)
3581 class archipack_roof_segment(ArchipackSegment, PropertyGroup):
3583 bound_idx : IntProperty(
3584 name="Link to",
3585 default=0,
3586 min=0,
3587 update=update_manipulators
3589 width_left : FloatProperty(
3590 name="L Width",
3591 min=0.01,
3592 default=3.0,
3593 update=update_cutter
3595 width_right : FloatProperty(
3596 name="R Width",
3597 min=0.01,
3598 default=3.0,
3599 update=update_cutter
3601 slope_left : FloatProperty(
3602 name="L slope",
3603 min=0.0,
3604 default=0.3,
3605 update=update_cutter
3607 slope_right : FloatProperty(
3608 name="R slope",
3609 min=0.0,
3610 default=0.3,
3611 update=update_cutter
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),
3622 default="AUTO",
3623 update=update_manipulators
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),
3634 default="AUTO",
3635 update=update_manipulators
3637 triangular_end : BoolProperty(
3638 name="Triangular end",
3639 default=False,
3640 update=update
3642 take_precedence : BoolProperty(
3643 name="Take precedence",
3644 description="On T segment take width precedence",
3645 default=False,
3646 update=update
3649 constraint_type : EnumProperty(
3650 items=(
3651 ('HORIZONTAL', 'Horizontal', '', 0),
3652 ('SLOPE', 'Slope', '', 1)
3654 default='HORIZONTAL',
3655 update=update_manipulators
3658 enforce_part : EnumProperty(
3659 name="Enforce part",
3660 items=(
3661 ('AUTO', 'Auto', '', 0),
3662 ('VALLEY', 'Valley', '', 1),
3663 ('HIP', 'Hip', '', 2)
3665 default='AUTO',
3666 update=update
3669 def find_in_selection(self, context):
3671 find witch selected object this instance belongs to
3672 provide support for "copy to selected"
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
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")
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")
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)
3722 class 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
3730 slope_left : FloatProperty(
3731 name="L slope",
3732 default=0.5, precision=2, step=1,
3733 update=update_childs
3735 slope_right : FloatProperty(
3736 name="R slope",
3737 default=0.5, precision=2, step=1,
3738 update=update_childs
3740 width_left : FloatProperty(
3741 name="L width",
3742 default=3, precision=2, step=1,
3743 unit='LENGTH', subtype='DISTANCE',
3744 update=update_cutter
3746 width_right : FloatProperty(
3747 name="R width",
3748 default=3, precision=2, step=1,
3749 unit='LENGTH', subtype='DISTANCE',
3750 update=update_cutter
3752 draft : BoolProperty(
3753 options={'SKIP_SAVE'},
3754 name="Draft mode",
3755 default=False,
3756 update=update_manipulators
3758 auto_update : BoolProperty(
3759 options={'SKIP_SAVE'},
3760 default=True,
3761 update=update_manipulators
3763 quick_edit : BoolProperty(
3764 options={'SKIP_SAVE'},
3765 name="Quick Edit",
3766 default=False
3769 tile_enable : BoolProperty(
3770 name="Enable",
3771 default=True,
3772 update=update_components
3774 tile_solidify : BoolProperty(
3775 name="Solidify",
3776 default=True,
3777 update=update_components
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
3787 tile_bevel : BoolProperty(
3788 name="Bevel",
3789 default=False,
3790 update=update_components
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
3800 tile_bevel_segs : IntProperty(
3801 name="Segs",
3802 description="Bevel Segs",
3803 min=1,
3804 default=2,
3805 update=update_components
3807 tile_alternate : BoolProperty(
3808 name="Alternate",
3809 default=False,
3810 update=update_components
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
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
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
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
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
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
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
3867 tile_fit_x : BoolProperty(
3868 name="Fit x",
3869 description="Fit roof on x axis",
3870 default=True,
3871 update=update_components
3873 tile_fit_y : BoolProperty(
3874 name="Fit y",
3875 description="Fit roof on y axis",
3876 default=True,
3877 update=update_components
3879 tile_expand : BoolProperty(
3880 options={'SKIP_SAVE'},
3881 name="Tiles",
3882 description="Expand tiles panel",
3883 default=False
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)
3899 default="BRAAS2",
3900 update=update_components
3902 tile_side : FloatProperty(
3903 name="Side",
3904 description="Space on side",
3905 default=0,
3906 unit='LENGTH', subtype='DISTANCE',
3907 update=update_components
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
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
3925 gutter_expand : BoolProperty(
3926 options={'SKIP_SAVE'},
3927 name="Gutter",
3928 description="Expand gutter panel",
3929 default=False
3931 gutter_enable : BoolProperty(
3932 name="Enable",
3933 default=True,
3934 update=update_components
3936 gutter_alt : FloatProperty(
3937 name="Altitude",
3938 description="altitude",
3939 default=0,
3940 unit='LENGTH', subtype='DISTANCE',
3941 update=update_components
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
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
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
3967 gutter_segs : IntProperty(
3968 default=6,
3969 min=1,
3970 name="Segs",
3971 update=update_components
3974 beam_expand : BoolProperty(
3975 options={'SKIP_SAVE'},
3976 name="Beam",
3977 description="Expand beam panel",
3978 default=False
3980 beam_enable : BoolProperty(
3981 name="Ridge pole",
3982 default=True,
3983 update=update_components
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
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
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
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
4015 beam_sec_enable : BoolProperty(
4016 name="Hip rafter",
4017 default=True,
4018 update=update_components
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
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
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
4043 rafter_enable : BoolProperty(
4044 name="Rafter",
4045 default=True,
4046 update=update_components
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
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
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
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
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
4089 hip_enable : BoolProperty(
4090 name="Enable",
4091 default=True,
4092 update=update_components
4094 hip_expand : BoolProperty(
4095 options={'SKIP_SAVE'},
4096 name="Hips",
4097 description="Expand hips panel",
4098 default=False
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
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
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
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
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
4139 hip_model : EnumProperty(
4140 name="Model",
4141 items=(
4142 ('ROUND', 'Round', '', 0),
4143 ('ETERNIT', 'Eternit', '', 1),
4144 ('FLAT', 'Flat', '', 2)
4146 default="ROUND",
4147 update=update_components
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
4156 valley_enable : BoolProperty(
4157 name="Valley",
4158 default=True,
4159 update=update_components
4162 fascia_enable : BoolProperty(
4163 name="Enable",
4164 description="Enable Fascia",
4165 default=True,
4166 update=update_components
4168 fascia_expand : BoolProperty(
4169 options={'SKIP_SAVE'},
4170 name="Fascia",
4171 description="Expand fascia panel",
4172 default=False
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
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
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
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
4205 bargeboard_enable : BoolProperty(
4206 name="Enable",
4207 description="Enable Bargeboard",
4208 default=True,
4209 update=update_components
4211 bargeboard_expand : BoolProperty(
4212 options={'SKIP_SAVE'},
4213 name="Bargeboard",
4214 description="Expand Bargeboard panel",
4215 default=False
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
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
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
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
4248 t_parent : StringProperty(
4249 name="Parent",
4250 default="",
4251 update=update_parent
4253 t_part : IntProperty(
4254 name="Part",
4255 description="Parent part index",
4256 default=0,
4257 min=0,
4258 update=update_cutter
4260 t_dist_x : FloatProperty(
4261 name="Dist x",
4262 description="Location on axis ",
4263 default=0,
4264 update=update_cutter
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
4273 z_parent: FloatProperty(
4274 description="Delta z of t child for grand childs",
4275 default=0
4277 hole_offset_left : FloatProperty(
4278 name="Left",
4279 description="Left distance from border",
4280 min=0,
4281 default=0,
4282 update=update_cutter
4284 hole_offset_right : FloatProperty(
4285 name="Right",
4286 description="Right distance from border",
4287 min=0,
4288 default=0,
4289 update=update_cutter
4291 hole_offset_front : FloatProperty(
4292 name="Front",
4293 description="Front distance from border",
4294 default=0,
4295 update=update_cutter
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)
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)
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
4317 self.setup_manipulators()
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"
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"
4367 def get_generator(self, origin=Vector((0, 0, 0))):
4368 g = RoofGenerator(self, origin)
4370 # TODO: sort part by bound idx so deps always find parent
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
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()
4392 def find_parent(self, context):
4393 o = context.scene.objects.get(self.t_parent.strip())
4394 return o, archipack_roof.datablock(o)
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)
4412 def relocate_child(self, context, o, g, child):
4414 d = archipack_roof.datablock(child)
4416 if d is not None and d.t_part - 1 < len(g.segs):
4417 # print("relocate_child(%s)" % (child.name))
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]
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)
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
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):
4460 update_hole: on t_child must update parent
4461 update_childs: force childs update
4462 force_update: skip throttle
4464 # print("update")
4465 o = self.find_in_selection(context, self.auto_update)
4467 if o is None:
4468 return
4470 # clean up manipulators before any data model change
4471 if manipulable_refresh:
4472 self.manipulable_disable(context)
4474 self.update_parts()
4476 verts, edges, faces, matids, uvs = [], [], [], [], []
4478 y = 0
4479 z = self.z
4480 p, d = self.find_parent(context)
4481 g = None
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)
4489 if self.t_part - 1 < len(pg.segs):
4491 seg = pg.nodes[self.t_part].root
4493 d.relocate_child(context, p, pg, o)
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
4513 if slope == 0:
4514 slope = 0.0001
4516 # print("slope: %s" % (slope))
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
4522 b_right = self.intersection_angle(
4523 s_left,
4524 w_left,
4525 slope,
4526 a_axis)
4528 a_right = b_right + a_offset
4530 b_left = self.intersection_angle(
4531 s_right,
4532 w_right,
4533 slope,
4534 a_axis)
4536 a_left = b_left + a_offset
4538 g = self.get_generator(origin=Vector((0, y, z)))
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
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)
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)
4573 if g is None:
4574 g = self.get_generator(origin=Vector((0, y, z)))
4576 # setup per segment manipulators
4577 if len(g.segs) > 0:
4578 f = g.segs[0]
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)])
4589 g.make_roof(context)
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)
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")
4602 # add cutters
4603 g.boundary(context, o)
4605 if self.draft:
4607 g.draft(context, verts, edges)
4608 g.gutter(self, verts, faces, edges, matids, uvs)
4609 self.make_surface(o, verts, edges)
4611 else:
4613 if self.bargeboard_enable:
4614 g.bargeboard(self, verts, faces, edges, matids, uvs)
4616 if self.fascia_enable:
4617 g.fascia(self, verts, faces, edges, matids, uvs)
4619 if self.beam_enable:
4620 g.beam_primary(self, verts, faces, edges, matids, uvs)
4622 g.hips(self, verts, faces, edges, matids, uvs)
4624 if self.gutter_enable:
4625 g.gutter(self, verts, faces, edges, matids, uvs)
4627 bmed.buildmesh(
4629 context, o, verts, faces, matids=matids, uvs=uvs,
4630 weld=False, clean=False, auto_smooth=True, temporary=False)
4632 # bpy.ops.object.mode_set(mode='EDIT')
4633 g.lambris(context, o, self)
4634 # print("lambris")
4636 if self.rafter_enable:
4637 # bpy.ops.object.mode_set(mode='EDIT')
4638 g.rafter(context, o, self)
4639 # print("rafter")
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")
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")
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
4667 def manipulable_setup(self, context):
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
4674 self.manipulable_disable(context)
4676 o = context.active_object
4678 self.setup_manipulators()
4680 for i, part in enumerate(self.parts):
4682 if i > 0:
4683 # start angle
4684 self.manip_stack.append(part.manipulators[0].setup(context, o, part))
4686 if part.constraint_type == 'HORIZONTAL':
4687 # length / radius + angle
4688 self.manip_stack.append(part.manipulators[1].setup(context, o, part))
4690 # index
4691 self.manip_stack.append(part.manipulators[2].setup(context, o, self))
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))
4706 for m in self.manipulators:
4707 self.manip_stack.append(m.setup(context, o, self))
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)
4722 def update_hole(self, context):
4723 # update parent's roof only when manipulated
4724 self.update(context, update_parent=True)
4727 def update_operation(self, context):
4728 self.reverse(context, make_ccw=(self.operation == 'INTERSECTION'))
4731 class 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)
4743 default='SIDE',
4744 update=update_hole
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
4758 class 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"
4767 def update_points(self, context, o, pts, update_parent=False):
4769 Create boundary from roof
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")
4780 def update_parent(self, context, o):
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")
4792 class 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'
4799 @classmethod
4800 def poll(cls, context):
4801 return archipack_roof_cutter.filter(context.active_object)
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')
4824 box.prop_search(prop, "boundary", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
4826 prop.draw(layout, context)
4829 class 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'
4836 @classmethod
4837 def poll(cls, context):
4838 return archipack_roof.filter(context.active_object)
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')
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="")
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')
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')
4912 box.label(text="Spacing")
4913 box.prop(prop, 'tile_space_x')
4914 box.prop(prop, 'tile_space_y')
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')
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')
4940 box = layout.box()
4941 row = box.row(align=True)
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')
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')
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')
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')
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')
5017 # ------------------------------------------------------------------
5018 # Define operator class to create object
5019 # ------------------------------------------------------------------
5022 class 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'}
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)
5040 # disable progress bar when
5041 # background render thumbs
5042 if not self.auto_manipulate:
5043 d.quick_edit = False
5045 self.load_preset(d)
5046 return o
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'}
5065 class 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'}
5072 parent : StringProperty("")
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]
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
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'}
5134 # ------------------------------------------------------------------
5135 # Define operator class to create object
5136 # ------------------------------------------------------------------
5139 class 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'}
5146 auto_manipulate : BoolProperty(default=True)
5148 @classmethod
5149 def poll(self, context):
5150 return context.active_object is not None and context.active_object.type == 'CURVE'
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')
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)
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]
5184 o.select_set(state=True)
5185 context.view_layer.objects.active = o
5186 return o
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'}
5203 # ------------------------------------------------------------------
5204 # Define operator class to manipulate object
5205 # ------------------------------------------------------------------
5208 class ARCHIPACK_OT_roof_manipulate(Operator):
5209 bl_idname = "archipack.roof_manipulate"
5210 bl_label = "Manipulate"
5211 bl_description = "Manipulate"
5212 bl_options = {'REGISTER', 'UNDO'}
5214 @classmethod
5215 def poll(self, context):
5216 return archipack_roof.filter(context.active_object)
5218 def invoke(self, context, event):
5219 d = archipack_roof.datablock(context.active_object)
5220 d.manipulable_invoke(context)
5221 return {'FINISHED'}
5224 class 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'}
5230 @classmethod
5231 def poll(self, context):
5232 return archipack_roof_cutter.filter(context.active_object)
5234 def invoke(self, context, event):
5235 d = archipack_roof_cutter.datablock(context.active_object)
5236 d.manipulable_invoke(context)
5237 return {'FINISHED'}
5240 # Update throttle
5241 class ArchipackThrottleHandler():
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
5248 def __init__(self, context, delay):
5249 self._timer = None
5250 self.start = 0
5251 self.update_state = False
5252 self.delay = delay
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)
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
5263 def execute(self, context):
5265 refresh timer on execute
5266 return
5267 True if modal should run
5268 False on complete
5270 if self._timer is None:
5271 self.update_state = False
5272 self.start_timer(context)
5273 return True
5275 # already a timer running
5276 self.stop_timer(context)
5278 # prevent race conditions when already in update mode
5279 if self.is_updating:
5280 return False
5282 self.start_timer(context)
5283 return False
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
5293 @property
5294 def is_updating(self):
5295 return self.update_state
5298 throttle_handlers = {}
5299 throttle_delay = 1
5302 class ARCHIPACK_OT_roof_throttle_update(Operator):
5303 bl_idname = "archipack.roof_throttle_update"
5304 bl_label = "Update childs with a delay"
5306 name : StringProperty()
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]
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]
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'}
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'}
5353 # ------------------------------------------------------------------
5354 # Define operator class to load / save presets
5355 # ------------------------------------------------------------------
5358 class 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"
5365 class 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"
5371 @property
5372 def blacklist(self):
5373 return ['n_parts', 'parts', 'manipulators', 'user_defined_path', 'quick_edit', 'draft']
5376 def 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)
5396 def 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)