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 #####
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
26 # ----------------------------------------------------------
27 # noinspection PyUnresolvedReferences
30 # noinspection PyUnresolvedReferences
31 from bpy
.types
import Operator
, PropertyGroup
, Mesh
, Panel
32 from bpy
.props
import (
33 FloatProperty
, BoolProperty
, IntProperty
,
34 StringProperty
, EnumProperty
,
37 from .bmesh_utils
import BmeshEdit
as bmed
38 from random
import randint
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
,
59 self
.constraint_type
= None
64 self
.auto_left
= 'AUTO'
65 self
.auto_right
= 'AUTO'
68 self
.enforce_part
= 'AUTO'
69 self
.triangular_end
= 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
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
92 s
= StraightRoof(self
.p
.copy(), self
.v
.copy())
96 def straight(self
, length
, t
=1):
99 s
.v
= self
.v
.normalized() * length
102 def set_offset(self
, offset
, last
=None):
104 Offset line and compute intersection point
107 self
.line
= self
.make_offset(offset
, last
)
109 def offset(self
, offset
):
111 o
.p
+= offset
* self
.cross_z
.normalized()
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
)
131 def curved_roof(self
, a0
, da
, radius
):
132 n
= self
.normal(1).rotate(a0
).scale(radius
)
136 r
= CurvedRoof(c
, radius
, n
.angle
, da
)
141 class StraightRoof(Roof
, Line
):
143 return "p0:{} p1:{}".format(self
.p0
, self
.p1
)
145 def __init__(self
, p
, v
):
146 Line
.__init
__(self
, p
, v
)
150 class CurvedRoof(Roof
, Arc
):
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
)
161 Roof part with 2 polygons
162 and "axis" StraightRoof segment
164 def __init__(self
, seg
, left
, right
):
169 self
.reversed = False
172 class RoofAxisNode():
174 Connection between parts
182 # store count of horizontal segs
183 self
.n_horizontal
= 0
184 # store count of slopes segs
189 return len(self
.segs
)
194 last segments in this node
198 def left(self
, index
):
199 if index
+ 1 >= self
.count
:
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':
213 s
= RoofSegment(seg
, left
, right
)
215 s
.reversed = reversed
220 def update_center(self
):
221 for i
, s
in enumerate(self
.segs
):
226 # sort tree segments by angle
227 def partition(self
, array
, begin
, end
):
229 for i
in range(begin
+ 1, end
+ 1):
230 if array
[i
].a0
< array
[begin
].a0
:
232 array
[i
], array
[pivot
] = array
[pivot
], array
[i
]
233 array
[pivot
], array
[begin
] = array
[begin
], array
[pivot
]
237 def _quicksort(array
, begin
, end
):
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
251 class RoofPolygon(CutAblePolygon
):
253 ccw roof pitch boundary
254 closed by explicit segment
255 handle triangular shape with zero axis length
257 mov <_________________
260 | | left last <> next
263 node <_____axis________ next
266 | | right last <> next
267 mov \/________________>|
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
278 when axis length is null (eg: triangular shape)
279 use "fake_axis" with a 1 length to handle
280 distance from segment
284 self
.slope
= axis
.slope_left
286 self
.width
= axis
.width_left
288 self
.auto_mode
= axis
.auto_left
291 self
.slope
= axis
.slope_right
293 self
.width
= axis
.width_right
295 self
.auto_mode
= axis
.auto_right
299 self
.backward
= False
300 # pointers to neighbors along axis
303 self
.other_side
= None
307 self
.axis
= axis
.oposite
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
322 self
.fake_axis
= fake_axis
.oposite
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)
335 # segments from axis end in ccw order
336 # closed by explicit segment
342 self
.node_tri
= False
343 self
.next_tri
= False
356 def move_node(self
, p
):
358 Move slope point in node side
360 if self
.side
== 'LEFT':
367 def move_next(self
, p
):
369 Move slope point in next side
371 if self
.side
== 'LEFT':
378 def node_link(self
, da
):
379 angle_90
= round(pi
/ 2, 4)
380 if self
.side
== 'LEFT':
384 da
= abs(round(da
, 4))
390 self
.segs
[idx
].type = type
392 def next_link(self
, da
):
393 angle_90
= round(pi
/ 2, 4)
394 if self
.side
== 'LEFT':
398 da
= abs(round(da
, 4))
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
412 self
.backward
= self
.side
== last
.side
414 if self
.side
== last
.side
:
415 last
.next_cross
= self
.cross
417 last
.last_cross
= self
.cross
419 self
.last_cross
= last
.cross
421 # axis of last / next segments
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
441 res
, p
, t
= self
.segs
[2].intersect(last
.segs
[2])
444 # dont move anything when no intersection found
445 # aka when delta angle == 0
447 if self
.side
!= last
.side
:
454 # and find intersections
456 if self
.auto_mode
== 'ALL':
457 s0
= self
._axis
.offset(-self
.width
)
458 res
, p0
, t
= self
.segs
[1].intersect(s0
)
462 res
, p1
, t
= self
.segs
[-1].intersect(s0
)
465 self
.segs
[-1].p0
= p1
472 if self
.side
== 'LEFT':
477 if last
.side
== self
.side
:
478 # contiguous, v0 node <- next
480 # half angle between segments
481 if self
.side
== 'LEFT':
485 da
= v0
.angle_signed(v1
)
491 last
.next_link(0.5 * da
)
494 # alternate v0 node -> next
495 # half angle between segments
496 if last
.side
== 'LEFT':
500 da
= v0
.angle_signed(v1
)
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:
522 s1
= s0
.straight(w
, 1).rotate(pi
/ 2)
524 s3
= s0
.straight(w
, 0).rotate(pi
/ 2).oposite
526 s2
= StraightRoof(s1
.p1
, s3
.p0
- s1
.p1
)
528 self
.segs
= [s0
, s1
, s2
, s3
]
530 def move_side(self
, pt
):
535 d0
, t
= self
.distance(s2
.p0
)
536 d1
, t
= self
.distance(pt
)
537 # adjust width and slope according
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
551 # offset side to point
554 # move verts on node side
557 if self
.side
== 'LEFT':
558 # move verts on next side
559 res
, p
, t
= self
.segs
[-1].intersect(self
.segs
[2])
561 # move verts on next side
562 res
, p
, t
= self
.segs
[1].intersect(self
.segs
[2])
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
578 # offset side to point
581 # move verts on node side
583 if self
.side
== 'LEFT':
584 # move verts on next side
585 res
, p
, t
= self
.segs
[1].intersect(self
.segs
[2])
587 # move verts on next side
588 res
, p
, t
= self
.segs
[-1].intersect(self
.segs
[2])
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
)
602 res
, p
, t
= s1
.intersect(s0
)
606 res
, p
, t
= s1
.oposite
.rotate(-a0
).intersect(s0
)
612 if self
.next
is not None:
613 if self
.next
.auto_mode
== 'ALL':
615 if self
.next
.backward
:
616 self
.next
.propagate_backward(p
)
618 self
.next
.propagate_forward(p
)
620 def rotate_node_slope(self
, a0
):
622 Rotate node slope part
624 if self
.side
== 'LEFT':
627 res
, p
, t
= s1
.oposite
.rotate(-a0
).intersect(s0
)
629 s0
= self
.segs
[1].rotate(a0
)
631 res
, p
, t
= s1
.intersect(s0
)
637 if self
.next
is not None:
638 if self
.next
.auto_mode
== 'ALL':
640 if self
.next
.backward
:
641 self
.next
.propagate_backward(p
)
643 self
.next
.propagate_forward(p
)
645 def distance(self
, pt
):
648 always use fake_axis here to
649 allow axis being cut and
652 res
, d
, t
= self
.fake_axis
.point_sur_segment(pt
)
655 def altitude(self
, pt
):
656 d
, t
= self
.distance(pt
)
657 return -d
* self
.slope
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
671 res
, p
, t
, u
= seg
.intersect_ext(s
)
676 def merge(self
, other
):
678 raise NotImplementedError
680 def draw(self
, context
, z
, verts
, edges
):
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
])
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])
698 for hole
in self
.holes
:
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
])
712 verts.extend([self.axis.p0.to_3d(), self.axis.p1.to_3d()])
713 edges.append([f, f + 1])
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
:
724 s0
= self
._axis
.offset(-0.5 * self
.ysize
)
725 p0
= s0
.lerp(0.4).to_3d()
727 p1
= s0
.lerp(0.6).to_3d()
729 if self
.side
== 'RIGHT':
733 s1
= s0
.sized_normal(0.5, w
)
734 s2
= s0
.sized_normal(0.5, -w
)
740 verts
.extend([p1
, p0
, p2
, p3
])
741 edges
.extend([[f
+ 1, f
], [f
+ 2, f
], [f
+ 3, f
]])
745 Print strips relationships
749 print("%s next" % (dir))
752 print("%s node" % (dir))
753 print("%s %s" % (dir, self
.side
))
755 print("%s node" % (dir))
757 print("%s next" % (dir))
760 self
.next
.as_string()
768 res
, d
, t
= self
.fake_axis
.point_sur_segment(s
.p0
)
773 self
.tmin
= min(param_t
)
774 self
.tmax
= max(param_t
)
779 self
.dt
= self
.tmax
- self
.tmin
782 self
.ysize
= max(dist
)
786 self
.xsize
= self
.fake_axis
.length
* self
.dt
787 # vectors components of part matrix
788 # where x is is axis direction
791 vx
= -self
.fake_axis
.v
.normalized().to_3d()
792 vy
= Vector((-vx
.y
, vx
.x
, self
.slope
)).normalized()
795 self
.vz
= vx
.cross(vy
)
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]
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):
812 for j, loop in enumerate(face.loops):
814 uv.append((round(co.x, 2), round(co.y, 2)))
820 class RoofGenerator(CutAbleGenerator
):
822 def __init__(self
, d
, origin
=Vector((0, 0, 0))):
828 self
.origin
= origin
.to_2d()
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:
844 s
= self
.segs
[part
.bound_idx
- 1]
848 if part
.constraint_type
== 'SLOPE' and a0
== 0:
853 v
= part
.length
* Vector((cos(a0
), sin(a0
)))
854 s
= StraightRoof(self
.origin
, v
)
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
866 s
.enforce_part
= 'AUTO'
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
877 s
.triangular_end
= part
.triangular_end
880 def locate_manipulators(self
):
884 for i
, f
in enumerate(self
.segs
):
886 manipulators
= self
.parts
[i
].manipulators
891 # angle from last to current segment
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
])
900 manipulators
[1].type_key
= 'SIZE'
901 manipulators
[1].prop1_name
= "length"
902 manipulators
[1].set_pts([p0
, p1
, (1.0, 0, 0)])
905 manipulators
[2].set_pts([p0
, p1
, (1, 0, 0)])
907 p0
= f
.lerp(0.5).to_3d()
910 p1
= f
.sized_normal(0.5, -self
.parts
[i
].width_left
).p1
.to_3d()
912 manipulators
[3].set_pts([p0
, p1
, (1, 0, 0)])
915 p1
= f
.sized_normal(0.5, self
.parts
[i
].width_right
).p1
.to_3d()
917 manipulators
[4].set_pts([p0
, p1
, (-1, 0, 0)])
920 n0
= f
.sized_normal(0.5, -1)
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())
928 n0
= f
.sized_normal(0.5, 1)
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
940 for i
in range(begin
+ 1, end
+ 1):
941 if array
[i
].a0
< array
[begin
].a0
:
943 array
[i
], array
[pivot
] = array
[pivot
], array
[i
]
944 array
[pivot
], array
[begin
] = array
[begin
], array
[pivot
]
947 def sort_seg(self
, array
, begin
=0, end
=None):
948 # print("sort_child")
952 def _quicksort(array
, begin
, end
):
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
969 # node are connected segments
972 # (angle from root part > 0 right)
973 # (reversed) a seg connected by p1
975 nodes
= [RoofAxisNode() for s
in range(len(self
.segs
) + 1)]
977 # Init width on seg 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
):
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
)
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]
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
1016 # l _1_ / b = backward
1021 # X: right one or left one l (x % 2 == 0)
1022 # inherits from side
1025 # l__1_|_2_l r = right
1026 # r | r b = backward -> propagate in reverse axis direction
1029 # for idx, node in enumerate(nodes):
1030 # print("idx:%s node:%s" % (idx, node.root))
1032 for idx
, node
in enumerate(nodes
):
1036 nb_segs
= node
.count
1038 if node
.root
is None:
1041 left
= node
.root
.left
1042 right
= node
.root
.right
1044 # basic one single node
1046 left
.make_segments()
1047 right
.make_segments()
1050 # get "root" slope and width
1054 # simple case: 2 contiguous segments
1057 s
.right
.bind(r_bind
, ccw
=False)
1058 s
.left
.bind(l_bind
, ccw
=True)
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
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
:
1076 # bind right side to center
1077 for i
, s
in enumerate(node
.segs
):
1081 # right contiguous with last
1082 s
.right
.bind(r_bind
, ccw
=False)
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
1095 # right bound to last
1096 s
.right
.bind(r_bind
, ccw
=False)
1099 # bind left side to center
1100 for i
, s
in enumerate(reversed(node
.segs
)):
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
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
1115 # right bound to last
1116 s
.left
.bind(l_bind
, ccw
=True)
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
1125 # |______section_1___|___section_2_____|
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
):
1140 # remove dup between all
1141 # but start / end nodes
1142 if node
.n_horizontal
> 1:
1144 for i
, s
in enumerate(node
.segs
):
1145 if s
.seg
.constraint_type
== last
:
1146 if s
.seg
.constraint_type
== 'SLOPE':
1148 last
= s
.seg
.constraint_type
1149 for i
in reversed(to_remove
):
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)
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
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
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
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:
1210 if a0
> 0 and a1
< 0:
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:
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
1222 # slope at start of segment
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
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:
1239 if a0
> 0 and a1
< 0:
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:
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
1251 # slopes between segments
1252 # does change next segment slope
1253 for i
, s0
in enumerate(node
.segs
):
1255 s2
= node
.left(i
+ 1)
1257 if s1
.seg
.constraint_type
== 'SLOPE':
1260 # s0 is root contiguous -> sides are same
1261 # s2 is root contiguous -> sides are same
1262 # back to back -> sides are not same
1265 # contiguous right / right
1269 if s2
.right
.backward
:
1276 v
= -main
.segs
[-1].v
1277 res
, p
, t
= s1
.seg
.intersect(main
.segs
[2])
1281 a0
= dp
.angle_signed(v
)
1282 if s2
.right
.backward
:
1283 main
.rotate_node_slope(a0
)
1285 main
.rotate_next_slope(-a0
)
1287 # contiguous left / left
1291 if s0
.left
.backward
:
1294 v
= -main
.segs
[-1].v
1299 res
, p
, t
= s1
.seg
.intersect(main
.segs
[2])
1303 a0
= dp
.angle_signed(v
)
1304 if s0
.left
.backward
:
1305 main
.rotate_node_slope(-a0
)
1307 main
.rotate_next_slope(a0
)
1313 if s0
.left
.backward
:
1316 v
= -main
.segs
[-1].v
1322 res
, p
, t
= s1
.seg
.intersect(main
.segs
[2])
1326 a0
= dp
.angle_signed(v
)
1327 if s0
.left
.backward
:
1328 main
.rotate_node_slope(-a0
)
1330 main
.rotate_node_slope(a0
)
1335 for node
in self
.nodes
:
1336 if node
.root
is None:
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
1350 p0
= s1
.lerp(-left
.width
/ s1
.length
)
1352 p2
= s3
.lerp(1 + right
.width
/ s3
.length
)
1354 # compute slope from points
1356 p3
.z
= -left
.width
* left
.slope
1359 p5
.z
= -right
.width
* right
.slope
1360 n
= (p3
- p4
).normalized().cross((p5
- p4
).normalized())
1361 v
= n
.cross(Vector((0, 0, 1)))
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')
1370 p
.slope
= -dz
.z
/ dz
.to_2d().length
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
1386 p
.segs
[1].type = 'LINK_HIP'
1387 p
.segs
[-1].type = 'LINK_HIP'
1389 # adjust left and side borders
1394 s0
.type = 'LINK_HIP'
1395 s2
.type = 'LINK_HIP'
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
1409 p0
= s1
.lerp(-right
.width
/ s1
.length
)
1411 p2
= s3
.lerp(1 + left
.width
/ s3
.length
)
1413 # compute axis and slope from points
1415 p3
.z
= -right
.width
* right
.slope
1418 p5
.z
= -left
.width
* left
.slope
1419 n
= (p3
- p4
).normalized().cross((p5
- p4
).normalized())
1420 v
= n
.cross(Vector((0, 0, 1)))
1423 s
= StraightRoof(p1
, v
)
1424 p
= RoofPolygon(s
, 'RIGHT')
1426 p
.slope
= -dz
.z
/ dz
.to_2d().length
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
1442 p
.segs
[1].type = 'LINK_HIP'
1443 p
.segs
[-1].type = 'LINK_HIP'
1445 # adjust left and side borders
1450 s0
.type = 'LINK_HIP'
1451 s2
.type = 'LINK_HIP'
1456 self
.pans
.extend([pan
.left
, pan
.right
])
1458 # merge contiguous with 0 angle diff
1460 for i
, pan
in enumerate(self
.pans
):
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:
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':
1475 next
._axis
.p1
= pan
._axis
.p1
1476 next
.segs
[1] = pan
.segs
[1]
1477 next
.segs
[2].p0
= pan
.segs
[2].p0
1479 next
._axis
.p0
= pan
._axis
.p0
1480 next
.segs
[-1] = pan
.segs
[-1]
1481 next
.segs
[2].p1
= pan
.segs
[2].p1
1484 next
._axis
.p0
= pan
._axis
.p0
1485 next
.segs
[-1] = pan
.segs
[-1]
1486 next
.segs
[2].p1
= pan
.segs
[2].p1
1488 next
._axis
.p1
= pan
._axis
.p1
1489 next
.segs
[1] = pan
.segs
[1]
1490 next
.segs
[2].p0
= pan
.segs
[2].p0
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:
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':
1505 next
._axis
.p1
= pan
._axis
.p1
1506 next
.segs
[1] = pan
.segs
[1]
1507 next
.segs
[2].p0
= pan
.segs
[2].p0
1509 next
._axis
.p0
= pan
._axis
.p0
1510 next
.segs
[-1] = pan
.segs
[-1]
1511 next
.segs
[2].p1
= pan
.segs
[2].p1
1514 next
._axis
.p0
= pan
._axis
.p0
1515 next
.segs
[-1] = pan
.segs
[-1]
1516 next
.segs
[2].p1
= pan
.segs
[2].p1
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
):
1526 for pan
in self
.pans
:
1530 for pan in self.pans:
1531 if pan.last is None:
1536 def lambris(self
, context
, o
, d
):
1539 lambris_height
= 0.02
1540 alt
= self
.z
- lambris_height
1541 for pan
in self
.pans
:
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
)]
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
,
1564 use_dissolve_boundaries
=False,
1567 delimit
={'MATERIAL'})
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
)
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
):
1583 ttl
= len(self
.pans
)
1587 sx
, sy
, sz
= d
.tile_size_x
, d
.tile_size_y
, d
.tile_size_z
1590 /* Bevel offset_type slot values */
1598 offset_type
= 'PERCENT'
1600 if d
.tile_offset
> 0:
1601 offset
= - d
.tile_offset
/ 100
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)]]
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)]]
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)]
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)]
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
1698 # context.scene.archipack_progress_text = "Build tiles:"
1700 for i
, pan
in enumerate(self
.pans
):
1703 # compute base matrix top left of face
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
)
1722 if d
.tile_alternate
:
1726 [vx
.x
, vy
.x
, vz
.x
, x0
],
1727 [vx
.y
, vy
.y
, vz
.y
, y0
],
1728 [vx
.z
, vy
.z
, vz
.z
, z0
],
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))
1746 # context.scene.archipack_progress = progress
1750 x0
= offset
* dx
- d
.tile_side
1753 if d
.tile_alternate
and k
% 2 == 1:
1757 if d
.tile_offset
> 0:
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
)
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
1788 # seg without length lead to invalid normal
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
)
1796 vx
.z
= pan
.altitude(s0
.p1
) - dz
1797 vy
= vz
.cross(vx
.normalized())
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
1813 /* del "context" slot values, used for operator too */
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,
1826 # Build boundary including borders and bottom offsets
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
1839 new_s
= s
.make_offset(offset
, new_s
)
1843 # last / first intersection
1844 res
, p
, t
= segs
[0].intersect(segs
[-1])
1848 f_geom
= [f
for f
in bm
.faces
if not pan
.inside(f
.calc_center_median().to_2d(), segs
)]
1850 bmesh
.ops
.delete(bm
, geom
=f_geom
, context
="FACES")
1852 self
.cut_holes(bm
, pan
)
1854 bmesh
.ops
.dissolve_limit(bm
,
1856 use_dissolve_boundaries
=False,
1859 delimit
={'MATERIAL'})
1863 geom
.extend(bm
.edges
[:])
1866 offset
=d
.tile_bevel_amt
,
1867 offset_type
=offset_type
,
1868 segments
=d
.tile_bevel_segs
,
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
)
1881 bmed
.bmesh_join(context
, o
, [bm
], normal_update
=True)
1882 bpy
.ops
.object.mode_set(mode
='OBJECT')
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
):
1893 s0
= s
.offset(offset
- width
)
1894 s1
= s
.offset(offset
)
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
)
1911 res
, p
, t
= s5
.intersect(s1
)
1915 elif s2
.type == 'AXIS' or 'LINK' in s2
.type:
1916 # intersect axis or link seg
1917 res
, p
, t
= s2
.intersect(s0
)
1920 res
, p
, t
= s2
.intersect(s1
)
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
)
1931 res
, p
, t
= s5
.intersect(s1
)
1935 elif s3
.type == 'AXIS' or 'LINK' in s3
.type:
1936 # intersect axis or link seg
1937 res
, p
, t
= s3
.intersect(s0
)
1940 res
, p
, t
= s3
.intersect(s1
)
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
)
1973 (f
, f
+ 1, f
+ 2, f
+ 3),
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
),
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
])
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 #####################
2001 #####################
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':
2013 d
.bargeboard_height
,
2014 d
.bargeboard_altitude
,
2015 d
.bargeboard_offset
,
2023 for i
, s
in enumerate(pan
.segs
):
2024 if s
.type == 'SIDE':
2029 d
.bargeboard_height
,
2030 d
.bargeboard_altitude
,
2031 d
.bargeboard_offset
,
2039 def _fascia(self
, s
, i
, boundary
, pan
, tri_0
, tri_1
,
2040 width
, height
, altitude
, offset
, idmat
,
2041 verts
, faces
, edges
, matids
, uvs
):
2044 s0
= s
.offset(offset
)
2045 s1
= s
.offset(offset
+ width
)
2047 s2
= boundary
.last_seg(i
)
2048 s3
= boundary
.next_seg(i
)
2057 # find last neighbor depending on type
2058 if s2
.type == 'AXIS' or 'LINK' in s2
.type:
2059 # apply only on boundaries
2062 if pan
.side
== 'LEFT':
2070 s2
.v
= (s
.sized_normal(0, 1).v
+ s6
.v
).normalized()
2073 elif s2
.type == 'SIDE':
2076 s2
.v
= s
.sized_normal(0, 1).v
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:
2086 if pan
.side
== 'LEFT':
2094 s3
.v
= (s
.sized_normal(0, 1).v
+ s6
.v
).normalized()
2096 elif s3
.type == 'SIDE':
2097 # when next is side, use perpendicular
2100 s3
.v
= s
.sized_normal(0, 1).v
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
)
2112 res
, p
, t
= s0
.intersect(s3
)
2115 res
, p
, t
= s1
.intersect(s4
)
2118 res
, p
, t
= s1
.intersect(s5
)
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
)
2152 (f
, f
+ 1, f
+ 2, f
+ 3),
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
),
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
])
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 #####################
2179 #####################
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':
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
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
2253 if pan
.side
== 'LEFT':
2261 s1
.v
= (s
.sized_normal(0, 1).v
+ s3
.v
).normalized()
2262 elif s1
.type == 'SIDE':
2265 s1
.v
= s
.sized_normal(0, 1).v
2267 s1
= s1
.offset(d
.fascia_width
)
2269 # find next neighbor depending on type
2270 if s2
.type == 'AXIS' or 'LINK' in s2
.type:
2273 if pan
.side
== 'LEFT':
2281 s2
.v
= (s
.sized_normal(0, 1).v
+ s3
.v
).normalized()
2282 elif s2
.type == 'SIDE':
2285 s2
.v
= s
.sized_normal(0, 1).v
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
)
2300 z0
= self
.z
+ d
.fascia_altitude
+ pan
.altitude(s
.p0
)
2301 z1
= self
.z
+ d
.fascia_altitude
+ pan
.altitude(s
.p1
)
2308 z0
-= d
.fascia_height
2309 z1
-= d
.fascia_height
2319 (f
, f
+ 1, f
+ 2, f
+ 3),
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
),
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
])
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 #####################
2346 #####################
2350 # caps at start and end
2351 if d
.gutter_segs
% 2 == 1:
2352 n_faces
= int((d
.gutter_segs
- 1) / 2)
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':
2364 s0
= s
.offset(d
.gutter_dist
+ d
.gutter_width
)
2366 s1
= pan
.last_seg(i
)
2367 s2
= pan
.next_seg(i
)
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
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
2386 if pan
.side
== 'LEFT':
2394 s1
.v
= (s
.sized_normal(0, 1).v
+ s3
.v
).normalized()
2395 elif s1
.type == 'SIDE':
2398 s1
.v
= s
.sized_normal(0, 1).v
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:
2406 if pan
.side
== 'LEFT':
2414 s2
.v
= (s
.sized_normal(0, 1).v
+ s3
.v
).normalized()
2415 elif s2
.type == 'SIDE':
2418 s2
.v
= s
.sized_normal(0, 1).v
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
)
2428 res
, p
, t
= s0
.intersect(s2
)
2433 verts.extend([s1.p0.to_3d(), s1.p1.to_3d()])
2434 edges.append([f, f + 1])
2437 verts.extend([s2.p0.to_3d(), s2.p1.to_3d()])
2438 edges.append([f, f + 1])
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
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
)
2466 xt
, yt
= s3
.lerp(tt
)
2469 x0
, y0
= s3
.lerp(t0
)
2471 x1
, y1
= s3
.lerp(t1
)
2472 # bord boudin interieur
2473 x2
, y2
= s3
.lerp(t2
)
2475 x3
, y3
= s3
.lerp(t3
)
2480 verts
.append((xt
, yt
, zt
))
2482 da
= pi
/ d
.gutter_segs
2483 for i
in range(d
.gutter_segs
):
2486 verts
.append((x1
+ dx
* ca
, y1
+ dy
* ca
, z1
+ dz0
* sa
))
2492 da
= -pi
/ (0.75 * d
.gutter_segs
)
2493 for i
in range(d
.gutter_segs
):
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
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
)
2512 xt
, yt
= s4
.lerp(tt
)
2515 x0
, y0
= s4
.lerp(t0
)
2517 x1
, y1
= s4
.lerp(t1
)
2518 # bord boudin interieur
2519 x2
, y2
= s4
.lerp(t2
)
2521 x3
, y3
= s4
.lerp(t3
)
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
)
2533 da
= pi
/ d
.gutter_segs
2534 for i
in range(d
.gutter_segs
):
2537 verts
.append((x1
+ dx
* ca
, y1
+ dy
* ca
, z1
+ dz0
* sa
))
2543 da
= -pi
/ (0.75 * d
.gutter_segs
)
2544 for i
in range(d
.gutter_segs
):
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
):
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
)
2560 n_faces = segs / 2 - 1
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
):
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
)
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
):
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
):
2605 for pan
in self
.pans
:
2606 for i
, s
in enumerate(pan
.segs
):
2608 if s
.type == 'AXIS':
2610 ####################
2612 ####################
2615 1___________________2 left
2616 0|___________________|3 axis
2617 |___________________| right
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
)
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
2644 res
, p0
, t
= s2
.intersect(s0
)
2648 if s1
.type == 'SIDE' and s
.length
> 0:
2649 s1
= s1
.offset(d
.beam_offset
)
2650 t1
= 1 + d
.beam_offset
/ s
.length
2656 res
, p1
, t
= s2
.intersect(s1
)
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
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
)
2689 # (f + 1, f + 5, f + 7, f + 3),
2691 (f
+ 2, f
+ 6, f
+ 4, f
),
2693 (f
, f
+ 1, f
+ 3, f
+ 2),
2695 (f
+ 5, f
+ 4, f
+ 6, f
+ 7)
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
):
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))
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
)
2745 z0
= self
.z
+ alt
+ pan
.altitude(n0
.p0
)
2747 z1
= self
.z
+ alt
+ pan
.altitude(n0
.p1
)
2749 z2
= self
.z
+ alt
+ pan
.altitude(n1
.p0
)
2751 z3
= self
.z
+ alt
+ pan
.altitude(n1
.p1
)
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
,
2774 use_dissolve_boundaries
=False,
2777 delimit
={'MATERIAL'})
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
)
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
]
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
):
2802 sx
, sy
, sz
= d
.hip_size_x
, d
.hip_size_y
, d
.hip_size_z
2804 if d
.hip_model
== 'ROUND':
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)
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)
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
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)]
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)
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),
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)]
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)]
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
2916 s0
= s
.offset(-0.5 * d
.beam_sec_width
)
2918 s2
= pan
.last_seg(i
)
2919 s3
= pan
.next_seg(i
)
2924 res
, p
, t
= s0
.intersect(s2
)
2928 res
, p
, t
= s0
.intersect(s3
)
2936 x0
, y0
= s0
.lerp(t0
)
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
2951 x2
, y2
= s0
.lerp(t1
)
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
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)
2975 idmat_poutre
, idmat_poutre
, idmat_poutre
,
2976 idmat_poutre
, idmat_poutre
, idmat_poutre
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':
2990 # Slice borders properly
2994 s0
= pan
.last_seg(i
)
2995 s1
= pan
.next_seg(i
)
2999 z0
= pan
.altitude(p0
)
3000 z1
= pan
.altitude(p1
)
3007 dz
= pan
.altitude(s2
.sized_normal(0, 1).p1
) - z0
3010 s1
= s1
.offset(d
.tile_border
)
3013 v
= Vector((x
, y
, z1
- z0
))
3015 vy
= vx
.cross(Vector((0, 0, 1)))
3018 x0
, y0
= p0
+ d
.hip_alt
* vz
.to_2d()
3019 z2
= z0
+ self
.z
+ d
.hip_alt
* vz
.z
3021 [vx
.x
, vy
.x
, vz
.x
, x0
],
3022 [vx
.y
, vy
.y
, vz
.y
, y0
],
3023 [vx
.z
, vy
.z
, vz
.z
, z2
],
3026 space_x
= v
.length
- d
.tile_border
3027 n_x
= 1 + int(space_x
/ d
.hip_space_x
)
3031 t_verts
= [p
for p
in t_pts
]
3036 t_verts
[i
] = t_verts
[i
].copy()
3037 t_verts
[i
].z
-= dz
* t_verts
[i
].y
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
):
3044 [1, 0, 0, x0
+ k
* dx
],
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
)
3056 elif s
.type == 'LINK_VALLEY':
3059 s0
= s
.offset(-2 * d
.tile_couloir
)
3060 s1
= pan
.last_seg(i
)
3061 s2
= pan
.next_seg(i
)
3064 res
, p
, t
= s0
.intersect(s1
)
3067 res
, p
, t
= s0
.intersect(s2
)
3070 alt
= self
.z
+ d
.valley_altitude
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
)
3087 (f
, f
+ 3, f
+ 2, f
+ 1)
3093 [(0, 0), (1, 0), (1, 1), (0, 1)]
3096 elif s
.type == 'AXIS' and d
.hip_enable
and pan
.side
== 'LEFT':
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 ####################
3111 ####################
3114 s_len
= (tmax
- tmin
) * s
.length
3115 n_obj
= 1 + int(s_len
/ d
.hip_space_x
)
3118 v
= s
.v
.normalized()
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
],
3126 t_verts
= [p
.copy() for p
in t_pts
]
3130 t_verts
[i
].z
+= t_verts
[i
].y
* (pan
.other_side
.slope
- d
.tile_size_z
/ d
.tile_size_y
)
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
):
3136 [1, 0, 0, x0
+ k
* dx
],
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
)
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
3154 # print("Make hole :%s hole_obj:%s" % (o.name, hole_obj))
3155 if o
.parent
is None:
3157 # root is a RoofSegment
3158 root
= self
.nodes
[0].root
3166 # /_____axis_____| 3 <- kill axis and this one
3169 # 1 \____________| 2
3180 last
= len(r_pan
.segs
) - 1
3181 for i
, seg
in enumerate(r_pan
.segs
):
3182 # r_pan start parent roof side
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
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:
3204 # 0\ p0___s0_______| p1
3207 s0
= root
.left
._axis
.offset(
3210 root
.right
.ysize
- 0.001,
3211 root
.right
.ysize
- d
.hole_offset_right
3214 s1
= root
.left
._axis
.offset(
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
)
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
)
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:
3238 pts
.extend([p2
, p1
])
3239 if (segs
[0].p1
- p0
).length
> 0.001:
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
3250 context
.view_layer
.objects
.active
= hole_obj
3252 hole_obj
.select_set(state
=True)
3253 if d
.parts
[0].a0
< 0:
3258 hole_obj
.matrix_world
= o
.matrix_world
@ Matrix([
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())
3285 tp
= (rM
@ s
.p0
) - s
.p0
+ dp
3289 def t_partition(self
, array
, begin
, end
):
3291 for i
in range(begin
+ 1, end
+ 1):
3293 if array
[i
][0] < array
[begin
][0]:
3295 array
[i
], array
[pivot
] = array
[pivot
], array
[i
]
3296 array
[pivot
], array
[begin
] = array
[begin
], array
[pivot
]
3299 def sort_t(self
, array
, begin
=0, end
=None):
3300 # print("sort_child")
3302 end
= len(array
) - 1
3304 def _quicksort(array
, begin
, end
):
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()
3317 # wg in roof coordsys
3318 wg
.change_coordsys(wall
.matrix_world
, o
.matrix_world
)
3322 offset
= -0.5 * (1 - wd
.x_offset
) * wd
.width
3327 wg
.set_offset(offset
)
3329 wall_t
= [[] for w
in wg
.segs
]
3331 for pan
in self
.pans
:
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
)
3343 z
= z0
+ pan
.altitude(p
)
3344 wall_t
[widx
].append((t
, z
, t
* ls
))
3347 if type(wseg
).__name
__ == "CurvedWall":
3348 for step
in range(12):
3350 p
= wseg
.line
.lerp(t
)
3352 z
= z0
+ pan
.altitude(p
)
3353 wall_t
[widx
].append((t
, z
, t
* ls
))
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
):
3368 # print("seg: %s" % seg)
3371 wd
.parts
[widx
].n_splits
= len(seg
) + 1
3372 wd
.parts
[widx
].z
[0] = 0
3373 wd
.parts
[widx
].t
[0] = 0
3376 # add splits, skip dups
3377 for widx
, seg
in enumerate(wall_t
):
3384 # add at end of last segment
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
3390 wd
.parts
[widx
].z
[0] = z
3391 wd
.parts
[widx
].t
[0] = t
3394 if d
- last_d
< 0.001:
3395 wd
.parts
[widx
].n_splits
-= 1
3397 wd
.parts
[widx
].z
[sid
] = z
3398 wd
.parts
[widx
].t
[sid
] = t
- t0
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
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
3423 for b
in o
.children
:
3424 d
= archipack_roof_cutter
.datablock(b
)
3426 g
= d
.ensure_direction()
3427 g
.change_coordsys(b
.matrix_world
, o
.matrix_world
)
3428 for i
, pan
in enumerate(self
.pans
):
3431 if i
not in to_remove
:
3435 for i
in reversed(to_remove
):
3438 def draft(self
, context
, verts
, edges
):
3439 for pan
in self
.pans
:
3440 pan
.draw(context
, self
.z
, verts
, edges
)
3443 if s
.constraint_type
== 'SLOPE':
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
):
3468 o
= context
.active_object
3469 p
, d
= self
.find_parent(context
)
3475 # trigger object update
3476 # hole creation and parent's update
3478 self
.parts
[0].a0
= pi
/ 2
3480 elif 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(
3509 subtype
='ANGLE', unit
='ROTATION',
3510 update
=update_cutter
3512 manipulators
: CollectionProperty(type=archipack_manipulator
)
3515 class ArchipackLines():
3516 n_parts
: IntProperty(
3519 default
=1, update
=update_manipulators
3522 parts_expand
: BoolProperty(
3526 def draw(self
, layout
, context
):
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
)
3535 row
.prop(self
, 'parts_expand', icon
="TRIA_RIGHT", text
="Parts", emboss
=False)
3537 def update_parts(self
):
3538 # print("update_parts")
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)
3547 for i
in range(len(self
.parts
), self
.n_parts
+ 1):
3550 self
.setup_manipulators()
3552 def setup_parts_manipulators(self
):
3553 for i
in range(self
.n_parts
+ 1):
3555 n_manips
= len(p
.manipulators
)
3557 s
= p
.manipulators
.add()
3558 s
.type_key
= "ANGLE"
3561 s
= p
.manipulators
.add()
3563 s
.prop1_name
= "length"
3565 s
= p
.manipulators
.add()
3566 s
.type_key
= 'WALL_SNAP'
3567 s
.prop1_name
= str(i
)
3570 s
= p
.manipulators
.add()
3571 s
.type_key
= 'DUMB_STRING'
3572 s
.prop1_name
= str(i
+ 1)
3574 s
= p
.manipulators
.add()
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(
3587 update
=update_manipulators
3589 width_left
: FloatProperty(
3593 update
=update_cutter
3595 width_right
: FloatProperty(
3599 update
=update_cutter
3601 slope_left
: FloatProperty(
3605 update
=update_cutter
3607 slope_right
: FloatProperty(
3611 update
=update_cutter
3613 auto_left
: EnumProperty(
3614 description
="Left mode",
3617 ('AUTO', 'Auto', '', 0),
3618 ('WIDTH', 'Width', '', 1),
3619 ('SLOPE', 'Slope', '', 2),
3620 ('ALL', 'All', '', 3),
3623 update
=update_manipulators
3625 auto_right
: EnumProperty(
3626 description
="Right mode",
3629 ('AUTO', 'Auto', '', 0),
3630 ('WIDTH', 'Width', '', 1),
3631 ('SLOPE', 'Slope', '', 2),
3632 ('ALL', 'All', '', 3),
3635 update
=update_manipulators
3637 triangular_end
: BoolProperty(
3638 name
="Triangular end",
3642 take_precedence
: BoolProperty(
3643 name
="Take precedence",
3644 description
="On T segment take width precedence",
3649 constraint_type
: EnumProperty(
3651 ('HORIZONTAL', 'Horizontal', '', 0),
3652 ('SLOPE', 'Slope', '', 1)
3654 default
='HORIZONTAL',
3655 update
=update_manipulators
3658 enforce_part
: EnumProperty(
3659 name
="Enforce part",
3661 ('AUTO', 'Auto', '', 0),
3662 ('VALLEY', 'Valley', '', 1),
3663 ('HIP', 'Hip', '', 2)
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
[:]
3676 d
= archipack_roof
.datablock(o
)
3678 for part
in d
.parts
:
3683 def draw(self
, layout
, context
, index
):
3686 box
.prop(self
, "constraint_type", text
=str(index
+ 1))
3687 if self
.constraint_type
== 'SLOPE':
3688 box
.prop(self
, "enforce_part", text
="")
3690 box
.label(text
="Part 1:")
3691 box
.prop(self
, "length")
3692 box
.prop(self
, "a0")
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
,
3722 class archipack_roof(ArchipackLines
, ArchipackObject
, Manipulable
, PropertyGroup
):
3723 parts
: CollectionProperty(type=archipack_roof_segment
)
3726 default
=3, precision
=2, step
=1,
3727 unit
='LENGTH', subtype
='DISTANCE',
3728 update
=update_childs
3730 slope_left
: FloatProperty(
3732 default
=0.5, precision
=2, step
=1,
3733 update
=update_childs
3735 slope_right
: FloatProperty(
3737 default
=0.5, precision
=2, step
=1,
3738 update
=update_childs
3740 width_left
: FloatProperty(
3742 default
=3, precision
=2, step
=1,
3743 unit
='LENGTH', subtype
='DISTANCE',
3744 update
=update_cutter
3746 width_right
: FloatProperty(
3748 default
=3, precision
=2, step
=1,
3749 unit
='LENGTH', subtype
='DISTANCE',
3750 update
=update_cutter
3752 draft
: BoolProperty(
3753 options
={'SKIP_SAVE'},
3756 update
=update_manipulators
3758 auto_update
: BoolProperty(
3759 options
={'SKIP_SAVE'},
3761 update
=update_manipulators
3763 quick_edit
: BoolProperty(
3764 options
={'SKIP_SAVE'},
3769 tile_enable
: BoolProperty(
3772 update
=update_components
3774 tile_solidify
: BoolProperty(
3777 update
=update_components
3779 tile_height
: FloatProperty(
3781 description
="Amount for solidify",
3784 unit
='LENGTH', subtype
='DISTANCE',
3785 update
=update_components
3787 tile_bevel
: BoolProperty(
3790 update
=update_components
3792 tile_bevel_amt
: FloatProperty(
3794 description
="Amount for bevel",
3797 unit
='LENGTH', subtype
='DISTANCE',
3798 update
=update_components
3800 tile_bevel_segs
: IntProperty(
3802 description
="Bevel Segs",
3805 update
=update_components
3807 tile_alternate
: BoolProperty(
3810 update
=update_components
3812 tile_offset
: FloatProperty(
3814 description
="Offset from start",
3817 subtype
="PERCENTAGE",
3818 update
=update_components
3820 tile_altitude
: FloatProperty(
3822 description
="Altitude from roof",
3824 unit
='LENGTH', subtype
='DISTANCE',
3825 update
=update_components
3827 tile_size_x
: FloatProperty(
3829 description
="Size of tiles on x axis",
3832 unit
='LENGTH', subtype
='DISTANCE',
3833 update
=update_components
3835 tile_size_y
: FloatProperty(
3837 description
="Size of tiles on y axis",
3840 unit
='LENGTH', subtype
='DISTANCE',
3841 update
=update_components
3843 tile_size_z
: FloatProperty(
3845 description
="Size of tiles on z axis",
3848 unit
='LENGTH', subtype
='DISTANCE',
3849 update
=update_components
3851 tile_space_x
: FloatProperty(
3853 description
="Space between tiles on x axis",
3856 unit
='LENGTH', subtype
='DISTANCE',
3857 update
=update_components
3859 tile_space_y
: FloatProperty(
3861 description
="Space between tiles on y axis",
3864 unit
='LENGTH', subtype
='DISTANCE',
3865 update
=update_components
3867 tile_fit_x
: BoolProperty(
3869 description
="Fit roof on x axis",
3871 update
=update_components
3873 tile_fit_y
: BoolProperty(
3875 description
="Fit roof on y axis",
3877 update
=update_components
3879 tile_expand
: BoolProperty(
3880 options
={'SKIP_SAVE'},
3882 description
="Expand tiles panel",
3885 tile_model
: EnumProperty(
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)
3900 update
=update_components
3902 tile_side
: FloatProperty(
3904 description
="Space on side",
3906 unit
='LENGTH', subtype
='DISTANCE',
3907 update
=update_components
3909 tile_couloir
: FloatProperty(
3911 description
="Space between tiles on valley",
3914 unit
='LENGTH', subtype
='DISTANCE',
3915 update
=update_components
3917 tile_border
: FloatProperty(
3919 description
="Tiles offset from bottom",
3921 unit
='LENGTH', subtype
='DISTANCE',
3922 update
=update_components
3925 gutter_expand
: BoolProperty(
3926 options
={'SKIP_SAVE'},
3928 description
="Expand gutter panel",
3931 gutter_enable
: BoolProperty(
3934 update
=update_components
3936 gutter_alt
: FloatProperty(
3938 description
="altitude",
3940 unit
='LENGTH', subtype
='DISTANCE',
3941 update
=update_components
3943 gutter_width
: FloatProperty(
3945 description
="Width",
3948 unit
='LENGTH', subtype
='DISTANCE',
3949 update
=update_components
3951 gutter_dist
: FloatProperty(
3953 description
="Spacing",
3956 unit
='LENGTH', subtype
='DISTANCE',
3957 update
=update_components
3959 gutter_boudin
: FloatProperty(
3961 description
="Small width",
3964 unit
='LENGTH', subtype
='DISTANCE',
3965 update
=update_components
3967 gutter_segs
: IntProperty(
3971 update
=update_components
3974 beam_expand
: BoolProperty(
3975 options
={'SKIP_SAVE'},
3977 description
="Expand beam panel",
3980 beam_enable
: BoolProperty(
3983 update
=update_components
3985 beam_width
: FloatProperty(
3987 description
="Width",
3990 unit
='LENGTH', subtype
='DISTANCE',
3991 update
=update_components
3993 beam_height
: FloatProperty(
3995 description
="Height",
3998 unit
='LENGTH', subtype
='DISTANCE',
3999 update
=update_components
4001 beam_offset
: FloatProperty(
4003 description
="Distance from roof border",
4005 unit
='LENGTH', subtype
='DISTANCE',
4006 update
=update_components
4008 beam_alt
: FloatProperty(
4010 description
="Altitude from roof",
4012 unit
='LENGTH', subtype
='DISTANCE',
4013 update
=update_components
4015 beam_sec_enable
: BoolProperty(
4018 update
=update_components
4020 beam_sec_width
: FloatProperty(
4022 description
="Width",
4025 unit
='LENGTH', subtype
='DISTANCE',
4026 update
=update_components
4028 beam_sec_height
: FloatProperty(
4030 description
="Height",
4033 unit
='LENGTH', subtype
='DISTANCE',
4034 update
=update_components
4036 beam_sec_alt
: FloatProperty(
4038 description
="Distance from roof",
4040 unit
='LENGTH', subtype
='DISTANCE',
4041 update
=update_components
4043 rafter_enable
: BoolProperty(
4046 update
=update_components
4048 rafter_width
: FloatProperty(
4050 description
="Width",
4053 unit
='LENGTH', subtype
='DISTANCE',
4054 update
=update_components
4056 rafter_height
: FloatProperty(
4058 description
="Height",
4061 unit
='LENGTH', subtype
='DISTANCE',
4062 update
=update_components
4064 rafter_spacing
: FloatProperty(
4066 description
="Spacing",
4069 unit
='LENGTH', subtype
='DISTANCE',
4070 update
=update_components
4072 rafter_start
: FloatProperty(
4074 description
="Spacing from roof border",
4077 unit
='LENGTH', subtype
='DISTANCE',
4078 update
=update_components
4080 rafter_alt
: FloatProperty(
4082 description
="Altitude from roof",
4085 unit
='LENGTH', subtype
='DISTANCE',
4086 update
=update_components
4089 hip_enable
: BoolProperty(
4092 update
=update_components
4094 hip_expand
: BoolProperty(
4095 options
={'SKIP_SAVE'},
4097 description
="Expand hips panel",
4100 hip_alt
: FloatProperty(
4102 description
="Hip altitude from roof",
4104 unit
='LENGTH', subtype
='DISTANCE',
4105 update
=update_components
4107 hip_space_x
: FloatProperty(
4109 description
="Space between hips",
4112 unit
='LENGTH', subtype
='DISTANCE',
4113 update
=update_components
4115 hip_size_x
: FloatProperty(
4117 description
="Length of hip",
4120 unit
='LENGTH', subtype
='DISTANCE',
4121 update
=update_components
4123 hip_size_y
: FloatProperty(
4125 description
="Width of hip",
4128 unit
='LENGTH', subtype
='DISTANCE',
4129 update
=update_components
4131 hip_size_z
: FloatProperty(
4133 description
="Height of hip",
4136 unit
='LENGTH', subtype
='DISTANCE',
4137 update
=update_components
4139 hip_model
: EnumProperty(
4142 ('ROUND', 'Round', '', 0),
4143 ('ETERNIT', 'Eternit', '', 1),
4144 ('FLAT', 'Flat', '', 2)
4147 update
=update_components
4149 valley_altitude
: FloatProperty(
4151 description
="Valley altitude from roof",
4153 unit
='LENGTH', subtype
='DISTANCE',
4154 update
=update_components
4156 valley_enable
: BoolProperty(
4159 update
=update_components
4162 fascia_enable
: BoolProperty(
4164 description
="Enable Fascia",
4166 update
=update_components
4168 fascia_expand
: BoolProperty(
4169 options
={'SKIP_SAVE'},
4171 description
="Expand fascia panel",
4174 fascia_height
: FloatProperty(
4176 description
="Height",
4179 unit
='LENGTH', subtype
='DISTANCE',
4180 update
=update_components
4182 fascia_width
: FloatProperty(
4184 description
="Width",
4187 unit
='LENGTH', subtype
='DISTANCE',
4188 update
=update_components
4190 fascia_offset
: FloatProperty(
4192 description
="Offset from roof border",
4194 unit
='LENGTH', subtype
='DISTANCE',
4195 update
=update_components
4197 fascia_altitude
: FloatProperty(
4199 description
="Fascia altitude from roof",
4201 unit
='LENGTH', subtype
='DISTANCE',
4202 update
=update_components
4205 bargeboard_enable
: BoolProperty(
4207 description
="Enable Bargeboard",
4209 update
=update_components
4211 bargeboard_expand
: BoolProperty(
4212 options
={'SKIP_SAVE'},
4214 description
="Expand Bargeboard panel",
4217 bargeboard_height
: FloatProperty(
4219 description
="Height",
4222 unit
='LENGTH', subtype
='DISTANCE',
4223 update
=update_components
4225 bargeboard_width
: FloatProperty(
4227 description
="Width",
4230 unit
='LENGTH', subtype
='DISTANCE',
4231 update
=update_components
4233 bargeboard_offset
: FloatProperty(
4235 description
="Offset from roof border",
4237 unit
='LENGTH', subtype
='DISTANCE',
4238 update
=update_components
4240 bargeboard_altitude
: FloatProperty(
4242 description
="Fascia altitude from roof",
4244 unit
='LENGTH', subtype
='DISTANCE',
4245 update
=update_components
4248 t_parent
: StringProperty(
4251 update
=update_parent
4253 t_part
: IntProperty(
4255 description
="Parent part index",
4258 update
=update_cutter
4260 t_dist_x
: FloatProperty(
4262 description
="Location on axis ",
4264 update
=update_cutter
4266 t_dist_y
: FloatProperty(
4268 description
="Lateral distance from axis",
4271 update
=update_cutter
4273 z_parent
: FloatProperty(
4274 description
="Delta z of t child for grand childs",
4277 hole_offset_left
: FloatProperty(
4279 description
="Left distance from border",
4282 update
=update_cutter
4284 hole_offset_right
: FloatProperty(
4286 description
="Right distance from border",
4289 update
=update_cutter
4291 hole_offset_front
: FloatProperty(
4293 description
="Front distance from border",
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
):
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)
4312 for i
in range(len(self
.parts
), self
.n_parts
):
4313 bound_idx
= len(self
.parts
)
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()
4324 s
.normal
= (0, 1, 0)
4325 if len(self
.manipulators
) < 2:
4326 s
= self
.manipulators
.add()
4328 s
.prop1_name
= "width_left"
4329 if len(self
.manipulators
) < 3:
4330 s
= self
.manipulators
.add()
4332 s
.prop1_name
= "width_right"
4334 for i
in range(self
.n_parts
):
4336 n_manips
= len(p
.manipulators
)
4338 s
= p
.manipulators
.add()
4339 s
.type_key
= "ANGLE"
4342 s
= p
.manipulators
.add()
4344 s
.prop1_name
= "length"
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)
4351 s
= p
.manipulators
.add()
4353 s
.prop1_name
= "width_left"
4355 s
= p
.manipulators
.add()
4357 s
.prop1_name
= "width_right"
4359 s
= p
.manipulators
.add()
4361 s
.prop1_name
= "slope_left"
4363 s
= p
.manipulators
.add()
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
:
4377 g
.locate_manipulators()
4380 def make_surface(self
, o
, verts
, edges
):
4384 bm
.verts
.ensure_lookup_table()
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)
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
)
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
],
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
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
4454 manipulable_refresh
=False,
4455 update_childs
=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
4465 o
= self
.find_in_selection(context
, self
.auto_update
)
4470 # clean up manipulators before any data model change
4471 if manipulable_refresh
:
4472 self
.manipulable_disable(context
)
4476 verts
, edges
, faces
, matids
, uvs
= [], [], [], [], []
4480 p
, d
= self
.find_parent(context
)
4483 # t childs: use parent to relocate
4484 # setup slopes into generator
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
4498 s_left
= self
.slope_left
4499 w_left
= -self
.width_left
4500 s_right
= self
.slope_right
4501 w_right
= self
.width_right
4503 # a_axis est mesure depuis la perpendiculaire à l'axe
4504 slope
= seg
.right
.slope
4508 slope
= seg
.left
.slope
4510 s_left
, s_right
= s_right
, s_left
4511 w_left
, w_right
= -w_right
, -w_left
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(
4528 a_right
= b_right
+ a_offset
4530 b_left
= self
.intersection_angle(
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
4544 if (s
.constraint_type
== 'SLOPE' and
4546 da
= g
.segs
[0].v
.angle_signed(s
.v
)
4553 # Add 'SLOPE' constraints for segment 0
4554 v
= Vector((cos(a_left
), sin(a_left
)))
4555 s
= StraightRoof(g
.origin
, v
)
4557 s
.constraint_type
= 'SLOPE'
4558 # s.enforce_part = 'VALLEY'
4560 s
.take_precedence
= False
4564 v
= Vector((cos(a_right
), sin(a_right
)))
4565 s
= StraightRoof(g
.origin
, v
)
4567 s
.constraint_type
= 'SLOPE'
4568 # s.enforce_part = 'VALLEY'
4570 s
.take_precedence
= False
4574 g
= self
.get_generator(origin
=Vector((0, y
, z
)))
4576 # setup per segment manipulators
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
)
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)])
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
4594 self
.update_childs(context
, o
, g
)
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")
4603 g
.boundary(context
, o
)
4607 g
.draft(context
, verts
, edges
)
4608 g
.gutter(self
, verts
, faces
, edges
, matids
, uvs
)
4609 self
.make_surface(o
, verts
, edges
)
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
)
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
)
4636 if self
.rafter_enable
:
4637 # bpy.ops.object.mode_set(mode='EDIT')
4638 g
.rafter(context
, o
, self
)
4641 if self
.quick_edit
and not force_update
:
4642 if self
.tile_enable
:
4643 bpy
.ops
.archipack
.roof_throttle_update(name
=o
.name
)
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
4655 self
.restore_context(context
)
4656 # print("restore context")
4658 def find_hole(self
, context
, o
):
4659 p
, d
= self
.find_parent(context
)
4661 for child
in p
.children
:
4662 cd
= archipack_roof_cutter
.datablock(child
)
4663 if cd
is not None and cd
.boundary
== o
.name
:
4667 def manipulable_setup(self
, context
):
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
):
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
))
4691 self
.manip_stack
.append(part
.manipulators
[2].setup(context
, o
, self
))
4694 if part
.auto_left
in {'WIDTH', 'ALL'}:
4695 self
.manip_stack
.append(part
.manipulators
[3].setup(context
, o
, part
))
4697 if part
.auto_right
in {'WIDTH', 'ALL'}:
4698 self
.manip_stack
.append(part
.manipulators
[4].setup(context
, o
, part
))
4700 if part
.auto_left
in {'SLOPE', 'ALL'}:
4701 self
.manip_stack
.append(part
.manipulators
[5].setup(context
, o
, part
))
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
):
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
)
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(
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)
4747 def find_in_selection(self
, context
):
4748 selected
= context
.selected_objects
[:]
4750 d
= archipack_roof_cutter
.datablock(o
)
4752 for part
in d
.parts
:
4758 class archipack_roof_cutter(ArchipackCutter
, ArchipackObject
, Manipulable
, PropertyGroup
):
4760 parts
: CollectionProperty(type=archipack_roof_cutter_segment
)
4761 boundary
: StringProperty(
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
4777 self
.update_parent(context
, o
)
4778 # print("update_points")
4780 def update_parent(self
, context
, o
):
4782 d
= archipack_roof
.datablock(o
.parent
)
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'
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
)
4807 layout
= self
.layout
4808 scene
= context
.scene
4810 if prop
.boundary
!= "":
4811 box
.label(text
="Auto Cutter:")
4812 box
.label(text
=prop
.boundary
)
4814 box
.operator('archipack.roof_cutter_manipulate', icon
='VIEW_PAN')
4815 box
.prop(prop
, 'operation', text
="")
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"
4832 bl_space_type
= 'VIEW_3D'
4833 bl_region_type
= 'UI'
4834 bl_category
= 'Archipack'
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
)
4845 scene
= context
.scene
4846 layout
= self
.layout
4847 row
= layout
.row(align
=True)
4848 row
.operator('archipack.roof_manipulate', icon
='VIEW_PAN')
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
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
)
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')
4868 box
.prop(prop
, 'quick_edit', icon
="MOD_MULTIRES")
4869 box
.prop(prop
, 'draft')
4872 box
.prop(prop
, 'slope_left')
4873 box
.prop(prop
, 'slope_right')
4874 box
.prop(prop
, 'width_left')
4875 box
.prop(prop
, 'width_right')
4877 prop
.draw(layout
, context
)
4880 row
= box
.row(align
=True)
4881 if prop
.tile_expand
:
4882 row
.prop(prop
, 'tile_expand', icon
="TRIA_DOWN", text
="Covering", emboss
=False)
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')
4893 box
.prop(prop
, 'tile_bevel', icon
='MOD_BEVEL')
4895 box
.prop(prop
, 'tile_bevel_amt')
4896 box
.prop(prop
, 'tile_bevel_segs')
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')
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')
4923 row
= box
.row(align
=True)
4925 row
.prop(prop
, 'hip_expand', icon
="TRIA_DOWN", text
="Hip", emboss
=False)
4927 row
.prop(prop
, 'hip_expand', icon
="TRIA_RIGHT", text
="Hip", emboss
=False)
4928 row
.prop(prop
, 'hip_enable')
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')
4937 box
.prop(prop
, 'valley_enable')
4938 box
.prop(prop
, 'valley_altitude')
4941 row
= box
.row(align
=True)
4943 if prop
.beam_expand
:
4944 row
.prop(prop
, 'beam_expand', icon
="TRIA_DOWN", text
="Beam", emboss
=False)
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')
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')
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')
4970 row
= box
.row(align
=True)
4971 if prop
.gutter_expand
:
4972 row
.prop(prop
, 'gutter_expand', icon
="TRIA_DOWN", text
="Gutter", emboss
=False)
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')
4984 row
= box
.row(align
=True)
4985 if prop
.fascia_expand
:
4986 row
.prop(prop
, 'fascia_expand', icon
="TRIA_DOWN", text
="Fascia", emboss
=False)
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')
4997 row
= box
.row(align
=True)
4998 if prop
.bargeboard_expand
:
4999 row
.prop(prop
, 'bargeboard_expand', icon
="TRIA_DOWN", text
="Bargeboard", emboss
=False)
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')
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"
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
5048 # -----------------------------------------------------
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
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:
5081 bbox
= parent
.bound_box
5087 o
.matrix_world
= parent
.matrix_world
@ Matrix([
5104 pd
= archipack_roof
.datablock(parent
)
5105 pd
.boundary
= o
.name
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
)
5115 update_operation(d
, context
)
5118 # -----------------------------------------------------
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
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)
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
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
5176 pt
= Vector((0, 0, 0))
5178 o
.matrix_world
= curve
.matrix_world
@ Matrix([
5184 o
.select_set(state
=True)
5185 context
.view_layer
.objects
.active
= o
5188 # -----------------------------------------------------
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')
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'}
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
)
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'}
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
)
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
5248 def __init__(self
, context
, delay
):
5251 self
.update_state
= False
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
)
5263 def execute(self
, context
):
5265 refresh timer on execute
5267 True if modal should run
5270 if self
._timer
is None:
5271 self
.update_state
= False
5272 self
.start_timer(context
)
5275 # already a timer running
5276 self
.stop_timer(context
)
5278 # prevent race conditions when already in update mode
5279 if self
.is_updating
:
5282 self
.start_timer(context
)
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
)
5294 def is_updating(self
):
5295 return self
.update_state
5298 throttle_handlers
= {}
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))
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]
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
]
5340 return {'PASS_THROUGH'}
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'}
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"
5372 def blacklist(self
):
5373 return ['n_parts', 'parts', 'manipulators', 'user_defined_path', 'quick_edit', 'draft']
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
)
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
)