Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'archipack/archipack_roof.py')
-rw-r--r--archipack/archipack_roof.py5376
1 files changed, 5376 insertions, 0 deletions
diff --git a/archipack/archipack_roof.py b/archipack/archipack_roof.py
new file mode 100644
index 00000000..43a63228
--- /dev/null
+++ b/archipack/archipack_roof.py
@@ -0,0 +1,5376 @@
+# -*- coding:utf-8 -*-
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+# ----------------------------------------------------------
+# Author: Stephen Leger (s-leger)
+#
+# ----------------------------------------------------------
+# noinspection PyUnresolvedReferences
+import bpy
+import time
+# noinspection PyUnresolvedReferences
+from bpy.types import Operator, PropertyGroup, Mesh, Panel
+from bpy.props import (
+ FloatProperty, BoolProperty, IntProperty,
+ StringProperty, EnumProperty,
+ CollectionProperty
+ )
+from .bmesh_utils import BmeshEdit as bmed
+from random import randint
+import bmesh
+from mathutils import Vector, Matrix
+from math import sin, cos, pi, atan2, sqrt, tan
+from .archipack_manipulator import Manipulable, archipack_manipulator
+from .archipack_2d import Line, Arc
+from .archipack_preset import ArchipackPreset, PresetMenuOperator
+from .archipack_object import ArchipackCreateTool, ArchipackObject
+from .archipack_cutter import (
+ CutAblePolygon, CutAbleGenerator,
+ ArchipackCutter,
+ ArchipackCutterPart
+ )
+
+
+class Roof():
+
+ def __init__(self):
+ self.angle_0 = 0
+ self.v0_idx = 0
+ self.v1_idx = 0
+ self.constraint_type = None
+ self.slope_left = 1
+ self.slope_right = 1
+ self.width_left = 1
+ self.width_right = 1
+ self.auto_left = 'AUTO'
+ self.auto_right = 'AUTO'
+ self.type = 'SIDE'
+ # force hip or valley
+ self.enforce_part = 'AUTO'
+ self.triangular_end = False
+ # seg is part of hole
+ self.is_hole = False
+
+ def copy_params(self, s):
+ s.angle_0 = self.angle_0
+ s.v0_idx = self.v0_idx
+ s.v1_idx = self.v1_idx
+ s.constraint_type = self.constraint_type
+ s.slope_left = self.slope_left
+ s.slope_right = self.slope_right
+ s.width_left = self.width_left
+ s.width_right = self.width_right
+ s.auto_left = self.auto_left
+ s.auto_right = self.auto_right
+ s.type = self.type
+ s.enforce_part = self.enforce_part
+ s.triangular_end = self.triangular_end
+ # segment is part of hole / slice
+ s.is_hole = self.is_hole
+
+ @property
+ def copy(self):
+ s = StraightRoof(self.p.copy(), self.v.copy())
+ self.copy_params(s)
+ return s
+
+ def straight(self, length, t=1):
+ s = self.copy
+ s.p = self.lerp(t)
+ s.v = self.v.normalized() * length
+ return s
+
+ def set_offset(self, offset, last=None):
+ """
+ Offset line and compute intersection point
+ between segments
+ """
+ self.line = self.make_offset(offset, last)
+
+ def offset(self, offset):
+ o = self.copy
+ o.p += offset * self.cross_z.normalized()
+ return o
+
+ @property
+ def oposite(self):
+ o = self.copy
+ o.p += o.v
+ o.v = -o.v
+ return o
+
+ @property
+ def t_diff(self):
+ return self.t_end - self.t_start
+
+ def straight_roof(self, a0, length):
+ s = self.straight(length).rotate(a0)
+ r = StraightRoof(s.p, s.v)
+ r.angle_0 = a0
+ return r
+
+ def curved_roof(self, a0, da, radius):
+ n = self.normal(1).rotate(a0).scale(radius)
+ if da < 0:
+ n.v = -n.v
+ c = n.p - n.v
+ r = CurvedRoof(c, radius, n.angle, da)
+ r.angle_0 = a0
+ return r
+
+
+class StraightRoof(Roof, Line):
+ def __str__(self):
+ return "p0:{} p1:{}".format(self.p0, self.p1)
+
+ def __init__(self, p, v):
+ Line.__init__(self, p, v)
+ Roof.__init__(self)
+
+
+class CurvedRoof(Roof, Arc):
+ def __str__(self):
+ return "t_start:{} t_end:{} dist:{}".format(self.t_start, self.t_end, self.dist)
+
+ def __init__(self, c, radius, a0, da):
+ Arc.__init__(self, c, radius, a0, da)
+ Roof.__init__(self)
+
+
+class RoofSegment():
+ """
+ Roof part with 2 polygons
+ and "axis" StraightRoof segment
+ """
+ def __init__(self, seg, left, right):
+ self.seg = seg
+ self.left = left
+ self.right = right
+ self.a0 = 0
+ self.reversed = False
+
+
+class RoofAxisNode():
+ """
+ Connection between parts
+ for radial analysis
+ """
+ def __init__(self):
+ # axis segments
+ self.segs = []
+ self.root = None
+ self.center = 0
+ # store count of horizontal segs
+ self.n_horizontal = 0
+ # store count of slopes segs
+ self.n_slope = 0
+
+ @property
+ def count(self):
+ return len(self.segs)
+
+ @property
+ def last(self):
+ """
+ last segments in this node
+ """
+ return self.segs[-1]
+
+ def left(self, index):
+ if index + 1 >= self.count:
+ return self.segs[0]
+ return self.segs[index + 1]
+
+ def right(self, index):
+ return self.segs[index - 1]
+
+ def add(self, a0, reversed, seg, left, right):
+
+ if seg.constraint_type == 'HORIZONTAL':
+ self.n_horizontal += 1
+ elif seg.constraint_type == 'SLOPE':
+ self.n_slope += 1
+
+ s = RoofSegment(seg, left, right)
+ s.a0 = a0
+ s.reversed = reversed
+ if reversed:
+ self.root = s
+ self.segs.append(s)
+
+ def update_center(self):
+ for i, s in enumerate(self.segs):
+ if s is self.root:
+ self.center = i
+ return
+
+ # sort tree segments by angle
+ def partition(self, array, begin, end):
+ pivot = begin
+ for i in range(begin + 1, end + 1):
+ if array[i].a0 < array[begin].a0:
+ pivot += 1
+ array[i], array[pivot] = array[pivot], array[i]
+ array[pivot], array[begin] = array[begin], array[pivot]
+ return pivot
+
+ def sort(self):
+ def _quicksort(array, begin, end):
+ if begin >= end:
+ return
+ pivot = self.partition(array, begin, end)
+ _quicksort(array, begin, pivot - 1)
+ _quicksort(array, pivot + 1, end)
+
+ end = len(self.segs) - 1
+ _quicksort(self.segs, 0, end)
+
+ # index of root in segs array
+ self.update_center()
+
+
+class RoofPolygon(CutAblePolygon):
+ """
+ ccw roof pitch boundary
+ closed by explicit segment
+ handle triangular shape with zero axis length
+
+ mov <_________________
+ | /\
+ | | rot
+ | | left last <> next
+ \/_____axis_______>|
+
+ node <_____axis________ next
+ | /\
+ | | rot
+ | | right last <> next
+ mov \/________________>|
+ side angle
+ """
+ def __init__(self, axis, side, fake_axis=None):
+ """
+ Create a default rectangle
+ axis from node to next
+ slope float -z for 1 in side direction
+ side in ['LEFT', 'RIGHT'] in axis direction
+
+ NOTE:
+ when axis length is null (eg: triangular shape)
+ use "fake_axis" with a 1 length to handle
+ distance from segment
+ """
+ if side == 'LEFT':
+ # slope
+ self.slope = axis.slope_left
+ # width
+ self.width = axis.width_left
+ # constraint width
+ self.auto_mode = axis.auto_left
+ else:
+ # slope
+ self.slope = axis.slope_right
+ # width
+ self.width = axis.width_right
+ # constraint width
+ self.auto_mode = axis.auto_right
+
+ self.side = side
+ # backward deps
+ self.backward = False
+ # pointers to neighboors along axis
+ self.last = None
+ self.next = None
+ self.other_side = None
+
+ # axis segment
+ if side == 'RIGHT':
+ self.axis = axis.oposite
+ else:
+ self.axis = axis
+
+ self.fake_axis = None
+
+ # _axis is either a fake one or real one
+ # to prevent further check
+ if fake_axis is None:
+ self._axis = self.axis
+ self.fake_axis = self.axis
+ self.next_cross = axis
+ self.last_cross = axis
+ else:
+ if side == 'RIGHT':
+ self.fake_axis = fake_axis.oposite
+ else:
+ self.fake_axis = fake_axis
+ self._axis = self.fake_axis
+
+ # unit vector perpendicular to axis
+ # looking at outside part
+ v = self.fake_axis.sized_normal(0, -1)
+ self.cross = v
+ self.next_cross = v
+ self.last_cross = v
+
+ self.convex = True
+ # segments from axis end in ccw order
+ # closed by explicit segment
+ self.segs = []
+ # holes
+ self.holes = []
+
+ # Triangular ends
+ self.node_tri = False
+ self.next_tri = False
+ self.is_tri = False
+
+ # sizes
+ self.tmin = 0
+ self.tmax = 1
+ self.dt = 1
+ self.ysize = 0
+ self.xsize = 0
+ self.vx = Vector()
+ self.vy = Vector()
+ self.vz = Vector()
+
+ def move_node(self, p):
+ """
+ Move slope point in node side
+ """
+ if self.side == 'LEFT':
+ self.segs[-1].p0 = p
+ self.segs[2].p1 = p
+ else:
+ self.segs[2].p0 = p
+ self.segs[1].p1 = p
+
+ def move_next(self, p):
+ """
+ Move slope point in next side
+ """
+ if self.side == 'LEFT':
+ self.segs[2].p0 = p
+ self.segs[1].p1 = p
+ else:
+ self.segs[-1].p0 = p
+ self.segs[2].p1 = p
+
+ def node_link(self, da):
+ angle_90 = round(pi / 2, 4)
+ if self.side == 'LEFT':
+ idx = -1
+ else:
+ idx = 1
+ da = abs(round(da, 4))
+ type = "LINK"
+ if da < angle_90:
+ type += "_VALLEY"
+ elif da > angle_90:
+ type += "_HIP"
+ self.segs[idx].type = type
+
+ def next_link(self, da):
+ angle_90 = round(pi / 2, 4)
+ if self.side == 'LEFT':
+ idx = 1
+ else:
+ idx = -1
+ da = abs(round(da, 4))
+ type = "LINK"
+ if da < angle_90:
+ type += "_VALLEY"
+ elif da > angle_90:
+ type += "_HIP"
+ self.segs[idx].type = type
+
+ def bind(self, last, ccw=False):
+ """
+ always in axis real direction
+ """
+ # backward dependancy relative to axis
+ if last.backward:
+ self.backward = self.side == last.side
+
+ if self.side == last.side:
+ last.next_cross = self.cross
+ else:
+ last.last_cross = self.cross
+
+ self.last_cross = last.cross
+
+ # axis of last / next segments
+ if self.backward:
+ self.next = last
+ last.last = self
+ else:
+ self.last = last
+ last.next = self
+
+ # width auto
+ if self.auto_mode == 'AUTO':
+ self.width = last.width
+ self.slope = last.slope
+ elif self.auto_mode == 'WIDTH' and self.width != 0:
+ self.slope = last.slope * last.width / self.width
+ elif self.auto_mode == 'SLOPE' and self.slope != 0:
+ self.width = last.width * last.slope / self.slope
+
+ self.make_segments()
+ last.make_segments()
+
+ res, p, t = self.segs[2].intersect(last.segs[2])
+
+ if res:
+ # dont move anything when no intersection found
+ # aka when delta angle == 0
+ self.move_node(p)
+ if self.side != last.side:
+ last.move_node(p)
+ else:
+ last.move_next(p)
+
+ # Free mode
+ # move border
+ # and find intersections
+ # with sides
+ if self.auto_mode == 'ALL':
+ s0 = self._axis.offset(-self.width)
+ res, p0, t = self.segs[1].intersect(s0)
+ if res:
+ self.segs[2].p0 = p0
+ self.segs[1].p1 = p0
+ res, p1, t = self.segs[-1].intersect(s0)
+ if res:
+ self.segs[2].p1 = p1
+ self.segs[-1].p0 = p1
+
+ # /\
+ # | angle
+ # |____>
+ #
+ # v1 node -> next
+ if self.side == 'LEFT':
+ v1 = self._axis.v
+ else:
+ v1 = -self._axis.v
+
+ if last.side == self.side:
+ # contigous, v0 node <- next
+
+ # half angle between segments
+ if self.side == 'LEFT':
+ v0 = -last._axis.v
+ else:
+ v0 = last._axis.v
+ da = v0.angle_signed(v1)
+ if ccw:
+ if da < 0:
+ da = 2 * pi + da
+ elif da > 0:
+ da = da - 2 * pi
+ last.next_link(0.5 * da)
+
+ else:
+ # alternate v0 node -> next
+ # half angle between segments
+ if last.side == 'LEFT':
+ v0 = last._axis.v
+ else:
+ v0 = -last._axis.v
+ da = v0.angle_signed(v1)
+ # angle always ccw
+ if ccw:
+ if da < 0:
+ da = 2 * pi + da
+ elif da > 0:
+ da = da - 2 * pi
+ last.node_link(0.5 * da)
+
+ self.node_link(-0.5 * da)
+
+ def next_seg(self, index):
+ idx = self.get_index(index + 1)
+ return self.segs[idx]
+
+ def last_seg(self, index):
+ return self.segs[index - 1]
+
+ def make_segments(self):
+ if len(self.segs) < 1:
+ s0 = self._axis
+ w = self.width
+ s1 = s0.straight(w, 1).rotate(pi / 2)
+ s1.type = 'SIDE'
+ s3 = s0.straight(w, 0).rotate(pi / 2).oposite
+ s3.type = 'SIDE'
+ s2 = StraightRoof(s1.p1, s3.p0 - s1.p1)
+ s2.type = 'BOTTOM'
+ self.segs = [s0, s1, s2, s3]
+
+ def move_side(self, pt):
+ """
+ offset side to point
+ """
+ s2 = self.segs[2]
+ d0, t = self.distance(s2.p0)
+ d1, t = self.distance(pt)
+ # adjust width and slope according
+ self.width = d1
+ self.slope = self.slope * d0 / d1
+ self.segs[2] = s2.offset(d1 - d0)
+
+ def propagate_backward(self, pt):
+ """
+ Propagate slope, keep 2d angle of slope
+ Move first point and border
+ keep border parallel
+ adjust slope
+ and next shape
+ """
+ # distance of p
+ # offset side to point
+ self.move_side(pt)
+
+ # move verts on node side
+ self.move_next(pt)
+
+ if self.side == 'LEFT':
+ # move verts on next side
+ res, p, t = self.segs[-1].intersect(self.segs[2])
+ else:
+ # move verts on next side
+ res, p, t = self.segs[1].intersect(self.segs[2])
+
+ if res:
+ self.move_node(p)
+
+ if self.next is not None and self.next.auto_mode in {'AUTO'}:
+ self.next.propagate_backward(p)
+
+ def propagate_forward(self, pt):
+ """
+ Propagate slope, keep 2d angle of slope
+ Move first point and border
+ keep border parallel
+ adjust slope
+ and next shape
+ """
+ # offset side to point
+ self.move_side(pt)
+
+ # move verts on node side
+ self.move_node(pt)
+ if self.side == 'LEFT':
+ # move verts on next side
+ res, p, t = self.segs[1].intersect(self.segs[2])
+ else:
+ # move verts on next side
+ res, p, t = self.segs[-1].intersect(self.segs[2])
+
+ if res:
+ self.move_next(p)
+ if self.next is not None and self.next.auto_mode in {'AUTO'}:
+ self.next.propagate_forward(p)
+
+ def rotate_next_slope(self, a0):
+ """
+ Rotate next slope part
+ """
+ if self.side == 'LEFT':
+ s0 = self.segs[1].rotate(a0)
+ s1 = self.segs[2]
+ res, p, t = s1.intersect(s0)
+ else:
+ s0 = self.segs[2]
+ s1 = self.segs[-1]
+ res, p, t = s1.oposite.rotate(-a0).intersect(s0)
+
+ if res:
+ s1.p0 = p
+ s0.p1 = p
+
+ if self.next is not None:
+ if self.next.auto_mode == 'ALL':
+ return
+ if self.next.backward:
+ self.next.propagate_backward(p)
+ else:
+ self.next.propagate_forward(p)
+
+ def rotate_node_slope(self, a0):
+ """
+ Rotate node slope part
+ """
+ if self.side == 'LEFT':
+ s0 = self.segs[2]
+ s1 = self.segs[-1]
+ res, p, t = s1.oposite.rotate(-a0).intersect(s0)
+ else:
+ s0 = self.segs[1].rotate(a0)
+ s1 = self.segs[2]
+ res, p, t = s1.intersect(s0)
+
+ if res:
+ s1.p0 = p
+ s0.p1 = p
+
+ if self.next is not None:
+ if self.next.auto_mode == 'ALL':
+ return
+ if self.next.backward:
+ self.next.propagate_backward(p)
+ else:
+ self.next.propagate_forward(p)
+
+ def distance(self, pt):
+ """
+ distance from axis
+ always use fake_axis here to
+ allow axis being cut and
+ still work
+ """
+ res, d, t = self.fake_axis.point_sur_segment(pt)
+ return d, t
+
+ def altitude(self, pt):
+ d, t = self.distance(pt)
+ return -d * self.slope
+
+ def uv(self, pt):
+ d, t = self.distance(pt)
+ return ((t - self.tmin) * self.xsize, d)
+
+ def intersect(self, seg):
+ """
+ compute intersections of a segment with boundaries
+ segment must start on axis
+ return segments inside
+ """
+ it = []
+ for s in self.segs:
+ res, p, t, u = seg.intersect_ext(s)
+ if res:
+ it.append((t, p))
+ return it
+
+ def merge(self, other):
+
+ raise NotImplementedError
+
+ def draw(self, context, z, verts, edges):
+ f = len(verts)
+ #
+ # 0_______1
+ # |_______|
+ # 3 2
+ verts.extend([(s.p0.x, s.p0.y, z + self.altitude(s.p0)) for s in self.segs])
+ n_segs = len(self.segs) - 1
+ edges.extend([[f + i, f + i + 1] for i in range(n_segs)])
+ edges.append([f + n_segs, f])
+ """
+ f = len(verts)
+ verts.extend([(s.p1.x, s.p1.y, z + self.altitude(s.p1)) for s in self.segs])
+ n_segs = len(self.segs) - 1
+ edges.extend([[f + i, f + i + 1] for i in range(n_segs)])
+ edges.append([f + n_segs, f])
+ """
+ # holes
+ for hole in self.holes:
+ f = len(verts)
+ #
+ # 0_______1
+ # |_______|
+ # 3 2
+ verts.extend([(s.p0.x, s.p0.y, z + self.altitude(s.p0)) for s in hole.segs])
+ n_segs = len(hole.segs) - 1
+ edges.extend([[f + i, f + i + 1] for i in range(n_segs)])
+ edges.append([f + n_segs, f])
+
+ # axis
+ """
+ f = len(verts)
+ verts.extend([self.axis.p0.to_3d(), self.axis.p1.to_3d()])
+ edges.append([f, f + 1])
+
+ # cross
+ f = len(verts)
+ verts.extend([self.axis.lerp(0.5).to_3d(), (self.axis.lerp(0.5) + self.cross.v).to_3d()])
+ edges.append([f, f + 1])
+ """
+
+ # relationships arrows
+ if self.next or self.last:
+ w = 0.2
+ s0 = self._axis.offset(-0.5 * self.ysize)
+ p0 = s0.lerp(0.4).to_3d()
+ p0.z = z
+ p1 = s0.lerp(0.6).to_3d()
+ p1.z = z
+ if self.side == 'RIGHT':
+ p0, p1 = p1, p0
+ if self.backward:
+ p0, p1 = p1, p0
+ s1 = s0.sized_normal(0.5, w)
+ s2 = s0.sized_normal(0.5, -w)
+ f = len(verts)
+ p2 = s1.p1.to_3d()
+ p2.z = z
+ p3 = s2.p1.to_3d()
+ p3.z = z
+ verts.extend([p1, p0, p2, p3])
+ edges.extend([[f + 1, f], [f + 2, f], [f + 3, f]])
+
+ def as_string(self):
+ """
+ Print strips relationships
+ """
+ if self.backward:
+ dir = "/\\"
+ print("%s next" % (dir))
+ else:
+ dir = "\\/"
+ print("%s node" % (dir))
+ print("%s %s" % (dir, self.side))
+ if self.backward:
+ print("%s node" % (dir))
+ else:
+ print("%s next" % (dir))
+ if self.next:
+ print("_________")
+ self.next.as_string()
+ else:
+ print("#########")
+
+ def limits(self):
+ dist = []
+ param_t = []
+ for s in self.segs:
+ res, d, t = self.fake_axis.point_sur_segment(s.p0)
+ param_t.append(t)
+ dist.append(d)
+
+ if len(param_t) > 0:
+ self.tmin = min(param_t)
+ self.tmax = max(param_t)
+ else:
+ self.tmin = 0
+ self.tmax = 1
+
+ self.dt = self.tmax - self.tmin
+
+ if len(dist) > 0:
+ self.ysize = max(dist)
+ else:
+ self.ysize = 0
+
+ self.xsize = self.fake_axis.length * self.dt
+ # vectors components of part matrix
+ # where x is is axis direction
+ # y down
+ # z up
+ vx = -self.fake_axis.v.normalized().to_3d()
+ vy = Vector((-vx.y, vx.x, self.slope)).normalized()
+ self.vx = vx
+ self.vy = vy
+ self.vz = vx.cross(vy)
+
+
+"""
+import bmesh
+m = C.object.data
+[(round(v.co.x, 3), round(v.co.y, 3), round(v.co.z, 3)) for v in m.vertices]
+[tuple(p.vertices) for p in m.polygons]
+
+uvs = []
+bpy.ops.object.mode_set(mode='EDIT')
+bm = bmesh.from_edit_mesh(m)
+[tuple(i.index for i in edge.verts) for edge in bm.edges]
+
+layer = bm.loops.layers.uv.verify()
+for i, face in enumerate(bm.faces):
+ uv = []
+ for j, loop in enumerate(face.loops):
+ co = loop[layer].uv
+ uv.append((round(co.x, 2), round(co.y, 2)))
+ uvs.append(uv)
+uvs
+"""
+
+
+class RoofGenerator(CutAbleGenerator):
+
+ def __init__(self, d, origin=Vector((0, 0, 0))):
+ self.parts = d.parts
+ self.segs = []
+ self.nodes = []
+ self.pans = []
+ self.length = 0
+ self.origin = origin.to_2d()
+ self.z = origin.z
+ self.width_right = d.width_right
+ self.width_left = d.width_left
+ self.slope_left = d.slope_left
+ self.slope_right = d.slope_right
+ self.user_defined_tile = None
+ self.user_defined_uvs = None
+ self.user_defined_mat = None
+ self.is_t_child = d.t_parent != ""
+
+ def add_part(self, part):
+
+ if len(self.segs) < 1 or part.bound_idx < 1:
+ s = None
+ else:
+ s = self.segs[part.bound_idx - 1]
+
+ a0 = part.a0
+
+ if part.constraint_type == 'SLOPE' and a0 == 0:
+ a0 = 90
+
+ # start a new roof
+ if s is None:
+ v = part.length * Vector((cos(a0), sin(a0)))
+ s = StraightRoof(self.origin, v)
+ else:
+ s = s.straight_roof(a0, part.length)
+
+ # parent segment (root) index is v0_idx - 1
+ s.v0_idx = min(len(self.segs), part.bound_idx)
+
+ s.constraint_type = part.constraint_type
+
+ if part.constraint_type == 'SLOPE':
+ s.enforce_part = part.enforce_part
+ else:
+ s.enforce_part = 'AUTO'
+
+ s.angle_0 = a0
+ s.take_precedence = part.take_precedence
+ s.auto_right = part.auto_right
+ s.auto_left = part.auto_left
+ s.width_left = part.width_left
+ s.width_right = part.width_right
+ s.slope_left = part.slope_left
+ s.slope_right = part.slope_right
+ s.type = 'AXIS'
+ s.triangular_end = part.triangular_end
+ self.segs.append(s)
+
+ def locate_manipulators(self):
+ """
+
+ """
+ for i, f in enumerate(self.segs):
+
+ manipulators = self.parts[i].manipulators
+ p0 = f.p0.to_3d()
+ p0.z = self.z
+ p1 = f.p1.to_3d()
+ p1.z = self.z
+ # angle from last to current segment
+ if i > 0:
+
+ manipulators[0].type_key = 'ANGLE'
+ v0 = self.segs[f.v0_idx - 1].straight(-1, 1).v.to_3d()
+ v1 = f.straight(1, 0).v.to_3d()
+ manipulators[0].set_pts([p0, v0, v1])
+
+ # segment length
+ manipulators[1].type_key = 'SIZE'
+ manipulators[1].prop1_name = "length"
+ manipulators[1].set_pts([p0, p1, (1.0, 0, 0)])
+
+ # dumb segment id
+ manipulators[2].set_pts([p0, p1, (1, 0, 0)])
+
+ p0 = f.lerp(0.5).to_3d()
+ p0.z = self.z
+ # size left
+ p1 = f.sized_normal(0.5, -self.parts[i].width_left).p1.to_3d()
+ p1.z = self.z
+ manipulators[3].set_pts([p0, p1, (1, 0, 0)])
+
+ # size right
+ p1 = f.sized_normal(0.5, self.parts[i].width_right).p1.to_3d()
+ p1.z = self.z
+ manipulators[4].set_pts([p0, p1, (-1, 0, 0)])
+
+ # slope left
+ n0 = f.sized_normal(0.5, -1)
+ p0 = n0.p1.to_3d()
+ p0.z = self.z
+ p1 = p0.copy()
+ p1.z = self.z - self.parts[i].slope_left
+ manipulators[5].set_pts([p0, p1, (-1, 0, 0)], normal=n0.v.to_3d())
+
+ # slope right
+ n0 = f.sized_normal(0.5, 1)
+ p0 = n0.p1.to_3d()
+ p0.z = self.z
+ p1 = p0.copy()
+ p1.z = self.z - self.parts[i].slope_right
+ manipulators[6].set_pts([p0, p1, (1, 0, 0)], normal=n0.v.to_3d())
+
+ def seg_partition(self, array, begin, end):
+ """
+ sort tree segments by angle
+ """
+ pivot = begin
+ for i in range(begin + 1, end + 1):
+ if array[i].a0 < array[begin].a0:
+ pivot += 1
+ array[i], array[pivot] = array[pivot], array[i]
+ array[pivot], array[begin] = array[begin], array[pivot]
+ return pivot
+
+ def sort_seg(self, array, begin=0, end=None):
+ # print("sort_child")
+ if end is None:
+ end = len(array) - 1
+
+ def _quicksort(array, begin, end):
+ if begin >= end:
+ return
+ pivot = self.seg_partition(array, begin, end)
+ _quicksort(array, begin, pivot - 1)
+ _quicksort(array, pivot + 1, end)
+ return _quicksort(array, begin, end)
+
+ def make_roof(self, context):
+ """
+ Init data structure for possibly multi branched nodes
+ nodes : radial relationships
+ pans : quad strip linear relationships
+ """
+
+ pans = []
+
+ # node are connected segments
+ # node
+ # (segment idx)
+ # (angle from root part > 0 right)
+ # (reversed) a seg connected by p1
+ # "root" of node
+ nodes = [RoofAxisNode() for s in range(len(self.segs) + 1)]
+
+ # Init width on seg 0
+ s0 = self.segs[0]
+ if self.parts[0].auto_left in {'AUTO', 'SLOPE'}:
+ s0.width_left = self.width_left
+ if self.parts[0].auto_right in {'AUTO', 'SLOPE'}:
+ s0.width_right = self.width_right
+ if self.parts[0].auto_left in {'AUTO', 'WIDTH'}:
+ s0.slope_left = self.slope_left
+ if self.parts[0].auto_left in {'AUTO', 'WIDTH'}:
+ s0.slope_right = self.slope_right
+
+ # make nodes with HORIZONTAL constraints
+ for idx, s in enumerate(self.segs):
+ s.v1_idx = idx + 1
+ if s.constraint_type == 'HORIZONTAL':
+ left = RoofPolygon(s, 'LEFT')
+ right = RoofPolygon(s, 'RIGHT')
+ left.other_side = right
+ right.other_side = left
+ rs = RoofSegment(s, left, right)
+ pans.append(rs)
+ nodes[s.v0_idx].add(s.angle_0, False, s, left, right)
+ nodes[s.v1_idx].add(-pi, True, s, left, right)
+
+ # set first node root
+ # so regular sort does work
+ nodes[0].root = nodes[0].segs[0]
+ self.nodes = nodes
+ # Propagate slope and width
+ # on node basis along axis
+ # bi-direction Radial around node
+ # from left and right to center
+ # contigous -> same
+ # T: and (x % 2 == 1)
+ # First one take precedence over others
+ # others inherit from side
+ #
+ # l / rb l = left
+ # 3 r = right
+ # l _1_ / b = backward
+ # r \
+ # 2
+ # r\ l
+ #
+ # X: rigth one r left one l (x % 2 == 0)
+ # inherits from side
+ #
+ # l 3 lb l = left
+ # l__1_|_2_l r = right
+ # r | r b = backward -> propagate in reverse axis direction
+ # r 4 rb
+ #
+ # for idx, node in enumerate(nodes):
+ # print("idx:%s node:%s" % (idx, node.root))
+
+ for idx, node in enumerate(nodes):
+
+ node.sort()
+
+ nb_segs = node.count
+
+ if node.root is None:
+ continue
+
+ left = node.root.left
+ right = node.root.right
+
+ # basic one single node
+ if nb_segs < 2:
+ left.make_segments()
+ right.make_segments()
+ continue
+
+ # get "root" slope and width
+ l_bind = left
+ r_bind = right
+
+ # simple case: 2 contigous segments
+ if nb_segs == 2:
+ s = node.last
+ s.right.bind(r_bind, ccw=False)
+ s.left.bind(l_bind, ccw=True)
+ continue
+
+ # More than 2 segments, uneven distribution
+ if nb_segs % 2 == 1:
+ # find wich child does take precedence
+ # first one on rootline (arbitrary)
+ center = (nb_segs - 1) / 2
+ else:
+ # even distribution
+ center = nb_segs / 2
+
+ # user defined precedence if any
+ for i, s in enumerate(node.segs):
+ if s.seg.take_precedence:
+ center = i
+ break
+
+ # bind right side to center
+ for i, s in enumerate(node.segs):
+ # skip axis
+ if i > 0:
+ if i < center:
+ # right contigous with last
+ s.right.bind(r_bind, ccw=False)
+
+ # next bind to left
+ r_bind = s.left
+
+ # left backward, not bound
+ # so setup width and slope
+ if s.left.auto_mode in {'AUTO', 'WIDTH'}:
+ s.left.slope = right.slope
+ if s.left.auto_mode in {'AUTO', 'SLOPE'}:
+ s.left.width = right.width
+ s.left.backward = True
+ else:
+ # right bound to last
+ s.right.bind(r_bind, ccw=False)
+ break
+
+ # bind left side to center
+ for i, s in enumerate(reversed(node.segs)):
+ # skip axis
+ if i < nb_segs - center - 1:
+ # left contigous with last
+ s.left.bind(l_bind, ccw=True)
+ # next bind to right
+ l_bind = s.right
+ # right backward, not bound
+ # so setup width and slope
+ if s.right.auto_mode in {'AUTO', 'WIDTH'}:
+ s.right.slope = left.slope
+ if s.right.auto_mode in {'AUTO', 'SLOPE'}:
+ s.right.width = left.width
+ s.right.backward = True
+ else:
+ # right bound to last
+ s.left.bind(l_bind, ccw=True)
+ break
+
+ # slope constraints allowed between segments
+ # multiple (up to 2) on start and end
+ # single between others
+ #
+ # 2 slope 2 slope 2 slope
+ # | | |
+ # |______section_1___|___section_2_____|
+ # | | |
+ # | | |
+ # multiple single multiple
+
+ # add slopes constraints to nodes
+ for i, s in enumerate(self.segs):
+ if s.constraint_type == 'SLOPE':
+ nodes[s.v0_idx].add(s.angle_0, False, s, None, None)
+
+ # sort nodes, remove duplicate slopes between
+ # horizontal, keeping only first one
+ for idx, node in enumerate(nodes):
+ to_remove = []
+ node.sort()
+ # remove dup between all
+ # but start / end nodes
+ if node.n_horizontal > 1:
+ last = None
+ for i, s in enumerate(node.segs):
+ if s.seg.constraint_type == last:
+ if s.seg.constraint_type == 'SLOPE':
+ to_remove.append(i)
+ last = s.seg.constraint_type
+ for i in reversed(to_remove):
+ node.segs.pop(i)
+ node.update_center()
+
+ for idx, node in enumerate(nodes):
+
+ # a node may contain many slopes
+ # 2 * (part starting from node - 1)
+ #
+ # s0
+ # root 0 |_______
+ # |
+ # s1
+ #
+ # s1
+ # root _______|
+ # |
+ # s0
+ #
+ # s3 3 s2
+ # l \l|r/ l
+ # root ___\|/___ 2
+ # r /|\ r
+ # /r|l\
+ # s0 1 s1
+ #
+ # s2 s1=slope
+ # |r /
+ # | / l
+ # |/____s
+ #
+ # root to first child -> equal side
+ # any other childs -> oposite sides
+
+ if node.n_horizontal == 1:
+ # slopes at start or end of segment
+ # segment slope is not affected
+ if node.n_slope > 0:
+ # node has user def slope
+ s = node.root
+ s0 = node.left(node.center)
+ a0 = s0.seg.delta_angle(s.seg)
+ if node.root.reversed:
+ # slope at end of segment
+ # first one is right or left
+ if a0 < 0:
+ # right side
+ res, p, t = s0.seg.intersect(s.right.segs[2])
+ s.right.segs[-1].p0 = p
+ s.right.segs[2].p1 = p
+ else:
+ # left side
+ res, p, t = s0.seg.intersect(s.left.segs[2])
+ s.left.segs[1].p1 = p
+ s.left.segs[2].p0 = p
+ if node.n_slope > 1:
+ # last one must be left
+ s1 = node.right(node.center)
+ a1 = s1.seg.delta_angle(s.seg)
+ # both slopes on same side:
+ # skip this one
+ if a0 > 0 and a1 < 0:
+ # right side
+ res, p, t = s1.seg.intersect(s.right.segs[2])
+ s.right.segs[-1].p0 = p
+ s.right.segs[2].p1 = p
+ if a0 < 0 and a1 > 0:
+ # left side
+ res, p, t = s1.seg.intersect(s.left.segs[2])
+ s.left.segs[1].p1 = p
+ s.left.segs[2].p0 = p
+
+ else:
+ # slope at start of segment
+ if a0 < 0:
+ # right side
+ res, p, t = s0.seg.intersect(s.right.segs[2])
+ s.right.segs[1].p1 = p
+ s.right.segs[2].p0 = p
+ else:
+ # left side
+ res, p, t = s0.seg.intersect(s.left.segs[2])
+ s.left.segs[-1].p0 = p
+ s.left.segs[2].p1 = p
+ if node.n_slope > 1:
+ # last one must be right
+ s1 = node.right(node.center)
+ a1 = s1.seg.delta_angle(s.seg)
+ # both slopes on same side:
+ # skip this one
+ if a0 > 0 and a1 < 0:
+ # right side
+ res, p, t = s1.seg.intersect(s.right.segs[2])
+ s.right.segs[1].p1 = p
+ s.right.segs[2].p0 = p
+ if a0 < 0 and a1 > 0:
+ # left side
+ res, p, t = s1.seg.intersect(s.left.segs[2])
+ s.left.segs[-1].p0 = p
+ s.left.segs[2].p1 = p
+
+ else:
+ # slopes between segments
+ # does change next segment slope
+ for i, s0 in enumerate(node.segs):
+ s1 = node.left(i)
+ s2 = node.left(i + 1)
+
+ if s1.seg.constraint_type == 'SLOPE':
+
+ # 3 cases:
+ # s0 is root contigous -> sides are same
+ # s2 is root contigous -> sides are same
+ # back to back -> sides are not same
+
+ if s0.reversed:
+ # contigous right / right
+ # 2 cases
+ # right is backward
+ # right is forward
+ if s2.right.backward:
+ # s0 depends on s2
+ main = s2.right
+ v = main.segs[1].v
+ else:
+ # s2 depends on s0
+ main = s0.right
+ v = -main.segs[-1].v
+ res, p, t = s1.seg.intersect(main.segs[2])
+ if res:
+ # slope vector
+ dp = p - s1.seg.p0
+ a0 = dp.angle_signed(v)
+ if s2.right.backward:
+ main.rotate_node_slope(a0)
+ else:
+ main.rotate_next_slope(-a0)
+ elif s2.reversed:
+ # contigous left / left
+ # 2 cases
+ # left is backward
+ # left is forward
+ if s0.left.backward:
+ # s0 depends on s2
+ main = s0.left
+ v = -main.segs[-1].v
+ else:
+ # s2 depends on s0
+ main = s2.left
+ v = main.segs[1].v
+ res, p, t = s1.seg.intersect(main.segs[2])
+ if res:
+ # slope vector
+ dp = p - s1.seg.p0
+ a0 = dp.angle_signed(v)
+ if s0.left.backward:
+ main.rotate_node_slope(-a0)
+ else:
+ main.rotate_next_slope(a0)
+ else:
+ # back left / right
+ # 2 cases
+ # left is backward
+ # left is forward
+ if s0.left.backward:
+ # s2 depends on s0
+ main = s0.left
+ v = -main.segs[-1].v
+ else:
+ # s0 depends on s2
+ main = s2.right
+ v = main.segs[1].v
+
+ res, p, t = s1.seg.intersect(main.segs[2])
+ if res:
+ # slope vector
+ dp = p - s1.seg.p0
+ a0 = dp.angle_signed(v)
+ if s0.left.backward:
+ main.rotate_node_slope(-a0)
+ else:
+ main.rotate_node_slope(a0)
+
+ self.pans = []
+
+ # triangular ends
+ for node in self.nodes:
+ if node.n_horizontal == 1 and node.root.seg.triangular_end:
+ if node.root.reversed:
+ # Next side (segment end)
+ left = node.root.left
+ right = node.root.right
+ left.next_tri = True
+ right.next_tri = True
+
+ s0 = left.segs[1]
+ s1 = left.segs[2]
+ s2 = right.segs[-1]
+ s3 = right.segs[2]
+ p0 = s1.lerp(-left.width / s1.length)
+ p1 = s0.p0
+ p2 = s3.lerp(1 + right.width / s3.length)
+
+ # compute slope from points
+ p3 = p0.to_3d()
+ p3.z = -left.width * left.slope
+ p4 = p1.to_3d()
+ p5 = p2.to_3d()
+ p5.z = -right.width * right.slope
+ n = (p3 - p4).normalized().cross((p5 - p4).normalized())
+ v = n.cross(Vector((0, 0, 1)))
+ dz = n.cross(v)
+
+ # compute axis
+ s = StraightRoof(p1, v)
+ res, d0, t = s.point_sur_segment(p0)
+ res, d1, t = s.point_sur_segment(p2)
+ p = RoofPolygon(s, 'RIGHT')
+ p.make_segments()
+ p.slope = -dz.z / dz.to_2d().length
+ p.is_tri = True
+
+ p.cross = StraightRoof(p1, (p2 - p0)).sized_normal(0, -1)
+ p.next_cross = left.cross
+ p.last_cross = right.cross
+ right.next_cross = p.cross
+ left.next_cross = p.cross
+
+ # remove axis seg of tri
+ p.segs[-1].p0 = p0
+ p.segs[-1].p1 = p1
+ p.segs[2].p0 = p2
+ p.segs[2].p1 = p0
+ p.segs[1].p1 = p2
+ p.segs[1].p0 = p1
+ p.segs[1].type = 'LINK_HIP'
+ p.segs[-1].type = 'LINK_HIP'
+ p.segs.pop(0)
+ # adjust left and side borders
+ s0.p1 = p0
+ s1.p0 = p0
+ s2.p0 = p2
+ s3.p1 = p2
+ s0.type = 'LINK_HIP'
+ s2.type = 'LINK_HIP'
+ self.pans.append(p)
+
+ elif not self.is_t_child:
+ # no triangular part with t_child
+ # on "node" parent roof side
+ left = node.root.left
+ right = node.root.right
+ left.node_tri = True
+ right.node_tri = True
+ s0 = right.segs[1]
+ s1 = right.segs[2]
+ s2 = left.segs[-1]
+ s3 = left.segs[2]
+ p0 = s1.lerp(-right.width / s1.length)
+ p1 = s0.p0
+ p2 = s3.lerp(1 + left.width / s3.length)
+
+ # compute axis and slope from points
+ p3 = p0.to_3d()
+ p3.z = -right.width * right.slope
+ p4 = p1.to_3d()
+ p5 = p2.to_3d()
+ p5.z = -left.width * left.slope
+ n = (p3 - p4).normalized().cross((p5 - p4).normalized())
+ v = n.cross(Vector((0, 0, 1)))
+ dz = n.cross(v)
+
+ s = StraightRoof(p1, v)
+ p = RoofPolygon(s, 'RIGHT')
+ p.make_segments()
+ p.slope = -dz.z / dz.to_2d().length
+ p.is_tri = True
+
+ p.cross = StraightRoof(p1, (p2 - p0)).sized_normal(0, -1)
+ p.next_cross = right.cross
+ p.last_cross = left.cross
+ right.last_cross = p.cross
+ left.last_cross = p.cross
+
+ # remove axis seg of tri
+ p.segs[-1].p0 = p0
+ p.segs[-1].p1 = p1
+ p.segs[2].p0 = p2
+ p.segs[2].p1 = p0
+ p.segs[1].p1 = p2
+ p.segs[1].p0 = p1
+ p.segs[1].type = 'LINK_HIP'
+ p.segs[-1].type = 'LINK_HIP'
+ p.segs.pop(0)
+ # adjust left and side borders
+ s0.p1 = p0
+ s1.p0 = p0
+ s2.p0 = p2
+ s3.p1 = p2
+ s0.type = 'LINK_HIP'
+ s2.type = 'LINK_HIP'
+ self.pans.append(p)
+
+ # make flat array
+ for pan in pans:
+ self.pans.extend([pan.left, pan.right])
+
+ # merge contigous with 0 angle diff
+ to_remove = []
+ for i, pan in enumerate(self.pans):
+ if pan.backward:
+ next = pan.last
+ if next is not None:
+ # same side only can merge
+ if next.side == pan.side:
+ if round(next._axis.delta_angle(pan._axis), 4) == 0:
+ to_remove.append(i)
+ next.next = pan.next
+ next.last_cross = pan.last_cross
+ next.node_tri = pan.node_tri
+
+ next.slope = pan.slope
+ if pan.side == 'RIGHT':
+ if next.backward:
+ next._axis.p1 = pan._axis.p1
+ next.segs[1] = pan.segs[1]
+ next.segs[2].p0 = pan.segs[2].p0
+ else:
+ next._axis.p0 = pan._axis.p0
+ next.segs[-1] = pan.segs[-1]
+ next.segs[2].p1 = pan.segs[2].p1
+ else:
+ if next.backward:
+ next._axis.p0 = pan._axis.p0
+ next.segs[-1] = pan.segs[-1]
+ next.segs[2].p1 = pan.segs[2].p1
+ else:
+ next._axis.p1 = pan._axis.p1
+ next.segs[1] = pan.segs[1]
+ next.segs[2].p0 = pan.segs[2].p0
+ else:
+ next = pan.next
+ if next is not None:
+ # same side only can merge
+ if next.side == pan.side:
+ if round(next._axis.delta_angle(pan._axis), 4) == 0:
+ to_remove.append(i)
+ next.last = pan.last
+ next.last_cross = pan.last_cross
+ next.node_tri = pan.node_tri
+
+ next.slope = pan.slope
+ if pan.side == 'LEFT':
+ if next.backward:
+ next._axis.p1 = pan._axis.p1
+ next.segs[1] = pan.segs[1]
+ next.segs[2].p0 = pan.segs[2].p0
+ else:
+ next._axis.p0 = pan._axis.p0
+ next.segs[-1] = pan.segs[-1]
+ next.segs[2].p1 = pan.segs[2].p1
+ else:
+ if next.backward:
+ next._axis.p0 = pan._axis.p0
+ next.segs[-1] = pan.segs[-1]
+ next.segs[2].p1 = pan.segs[2].p1
+ else:
+ next._axis.p1 = pan._axis.p1
+ next.segs[1] = pan.segs[1]
+ next.segs[2].p0 = pan.segs[2].p0
+
+ for i in reversed(to_remove):
+ self.pans.pop(i)
+
+ # compute limits
+ for pan in self.pans:
+ pan.limits()
+
+ """
+ for pan in self.pans:
+ if pan.last is None:
+ pan.as_string()
+ """
+ return
+
+ def lambris(self, context, o, d):
+
+ idmat = 0
+ lambris_height = 0.02
+ alt = self.z - lambris_height
+ for pan in self.pans:
+
+ verts = []
+ faces = []
+ matids = []
+ uvs = []
+
+ f = len(verts)
+ verts.extend([(s.p0.x, s.p0.y, alt + pan.altitude(s.p0)) for s in pan.segs])
+ uvs.append([pan.uv(s.p0) for s in pan.segs])
+ n_segs = len(pan.segs)
+ face = [f + i for i in range(n_segs)]
+ faces.append(face)
+ matids.append(idmat)
+
+ bm = bmed.buildmesh(
+ context, o, verts, faces, matids=matids, uvs=uvs,
+ weld=False, clean=False, auto_smooth=True, temporary=True)
+
+ self.cut_holes(bm, pan)
+
+ bmesh.ops.dissolve_limit(bm,
+ angle_limit=0.01,
+ use_dissolve_boundaries=False,
+ verts=bm.verts,
+ edges=bm.edges,
+ delimit=1)
+
+ geom = bm.faces[:]
+ verts = bm.verts[:]
+ bmesh.ops.solidify(bm, geom=geom, thickness=0.0001)
+ bmesh.ops.translate(bm, vec=Vector((0, 0, lambris_height)), space=o.matrix_world, verts=verts)
+
+ # merge with object
+ bmed.bmesh_join(context, o, [bm], normal_update=True)
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ def couverture(self, context, o, d):
+
+ idmat = 7
+ rand = 3
+
+ sx, sy, sz = d.tile_size_x, d.tile_size_y, d.tile_size_z
+
+ """
+ /* Bevel offset_type slot values */
+ enum {
+ BEVEL_AMT_OFFSET,
+ BEVEL_AMT_WIDTH,
+ BEVEL_AMT_DEPTH,
+ BEVEL_AMT_PERCENT
+ };
+ """
+ offset_type = 3
+
+ if d.tile_offset > 0:
+ offset = - d.tile_offset / 100
+ else:
+ offset = 0
+
+ if d.tile_model == 'BRAAS2':
+ t_pts = [Vector(p) for p in [
+ (0.06, -1.0, 1.0), (0.19, -1.0, 0.5), (0.31, -1.0, 0.5), (0.44, -1.0, 1.0),
+ (0.56, -1.0, 1.0), (0.69, -1.0, 0.5), (0.81, -1.0, 0.5), (0.94, -1.0, 1.0),
+ (0.06, 0.0, 0.5), (0.19, 0.0, 0.0), (0.31, 0.0, 0.0), (0.44, 0.0, 0.5),
+ (0.56, 0.0, 0.5), (0.69, 0.0, 0.0), (0.81, 0.0, 0.0), (0.94, 0.0, 0.5),
+ (-0.0, -1.0, 1.0), (-0.0, 0.0, 0.5), (1.0, -1.0, 1.0), (1.0, 0.0, 0.5)]]
+ t_faces = [
+ (16, 0, 8, 17), (0, 1, 9, 8), (1, 2, 10, 9), (2, 3, 11, 10),
+ (3, 4, 12, 11), (4, 5, 13, 12), (5, 6, 14, 13), (6, 7, 15, 14), (7, 18, 19, 15)]
+ elif d.tile_model == 'BRAAS1':
+ t_pts = [Vector(p) for p in [
+ (0.1, -1.0, 1.0), (0.2, -1.0, 0.5), (0.6, -1.0, 0.5), (0.7, -1.0, 1.0),
+ (0.1, 0.0, 0.5), (0.2, 0.0, 0.0), (0.6, 0.0, 0.0), (0.7, 0.0, 0.5),
+ (-0.0, -1.0, 1.0), (-0.0, 0.0, 0.5), (1.0, -1.0, 1.0), (1.0, 0.0, 0.5)]]
+ t_faces = [(8, 0, 4, 9), (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (3, 10, 11, 7)]
+ elif d.tile_model == 'ETERNIT':
+ t_pts = [Vector(p) for p in [
+ (0.11, -1.0, 1.0), (0.9, -1.0, 1.0), (0.0, -0.79, 0.79),
+ (1.0, -0.79, 0.79), (0.0, 2.0, -2.0), (1.0, 2.0, -2.0)]]
+ t_faces = [(0, 1, 3, 5, 4, 2)]
+ elif d.tile_model == 'ONDULEE':
+ t_pts = [Vector(p) for p in [
+ (0.0, -1.0, 0.1), (0.05, -1.0, 1.0), (0.1, -1.0, 0.1),
+ (0.15, -1.0, 1.0), (0.2, -1.0, 0.1), (0.25, -1.0, 1.0),
+ (0.3, -1.0, 0.1), (0.35, -1.0, 1.0), (0.4, -1.0, 0.1),
+ (0.45, -1.0, 1.0), (0.5, -1.0, 0.1), (0.55, -1.0, 1.0),
+ (0.6, -1.0, 0.1), (0.65, -1.0, 1.0), (0.7, -1.0, 0.1),
+ (0.75, -1.0, 1.0), (0.8, -1.0, 0.1), (0.85, -1.0, 1.0),
+ (0.9, -1.0, 0.1), (0.95, -1.0, 1.0), (1.0, -1.0, 0.1),
+ (0.0, 0.0, 0.0), (0.05, 0.0, 0.9), (0.1, 0.0, 0.0),
+ (0.15, 0.0, 0.9), (0.2, 0.0, 0.0), (0.25, 0.0, 0.9),
+ (0.3, 0.0, 0.0), (0.35, 0.0, 0.9), (0.4, 0.0, 0.0),
+ (0.45, 0.0, 0.9), (0.5, 0.0, 0.0), (0.55, 0.0, 0.9),
+ (0.6, 0.0, 0.0), (0.65, 0.0, 0.9), (0.7, 0.0, 0.0),
+ (0.75, 0.0, 0.9), (0.8, 0.0, 0.0), (0.85, 0.0, 0.9),
+ (0.9, 0.0, 0.0), (0.95, 0.0, 0.9), (1.0, 0.0, 0.0)]]
+ t_faces = [
+ (0, 1, 22, 21), (1, 2, 23, 22), (2, 3, 24, 23),
+ (3, 4, 25, 24), (4, 5, 26, 25), (5, 6, 27, 26),
+ (6, 7, 28, 27), (7, 8, 29, 28), (8, 9, 30, 29),
+ (9, 10, 31, 30), (10, 11, 32, 31), (11, 12, 33, 32),
+ (12, 13, 34, 33), (13, 14, 35, 34), (14, 15, 36, 35),
+ (15, 16, 37, 36), (16, 17, 38, 37), (17, 18, 39, 38),
+ (18, 19, 40, 39), (19, 20, 41, 40)]
+ elif d.tile_model == 'METAL':
+ t_pts = [Vector(p) for p in [
+ (0.0, -1.0, 0.0), (0.99, -1.0, 0.0), (1.0, -1.0, 0.0),
+ (0.0, 0.0, 0.0), (0.99, 0.0, 0.0), (1.0, 0.0, 0.0),
+ (0.99, -1.0, 1.0), (1.0, -1.0, 1.0), (1.0, 0.0, 1.0), (0.99, 0.0, 1.0)]]
+ t_faces = [(0, 1, 4, 3), (7, 2, 5, 8), (1, 6, 9, 4), (6, 7, 8, 9)]
+ elif d.tile_model == 'LAUZE':
+ t_pts = [Vector(p) for p in [
+ (0.75, -0.8, 0.8), (0.5, -1.0, 1.0), (0.25, -0.8, 0.8),
+ (0.0, -0.5, 0.5), (1.0, -0.5, 0.5), (0.0, 0.5, -0.5), (1.0, 0.5, -0.5)]]
+ t_faces = [(1, 0, 4, 6, 5, 3, 2)]
+ elif d.tile_model == 'PLACEHOLDER':
+ 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)]]
+ t_faces = [(0, 1, 3, 2)]
+ elif d.tile_model == 'ROMAN':
+ t_pts = [Vector(p) for p in [
+ (0.18, 0.0, 0.3), (0.24, 0.0, 0.58), (0.76, 0.0, 0.58),
+ (0.82, 0.0, 0.3), (0.05, -1.0, 0.5), (0.14, -1.0, 0.8),
+ (0.86, -1.0, 0.8), (0.95, -1.0, 0.5), (0.45, 0.0, 0.5),
+ (0.36, 0.0, 0.2), (-0.36, 0.0, 0.2), (-0.45, -0.0, 0.5),
+ (0.32, -1.0, 0.7), (0.26, -1.0, 0.42), (-0.26, -1.0, 0.42),
+ (-0.32, -1.0, 0.7), (0.5, 0.0, 0.74), (0.5, -1.0, 1.0),
+ (-0.0, -1.0, 0.26), (-0.0, 0.0, 0.0)]
+ ]
+ t_faces = [
+ (0, 4, 5, 1), (16, 17, 6, 2), (2, 6, 7, 3),
+ (13, 12, 8, 9), (18, 13, 9, 19), (15, 14, 10, 11),
+ (14, 18, 19, 10), (1, 5, 17, 16)
+ ]
+ elif d.tile_model == 'ROUND':
+ t_pts = [Vector(p) for p in [
+ (0.0, -0.5, 0.5), (1.0, -0.5, 0.5), (0.0, 0.0, 0.0),
+ (1.0, 0.0, 0.0), (0.93, -0.71, 0.71), (0.78, -0.88, 0.88),
+ (0.39, -0.97, 0.97), (0.61, -0.97, 0.97), (0.07, -0.71, 0.71),
+ (0.22, -0.88, 0.88)]
+ ]
+ t_faces = [(6, 7, 5, 4, 1, 3, 2, 0, 8, 9)]
+ else:
+ return
+
+ n_faces = len(t_faces)
+ t_uvs = [[(t_pts[i].x, t_pts[i].y) for i in f] for f in t_faces]
+
+ dx, dy = d.tile_space_x, d.tile_space_y
+
+ ttl = len(self.pans)
+ step = 100 / ttl
+
+ context.scene.archipack_progress_text = "Build tiles:"
+
+ for i, pan in enumerate(self.pans):
+
+ seg = pan.fake_axis
+ # compute base matrix top left of face
+ vx = pan.vx
+ vy = pan.vy
+ vz = pan.vz
+
+ x0, y0 = seg.lerp(pan.tmax)
+ z0 = self.z + d.tile_altitude
+ ysize_2d = (d.tile_border + pan.ysize)
+ space_x = pan.xsize + 2 * d.tile_side
+ space_y = ysize_2d * sqrt(1 + pan.slope * pan.slope)
+ n_x = 1 + int(space_x / dx)
+ n_y = 1 + int(space_y / dy)
+
+ if d.tile_fit_x:
+ dx = space_x / n_x
+
+ if d.tile_fit_y:
+ dy = space_y / n_y
+
+ if d.tile_alternate:
+ n_y += 1
+
+ tM = Matrix([
+ [vx.x, vy.x, vz.x, x0],
+ [vx.y, vy.y, vz.y, y0],
+ [vx.z, vy.z, vz.z, z0],
+ [0, 0, 0, 1]
+ ])
+
+ verts = []
+ faces = []
+ matids = []
+ uvs = []
+
+ # steps for this pan
+ substep = step / n_y
+ # print("step:%s sub:%s" % (step, substep))
+
+ for k in range(n_y):
+
+ progress = step * i + substep * k
+ # print("progress %s" % (progress))
+ context.scene.archipack_progress = progress
+
+ y = k * dy
+
+ x0 = offset * dx - d.tile_side
+ nx = n_x
+
+ if d.tile_alternate and k % 2 == 1:
+ x0 -= 0.5 * dx
+ nx += 1
+
+ if d.tile_offset > 0:
+ nx += 1
+
+ for j in range(nx):
+ x = x0 + j * dx
+ lM = tM * Matrix([
+ [sx, 0, 0, x],
+ [0, sy, 0, -y],
+ [0, 0, sz, 0],
+ [0, 0, 0, 1]
+ ])
+
+ v = len(verts)
+
+ verts.extend([lM * p for p in t_pts])
+ faces.extend([tuple(i + v for i in f) for f in t_faces])
+ id = randint(idmat, idmat + rand)
+ t_mats = [id for i in range(n_faces)]
+ matids.extend(t_mats)
+ uvs.extend(t_uvs)
+
+ # build temp bmesh and bissect
+ bm = bmed.buildmesh(
+ context, o, verts, faces, matids=matids, uvs=uvs,
+ weld=False, clean=False, auto_smooth=True, temporary=True)
+
+ # clean outer on convex parts
+ # pan.convex = False
+ remove = pan.convex
+
+ for s in pan.segs:
+ # seg without length lead to invalid normal
+ if s.length > 0:
+ if s.type == 'AXIS':
+ self.bissect(bm, s.p1.to_3d(), s.cross_z.to_3d(), clear_outer=remove)
+ elif s.type == 'BOTTOM':
+ s0 = s.offset(d.tile_border)
+ dz = pan.altitude(s0.p0)
+ vx = s0.v.to_3d()
+ vx.z = pan.altitude(s0.p1) - dz
+ vy = vz.cross(vx.normalized())
+ x, y = s0.p0
+ z = z0 + dz
+ self.bissect(bm, Vector((x, y, z)), -vy, clear_outer=remove)
+ elif s.type == 'SIDE':
+ p0 = s.p0 + s.cross_z.normalized() * d.tile_side
+ self.bissect(bm, p0.to_3d(), s.cross_z.to_3d(), clear_outer=remove)
+ elif s.type == 'LINK_VALLEY':
+ p0 = s.p0 - s.cross_z.normalized() * d.tile_couloir
+ self.bissect(bm, p0.to_3d(), s.cross_z.to_3d(), clear_outer=remove)
+ elif s.type in {'LINK_HIP', 'LINK'}:
+ self.bissect(bm, s.p0.to_3d(), s.cross_z.to_3d(), clear_outer=remove)
+
+ # when not convex, select and remove outer parts
+ if not pan.convex:
+ """
+ /* del "context" slot values, used for operator too */
+ enum {
+ DEL_VERTS = 1,
+ DEL_EDGES,
+ DEL_ONLYFACES,
+ DEL_EDGESFACES,
+ DEL_FACES,
+ /* A version of 'DEL_FACES' that keeps edges on face boundaries,
+ * allowing the surrounding edge-loop to be kept from removed face regions. */
+ DEL_FACES_KEEP_BOUNDARY,
+ DEL_ONLYTAGGED
+ };
+ """
+ # Build boundary including borders and bottom offsets
+ new_s = None
+ segs = []
+ for s in pan.segs:
+ if s.length > 0:
+ if s.type == 'LINK_VALLEY':
+ offset = -d.tile_couloir
+ elif s.type == 'BOTTOM':
+ offset = d.tile_border
+ elif s.type == 'SIDE':
+ offset = d.tile_side
+ else:
+ offset = 0
+ new_s = s.make_offset(offset, new_s)
+ segs.append(new_s)
+
+ if len(segs) > 0:
+ # last / first intersection
+ res, p, t = segs[0].intersect(segs[-1])
+ if res:
+ segs[0].p0 = p
+ segs[-1].p1 = p
+ f_geom = [f for f in bm.faces if not pan.inside(f.calc_center_median().to_2d(), segs)]
+ if len(f_geom) > 0:
+ bmesh.ops.delete(bm, geom=f_geom, context=5)
+
+ self.cut_holes(bm, pan)
+
+ bmesh.ops.dissolve_limit(bm,
+ angle_limit=0.01,
+ use_dissolve_boundaries=False,
+ verts=bm.verts[:],
+ edges=bm.edges[:],
+ delimit=1)
+
+ if d.tile_bevel:
+ geom = bm.verts[:]
+ geom.extend(bm.edges[:])
+ bmesh.ops.bevel(bm,
+ geom=geom,
+ offset=d.tile_bevel_amt,
+ offset_type=offset_type,
+ segments=d.tile_bevel_segs,
+ profile=0.5,
+ vertex_only=False,
+ clamp_overlap=True,
+ material=-1)
+
+ if d.tile_solidify:
+ geom = bm.faces[:]
+ verts = bm.verts[:]
+ bmesh.ops.solidify(bm, geom=geom, thickness=0.0001)
+ bmesh.ops.translate(bm, vec=vz * d.tile_height, space=o.matrix_world, verts=verts)
+
+ # merge with object
+ bmed.bmesh_join(context, o, [bm], normal_update=True)
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ context.scene.archipack_progress = -1
+
+ def _rake(self, s, i, boundary, pan,
+ width, height, altitude, offset, idmat,
+ verts, faces, edges, matids, uvs):
+
+ f = len(verts)
+
+ s0 = s.offset(offset - width)
+ s1 = s.offset(offset)
+
+ p0 = s0.p0
+ p1 = s1.p0
+ p2 = s0.p1
+ p3 = s1.p1
+
+ s2 = boundary.last_seg(i)
+ s3 = boundary.next_seg(i)
+
+ if s2.type == 'SIDE':
+ # intersect last seg offset
+ s4 = s2.offset(offset - width)
+ s5 = s2.offset(offset)
+ res, p, t = s4.intersect(s0)
+ if res:
+ p0 = p
+ res, p, t = s5.intersect(s1)
+ if res:
+ p1 = p
+
+ elif s2.type == 'AXIS' or 'LINK' in s2.type:
+ # intersect axis or link seg
+ res, p, t = s2.intersect(s0)
+ if res:
+ p0 = p
+ res, p, t = s2.intersect(s1)
+ if res:
+ p1 = p
+
+ if s3.type == 'SIDE':
+ # intersect next seg offset
+ s4 = s3.offset(offset - width)
+ s5 = s3.offset(offset)
+ res, p, t = s4.intersect(s0)
+ if res:
+ p2 = p
+ res, p, t = s5.intersect(s1)
+ if res:
+ p3 = p
+
+ elif s3.type == 'AXIS' or 'LINK' in s3.type:
+ # intersect axis or link seg
+ res, p, t = s3.intersect(s0)
+ if res:
+ p2 = p
+ res, p, t = s3.intersect(s1)
+ if res:
+ p3 = p
+
+ x0, y0 = p0
+ x1, y1 = p1
+ x2, y2 = p3
+ x3, y3 = p2
+
+ z0 = self.z + altitude + pan.altitude(p0)
+ z1 = self.z + altitude + pan.altitude(p1)
+ z2 = self.z + altitude + pan.altitude(p3)
+ z3 = self.z + altitude + pan.altitude(p2)
+
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z1),
+ (x2, y2, z2),
+ (x3, y3, z3),
+ ])
+ z0 -= height
+ z1 -= height
+ z2 -= height
+ z3 -= height
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z1),
+ (x2, y2, z2),
+ (x3, y3, z3),
+ ])
+
+ faces.extend([
+ # top
+ (f, f + 1, f + 2, f + 3),
+ # sides
+ (f, f + 4, f + 5, f + 1),
+ (f + 1, f + 5, f + 6, f + 2),
+ (f + 2, f + 6, f + 7, f + 3),
+ (f + 3, f + 7, f + 4, f),
+ # bottom
+ (f + 4, f + 7, f + 6, f + 5)
+ ])
+ edges.append([f, f + 3])
+ edges.append([f + 1, f + 2])
+ edges.append([f + 4, f + 7])
+ edges.append([f + 5, f + 6])
+
+ matids.extend([idmat, idmat, idmat, idmat, idmat, idmat])
+ uvs.extend([
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)]
+ ])
+
+ def rake(self, d, verts, faces, edges, matids, uvs):
+
+ #####################
+ # Vire-vents
+ #####################
+
+ idmat = 1
+ for pan in self.pans:
+
+ for hole in pan.holes:
+ for i, s in enumerate(hole.segs):
+ if s.type == 'SIDE':
+ self._rake(s,
+ i,
+ hole, pan,
+ d.rake_width,
+ d.rake_height,
+ d.rake_altitude,
+ d.rake_offset,
+ idmat,
+ verts,
+ faces,
+ edges,
+ matids,
+ uvs)
+
+ for i, s in enumerate(pan.segs):
+ if s.type == 'SIDE':
+ self._rake(s,
+ i,
+ pan, pan,
+ d.rake_width,
+ d.rake_height,
+ d.rake_altitude,
+ d.rake_offset,
+ idmat,
+ verts,
+ faces,
+ edges,
+ matids,
+ uvs)
+
+ def _facia(self, s, i, boundary, pan, tri_0, tri_1,
+ width, height, altitude, offset, idmat,
+ verts, faces, edges, matids, uvs):
+
+ f = len(verts)
+ s0 = s.offset(offset)
+ s1 = s.offset(offset + width)
+
+ s2 = boundary.last_seg(i)
+ s3 = boundary.next_seg(i)
+ s4 = s2
+ s5 = s3
+
+ p0 = s0.p0
+ p1 = s1.p0
+ p2 = s0.p1
+ p3 = s1.p1
+
+ # find last neighboor depending on type
+ if s2.type == 'AXIS' or 'LINK' in s2.type:
+ # apply only on boundarys
+ if not s.is_hole:
+ # use last axis
+ if pan.side == 'LEFT':
+ s6 = pan.next_cross
+ else:
+ s6 = pan.last_cross
+ if tri_0:
+ s2 = s.copy
+ else:
+ s2 = s2.oposite
+ s2.v = (s.sized_normal(0, 1).v + s6.v).normalized()
+ s4 = s2
+
+ elif s2.type == 'SIDE':
+ s2 = s.copy
+ s2.type = 'SIDE'
+ s2.v = s.sized_normal(0, 1).v
+ s4 = s2
+ else:
+ s2 = s2.offset(offset)
+ s4 = s2.offset(offset + width)
+
+ # find next neighboor depending on type
+ if s3.type == 'AXIS' or 'LINK' in s3.type:
+ if not s.is_hole:
+ # use last axis
+ if pan.side == 'LEFT':
+ s6 = pan.last_cross
+ else:
+ s6 = pan.next_cross
+ if tri_1:
+ s3 = s.oposite
+ else:
+ s3 = s3.copy
+ s3.v = (s.sized_normal(0, 1).v + s6.v).normalized()
+ s5 = s3
+ elif s3.type == 'SIDE':
+ # when next is side, use perpendicular
+ s3 = s.oposite
+ s3.type = 'SIDE'
+ s3.v = s.sized_normal(0, 1).v
+ s5 = s3
+ else:
+ s3 = s3.offset(offset)
+ s5 = s3.offset(offset + width)
+
+ # units vectors and scale
+ # is unit normal on sides
+ # print("s.p:%s, s.v:%s s1.p::%s s1.v::%s" % (s.p, s.v, s1.p, s1.v))
+ res, p, t = s0.intersect(s2)
+ if res:
+ p0 = p
+ res, p, t = s0.intersect(s3)
+ if res:
+ p1 = p
+ res, p, t = s1.intersect(s4)
+ if res:
+ p2 = p
+ res, p, t = s1.intersect(s5)
+ if res:
+ p3 = p
+
+ x0, y0 = p0
+ x1, y1 = p2
+ x2, y2 = p3
+ x3, y3 = p1
+
+ z0 = self.z + altitude + pan.altitude(p0)
+ z1 = self.z + altitude + pan.altitude(p2)
+ z2 = self.z + altitude + pan.altitude(p3)
+ z3 = self.z + altitude + pan.altitude(p1)
+
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z1),
+ (x2, y2, z2),
+ (x3, y3, z3),
+ ])
+
+ z0 -= height
+ z1 -= height
+ z2 -= height
+ z3 -= height
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z1),
+ (x2, y2, z2),
+ (x3, y3, z3),
+ ])
+
+ faces.extend([
+ # top
+ (f, f + 1, f + 2, f + 3),
+ # sides
+ (f, f + 4, f + 5, f + 1),
+ (f + 1, f + 5, f + 6, f + 2),
+ (f + 2, f + 6, f + 7, f + 3),
+ (f + 3, f + 7, f + 4, f),
+ # bottom
+ (f + 4, f + 7, f + 6, f + 5)
+ ])
+ edges.append([f, f + 3])
+ edges.append([f + 1, f + 2])
+ edges.append([f + 4, f + 7])
+ edges.append([f + 5, f + 6])
+ matids.extend([idmat, idmat, idmat, idmat, idmat, idmat])
+ uvs.extend([
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)]
+ ])
+
+ def facia(self, d, verts, faces, edges, matids, uvs):
+
+ #####################
+ # Larmiers
+ #####################
+
+ idmat = 2
+ for pan in self.pans:
+
+ for hole in pan.holes:
+ for i, s in enumerate(hole.segs):
+ if s.type == 'BOTTOM':
+ self._facia(s,
+ i,
+ hole, pan,
+ False, False,
+ d.facia_width,
+ d.facia_height,
+ d.facia_altitude,
+ d.facia_offset,
+ idmat,
+ verts,
+ faces,
+ edges,
+ matids,
+ uvs)
+
+ for i, s in enumerate(pan.segs):
+ if s.type == 'BOTTOM':
+
+ tri_0 = pan.node_tri
+ tri_1 = pan.next_tri
+
+ # triangular ends apply on boundary only
+ # unless cut, boundary is parallel to axis
+ # except for triangular ends
+ if pan.side == 'LEFT':
+ tri_0, tri_1 = tri_1, tri_0
+
+ self._facia(s,
+ i,
+ pan, pan,
+ tri_0, tri_1,
+ d.facia_width,
+ d.facia_height,
+ d.facia_altitude,
+ d.facia_offset,
+ idmat,
+ verts,
+ faces,
+ edges,
+ matids,
+ uvs)
+
+ continue
+
+ f = len(verts)
+ s0 = s.offset(d.facia_width)
+
+ s1 = pan.last_seg(i)
+ s2 = pan.next_seg(i)
+
+ # triangular ends apply on boundary only
+ # unless cut, boundary is parallel to axis
+ # except for triangular ends
+
+ tri_0 = (pan.node_tri and not s.is_hole) or pan.is_tri
+ tri_1 = (pan.next_tri and not s.is_hole) or pan.is_tri
+
+ if pan.side == 'LEFT':
+ tri_0, tri_1 = tri_1, tri_0
+
+ # tiangular use bottom segment direction
+ # find last neighboor depending on type
+ if s1.type == 'AXIS' or 'LINK' in s1.type:
+ # apply only on boundarys
+ if not s.is_hole:
+ # use last axis
+ if pan.side == 'LEFT':
+ s3 = pan.next_cross
+ else:
+ s3 = pan.last_cross
+ if tri_0:
+ s1 = s.copy
+ else:
+ s1 = s1.oposite
+ s1.v = (s.sized_normal(0, 1).v + s3.v).normalized()
+ elif s1.type == 'SIDE':
+ s1 = s.copy
+ s1.type = 'SIDE'
+ s1.v = s.sized_normal(0, 1).v
+ else:
+ s1 = s1.offset(d.facia_width)
+
+ # find next neighboor depending on type
+ if s2.type == 'AXIS' or 'LINK' in s2.type:
+ if not s.is_hole:
+ # use last axis
+ if pan.side == 'LEFT':
+ s3 = pan.last_cross
+ else:
+ s3 = pan.next_cross
+ if tri_1:
+ s2 = s.oposite
+ else:
+ s2 = s2.copy
+ s2.v = (s.sized_normal(0, 1).v + s3.v).normalized()
+ elif s2.type == 'SIDE':
+ s2 = s.oposite
+ s2.type = 'SIDE'
+ s2.v = s.sized_normal(0, 1).v
+ else:
+
+ s2 = s2.offset(d.facia_width)
+
+ # units vectors and scale
+ # is unit normal on sides
+ # print("s.p:%s, s.v:%s s1.p::%s s1.v::%s" % (s.p, s.v, s1.p, s1.v))
+ res, p0, t = s0.intersect(s1)
+ res, p1, t = s0.intersect(s2)
+
+ x0, y0 = s.p0
+ x1, y1 = p0
+ x2, y2 = p1
+ x3, y3 = s.p1
+ z0 = self.z + d.facia_altitude + pan.altitude(s.p0)
+ z1 = self.z + d.facia_altitude + pan.altitude(s.p1)
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z0),
+ (x2, y2, z1),
+ (x3, y3, z1),
+ ])
+ z0 -= d.facia_height
+ z1 -= d.facia_height
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z0),
+ (x2, y2, z1),
+ (x3, y3, z1),
+ ])
+
+ faces.extend([
+ # top
+ (f, f + 1, f + 2, f + 3),
+ # sides
+ (f, f + 4, f + 5, f + 1),
+ (f + 1, f + 5, f + 6, f + 2),
+ (f + 2, f + 6, f + 7, f + 3),
+ (f + 3, f + 7, f + 4, f),
+ # bottom
+ (f + 4, f + 7, f + 6, f + 5)
+ ])
+ edges.append([f, f + 3])
+ edges.append([f + 1, f + 2])
+ edges.append([f + 4, f + 7])
+ edges.append([f + 5, f + 6])
+ matids.extend([idmat, idmat, idmat, idmat, idmat, idmat])
+ uvs.extend([
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)]
+ ])
+
+ def gutter(self, d, verts, faces, edges, matids, uvs):
+
+ #####################
+ # Chenaux
+ #####################
+
+ idmat = 5
+
+ # caps at start and end
+ if d.gutter_segs % 2 == 1:
+ n_faces = int((d.gutter_segs - 1) / 2)
+ else:
+ n_faces = int((d.gutter_segs / 2) - 1)
+
+ df = 2 * d.gutter_segs + 1
+
+ for pan in self.pans:
+ for i, s in enumerate(pan.segs):
+
+ if s.type == 'BOTTOM':
+ f = len(verts)
+
+ s0 = s.offset(d.gutter_dist + d.gutter_width)
+
+ s1 = pan.last_seg(i)
+ s2 = pan.next_seg(i)
+
+ p0 = s0.p0
+ p1 = s0.p1
+
+ tri_0 = pan.node_tri or pan.is_tri
+ tri_1 = pan.next_tri or pan.is_tri
+
+ if pan.side == 'LEFT':
+ tri_0, tri_1 = tri_1, tri_0
+
+ f = len(verts)
+
+ # tiangular use segment direction
+ # find last neighboor depending on type
+ if s1.type == 'AXIS' or 'LINK' in s1.type:
+ # apply only on boundarys
+ if not s.is_hole:
+ # use last axis
+ if pan.side == 'LEFT':
+ s3 = pan.next_cross
+ else:
+ s3 = pan.last_cross
+ if tri_0:
+ s1 = s.copy
+ else:
+ s1 = s1.oposite
+ s1.v = (s.sized_normal(0, 1).v + s3.v).normalized()
+ elif s1.type == 'SIDE':
+ s1 = s.copy
+ s1.type = 'SIDE'
+ s1.v = s.sized_normal(0, 1).v
+ else:
+ s1 = s1.offset(d.gutter_dist + d.gutter_width)
+
+ # find next neighboor depending on type
+ if s2.type == 'AXIS' or 'LINK' in s2.type:
+ if not s.is_hole:
+ # use last axis
+ if pan.side == 'LEFT':
+ s3 = pan.last_cross
+ else:
+ s3 = pan.next_cross
+ if tri_1:
+ s2 = s.oposite
+ else:
+ s2 = s2.copy
+ s2.v = (s.sized_normal(0, 1).v + s3.v).normalized()
+ elif s2.type == 'SIDE':
+ s2 = s.oposite
+ s2.type = 'SIDE'
+ s2.v = s.sized_normal(0, 1).v
+ else:
+ s2 = s2.offset(d.gutter_dist + d.gutter_width)
+
+ # units vectors and scale
+ # is unit normal on sides
+ # print("s.p:%s, s.v:%s s1.p::%s s1.v::%s" % (s.p, s.v, s1.p, s1.v))
+ res, p, t = s0.intersect(s1)
+ if res:
+ p0 = p
+ res, p, t = s0.intersect(s2)
+ if res:
+ p1 = p
+ """
+ f = len(verts)
+ verts.extend([s1.p0.to_3d(), s1.p1.to_3d()])
+ edges.append([f, f + 1])
+
+ f = len(verts)
+ verts.extend([s2.p0.to_3d(), s2.p1.to_3d()])
+ edges.append([f, f + 1])
+ continue
+ """
+
+ v0 = p0 - s.p0
+ v1 = p1 - s.p1
+
+ scale_0 = v0.length / (d.gutter_dist + d.gutter_width)
+ scale_1 = v1.length / (d.gutter_dist + d.gutter_width)
+
+ s3 = Line(s.p0, v0.normalized())
+ s4 = Line(s.p1, v1.normalized())
+
+ zt = self.z + d.facia_altitude + pan.altitude(s3.p0)
+ z0 = self.z + d.gutter_alt + pan.altitude(s3.p0)
+ z1 = z0 - 0.5 * d.gutter_width
+ z2 = z1 - 0.5 * d.gutter_width
+ z3 = z1 - 0.5 * d.gutter_boudin
+ dz0 = z2 - z1
+ dz1 = z3 - z1
+
+ tt = scale_0 * d.facia_width
+ t0 = scale_0 * d.gutter_dist
+ t1 = t0 + scale_0 * (0.5 * d.gutter_width)
+ t2 = t1 + scale_0 * (0.5 * d.gutter_width)
+ t3 = t2 + scale_0 * (0.5 * d.gutter_boudin)
+
+ # bord tablette
+ xt, yt = s3.lerp(tt)
+
+ # bord
+ x0, y0 = s3.lerp(t0)
+ # axe chenaux
+ x1, y1 = s3.lerp(t1)
+ # bord boudin interieur
+ x2, y2 = s3.lerp(t2)
+ # axe boudin
+ x3, y3 = s3.lerp(t3)
+
+ dx = x0 - x1
+ dy = y0 - y1
+
+ verts.append((xt, yt, zt))
+ # chenaux
+ da = pi / d.gutter_segs
+ for i in range(d.gutter_segs):
+ sa = sin(i * da)
+ ca = cos(i * da)
+ verts.append((x1 + dx * ca, y1 + dy * ca, z1 + dz0 * sa))
+
+ dx = x2 - x3
+ dy = y2 - y3
+
+ # boudin
+ da = -pi / (0.75 * d.gutter_segs)
+ for i in range(d.gutter_segs):
+ sa = sin(i * da)
+ ca = cos(i * da)
+ verts.append((x3 + dx * ca, y3 + dy * ca, z1 + dz1 * sa))
+
+ zt = self.z + d.facia_altitude + pan.altitude(s4.p0)
+ z0 = self.z + d.gutter_alt + pan.altitude(s4.p0)
+ z1 = z0 - 0.5 * d.gutter_width
+ z2 = z1 - 0.5 * d.gutter_width
+ z3 = z1 - 0.5 * d.gutter_boudin
+ dz0 = z2 - z1
+ dz1 = z3 - z1
+ tt = scale_1 * d.facia_width
+ t0 = scale_1 * d.gutter_dist
+ t1 = t0 + scale_1 * (0.5 * d.gutter_width)
+ t2 = t1 + scale_1 * (0.5 * d.gutter_width)
+ t3 = t2 + scale_1 * (0.5 * d.gutter_boudin)
+
+ # bord tablette
+ xt, yt = s4.lerp(tt)
+
+ # bord
+ x0, y0 = s4.lerp(t0)
+ # axe chenaux
+ x1, y1 = s4.lerp(t1)
+ # bord boudin interieur
+ x2, y2 = s4.lerp(t2)
+ # axe boudin
+ x3, y3 = s4.lerp(t3)
+
+ dx = x0 - x1
+ dy = y0 - y1
+
+ # tablette
+ verts.append((xt, yt, zt))
+ faces.append((f + df, f, f + 1, f + df + 1))
+ uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
+ matids.append(idmat)
+
+ # chenaux
+ da = pi / d.gutter_segs
+ for i in range(d.gutter_segs):
+ sa = sin(i * da)
+ ca = cos(i * da)
+ verts.append((x1 + dx * ca, y1 + dy * ca, z1 + dz0 * sa))
+
+ dx = x2 - x3
+ dy = y2 - y3
+
+ # boudin
+ da = -pi / (0.75 * d.gutter_segs)
+ for i in range(d.gutter_segs):
+ sa = sin(i * da)
+ ca = cos(i * da)
+ verts.append((x3 + dx * ca, y3 + dy * ca, z1 + dz1 * sa))
+
+ df = 2 * d.gutter_segs + 1
+
+ for i in range(1, 2 * d.gutter_segs):
+ j = i + f
+ faces.append((j, j + df, j + df + 1, j + 1))
+ uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
+ matids.append(idmat)
+
+ """
+ segs = 6
+
+ n_faces = segs / 2 - 1
+
+ 0 6
+ 1 5
+ 2 4
+ 3
+ """
+ # close start
+ if s1.type == 'SIDE':
+
+ if d.gutter_segs % 2 == 0:
+ faces.append((f + n_faces + 3, f + n_faces + 1, f + n_faces + 2))
+ uvs.append([(0, 0), (1, 0), (0.5, -0.5)])
+ matids.append(idmat)
+
+ for i in range(n_faces):
+
+ j = i + f + 1
+ k = f + d.gutter_segs - i
+ faces.append((j + 1, k, k + 1, j))
+ uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
+ matids.append(idmat)
+
+ # close end
+ if s2.type == 'SIDE':
+
+ f += 2 * d.gutter_segs + 1
+
+ if d.gutter_segs % 2 == 0:
+ faces.append((f + n_faces + 1, f + n_faces + 3, f + n_faces + 2))
+ uvs.append([(0, 0), (1, 0), (0.5, -0.5)])
+ matids.append(idmat)
+
+ for i in range(n_faces):
+
+ j = i + f + 1
+ k = f + d.gutter_segs - i
+ faces.append((j, k + 1, k, j + 1))
+ uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
+ matids.append(idmat)
+
+ def beam_primary(self, d, verts, faces, edges, matids, uvs):
+
+ idmat = 3
+
+ for pan in self.pans:
+ for i, s in enumerate(pan.segs):
+
+ if s.type == 'AXIS':
+
+ ####################
+ # Poutre Faitiere
+ ####################
+
+ """
+ 1___________________2 left
+ 0|___________________|3 axis
+ |___________________| right
+ 5 4
+ """
+ f = len(verts)
+
+ s2 = s.offset(-0.5 * d.beam_width)
+
+ # offset from roof border
+ s0 = pan.last_seg(i)
+ s1 = pan.next_seg(i)
+ t0 = 0
+ t1 = 1
+
+ s0_tri = pan.next_tri
+ s1_tri = pan.node_tri
+
+ if pan.side == 'LEFT':
+ s0_tri, s1_tri = s1_tri, s0_tri
+
+ if s0.type == 'SIDE':
+ s0 = s0.offset(d.beam_offset)
+ t0 = -d.beam_offset / s.length
+
+ if s0_tri:
+ p0 = s2.p0
+ t0 = 0
+ else:
+ res, p0, t = s2.intersect(s0)
+ if not res:
+ continue
+
+ if s1.type == 'SIDE':
+ s1 = s1.offset(d.beam_offset)
+ t1 = 1 + d.beam_offset / s.length
+
+ if s1_tri:
+ t1 = 1
+ p1 = s2.p1
+ else:
+ res, p1, t = s2.intersect(s1)
+ if not res:
+ continue
+
+ x0, y0 = p0
+ x1, y1 = s.lerp(t0)
+ x2, y2 = p1
+ x3, y3 = s.lerp(t1)
+ z0 = self.z + d.beam_alt
+ z1 = z0 - d.beam_height
+ z2 = self.z + d.beam_alt
+ z3 = z2 - d.beam_height
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z0),
+ (x2, y2, z2),
+ (x3, y3, z2),
+ (x0, y0, z1),
+ (x1, y1, z1),
+ (x2, y2, z3),
+ (x3, y3, z3),
+ ])
+ if s0_tri or s0.type == 'SIDE':
+ faces.append((f + 4, f + 5, f + 1, f))
+ uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
+ matids.append(idmat)
+ if s1_tri or s1.type == 'SIDE':
+ faces.append((f + 2, f + 3, f + 7, f + 6))
+ uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
+ matids.append(idmat)
+
+ faces.extend([
+ # internal side
+ # (f + 1, f + 5, f + 7, f + 3),
+ # external side
+ (f + 2, f + 6, f + 4, f),
+ # top
+ (f, f + 1, f + 3, f + 2),
+ # bottom
+ (f + 5, f + 4, f + 6, f + 7)
+ ])
+ matids.extend([
+ idmat, idmat, idmat
+ ])
+ uvs.extend([
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)],
+ [(0, 0), (0, 1), (1, 1), (1, 0)]
+ ])
+
+ def rafter(self, context, o, d):
+
+ idmat = 4
+
+ # Rafters / Chevrons
+ start = max(0.001 + 0.5 * d.rafter_width, d.rafter_start)
+
+ holes_offset = -d.rafter_width
+
+ # build temp bmesh and bissect
+ for pan in self.pans:
+ tmin, tmax, ysize = pan.tmin, pan.tmax, pan.ysize
+
+ # print("tmin:%s tmax:%s ysize:%s" % (tmin, tmax, ysize))
+
+ f = 0
+
+ verts = []
+ faces = []
+ matids = []
+ uvs = []
+ alt = d.rafter_alt
+ seg = pan.fake_axis
+
+ t0 = tmin + (start - 0.5 * d.rafter_width) / seg.length
+ t1 = tmin + (start + 0.5 * d.rafter_width) / seg.length
+
+ tx = start / seg.length
+ dt = d.rafter_spacing / seg.length
+
+ n_items = max(1, round((tmax - tmin) / dt, 0))
+
+ dt = ((tmax - tmin) - 2 * tx) / n_items
+
+ for j in range(int(n_items) + 1):
+ n0 = seg.sized_normal(t1 + j * dt, - ysize)
+ n1 = seg.sized_normal(t0 + j * dt, - ysize)
+ f = len(verts)
+
+ z0 = self.z + alt + pan.altitude(n0.p0)
+ x0, y0 = n0.p0
+ z1 = self.z + alt + pan.altitude(n0.p1)
+ x1, y1 = n0.p1
+ z2 = self.z + alt + pan.altitude(n1.p0)
+ x2, y2 = n1.p0
+ z3 = self.z + alt + pan.altitude(n1.p1)
+ x3, y3 = n1.p1
+
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z1),
+ (x2, y2, z2),
+ (x3, y3, z3)
+ ])
+
+ faces.append((f + 1, f, f + 2, f + 3))
+ matids.append(idmat)
+ uvs.append([(0, 0), (1, 0), (1, 1), (0, 1)])
+
+ bm = bmed.buildmesh(
+ context, o, verts, faces, matids=matids, uvs=uvs,
+ weld=False, clean=False, auto_smooth=True, temporary=True)
+
+ self.cut_boundary(bm, pan)
+ self.cut_holes(bm, pan, offset={'DEFAULT': holes_offset})
+
+ bmesh.ops.dissolve_limit(bm,
+ angle_limit=0.01,
+ use_dissolve_boundaries=False,
+ verts=bm.verts,
+ edges=bm.edges,
+ delimit=1)
+
+ geom = bm.faces[:]
+ verts = bm.verts[:]
+ bmesh.ops.solidify(bm, geom=geom, thickness=0.0001)
+ bmesh.ops.translate(bm, vec=Vector((0, 0, -d.rafter_height)), space=o.matrix_world, verts=verts)
+ # uvs for sides
+ uvs = [(0, 0), (1, 0), (1, 1), (0, 1)]
+ layer = bm.loops.layers.uv.verify()
+ for i, face in enumerate(bm.faces):
+ if len(face.loops) == 4:
+ for j, loop in enumerate(face.loops):
+ loop[layer].uv = uvs[j]
+
+ # merge with object
+ bmed.bmesh_join(context, o, [bm], normal_update=True)
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ def hips(self, d, verts, faces, edges, matids, uvs):
+
+ idmat_valley = 5
+ idmat = 6
+ idmat_poutre = 4
+
+ sx, sy, sz = d.hip_size_x, d.hip_size_y, d.hip_size_z
+
+ if d.hip_model == 'ROUND':
+
+ # round hips
+ t_pts = [Vector((sx * x, sy * y, sz * z)) for x, y, z in [
+ (-0.5, 0.34, 0.08), (-0.5, 0.32, 0.19), (0.5, -0.4, -0.5),
+ (0.5, 0.4, -0.5), (-0.5, 0.26, 0.28), (-0.5, 0.16, 0.34),
+ (-0.5, 0.05, 0.37), (-0.5, -0.05, 0.37), (-0.5, -0.16, 0.34),
+ (-0.5, -0.26, 0.28), (-0.5, -0.32, 0.19), (-0.5, -0.34, 0.08),
+ (-0.5, -0.25, -0.5), (-0.5, 0.25, -0.5), (0.5, -0.08, 0.5),
+ (0.5, -0.5, 0.08), (0.5, -0.24, 0.47), (0.5, -0.38, 0.38),
+ (0.5, -0.47, 0.24), (0.5, 0.5, 0.08), (0.5, 0.08, 0.5),
+ (0.5, 0.47, 0.24), (0.5, 0.38, 0.38), (0.5, 0.24, 0.47)
+ ]]
+ t_faces = [
+ (23, 22, 4, 5), (3, 19, 21, 22, 23, 20, 14, 16, 17, 18, 15, 2), (14, 20, 6, 7),
+ (18, 17, 9, 10), (15, 18, 10, 11), (21, 19, 0, 1), (17, 16, 8, 9),
+ (13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 1, 0), (19, 3, 13, 0), (20, 23, 5, 6), (22, 21, 1, 4),
+ (3, 2, 12, 13), (2, 15, 11, 12), (16, 14, 7, 8)
+ ]
+ t_uvs = [
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.5, 1.0), (0.75, 0.93), (0.93, 0.75),
+ (1.0, 0.5), (0.93, 0.25), (0.75, 0.07),
+ (0.5, 0.0), (0.25, 0.07), (0.07, 0.25),
+ (0.0, 0.5), (0.07, 0.75), (0.25, 0.93)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.5, 1.0), (0.75, 0.93), (0.93, 0.75),
+ (1.0, 0.5), (0.93, 0.25), (0.75, 0.07),
+ (0.5, 0.0), (0.25, 0.07), (0.07, 0.25),
+ (0.0, 0.5), (0.07, 0.75), (0.25, 0.93)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
+ ]
+ # affect vertex with slope
+ t_left = []
+ t_right = []
+
+ elif d.hip_model == 'ETERNIT':
+
+ # square hips "eternit like"
+ t_pts = [Vector((sx * x, sy * y, sz * z)) for x, y, z in [
+ (0.5, 0.5, 0.0), (-0.5, 0.5, -0.5), (0.5, -0.5, 0.0),
+ (-0.5, -0.5, -0.5), (0.5, 0.0, 0.0), (-0.5, -0.0, -0.5),
+ (0.5, 0.0, 0.5), (0.5, -0.5, 0.5), (-0.5, -0.5, 0.0),
+ (-0.5, -0.0, 0.0), (0.5, 0.5, 0.5), (-0.5, 0.5, 0.0)]
+ ]
+ t_faces = [
+ (4, 2, 3, 5), (0, 4, 5, 1), (6, 9, 8, 7),
+ (10, 11, 9, 6), (0, 10, 6, 4), (5, 9, 11, 1),
+ (2, 7, 8, 3), (1, 11, 10, 0), (4, 6, 7, 2), (3, 8, 9, 5)
+ ]
+ t_uvs = [
+ [(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)],
+ [(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)],
+ [(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)],
+ [(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)],
+ [(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)]
+ ]
+ t_left = [2, 3, 7, 8]
+ t_right = [0, 1, 10, 11]
+
+ elif d.hip_model == 'FLAT':
+ # square hips "eternit like"
+ t_pts = [Vector((sx * x, sy * y, sz * z)) for x, y, z in [
+ (-0.5, -0.4, 0.0), (-0.5, -0.4, 0.5), (-0.5, 0.4, 0.0),
+ (-0.5, 0.4, 0.5), (0.5, -0.5, 0.5), (0.5, -0.5, 1.0),
+ (0.5, 0.5, 0.5), (0.5, 0.5, 1.0), (-0.5, 0.33, 0.0),
+ (-0.5, -0.33, 0.0), (0.5, -0.33, 0.5), (0.5, 0.33, 0.5),
+ (-0.5, 0.33, -0.5), (-0.5, -0.33, -0.5), (0.5, -0.33, -0.5),
+ (0.5, 0.33, -0.5)]
+ ]
+ t_faces = [
+ (0, 1, 3, 2, 8, 9), (2, 3, 7, 6), (6, 7, 5, 4, 10, 11),
+ (4, 5, 1, 0), (9, 10, 4, 0), (7, 3, 1, 5),
+ (2, 6, 11, 8), (9, 8, 12, 13), (12, 15, 14, 13),
+ (8, 11, 15, 12), (10, 9, 13, 14), (11, 10, 14, 15)]
+ t_uvs = [
+ [(0.5, 1.0), (0.93, 0.75), (0.93, 0.25), (0.5, 0.0), (0.07, 0.25), (0.07, 0.75)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.5, 1.0), (0.93, 0.75), (0.93, 0.25), (0.5, 0.0), (0.07, 0.25), (0.07, 0.75)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
+ [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
+ ]
+ t_left = []
+ t_right = []
+
+ t_idmats = [idmat for f in t_faces]
+
+ for pan in self.pans:
+ for i, s in enumerate(pan.segs):
+ if ('LINK' in s.type and
+ d.beam_sec_enable):
+ ##############
+ # beam inside
+ ##############
+ f = len(verts)
+
+ s0 = s.offset(-0.5 * d.beam_sec_width)
+
+ s2 = pan.last_seg(i)
+ s3 = pan.next_seg(i)
+
+ res, p0, t0 = s0.intersect(s2)
+ res, p1, t1 = s0.intersect(s3)
+
+ p0 = s.lerp(t0)
+ p1 = s.lerp(t1)
+
+ x0, y0 = s0.lerp(t0)
+ x1, y1 = s.p0
+
+ z0 = self.z + d.beam_sec_alt + pan.altitude(p0)
+ z1 = z0 - d.beam_sec_height
+ z2 = self.z + d.beam_sec_alt + pan.altitude(s.p0)
+ z3 = z2 - d.beam_sec_height
+
+ verts.extend([
+ (x0, y0, z0),
+ (x0, y0, z1),
+ (x1, y1, z2),
+ (x1, y1, z3)
+ ])
+
+ x2, y2 = s0.lerp(t1)
+ x3, y3 = s.p1
+
+ z0 = self.z + d.beam_sec_alt + pan.altitude(p1)
+ z1 = z0 - d.beam_sec_height
+ z2 = self.z + d.beam_sec_alt + pan.altitude(s.p1)
+ z3 = z2 - d.beam_sec_height
+
+ verts.extend([
+ (x2, y2, z0),
+ (x2, y2, z1),
+ (x3, y3, z2),
+ (x3, y3, z3)
+ ])
+
+ faces.extend([
+ (f, f + 4, f + 5, f + 1),
+ (f + 1, f + 5, f + 7, f + 3),
+ (f + 2, f + 3, f + 7, f + 6),
+ (f + 2, f + 6, f + 4, f),
+ (f, f + 1, f + 3, f + 2),
+ (f + 5, f + 4, f + 6, f + 7)
+ ])
+ matids.extend([
+ idmat_poutre, idmat_poutre, idmat_poutre,
+ idmat_poutre, idmat_poutre, idmat_poutre
+ ])
+ uvs.extend([
+ [(0, 0), (1, 0), (1, 1), (0, 1)],
+ [(0, 0), (1, 0), (1, 1), (0, 1)],
+ [(0, 0), (1, 0), (1, 1), (0, 1)],
+ [(0, 0), (1, 0), (1, 1), (0, 1)],
+ [(0, 0), (1, 0), (1, 1), (0, 1)],
+ [(0, 0), (1, 0), (1, 1), (0, 1)]
+ ])
+
+ if s.type == 'LINK_HIP':
+
+ # TODO:
+ # Slice borders properly
+
+ if d.hip_enable:
+
+ s0 = pan.last_seg(i)
+ s1 = pan.next_seg(i)
+ s2 = s
+ p0 = s0.p1
+ p1 = s1.p0
+ z0 = pan.altitude(p0)
+ z1 = pan.altitude(p1)
+
+ # s0 is top seg
+ if z1 > z0:
+ p0, p1 = p1, p0
+ z0, z1 = z1, z0
+ s2 = s2.oposite
+ dz = pan.altitude(s2.sized_normal(0, 1).p1) - z0
+
+ if dz < 0:
+ s1 = s1.offset(d.tile_border)
+ # vx from p0 to p1
+ x, y = p1 - p0
+ v = Vector((x, y, z1 - z0))
+ vx = v.normalized()
+ vy = vx.cross(Vector((0, 0, 1)))
+ vz = vy.cross(vx)
+
+ x0, y0 = p0 + d.hip_alt * vz.to_2d()
+ z2 = z0 + self.z + d.hip_alt * vz.z
+ tM = Matrix([
+ [vx.x, vy.x, vz.x, x0],
+ [vx.y, vy.y, vz.y, y0],
+ [vx.z, vy.z, vz.z, z2],
+ [0, 0, 0, 1]
+ ])
+ space_x = v.length - d.tile_border
+ n_x = 1 + int(space_x / d.hip_space_x)
+ dx = space_x / n_x
+ x0 = 0.5 * dx
+
+ t_verts = [p for p in t_pts]
+
+ # apply slope
+
+ for i in t_left:
+ t_verts[i] = t_verts[i].copy()
+ t_verts[i].z -= dz * t_verts[i].y
+ for i in t_right:
+ t_verts[i] = t_verts[i].copy()
+ t_verts[i].z += dz * t_verts[i].y
+
+ for k in range(n_x):
+ lM = tM * Matrix([
+ [1, 0, 0, x0 + k * dx],
+ [0, -1, 0, 0],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]
+ ])
+ f = len(verts)
+
+ verts.extend([lM * p for p in t_verts])
+ faces.extend([tuple(i + f for i in p) for p in t_faces])
+ matids.extend(t_idmats)
+ uvs.extend(t_uvs)
+
+ elif s.type == 'LINK_VALLEY':
+ if d.valley_enable:
+ f = len(verts)
+ s0 = s.offset(-2 * d.tile_couloir)
+ s1 = pan.last_seg(i)
+ s2 = pan.next_seg(i)
+ res, p0, t = s0.intersect(s1)
+ res, p1, t = s0.intersect(s2)
+ alt = self.z + d.valley_altitude
+ x0, y0 = s1.p1
+ x1, y1 = p0
+ x2, y2 = p1
+ x3, y3 = s2.p0
+ z0 = alt + pan.altitude(s1.p1)
+ z1 = alt + pan.altitude(p0)
+ z2 = alt + pan.altitude(p1)
+ z3 = alt + pan.altitude(s2.p0)
+
+ verts.extend([
+ (x0, y0, z0),
+ (x1, y1, z1),
+ (x2, y2, z2),
+ (x3, y3, z3),
+ ])
+ faces.extend([
+ (f, f + 3, f + 2, f + 1)
+ ])
+ matids.extend([
+ idmat_valley
+ ])
+ uvs.extend([
+ [(0, 0), (1, 0), (1, 1), (0, 1)]
+ ])
+
+ elif s.type == 'AXIS' and d.hip_enable and pan.side == 'LEFT':
+
+ tmin = 0
+ tmax = 1
+ s0 = pan.last_seg(i)
+ if s0.type == 'SIDE':
+ tmin = 0 - d.tile_side / s.length
+ s1 = pan.next_seg(i)
+
+ if s1.type == 'SIDE':
+ tmax = 1 + d.tile_side / s.length
+
+ # print("tmin:%s tmax:%s" % (tmin, tmax))
+ ####################
+ # Faitiere
+ ####################
+
+ f = len(verts)
+ s_len = (tmax - tmin) * s.length
+ n_obj = 1 + int(s_len / d.hip_space_x)
+ dx = s_len / n_obj
+ x0 = 0.5 * dx
+ v = s.v.normalized()
+ p0 = s.lerp(tmin)
+ tM = Matrix([
+ [v.x, v.y, 0, p0.x],
+ [v.y, -v.x, 0, p0.y],
+ [0, 0, 1, self.z + d.hip_alt],
+ [0, 0, 0, 1]
+ ])
+ t_verts = [p.copy() for p in t_pts]
+
+ # apply slope
+ for i in t_left:
+ t_verts[i].z += t_verts[i].y * (pan.other_side.slope - d.tile_size_z / d.tile_size_y)
+ for i in t_right:
+ t_verts[i].z -= t_verts[i].y * (pan.slope - d.tile_size_z / d.tile_size_y)
+
+ for k in range(n_obj):
+ lM = tM * Matrix([
+ [1, 0, 0, x0 + k * dx],
+ [0, -1, 0, 0],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]
+ ])
+ v = len(verts)
+ verts.extend([lM * p for p in t_verts])
+ faces.extend([tuple(i + v for i in f) for f in t_faces])
+ matids.extend(t_idmats)
+ uvs.extend(t_uvs)
+
+ def make_hole(self, context, hole_obj, o, d, update_parent=False):
+ """
+ Hole for t child on parent
+ create / update a RoofCutter on parent
+ assume context object is child roof
+ with parent set
+ """
+ # print("Make hole :%s hole_obj:%s" % (o.name, hole_obj))
+ if o.parent is None:
+ return
+ # root is a RoofSegment
+ root = self.nodes[0].root
+ r_pan = root.right
+ l_pan = root.left
+
+ # merge :
+ # 5 ____________ 4
+ # / |
+ # / left |
+ # /_____axis_____| 3 <- kill axis and this one
+ # 0\ |
+ # \ right |
+ # 1 \____________| 2
+ #
+ # degenerate case:
+ #
+ # /|
+ # / |
+ # \ |
+ # \|
+ #
+
+ segs = []
+ last = len(r_pan.segs) - 1
+ for i, seg in enumerate(r_pan.segs):
+ # r_pan start parent roof side
+ if i == last:
+ to_merge = seg.copy
+ elif seg.type != 'AXIS':
+ segs.append(seg.copy)
+
+ for i, seg in enumerate(l_pan.segs):
+ # l_pan end parent roof side
+ if i == 1:
+ # 0 is axis
+ to_merge.p1 = seg.p1
+ segs.append(to_merge)
+ elif seg.type != 'AXIS':
+ segs.append(seg.copy)
+
+ # if there is side offset:
+ # create an arrow
+ #
+ # 4 s4
+ # /|
+ # / |___s1_______
+ # / p3 | p2 s3
+ # 0\ p0___s0_______| p1
+ # \ |
+ # 1 \|
+ s0 = root.left._axis.offset(
+ max(0.001,
+ min(
+ root.right.ysize - 0.001,
+ root.right.ysize - d.hole_offset_right
+ )
+ ))
+ s1 = root.left._axis.offset(
+ -max(0.001,
+ min(
+ root.left.ysize - 0.001,
+ root.left.ysize - d.hole_offset_left
+ )
+ ))
+
+ s3 = segs[2].offset(
+ -min(root.left.xsize - 0.001, d.hole_offset_front)
+ )
+ s4 = segs[0].copy
+ p1 = s4.p1
+ s4.p1 = segs[-1].p0
+ s4.p0 = p1
+ res, p0, t = s4.intersect(s0)
+ res, p1, t = s0.intersect(s3)
+ res, p2, t = s1.intersect(s3)
+ res, p3, t = s4.intersect(s1)
+ pts = []
+ # pts in cw order for 'DIFFERENCE' mode
+ pts.extend([segs[-1].p1, segs[-1].p0])
+ if (segs[-1].p0 - p3).length > 0.001:
+ pts.append(p3)
+ pts.extend([p2, p1])
+ if (segs[0].p1 - p0).length > 0.001:
+ pts.append(p0)
+ pts.extend([segs[0].p1, segs[0].p0])
+
+ pts = [p.to_3d() for p in pts]
+
+ if hole_obj is None:
+ context.scene.objects.active = o.parent
+ bpy.ops.archipack.roof_cutter(parent=d.t_parent)
+ hole_obj = context.active_object
+ else:
+ context.scene.objects.active = hole_obj
+
+ hole_obj.select = True
+ if d.parts[0].a0 < 0:
+ y = -d.t_dist_y
+ else:
+ y = d.t_dist_y
+
+ hole_obj.matrix_world = o.matrix_world * Matrix([
+ [1, 0, 0, 0],
+ [0, 1, 0, y],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]
+ ])
+
+ hd = archipack_roof_cutter.datablock(hole_obj)
+ hd.boundary = o.name
+ hd.update_points(context, hole_obj, pts, update_parent=update_parent)
+ hole_obj.select = False
+
+ context.scene.objects.active = o
+
+ def change_coordsys(self, fromTM, toTM):
+ """
+ move shape fromTM into toTM coordsys
+ """
+ dp = (toTM.inverted() * fromTM.translation).to_2d()
+ da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d())
+ ca = cos(da)
+ sa = sin(da)
+ rM = Matrix([
+ [ca, -sa],
+ [sa, ca]
+ ])
+ for s in self.segs:
+ tp = (rM * s.p0) - s.p0 + dp
+ s.rotate(da)
+ s.translate(tp)
+
+ def t_partition(self, array, begin, end):
+ pivot = begin
+ for i in range(begin + 1, end + 1):
+ # wall idx
+ if array[i][0] < array[begin][0]:
+ pivot += 1
+ array[i], array[pivot] = array[pivot], array[i]
+ array[pivot], array[begin] = array[begin], array[pivot]
+ return pivot
+
+ def sort_t(self, array, begin=0, end=None):
+ # print("sort_child")
+ if end is None:
+ end = len(array) - 1
+
+ def _quicksort(array, begin, end):
+ if begin >= end:
+ return
+ pivot = self.t_partition(array, begin, end)
+ _quicksort(array, begin, pivot - 1)
+ _quicksort(array, pivot + 1, end)
+ return _quicksort(array, begin, end)
+
+ def make_wall_fit(self, context, o, wall, inside):
+ wd = wall.data.archipack_wall2[0]
+ wg = wd.get_generator()
+ z0 = self.z - wd.z
+
+ # wg in roof coordsys
+ wg.change_coordsys(wall.matrix_world, o.matrix_world)
+
+ if inside:
+ # fit inside
+ offset = -0.5 * (1 - wd.x_offset) * wd.width
+ else:
+ # fit outside
+ offset = 0
+
+ wg.set_offset(offset)
+
+ wall_t = [[] for w in wg.segs]
+
+ for pan in self.pans:
+ # walls segment
+ for widx, wseg in enumerate(wg.segs):
+
+ ls = wseg.line.length
+
+ for seg in pan.segs:
+ # intersect with a roof segment
+ # any linked or axis intersection here
+ # will be dup as they are between 2 roof parts
+ res, p, t, v = wseg.line.intersect_ext(seg)
+ if res:
+ z = z0 + pan.altitude(p)
+ wall_t[widx].append((t, z, t * ls))
+
+ # lie under roof
+ if type(wseg).__name__ == "CurvedWall":
+ for step in range(12):
+ t = step / 12
+ p = wseg.line.lerp(t)
+ if pan.inside(p):
+ z = z0 + pan.altitude(p)
+ wall_t[widx].append((t, z, t * ls))
+ else:
+ if pan.inside(wseg.line.p0):
+ z = z0 + pan.altitude(wseg.line.p0)
+ wall_t[widx].append((0, z, 0))
+
+ old = context.active_object
+ old_sel = wall.select
+ wall.select = True
+ context.scene.objects.active = wall
+
+ wd.auto_update = False
+ # setup splits count and first split to 0
+ for widx, seg in enumerate(wall_t):
+ self.sort_t(seg)
+ # print("seg: %s" % seg)
+ for s in seg:
+ t, z, d = s
+ wd.parts[widx].n_splits = len(seg) + 1
+ wd.parts[widx].z[0] = 0
+ wd.parts[widx].t[0] = 0
+ break
+
+ # add splits, skip dups
+ for widx, seg in enumerate(wall_t):
+ t0 = 0
+ last_d = -1
+ id = 1
+ for s in seg:
+ t, z, d = s
+ if t == 0:
+ # add at end of last segment
+ if widx > 0:
+ lid = wd.parts[widx - 1].n_splits - 1
+ wd.parts[widx - 1].z[lid] = z
+ wd.parts[widx - 1].t[lid] = 1
+ else:
+ wd.parts[widx].z[0] = z
+ wd.parts[widx].t[0] = t
+ id = 1
+ else:
+ if d - last_d < 0.001:
+ wd.parts[widx].n_splits -= 1
+ continue
+ wd.parts[widx].z[id] = z
+ wd.parts[widx].t[id] = t - t0
+ t0 = t
+ id += 1
+ last_d = d
+
+ if wd.closed:
+ last = wd.parts[wd.n_parts].n_splits - 1
+ wd.parts[wd.n_parts].z[last] = wd.parts[0].z[0]
+ wd.parts[wd.n_parts].t[last] = 1.0
+
+ wd.auto_update = True
+ """
+ for s in self.segs:
+ s.as_curve(context)
+ for s in wg.segs:
+ s.as_curve(context)
+ """
+ wall.select = old_sel
+ context.scene.objects.active = old
+
+ def boundary(self, context, o):
+ """
+ either external or holes cuts
+ """
+ for b in o.children:
+ d = archipack_roof_cutter.datablock(b)
+ if d is not None:
+ g = d.ensure_direction()
+ g.change_coordsys(b.matrix_world, o.matrix_world)
+ for pan in self.pans:
+ pan.slice(g)
+ pan.limits()
+
+ def draft(self, context, verts, edges):
+ for pan in self.pans:
+ pan.draw(context, self.z, verts, edges)
+
+ for s in self.segs:
+ if s.constraint_type == 'SLOPE':
+ f = len(verts)
+ p0 = s.p0.to_3d()
+ p0.z = self.z
+ p1 = s.p1.to_3d()
+ p1.z = self.z
+ verts.extend([p0, p1])
+ edges.append([f, f + 1])
+
+
+def update(self, context):
+ self.update(context)
+
+
+def update_manipulators(self, context):
+ self.update(context, manipulable_refresh=True)
+
+
+def update_path(self, context):
+ self.update_path(context)
+
+
+def update_parent(self, context):
+
+ # update part a0
+ o = context.active_object
+ p, d = self.find_parent(context)
+
+ if d is not None:
+
+ o.parent = p
+
+ # trigger object update
+ # hole creation and parent's update
+
+ self.parts[0].a0 = pi / 2
+
+ elif self.t_parent != "":
+ self.t_parent = ""
+
+
+def update_cutter(self, context):
+ self.update(context, update_hole=True)
+
+
+def update_childs(self, context):
+ self.update(context, update_childs=True, update_hole=True)
+
+
+def update_components(self, context):
+ self.update(context, update_parent=False, update_hole=False)
+
+
+class ArchipackSegment():
+ length = FloatProperty(
+ name="length",
+ min=0.01,
+ max=1000.0,
+ default=2.0,
+ update=update
+ )
+ a0 = FloatProperty(
+ name="angle",
+ min=-2 * pi,
+ max=2 * pi,
+ default=0,
+ subtype='ANGLE', unit='ROTATION',
+ update=update_cutter
+ )
+ manipulators = CollectionProperty(type=archipack_manipulator)
+
+
+class ArchipackLines():
+ n_parts = IntProperty(
+ name="parts",
+ min=1,
+ default=1, update=update_manipulators
+ )
+ # UI layout related
+ parts_expand = BoolProperty(
+ default=False
+ )
+
+ def draw(self, layout, context):
+ box = layout.box()
+ row = box.row()
+ if self.parts_expand:
+ row.prop(self, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False)
+ box.prop(self, 'n_parts')
+ for i, part in enumerate(self.parts):
+ part.draw(layout, context, i)
+ else:
+ row.prop(self, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False)
+
+ def update_parts(self):
+ # print("update_parts")
+ # remove rows
+ # NOTE:
+ # n_parts+1
+ # as last one is end point of last segment or closing one
+ for i in range(len(self.parts), self.n_parts + 1, -1):
+ self.parts.remove(i - 1)
+
+ # add rows
+ for i in range(len(self.parts), self.n_parts + 1):
+ self.parts.add()
+
+ self.setup_manipulators()
+
+ def setup_parts_manipulators(self):
+ for i in range(self.n_parts + 1):
+ p = self.parts[i]
+ n_manips = len(p.manipulators)
+ if n_manips < 1:
+ s = p.manipulators.add()
+ s.type_key = "ANGLE"
+ s.prop1_name = "a0"
+ if n_manips < 2:
+ s = p.manipulators.add()
+ s.type_key = "SIZE"
+ s.prop1_name = "length"
+ if n_manips < 3:
+ s = p.manipulators.add()
+ s.type_key = 'WALL_SNAP'
+ s.prop1_name = str(i)
+ s.prop2_name = 'z'
+ if n_manips < 4:
+ s = p.manipulators.add()
+ s.type_key = 'DUMB_STRING'
+ s.prop1_name = str(i + 1)
+ if n_manips < 5:
+ s = p.manipulators.add()
+ s.type_key = "SIZE"
+ s.prop1_name = "offset"
+ p.manipulators[2].prop1_name = str(i)
+ p.manipulators[3].prop1_name = str(i + 1)
+
+
+class archipack_roof_segment(ArchipackSegment, PropertyGroup):
+
+ bound_idx = IntProperty(
+ default=0,
+ min=0,
+ update=update_manipulators
+ )
+ width_left = FloatProperty(
+ name="L Width",
+ min=0.01,
+ default=3.0,
+ update=update_cutter
+ )
+ width_right = FloatProperty(
+ name="R Width",
+ min=0.01,
+ default=3.0,
+ update=update_cutter
+ )
+ slope_left = FloatProperty(
+ name="L slope",
+ min=0.0,
+ default=0.3,
+ update=update_cutter
+ )
+ slope_right = FloatProperty(
+ name="R slope",
+ min=0.0,
+ default=0.3,
+ update=update_cutter
+ )
+ auto_left = EnumProperty(
+ description="Left mode",
+ name="Left",
+ items=(
+ ('AUTO', 'Auto', '', 0),
+ ('WIDTH', 'Width', '', 1),
+ ('SLOPE', 'Slope', '', 2),
+ ('ALL', 'All', '', 3),
+ ),
+ default="AUTO",
+ update=update_manipulators
+ )
+ auto_right = EnumProperty(
+ description="Right mode",
+ name="Right",
+ items=(
+ ('AUTO', 'Auto', '', 0),
+ ('WIDTH', 'Width', '', 1),
+ ('SLOPE', 'Slope', '', 2),
+ ('ALL', 'All', '', 3),
+ ),
+ default="AUTO",
+ update=update_manipulators
+ )
+ triangular_end = BoolProperty(
+ name="Tri end",
+ default=False,
+ update=update
+ )
+ take_precedence = BoolProperty(
+ name="Take precedence",
+ description="On T segment take width precedence",
+ default=False,
+ update=update
+ )
+
+ constraint_type = EnumProperty(
+ items=(
+ ('HORIZONTAL', 'Horizontal', '', 0),
+ ('SLOPE', 'Slope', '', 1)
+ ),
+ default='HORIZONTAL',
+ update=update_manipulators
+ )
+
+ enforce_part = EnumProperty(
+ name="Enforce part",
+ items=(
+ ('AUTO', 'Auto', '', 0),
+ ('VALLEY', 'Valley', '', 1),
+ ('HIP', 'Hip', '', 2)
+ ),
+ default='AUTO',
+ update=update
+ )
+
+ def find_in_selection(self, context):
+ """
+ find witch selected object this instance belongs to
+ provide support for "copy to selected"
+ """
+ selected = [o for o in context.selected_objects]
+ for o in selected:
+ d = archipack_roof.datablock(o)
+ if d:
+ for part in d.parts:
+ if part == self:
+ return d
+ return None
+
+ def draw(self, layout, context, index):
+ box = layout.box()
+ if index > 0:
+ box.prop(self, "constraint_type", text=str(index + 1))
+ if self.constraint_type == 'SLOPE':
+ box.prop(self, "enforce_part", text="")
+ else:
+ box.label("Part 1:")
+ box.prop(self, "length")
+ box.prop(self, "a0")
+
+ if index > 0:
+ box.prop(self, 'bound_idx')
+ if self.constraint_type == 'HORIZONTAL':
+ box.prop(self, "triangular_end")
+ row = box.row(align=True)
+ row.prop(self, "auto_left", text="")
+ row.prop(self, "auto_right", text="")
+ if self.auto_left in {'ALL', 'WIDTH'}:
+ box.prop(self, "width_left")
+ if self.auto_left in {'ALL', 'SLOPE'}:
+ box.prop(self, "slope_left")
+ if self.auto_right in {'ALL', 'WIDTH'}:
+ box.prop(self, "width_right")
+ if self.auto_right in {'ALL', 'SLOPE'}:
+ box.prop(self, "slope_right")
+
+ def update(self, context, manipulable_refresh=False, update_hole=False):
+ props = self.find_in_selection(context)
+ if props is not None:
+ props.update(context,
+ manipulable_refresh,
+ update_parent=True,
+ update_hole=True,
+ update_childs=True)
+
+
+class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup):
+ parts = CollectionProperty(type=archipack_roof_segment)
+ z = FloatProperty(
+ name="z",
+ default=3, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_childs
+ )
+ slope_left = FloatProperty(
+ name="L slope",
+ default=0.5, precision=2, step=1,
+ # unit='LENGTH', subtype='DISTANCE',
+ update=update_childs
+ )
+ slope_right = FloatProperty(
+ name="R slope",
+ default=0.5, precision=2, step=1,
+ # unit='LENGTH', subtype='DISTANCE',
+ update=update_childs
+ )
+ width_left = FloatProperty(
+ name="L width",
+ default=3, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_cutter
+ )
+ width_right = FloatProperty(
+ name="R width",
+ default=3, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_cutter
+ )
+ draft = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Draft mode",
+ default=False,
+ update=update_manipulators
+ )
+ auto_update = BoolProperty(
+ options={'SKIP_SAVE'},
+ default=True,
+ update=update_manipulators
+ )
+ quick_edit = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Quick Edit",
+ default=True
+ )
+ force_update = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Throttle",
+ default=True
+ )
+
+ tile_enable = BoolProperty(
+ name="Enable",
+ default=True,
+ update=update_components
+ )
+ tile_solidify = BoolProperty(
+ name="Solidify",
+ default=True,
+ update=update_components
+ )
+ tile_height = FloatProperty(
+ name="Height",
+ description="Amount for solidify",
+ min=0,
+ default=0.02,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_bevel = BoolProperty(
+ name="Bevel",
+ default=False,
+ update=update_components
+ )
+ tile_bevel_amt = FloatProperty(
+ name="Amount",
+ description="Amount for bevel",
+ min=0,
+ default=0.02,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_bevel_segs = IntProperty(
+ name="Segs",
+ description="Bevel Segs",
+ min=1,
+ default=2,
+ update=update_components
+ )
+ tile_alternate = BoolProperty(
+ name="Alternate",
+ default=False,
+ update=update_components
+ )
+ tile_offset = FloatProperty(
+ name="Offset",
+ description="Offset from start",
+ min=0,
+ max=100,
+ subtype="PERCENTAGE",
+ update=update_components
+ )
+ tile_altitude = FloatProperty(
+ name="Altitude",
+ description="Altitude from roof",
+ default=0.1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_size_x = FloatProperty(
+ name="x",
+ description="Size of tiles on x axis",
+ min=0.01,
+ default=0.2,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_size_y = FloatProperty(
+ name="y",
+ description="Size of tiles on y axis",
+ min=0.01,
+ default=0.3,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_size_z = FloatProperty(
+ name="z",
+ description="Size of tiles on z axis",
+ min=0.0,
+ default=0.02,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_space_x = FloatProperty(
+ name="x",
+ description="Space between tiles on x axis",
+ min=0.01,
+ default=0.2,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_space_y = FloatProperty(
+ name="y",
+ description="Space between tiles on y axis",
+ min=0.01,
+ default=0.3,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_fit_x = BoolProperty(
+ name="Fit x",
+ description="Fit roof on x axis",
+ default=True,
+ update=update_components
+ )
+ tile_fit_y = BoolProperty(
+ name="Fit y",
+ description="Fit roof on y axis",
+ default=True,
+ update=update_components
+ )
+ tile_expand = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Tiles",
+ description="Expand tiles panel",
+ default=False
+ )
+ tile_model = EnumProperty(
+ name="model",
+ items=(
+ ('BRAAS1', 'Braas 1', '', 0),
+ ('BRAAS2', 'Braas 2', '', 1),
+ ('ETERNIT', 'Eternit', '', 2),
+ ('LAUZE', 'Lauze', '', 3),
+ ('ROMAN', 'Roman', '', 4),
+ ('ROUND', 'Round', '', 5),
+ ('PLACEHOLDER', 'Square', '', 6),
+ ('ONDULEE', 'Ondule', '', 7),
+ ('METAL', 'Metal', '', 8),
+ # ('USER', 'User defined', '', 7)
+ ),
+ default="BRAAS2",
+ update=update_components
+ )
+ tile_side = FloatProperty(
+ name="Side",
+ description="Space on side",
+ default=0,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_couloir = FloatProperty(
+ name="Valley",
+ description="Space between tiles on valley",
+ min=0,
+ default=0.05,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ tile_border = FloatProperty(
+ name="Bottom",
+ description="Tiles offset from bottom",
+ default=0,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+
+ gutter_expand = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Gutter",
+ description="Expand gutter panel",
+ default=False
+ )
+ gutter_enable = BoolProperty(
+ name="Enable",
+ default=True,
+ update=update_components
+ )
+ gutter_alt = FloatProperty(
+ name="Altitude",
+ description="altitude",
+ default=0,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ gutter_width = FloatProperty(
+ name="Width",
+ description="Width",
+ min=0.01,
+ default=0.15,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ gutter_dist = FloatProperty(
+ name="Spacing",
+ description="Spacing",
+ min=0,
+ default=0.05,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ gutter_boudin = FloatProperty(
+ name="Small width",
+ description="Small width",
+ min=0,
+ default=0.015,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ gutter_segs = IntProperty(
+ default=6,
+ min=1,
+ name="Segs",
+ update=update_components
+ )
+
+ beam_expand = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Beam",
+ description="Expand beam panel",
+ default=False
+ )
+ beam_enable = BoolProperty(
+ name="Primary",
+ default=True,
+ update=update_components
+ )
+ beam_width = FloatProperty(
+ name="Width",
+ description="Width",
+ min=0.01,
+ default=0.2,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ beam_height = FloatProperty(
+ name="Height",
+ description="Height",
+ min=0.01,
+ default=0.35,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ beam_offset = FloatProperty(
+ name="Offset",
+ description="Distance from roof border",
+ default=0.02,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ beam_alt = FloatProperty(
+ name="Altitude",
+ description="Altitude from roof",
+ default=-0.15,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ beam_sec_enable = BoolProperty(
+ name="Hip rafter",
+ default=True,
+ update=update_components
+ )
+ beam_sec_width = FloatProperty(
+ name="Width",
+ description="Width",
+ min=0.01,
+ default=0.15,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ beam_sec_height = FloatProperty(
+ name="Height",
+ description="Height",
+ min=0.01,
+ default=0.2,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ beam_sec_alt = FloatProperty(
+ name="Altitude",
+ description="Distance from roof",
+ default=-0.1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rafter_enable = BoolProperty(
+ name="Rafter",
+ default=True,
+ update=update_components
+ )
+ rafter_width = FloatProperty(
+ name="Width",
+ description="Width",
+ min=0.01,
+ default=0.1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rafter_height = FloatProperty(
+ name="Height",
+ description="Height",
+ min=0.01,
+ default=0.2,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rafter_spacing = FloatProperty(
+ name="Spacing",
+ description="Spacing",
+ min=0.1,
+ default=0.7,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rafter_start = FloatProperty(
+ name="Offset",
+ description="Spacing from roof border",
+ min=0,
+ default=0.1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rafter_alt = FloatProperty(
+ name="Altitude",
+ description="Altitude from roof",
+ max=-0.0001,
+ default=-0.001,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+
+ hip_enable = BoolProperty(
+ name="Enable",
+ default=True,
+ update=update_components
+ )
+ hip_expand = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Hips",
+ description="Expand hips panel",
+ default=False
+ )
+ hip_alt = FloatProperty(
+ name="Altitude",
+ description="Hip altitude from roof",
+ default=0.1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ hip_space_x = FloatProperty(
+ name="Spacing",
+ description="Space between hips",
+ min=0.01,
+ default=0.4,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ hip_size_x = FloatProperty(
+ name="l",
+ description="Length of hip",
+ min=0.01,
+ default=0.4,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ hip_size_y = FloatProperty(
+ name="w",
+ description="Width of hip",
+ min=0.01,
+ default=0.15,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ hip_size_z = FloatProperty(
+ name="h",
+ description="Height of hip",
+ min=0.0,
+ default=0.15,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ hip_model = EnumProperty(
+ name="model",
+ items=(
+ ('ROUND', 'Round', '', 0),
+ ('ETERNIT', 'Eternit', '', 1),
+ ('FLAT', 'Flat', '', 2)
+ ),
+ default="ROUND",
+ update=update_components
+ )
+ valley_altitude = FloatProperty(
+ name="Altitude",
+ description="Valley altitude from roof",
+ default=0.1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ valley_enable = BoolProperty(
+ name="Valley",
+ default=True,
+ update=update_components
+ )
+
+ facia_enable = BoolProperty(
+ name="Enable",
+ description="Enable Facia",
+ default=True,
+ update=update_components
+ )
+ facia_expand = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Facia",
+ description="Expand facia panel",
+ default=False
+ )
+ facia_height = FloatProperty(
+ name="Height",
+ description="Height",
+ min=0.01,
+ default=0.3,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ facia_width = FloatProperty(
+ name="Width",
+ description="Width",
+ min=0.01,
+ default=0.02,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ facia_offset = FloatProperty(
+ name="Offset",
+ description="Offset from roof border",
+ default=0,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ facia_altitude = FloatProperty(
+ name="Altitude",
+ description="Facia altitude from roof",
+ default=0.1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+
+ rake_enable = BoolProperty(
+ name="Enable",
+ description="Enable Rake",
+ default=True,
+ update=update_components
+ )
+ rake_expand = BoolProperty(
+ options={'SKIP_SAVE'},
+ name="Rake",
+ description="Expand rake panel",
+ default=False
+ )
+ rake_height = FloatProperty(
+ name="Height",
+ description="Height",
+ min=0.01,
+ default=0.3,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rake_width = FloatProperty(
+ name="Width",
+ description="Width",
+ min=0.01,
+ default=0.02,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rake_offset = FloatProperty(
+ name="Offset",
+ description="Offset from roof border",
+ default=0.001,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+ rake_altitude = FloatProperty(
+ name="Altitude",
+ description="Facia altitude from roof",
+ default=0.1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update_components
+ )
+
+ t_parent = StringProperty(
+ name="Parent",
+ default="",
+ update=update_parent
+ )
+ t_part = IntProperty(
+ name="Part",
+ description="Parent part index",
+ default=0,
+ min=0,
+ update=update_cutter
+ )
+ t_dist_x = FloatProperty(
+ name="Dist x",
+ description="Location on axis ",
+ default=0,
+ update=update_cutter
+ )
+ t_dist_y = FloatProperty(
+ name="Dist y",
+ description="Lateral distance from axis",
+ min=0.0001,
+ default=0.0001,
+ update=update_cutter
+ )
+
+ hole_offset_left = FloatProperty(
+ name="Left",
+ description="Left distance from border",
+ min=0,
+ default=0,
+ update=update_cutter
+ )
+ hole_offset_right = FloatProperty(
+ name="Right",
+ description="Right distance from border",
+ min=0,
+ default=0,
+ update=update_cutter
+ )
+ hole_offset_front = FloatProperty(
+ name="Front",
+ description="Front distance from border",
+ default=0,
+ update=update_cutter
+ )
+
+ def make_wall_fit(self, context, o, wall, inside=False):
+ origin = Vector((0, 0, self.z))
+ g = self.get_generator(origin)
+ g.make_roof(context)
+ g.make_wall_fit(context, o, wall, inside)
+
+ def update_parts(self):
+ # NOTE:
+ # n_parts+1
+ # as last one is end point of last segment or closing one
+ for i in range(len(self.parts), self.n_parts, -1):
+ self.parts.remove(i - 1)
+
+ # add rows
+ for i in range(len(self.parts), self.n_parts):
+ bound_idx = len(self.parts)
+ self.parts.add()
+ self.parts[-1].bound_idx = bound_idx
+
+ self.setup_manipulators()
+
+ def setup_manipulators(self):
+ if len(self.manipulators) < 1:
+ s = self.manipulators.add()
+ s.type_key = "SIZE"
+ s.prop1_name = "z"
+ s.normal = (0, 1, 0)
+ if len(self.manipulators) < 2:
+ s = self.manipulators.add()
+ s.type_key = "SIZE"
+ s.prop1_name = "width_left"
+ if len(self.manipulators) < 3:
+ s = self.manipulators.add()
+ s.type_key = "SIZE"
+ s.prop1_name = "width_right"
+
+ for i in range(self.n_parts):
+ p = self.parts[i]
+ n_manips = len(p.manipulators)
+ if n_manips < 1:
+ s = p.manipulators.add()
+ s.type_key = "ANGLE"
+ s.prop1_name = "a0"
+ if n_manips < 2:
+ s = p.manipulators.add()
+ s.type_key = "SIZE"
+ s.prop1_name = "length"
+ if n_manips < 3:
+ s = p.manipulators.add()
+ s.type_key = 'DUMB_STRING'
+ s.prop1_name = str(i + 1)
+ p.manipulators[2].prop1_name = str(i + 1)
+ if n_manips < 4:
+ s = p.manipulators.add()
+ s.type_key = 'SIZE'
+ s.prop1_name = "width_left"
+ if n_manips < 5:
+ s = p.manipulators.add()
+ s.type_key = 'SIZE'
+ s.prop1_name = "width_right"
+ if n_manips < 6:
+ s = p.manipulators.add()
+ s.type_key = 'SIZE'
+ s.prop1_name = "slope_left"
+ if n_manips < 7:
+ s = p.manipulators.add()
+ s.type_key = 'SIZE'
+ s.prop1_name = "slope_right"
+
+ def get_generator(self, origin=Vector((0, 0, 0))):
+ g = RoofGenerator(self, origin)
+
+ # TODO: sort part by bound idx so deps always find parent
+
+ for i, part in enumerate(self.parts):
+ # skip part if bound_idx > parent
+ # so deps always see parent
+ if part.bound_idx <= i:
+ g.add_part(part)
+ g.locate_manipulators()
+ return g
+
+ def make_surface(self, o, verts, edges):
+ bm = bmesh.new()
+ for v in verts:
+ bm.verts.new(v)
+ bm.verts.ensure_lookup_table()
+ for ed in edges:
+ bm.edges.new((bm.verts[ed[0]], bm.verts[ed[1]]))
+ bm.edges.ensure_lookup_table()
+ # bmesh.ops.contextual_create(bm, geom=bm.edges)
+ bm.to_mesh(o.data)
+ bm.free()
+
+ def find_parent(self, context):
+ o = context.scene.objects.get(self.t_parent)
+ return o, archipack_roof.datablock(o)
+
+ def intersection_angle(self, t_slope, t_width, p_slope, angle):
+ # 2d intersection angle between two roofs parts
+ dy = abs(t_slope * t_width / p_slope)
+ ca = cos(angle)
+ ta = tan(angle)
+ if ta == 0:
+ w0 = 0
+ else:
+ w0 = dy * ta
+ if ca == 0:
+ w1 = 0
+ else:
+ w1 = t_width / ca
+ dx = w1 - w0
+ return atan2(dy, dx)
+
+ def relocate_child(self, context, o, g, child):
+
+ d = archipack_roof.datablock(child)
+
+ if d is not None and d.t_part - 1 < len(g.segs):
+ # print("relocate_child(%s)" % (child.name))
+
+ seg = g.segs[d.t_part]
+ # adjust T part matrix_world from parent
+ # T part origin located on parent axis
+ # with y in parent direction
+ t = (d.t_dist_x / seg.length)
+ x, y, z = seg.lerp(t).to_3d()
+ dy = -seg.v.normalized()
+ child.matrix_world = o.matrix_world * Matrix([
+ [dy.x, -dy.y, 0, x],
+ [dy.y, dy.x, 0, y],
+ [0, 0, 1, z],
+ [0, 0, 0, 1]
+ ])
+
+ def relocate_childs(self, context, o, g):
+ for child in o.children:
+ d = archipack_roof.datablock(child)
+ if d is not None and d.t_parent == o.name:
+ self.relocate_child(context, o, g, child)
+
+ def update_childs(self, context, o, g):
+ for child in o.children:
+ d = archipack_roof.datablock(child)
+ if d is not None and d.t_parent == o.name:
+ # print("upate_childs(%s)" % (child.name))
+ child.select = True
+ context.scene.objects.active = child
+ # regenerate hole
+ d.update(context, update_hole=True, update_parent=False)
+ child.select = False
+ o.select = True
+ context.scene.objects.active = o
+
+ def update(self,
+ context,
+ manipulable_refresh=False,
+ update_childs=False,
+ update_parent=True,
+ update_hole=False,
+ force_update=False):
+ """
+ update_hole: on t_child must update parent
+ update_childs: force childs update
+ force_update: skip throttle
+ """
+ # print("update")
+ o = self.find_in_selection(context, self.auto_update)
+
+ if o is None:
+ return
+
+ # clean up manipulators before any data model change
+ if manipulable_refresh:
+ self.manipulable_disable(context)
+
+ self.update_parts()
+
+ verts, edges, faces, matids, uvs = [], [], [], [], []
+
+ y = 0
+ z = self.z
+ p, d = self.find_parent(context)
+ g = None
+
+ # t childs: use parent to relocate
+ # setup slopes into generator
+ if d is not None:
+ pg = d.get_generator()
+ pg.make_roof(context)
+
+ if self.t_part - 1 < len(pg.segs):
+
+ seg = pg.nodes[self.t_part].root
+
+ d.relocate_child(context, p, pg, o)
+
+ a0 = self.parts[0].a0
+ a_axis = a0 - pi / 2
+ a_offset = 0
+ s_left = self.slope_left
+ w_left = -self.width_left
+ s_right = self.slope_right
+ w_right = self.width_right
+ if a0 > 0:
+ # a_axis est mesure depuis la perpendiculaire à l'axe
+ slope = seg.right.slope
+ y = self.t_dist_y
+ else:
+ a_offset = pi
+ slope = seg.left.slope
+ y = -self.t_dist_y
+ s_left, s_right = s_right, s_left
+ w_left, w_right = -w_right, -w_left
+
+ if slope == 0:
+ slope = 0.0001
+
+ # print("slope: %s" % (slope))
+
+ z = d.z - self.t_dist_y * slope
+
+ # a_right from axis cross z
+
+ b_right = self.intersection_angle(
+ s_left,
+ w_left,
+ slope,
+ a_axis)
+
+ a_right = b_right + a_offset
+
+ b_left = self.intersection_angle(
+ s_right,
+ w_right,
+ slope,
+ a_axis)
+
+ a_left = b_left + a_offset
+
+ g = self.get_generator(origin=Vector((0, y, z)))
+
+ # override by user defined slope if any
+ make_right = True
+ make_left = True
+ for s in g.segs:
+ if (s.constraint_type == 'SLOPE' and
+ s.v0_idx == 0):
+ da = g.segs[0].v.angle_signed(s.v)
+ if da > 0:
+ make_left = False
+ else:
+ make_right = False
+
+ if make_left:
+ # Add 'SLOPE' constraints for segment 0
+ v = Vector((cos(a_left), sin(a_left)))
+ s = StraightRoof(g.origin, v)
+ s.v0_idx = 0
+ s.constraint_type = 'SLOPE'
+ # s.enforce_part = 'VALLEY'
+ s.angle_0 = a_left
+ s.take_precedence = False
+ g.segs.append(s)
+
+ if make_right:
+ v = Vector((cos(a_right), sin(a_right)))
+ s = StraightRoof(g.origin, v)
+ s.v0_idx = 0
+ s.constraint_type = 'SLOPE'
+ # s.enforce_part = 'VALLEY'
+ s.angle_0 = a_right
+ s.take_precedence = False
+ g.segs.append(s)
+
+ if g is None:
+ g = self.get_generator(origin=Vector((0, y, z)))
+
+ # setup per segment manipulators
+ if len(g.segs) > 0:
+ f = g.segs[0]
+ # z
+ n = f.straight(-1, 0).v.to_3d()
+ self.manipulators[0].set_pts([(0, 0, 0), (0, 0, self.z), (1, 0, 0)], normal=n)
+ # left width
+ n = f.sized_normal(0, -self.width_left)
+ self.manipulators[1].set_pts([n.p0.to_3d(), n.p1.to_3d(), (-1, 0, 0)])
+ # right width
+ n = f.sized_normal(0, self.width_right)
+ self.manipulators[2].set_pts([n.p0.to_3d(), n.p1.to_3d(), (1, 0, 0)])
+
+ g.make_roof(context)
+
+ # update childs here so parent may use
+ # new holes when parent shape does change
+ if update_childs:
+ self.update_childs(context, o, g)
+
+ # on t_child
+ if d is not None and update_hole:
+ hole_obj = self.find_hole(context, o)
+ g.make_hole(context, hole_obj, o, self, update_parent)
+ # print("make_hole")
+
+ # add cutters
+ g.boundary(context, o)
+
+ if self.draft:
+
+ g.draft(context, verts, edges)
+ g.gutter(self, verts, faces, edges, matids, uvs)
+ self.make_surface(o, verts, edges)
+
+ else:
+
+ if self.rake_enable:
+ g.rake(self, verts, faces, edges, matids, uvs)
+
+ if self.facia_enable:
+ g.facia(self, verts, faces, edges, matids, uvs)
+
+ if self.beam_enable:
+ g.beam_primary(self, verts, faces, edges, matids, uvs)
+
+ g.hips(self, verts, faces, edges, matids, uvs)
+
+ if self.gutter_enable:
+ g.gutter(self, verts, faces, edges, matids, uvs)
+
+ bmed.buildmesh(
+
+ context, o, verts, faces, matids=matids, uvs=uvs,
+ weld=False, clean=False, auto_smooth=True, temporary=False)
+
+ # bpy.ops.object.mode_set(mode='EDIT')
+ g.lambris(context, o, self)
+ # print("lambris")
+
+ if self.rafter_enable:
+ # bpy.ops.object.mode_set(mode='EDIT')
+ g.rafter(context, o, self)
+ # print("rafter")
+
+ if self.quick_edit and not force_update:
+ if self.tile_enable:
+ bpy.ops.archipack.roof_throttle_update(name=o.name)
+ else:
+ # throttle here
+ if self.tile_enable:
+ g.couverture(context, o, self)
+ # print("couverture")
+
+ # enable manipulators rebuild
+ if manipulable_refresh:
+ self.manipulable_refresh = True
+ # print("rafter")
+ # restore context
+ self.restore_context(context)
+ # print("restore context")
+
+ def find_hole(self, context, o):
+ p, d = self.find_parent(context)
+ if d is not None:
+ for child in p.children:
+ cd = archipack_roof_cutter.datablock(child)
+ if cd is not None and cd.boundary == o.name:
+ return child
+ return None
+
+ def manipulable_setup(self, context):
+ """
+ NOTE:
+ this one assume context.active_object is the instance this
+ data belongs to, failing to do so will result in wrong
+ manipulators set on active object
+ """
+ self.manipulable_disable(context)
+
+ o = context.active_object
+
+ self.setup_manipulators()
+
+ for i, part in enumerate(self.parts):
+
+ if i > 0:
+ # start angle
+ self.manip_stack.append(part.manipulators[0].setup(context, o, part))
+
+ if part.constraint_type == 'HORIZONTAL':
+ # length / radius + angle
+ self.manip_stack.append(part.manipulators[1].setup(context, o, part))
+
+ # index
+ self.manip_stack.append(part.manipulators[2].setup(context, o, self))
+
+ # size left
+ if part.auto_left in {'WIDTH', 'ALL'}:
+ self.manip_stack.append(part.manipulators[3].setup(context, o, part))
+ # size right
+ if part.auto_right in {'WIDTH', 'ALL'}:
+ self.manip_stack.append(part.manipulators[4].setup(context, o, part))
+ # slope left
+ if part.auto_left in {'SLOPE', 'ALL'}:
+ self.manip_stack.append(part.manipulators[5].setup(context, o, part))
+ # slope right
+ if part.auto_right in {'SLOPE', 'ALL'}:
+ self.manip_stack.append(part.manipulators[6].setup(context, o, part))
+
+ for m in self.manipulators:
+ self.manip_stack.append(m.setup(context, o, self))
+
+ def draw(self, layout, context):
+ box = layout.box()
+ row = box.row()
+ if self.parts_expand:
+ row.prop(self, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False)
+ box.prop(self, 'n_parts')
+ # box.prop(self, 'closed')
+ for i, part in enumerate(self.parts):
+ part.draw(layout, context, i)
+ else:
+ row.prop(self, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False)
+
+
+def update_hole(self, context):
+ # update parent's roof only when manipulated
+ self.update(context, update_parent=True)
+
+
+def update_operation(self, context):
+ self.reverse(context, make_ccw=(self.operation == 'INTERSECTION'))
+
+
+class archipack_roof_cutter_segment(ArchipackCutterPart, PropertyGroup):
+ manipulators = CollectionProperty(type=archipack_manipulator)
+ type = EnumProperty(
+ name="Type",
+ items=(
+ ('SIDE', 'Side', 'Side with rake', 0),
+ ('BOTTOM', 'Bottom', 'Bottom with gutter', 1),
+ ('LINK', 'Side link', 'Side witout decoration', 2),
+ ('AXIS', 'Top', 'Top part with hip and beam', 3)
+ # ('LINK_VALLEY', 'Side valley', 'Side with valley', 3),
+ # ('LINK_HIP', 'Side hip', 'Side with hip', 4)
+ ),
+ default='SIDE',
+ update=update_hole
+ )
+
+ def find_in_selection(self, context):
+ selected = [o for o in context.selected_objects]
+ for o in selected:
+ d = archipack_roof_cutter.datablock(o)
+ if d:
+ for part in d.parts:
+ if part == self:
+ return d
+ return None
+
+
+class archipack_roof_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup):
+ # boundary
+ parts = CollectionProperty(type=archipack_roof_cutter_segment)
+ boundary = StringProperty(
+ default="",
+ name="Boundary",
+ description="Boundary of t child to cut parent"
+ )
+
+ def update_points(self, context, o, pts, update_parent=False):
+ """
+ Create boundary from roof
+ """
+ self.auto_update = False
+ self.from_points(pts)
+ self.auto_update = True
+ if update_parent:
+ self.update_parent(context, o)
+
+ def update_parent(self, context, o):
+
+ d = archipack_roof.datablock(o.parent)
+ if d is not None:
+ o.parent.select = True
+ context.scene.objects.active = o.parent
+ d.update(context, update_childs=False, update_hole=False)
+ o.parent.select = False
+ context.scene.objects.active = o
+
+
+class ARCHIPACK_PT_roof_cutter(Panel):
+ bl_idname = "ARCHIPACK_PT_roof_cutter"
+ bl_label = "Roof Cutter"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = 'ArchiPack'
+
+ @classmethod
+ def poll(cls, context):
+ return archipack_roof_cutter.filter(context.active_object)
+
+ def draw(self, context):
+ prop = archipack_roof_cutter.datablock(context.active_object)
+ if prop is None:
+ return
+ layout = self.layout
+ scene = context.scene
+ box = layout.box()
+ box.operator('archipack.roof_cutter_manipulate', icon='HAND')
+ box.prop(prop, 'operation', text="")
+ box = layout.box()
+ box.label(text="From curve")
+ box.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
+ if prop.user_defined_path != "":
+ box.prop(prop, 'user_defined_resolution')
+ # box.prop(prop, 'x_offset')
+ # box.prop(prop, 'angle_limit')
+ """
+ box.prop_search(prop, "boundary", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
+ """
+ prop.draw(layout, context)
+
+
+class ARCHIPACK_PT_roof(Panel):
+ bl_idname = "ARCHIPACK_PT_roof"
+ bl_label = "Roof"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = 'ArchiPack'
+
+ @classmethod
+ def poll(cls, context):
+ return archipack_roof.filter(context.active_object)
+
+ def draw(self, context):
+ o = context.active_object
+ prop = archipack_roof.datablock(o)
+ if prop is None:
+ return
+ scene = context.scene
+ layout = self.layout
+ row = layout.row(align=True)
+ row.operator('archipack.roof_manipulate', icon='HAND')
+
+ box = layout.box()
+ row = box.row(align=True)
+ row.operator("archipack.roof_preset_menu", text=bpy.types.ARCHIPACK_OT_roof_preset_menu.bl_label)
+ row.operator("archipack.roof_preset", text="", icon='ZOOMIN')
+ row.operator("archipack.roof_preset", text="", icon='ZOOMOUT').remove_active = True
+ box = layout.box()
+ box.prop_search(prop, "t_parent", scene, "objects", text="Parent", icon='OBJECT_DATA')
+ layout.operator('archipack.roof_cutter').parent = o.name
+ p, d = prop.find_parent(context)
+ if d is not None:
+ box.prop(prop, 't_part')
+ box.prop(prop, 't_dist_x')
+ box.prop(prop, 't_dist_y')
+ box.label(text="Hole")
+ box.prop(prop, 'hole_offset_front')
+ box.prop(prop, 'hole_offset_left')
+ box.prop(prop, 'hole_offset_right')
+ box = layout.box()
+ box.prop(prop, 'quick_edit', icon="MOD_MULTIRES")
+ box.prop(prop, 'draft')
+ if d is None:
+ box.prop(prop, 'z')
+ box.prop(prop, 'slope_left')
+ box.prop(prop, 'slope_right')
+ box.prop(prop, 'width_left')
+ box.prop(prop, 'width_right')
+ # parts
+ prop.draw(layout, context)
+ # tiles
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.tile_expand:
+ row.prop(prop, 'tile_expand', icon="TRIA_DOWN", text="Tiles", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'tile_expand', icon="TRIA_RIGHT", text="Tiles", icon_only=True, emboss=False)
+ row.prop(prop, 'tile_enable')
+ if prop.tile_expand:
+ box.prop(prop, 'tile_model', text="")
+
+ box.prop(prop, 'tile_solidify', icon='MOD_SOLIDIFY')
+ if prop.tile_solidify:
+ box.prop(prop, 'tile_height')
+ box.separator()
+ box.prop(prop, 'tile_bevel', icon='MOD_BEVEL')
+ if prop.tile_bevel:
+ box.prop(prop, 'tile_bevel_amt')
+ box.prop(prop, 'tile_bevel_segs')
+ box.separator()
+ box.label(text="Tile size")
+ row = box.row(align=True)
+ row.prop(prop, 'tile_size_x')
+ row.prop(prop, 'tile_size_y')
+ row.prop(prop, 'tile_size_z')
+ box.prop(prop, 'tile_altitude')
+
+ box.separator()
+ box.label(text="Distribution")
+ box.prop(prop, 'tile_alternate', icon='NLA')
+ row = box.row(align=True)
+ row.prop(prop, 'tile_fit_x', icon='ALIGN')
+ row.prop(prop, 'tile_fit_y', icon='ALIGN')
+ box.prop(prop, 'tile_offset')
+
+ box.label(text="Spacing")
+ row = box.row(align=True)
+ row.prop(prop, 'tile_space_x')
+ row.prop(prop, 'tile_space_y')
+
+ box.separator() # hip
+ box.label(text="Borders")
+ box.prop(prop, 'tile_side')
+ box.prop(prop, 'tile_couloir')
+ box.prop(prop, 'tile_border')
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.hip_expand:
+ row.prop(prop, 'hip_expand', icon="TRIA_DOWN", text="Hip", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'hip_expand', icon="TRIA_RIGHT", text="Hip", icon_only=True, emboss=False)
+ row.prop(prop, 'hip_enable')
+ if prop.hip_expand:
+ box.prop(prop, 'hip_model', text="")
+
+ box.label(text="Hip size")
+ row = box.row(align=True)
+ row.prop(prop, 'hip_size_x')
+ row.prop(prop, 'hip_size_y')
+ row.prop(prop, 'hip_size_z')
+ box.prop(prop, 'hip_alt')
+ box.prop(prop, 'hip_space_x')
+ box.separator()
+ box.prop(prop, 'valley_enable')
+ box.prop(prop, 'valley_altitude')
+
+ box = layout.box()
+ row = box.row(align=True)
+
+ if prop.beam_expand:
+ row.prop(prop, 'beam_expand', icon="TRIA_DOWN", text="Beam", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'beam_expand', icon="TRIA_RIGHT", text="Beam", icon_only=True, emboss=False)
+ if prop.beam_expand:
+ box.prop(prop, 'beam_enable')
+ if prop.beam_enable:
+ box.prop(prop, 'beam_width')
+ box.prop(prop, 'beam_height')
+ box.prop(prop, 'beam_offset')
+ box.prop(prop, 'beam_alt')
+ box.separator()
+ box.prop(prop, 'beam_sec_enable')
+ if prop.beam_sec_enable:
+ box.prop(prop, 'beam_sec_width')
+ box.prop(prop, 'beam_sec_height')
+ box.prop(prop, 'beam_sec_alt')
+ box.separator()
+ box.prop(prop, 'rafter_enable')
+ if prop.rafter_enable:
+ box.prop(prop, 'rafter_height')
+ box.prop(prop, 'rafter_width')
+ box.prop(prop, 'rafter_spacing')
+ box.prop(prop, 'rafter_start')
+ box.prop(prop, 'rafter_alt')
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.gutter_expand:
+ row.prop(prop, 'gutter_expand', icon="TRIA_DOWN", text="Gutter", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'gutter_expand', icon="TRIA_RIGHT", text="Gutter", icon_only=True, emboss=False)
+ row.prop(prop, 'gutter_enable')
+ if prop.gutter_expand:
+ box.prop(prop, 'gutter_alt')
+ box.prop(prop, 'gutter_width')
+ box.prop(prop, 'gutter_dist')
+ box.prop(prop, 'gutter_boudin')
+ box.prop(prop, 'gutter_segs')
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.facia_expand:
+ row.prop(prop, 'facia_expand', icon="TRIA_DOWN", text="Facia", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'facia_expand', icon="TRIA_RIGHT", text="Facia", icon_only=True, emboss=False)
+ row.prop(prop, 'facia_enable')
+ if prop.facia_expand:
+ box.prop(prop, 'facia_altitude')
+ box.prop(prop, 'facia_width')
+ box.prop(prop, 'facia_height')
+ box.prop(prop, 'facia_offset')
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.rake_expand:
+ row.prop(prop, 'rake_expand', icon="TRIA_DOWN", text="Rake", icon_only=True, emboss=False)
+ else:
+ row.prop(prop, 'rake_expand', icon="TRIA_RIGHT", text="Rake", icon_only=True, emboss=False)
+ row.prop(prop, 'rake_enable')
+ if prop.rake_expand:
+ box.prop(prop, 'rake_altitude')
+ box.prop(prop, 'rake_width')
+ box.prop(prop, 'rake_height')
+ box.prop(prop, 'rake_offset')
+
+ """
+ box = layout.box()
+ row.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
+ box.prop(prop, 'user_defined_resolution')
+ box.prop(prop, 'angle_limit')
+ """
+
+
+# ------------------------------------------------------------------
+# Define operator class to create object
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_roof(ArchipackCreateTool, Operator):
+ bl_idname = "archipack.roof"
+ bl_label = "Roof"
+ bl_description = "Roof"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def create(self, context):
+ m = bpy.data.meshes.new("Roof")
+ o = bpy.data.objects.new("Roof", m)
+ d = m.archipack_roof.add()
+ # make manipulators selectable
+ d.manipulable_selectable = True
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.objects.active = o
+ self.add_material(o)
+ self.load_preset(d)
+ return o
+
+ # -----------------------------------------------------
+ # Execute
+ # -----------------------------------------------------
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ bpy.ops.object.select_all(action="DESELECT")
+ o = self.create(context)
+ o.location = context.scene.cursor_location
+ o.select = True
+ context.scene.objects.active = o
+ self.manipulate()
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator):
+ bl_idname = "archipack.roof_cutter"
+ bl_label = "Roof Cutter"
+ bl_description = "Roof Cutter"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ parent = StringProperty("")
+
+ def create(self, context):
+ m = bpy.data.meshes.new("Roof Cutter")
+ o = bpy.data.objects.new("Roof Cutter", m)
+ d = m.archipack_roof_cutter.add()
+ parent = context.scene.objects.get(self.parent)
+ if parent is not None:
+ o.parent = parent
+ bbox = parent.bound_box
+ angle_90 = pi / 2
+ x0, y0, z = bbox[0]
+ x1, y1, z = bbox[6]
+ x = 0.2 * (x1 - x0)
+ y = 0.2 * (y1 - y0)
+ o.matrix_world = parent.matrix_world * Matrix([
+ [1, 0, 0, -3 * x],
+ [0, 1, 0, 0],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]
+ ])
+ p = d.parts.add()
+ p.a0 = - angle_90
+ p.length = y
+ p = d.parts.add()
+ p.a0 = angle_90
+ p.length = x
+ p = d.parts.add()
+ p.a0 = angle_90
+ p.length = y
+ d.n_parts = 3
+ # d.close = True
+ pd = archipack_roof.datablock(parent)
+ pd.boundary = o.name
+ else:
+ o.location = context.scene.cursor_location
+ # make manipulators selectable
+ d.manipulable_selectable = True
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.objects.active = o
+ self.add_material(o)
+ self.load_preset(d)
+ update_operation(d, context)
+ return o
+
+ # -----------------------------------------------------
+ # Execute
+ # -----------------------------------------------------
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ bpy.ops.object.select_all(action="DESELECT")
+ o = self.create(context)
+ o.select = True
+ context.scene.objects.active = o
+ self.manipulate()
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+# ------------------------------------------------------------------
+# Define operator class to create object
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_roof_from_curve(Operator):
+ bl_idname = "archipack.roof_from_curve"
+ bl_label = "Roof curve"
+ bl_description = "Create a roof from a curve"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ auto_manipulate = BoolProperty(default=True)
+
+ @classmethod
+ def poll(self, context):
+ return context.active_object is not None and context.active_object.type == 'CURVE'
+
+ def draw(self, context):
+ layout = self.layout
+ row = layout.row()
+ row.label("Use Properties panel (N) to define parms", icon='INFO')
+
+ def create(self, context):
+ curve = context.active_object
+ m = bpy.data.meshes.new("Roof")
+ o = bpy.data.objects.new("Roof", m)
+ d = m.archipack_roof.add()
+ # make manipulators selectable
+ d.manipulable_selectable = True
+ d.user_defined_path = curve.name
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.objects.active = o
+ d.update_path(context)
+
+ spline = curve.data.splines[0]
+ if spline.type == 'POLY':
+ pt = spline.points[0].co
+ elif spline.type == 'BEZIER':
+ pt = spline.bezier_points[0].co
+ else:
+ pt = Vector((0, 0, 0))
+ # pretranslate
+ o.matrix_world = curve.matrix_world * Matrix([
+ [1, 0, 0, pt.x],
+ [0, 1, 0, pt.y],
+ [0, 0, 1, pt.z],
+ [0, 0, 0, 1]
+ ])
+ o.select = True
+ context.scene.objects.active = o
+ return o
+
+ # -----------------------------------------------------
+ # Execute
+ # -----------------------------------------------------
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ bpy.ops.object.select_all(action="DESELECT")
+ self.create(context)
+ if self.auto_manipulate:
+ bpy.ops.archipack.roof_manipulate('INVOKE_DEFAULT')
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+# ------------------------------------------------------------------
+# Define operator class to manipulate object
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_roof_manipulate(Operator):
+ bl_idname = "archipack.roof_manipulate"
+ bl_label = "Manipulate"
+ bl_description = "Manipulate"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ return archipack_roof.filter(context.active_object)
+
+ def invoke(self, context, event):
+ d = archipack_roof.datablock(context.active_object)
+ d.manipulable_invoke(context)
+ return {'FINISHED'}
+
+
+class ARCHIPACK_OT_roof_cutter_manipulate(Operator):
+ bl_idname = "archipack.roof_cutter_manipulate"
+ bl_label = "Manipulate"
+ bl_description = "Manipulate"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ return archipack_roof_cutter.filter(context.active_object)
+
+ def invoke(self, context, event):
+ d = archipack_roof_cutter.datablock(context.active_object)
+ d.manipulable_invoke(context)
+ return {'FINISHED'}
+
+
+# Update throttle
+class ArchipackThrottleHandler():
+ """
+ One modal runs for each object at time
+ when call for 2nd one
+ update timer so first one wait more
+ and kill 2nd one
+ """
+ def __init__(self, context, delay):
+ self._timer = None
+ self.start = 0
+ self.update_state = False
+ self.delay = delay
+
+ def start_timer(self, context):
+ self.start = time.time()
+ self._timer = context.window_manager.event_timer_add(self.delay, context.window)
+
+ def stop_timer(self, context):
+ if self._timer is not None:
+ context.window_manager.event_timer_remove(self._timer)
+ self._timer = None
+
+ def execute(self, context):
+ """
+ refresh timer on execute
+ return
+ True if modal should run
+ False on complete
+ """
+ if self._timer is None:
+ self.update_state = False
+ self.start_timer(context)
+ return True
+
+ # allready a timer running
+ self.stop_timer(context)
+
+ # prevent race conditions when allready in update mode
+ if self.is_updating:
+ return False
+
+ self.start_timer(context)
+ return False
+
+ def modal(self, context, event):
+ if event.type == 'TIMER' and not self.is_updating:
+ if time.time() - self.start > self.delay:
+ self.update_state = True
+ self.stop_timer(context)
+ return True
+ return False
+
+ @property
+ def is_updating(self):
+ return self.update_state
+
+
+throttle_handlers = {}
+throttle_delay = 1
+
+
+class ARCHIPACK_OT_roof_throttle_update(Operator):
+ bl_idname = "archipack.roof_throttle_update"
+ bl_label = "Update childs with a delay"
+
+ name = StringProperty()
+
+ def kill_handler(self, context, name):
+ if name in throttle_handlers.keys():
+ throttle_handlers[name].stop_timer(context)
+ del throttle_handlers[self.name]
+
+ def get_handler(self, context, delay):
+ global throttle_handlers
+ if self.name not in throttle_handlers.keys():
+ throttle_handlers[self.name] = ArchipackThrottleHandler(context, delay)
+ return throttle_handlers[self.name]
+
+ def modal(self, context, event):
+ global throttle_handlers
+ if self.name in throttle_handlers.keys():
+ if throttle_handlers[self.name].modal(context, event):
+ act = context.active_object
+ o = context.scene.objects.get(self.name)
+ # print("delay update of %s" % (self.name))
+ if o is not None:
+ selected = o.select
+ o.select = True
+ context.scene.objects.active = o
+ d = o.data.archipack_roof[0]
+ d.update(context,
+ force_update=True,
+ update_parent=False)
+ # skip_parent_update=self.skip_parent_update)
+ o.select = selected
+ context.scene.objects.active = act
+ del throttle_handlers[self.name]
+ return {'FINISHED'}
+ else:
+ return {'PASS_THROUGH'}
+ else:
+ return {'FINISHED'}
+
+ def execute(self, context):
+ global throttle_delay
+ handler = self.get_handler(context, throttle_delay)
+ if handler.execute(context):
+ context.window_manager.modal_handler_add(self)
+ return {'RUNNING_MODAL'}
+ return {'FINISHED'}
+
+
+# ------------------------------------------------------------------
+# Define operator class to load / save presets
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_roof_preset_menu(PresetMenuOperator, Operator):
+ bl_description = "Show Roof presets"
+ bl_idname = "archipack.roof_preset_menu"
+ bl_label = "Roof Styles"
+ preset_subdir = "archipack_roof"
+
+
+class ARCHIPACK_OT_roof_preset(ArchipackPreset, Operator):
+ """Add a Roof Styles"""
+ bl_idname = "archipack.roof_preset"
+ bl_label = "Add Roof Style"
+ preset_menu = "ARCHIPACK_OT_roof_preset_menu"
+
+ @property
+ def blacklist(self):
+ return ['n_parts', 'parts', 'manipulators', 'user_defined_path']
+
+
+def register():
+ # bpy.utils.register_class(archipack_roof_material)
+ bpy.utils.register_class(archipack_roof_cutter_segment)
+ bpy.utils.register_class(archipack_roof_cutter)
+ bpy.utils.register_class(ARCHIPACK_PT_roof_cutter)
+ bpy.utils.register_class(ARCHIPACK_OT_roof_cutter)
+ bpy.utils.register_class(ARCHIPACK_OT_roof_cutter_manipulate)
+ Mesh.archipack_roof_cutter = CollectionProperty(type=archipack_roof_cutter)
+ bpy.utils.register_class(archipack_roof_segment)
+ bpy.utils.register_class(archipack_roof)
+ Mesh.archipack_roof = CollectionProperty(type=archipack_roof)
+ bpy.utils.register_class(ARCHIPACK_OT_roof_preset_menu)
+ bpy.utils.register_class(ARCHIPACK_PT_roof)
+ bpy.utils.register_class(ARCHIPACK_OT_roof)
+ bpy.utils.register_class(ARCHIPACK_OT_roof_preset)
+ bpy.utils.register_class(ARCHIPACK_OT_roof_manipulate)
+ bpy.utils.register_class(ARCHIPACK_OT_roof_from_curve)
+ bpy.utils.register_class(ARCHIPACK_OT_roof_throttle_update)
+
+
+def unregister():
+ # bpy.utils.unregister_class(archipack_roof_material)
+ bpy.utils.unregister_class(archipack_roof_cutter_segment)
+ bpy.utils.unregister_class(archipack_roof_cutter)
+ bpy.utils.unregister_class(ARCHIPACK_PT_roof_cutter)
+ bpy.utils.unregister_class(ARCHIPACK_OT_roof_cutter)
+ bpy.utils.unregister_class(ARCHIPACK_OT_roof_cutter_manipulate)
+ del Mesh.archipack_roof_cutter
+ bpy.utils.unregister_class(archipack_roof_segment)
+ bpy.utils.unregister_class(archipack_roof)
+ del Mesh.archipack_roof
+ bpy.utils.unregister_class(ARCHIPACK_OT_roof_preset_menu)
+ bpy.utils.unregister_class(ARCHIPACK_PT_roof)
+ bpy.utils.unregister_class(ARCHIPACK_OT_roof)
+ bpy.utils.unregister_class(ARCHIPACK_OT_roof_preset)
+ bpy.utils.unregister_class(ARCHIPACK_OT_roof_manipulate)
+ bpy.utils.unregister_class(ARCHIPACK_OT_roof_from_curve)
+ bpy.utils.unregister_class(ARCHIPACK_OT_roof_throttle_update)