diff options
author | Thomas Dinges <blender@dingto.org> | 2021-11-23 11:24:55 +0300 |
---|---|---|
committer | Thomas Dinges <blender@dingto.org> | 2021-11-23 11:24:55 +0300 |
commit | d7517a6f2a69071eab53c02a645f7651ccfffd45 (patch) | |
tree | 7a496b8ada3e764b7e6c438e30d8c2be49fb95cf /archipack/archipack_roof.py | |
parent | 162cba016c8c11bcebea4d8d3cf80da9faf4ce76 (diff) |
Remove Archipack to reflect new key requirements.
https://wiki.blender.org/wiki/Process/Addons
Diffstat (limited to 'archipack/archipack_roof.py')
-rw-r--r-- | archipack/archipack_roof.py | 5413 |
1 files changed, 0 insertions, 5413 deletions
diff --git a/archipack/archipack_roof.py b/archipack/archipack_roof.py deleted file mode 100644 index 8a9c8341..00000000 --- a/archipack/archipack_roof.py +++ /dev/null @@ -1,5413 +0,0 @@ -# -*- 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 neighbors 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 dependency 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: - # contiguous, 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 - # contiguous -> 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: right one or 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 contiguous 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 which 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 contiguous 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 contiguous 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 contiguous -> sides are same - # s2 is root contiguous -> sides are same - # back to back -> sides are not same - - if s0.reversed: - # contiguous 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: - # contiguous 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.root is None: - continue - 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 contiguous 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={'MATERIAL'}) - - 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 - ttl = len(self.pans) - if ttl < 1: - return - - 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 = 'PERCENT' - - 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 - - step = 100 / ttl - - # if d.quick_edit: - # 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)) - # if d.quick_edit: - # 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]) - mid = randint(idmat, idmat + rand) - t_mats = [mid 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="FACES") - - 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={'MATERIAL'}) - - 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') - - # if d.quick_edit: - # context.scene.archipack_progress = -1 - - def _bargeboard(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 bargeboard(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._bargeboard(s, - i, - hole, pan, - d.bargeboard_width, - d.bargeboard_height, - d.bargeboard_altitude, - d.bargeboard_offset, - idmat, - verts, - faces, - edges, - matids, - uvs) - - for i, s in enumerate(pan.segs): - if s.type == 'SIDE': - self._bargeboard(s, - i, - pan, pan, - d.bargeboard_width, - d.bargeboard_height, - d.bargeboard_altitude, - d.bargeboard_offset, - idmat, - verts, - faces, - edges, - matids, - uvs) - - def _fascia(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 neighbor depending on type - if s2.type == 'AXIS' or 'LINK' in s2.type: - # apply only on boundaries - 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 neighbor 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 fascia(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._fascia(s, - i, - hole, pan, - False, False, - d.fascia_width, - d.fascia_height, - d.fascia_altitude, - d.fascia_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._fascia(s, - i, - pan, pan, - tri_0, tri_1, - d.fascia_width, - d.fascia_height, - d.fascia_altitude, - d.fascia_offset, - idmat, - verts, - faces, - edges, - matids, - uvs) - - continue - - f = len(verts) - s0 = s.offset(d.fascia_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 neighbor depending on type - if s1.type == 'AXIS' or 'LINK' in s1.type: - # apply only on boundaries - 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.fascia_width) - - # find next neighbor 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.fascia_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.fascia_altitude + pan.altitude(s.p0) - z1 = self.z + d.fascia_altitude + pan.altitude(s.p1) - verts.extend([ - (x0, y0, z0), - (x1, y1, z0), - (x2, y2, z1), - (x3, y3, z1), - ]) - z0 -= d.fascia_height - z1 -= d.fascia_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 neighbor depending on type - if s1.type == 'AXIS' or 'LINK' in s1.type: - # apply only on boundaries - 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 neighbor 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.fascia_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.fascia_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.fascia_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.fascia_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' and s.length > 0: - 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' and s.length > 0: - 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 + pan.altitude(p0) - z1 = z0 - d.beam_height - z2 = self.z + d.beam_alt + pan.altitude(p1) - 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={'MATERIAL'}) - - 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) - p0 = s0.p0 - p1 = s0.p1 - t0 = 0 - t1 = 1 - res, p, t = s0.intersect(s2) - if res: - t0 = t - p0 = p - res, p, t = s0.intersect(s3) - if res: - t1 = t - p1 = p - - 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) - p0 = s0.p0 - p1 = s0.p1 - res, p, t = s0.intersect(s1) - if res: - p0 = p - res, p, t = s0.intersect(s2) - if res: - p1 = p - 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' and s.length > 0: - tmin = 0 - d.tile_side / s.length - s1 = pan.next_seg(i) - - if s1.type == 'SIDE' and s.length > 0: - 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.view_layer.objects.active = o.parent - bpy.ops.archipack.roof_cutter(parent=d.t_parent, auto_manipulate=False) - hole_obj = context.active_object - else: - context.view_layer.objects.active = hole_obj - - hole_obj.select_set(state=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_set(state=False) - - context.view_layer.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_get() - wall.select_set(state=True) - context.view_layer.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 - sid = 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 - sid = 1 - else: - if d - last_d < 0.001: - wd.parts[widx].n_splits -= 1 - continue - wd.parts[widx].z[sid] = z - wd.parts[widx].t[sid] = t - t0 - t0 = t - sid += 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_set(state=old_sel) - context.view_layer.objects.active = old - - def boundary(self, context, o): - """ - either external or holes cuts - """ - to_remove = [] - 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 i, pan in enumerate(self.pans): - keep = pan.slice(g) - if not keep: - if i not in to_remove: - to_remove.append(i) - pan.limits() - to_remove.sort() - for i in reversed(to_remove): - self.pans.pop(i) - - 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=4.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", 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", 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( - name="Link to", - 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="Triangular 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 = 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(text="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") - elif self.constraint_type == 'HORIZONTAL': - box.prop(self, "triangular_end") - - 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="Altitude", - 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, - update=update_childs - ) - slope_right : FloatProperty( - name="R slope", - default=0.5, precision=2, step=1, - 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=False - ) - - 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="Width", - 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="Length", - 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="Thickness", - 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="Width", - 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="Length", - 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="Ridge pole", - 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="Length", - description="Length of hip", - min=0.01, - default=0.4, - unit='LENGTH', subtype='DISTANCE', - update=update_components - ) - hip_size_y : FloatProperty( - name="Width", - description="Width of hip", - min=0.01, - default=0.15, - unit='LENGTH', subtype='DISTANCE', - update=update_components - ) - hip_size_z : FloatProperty( - name="Height", - 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 - ) - - fascia_enable : BoolProperty( - name="Enable", - description="Enable Fascia", - default=True, - update=update_components - ) - fascia_expand : BoolProperty( - options={'SKIP_SAVE'}, - name="Fascia", - description="Expand fascia panel", - default=False - ) - fascia_height : FloatProperty( - name="Height", - description="Height", - min=0.01, - default=0.3, - unit='LENGTH', subtype='DISTANCE', - update=update_components - ) - fascia_width : FloatProperty( - name="Width", - description="Width", - min=0.01, - default=0.02, - unit='LENGTH', subtype='DISTANCE', - update=update_components - ) - fascia_offset : FloatProperty( - name="Offset", - description="Offset from roof border", - default=0, - unit='LENGTH', subtype='DISTANCE', - update=update_components - ) - fascia_altitude : FloatProperty( - name="Altitude", - description="Fascia altitude from roof", - default=0.1, - unit='LENGTH', subtype='DISTANCE', - update=update_components - ) - - bargeboard_enable : BoolProperty( - name="Enable", - description="Enable Bargeboard", - default=True, - update=update_components - ) - bargeboard_expand : BoolProperty( - options={'SKIP_SAVE'}, - name="Bargeboard", - description="Expand Bargeboard panel", - default=False - ) - bargeboard_height : FloatProperty( - name="Height", - description="Height", - min=0.01, - default=0.3, - unit='LENGTH', subtype='DISTANCE', - update=update_components - ) - bargeboard_width : FloatProperty( - name="Width", - description="Width", - min=0.01, - default=0.02, - unit='LENGTH', subtype='DISTANCE', - update=update_components - ) - bargeboard_offset : FloatProperty( - name="Offset", - description="Offset from roof border", - default=0.001, - unit='LENGTH', subtype='DISTANCE', - update=update_components - ) - bargeboard_altitude : FloatProperty( - name="Altitude", - description="Fascia 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 - ) - z_parent: FloatProperty( - description="Delta z of t child for grand childs", - default=0 - ) - 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.strip()) - 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_set(state=True) - context.view_layer.objects.active = child - # regenerate hole - d.update(context, update_hole=True, update_parent=False) - child.select_set(state=False) - o.select_set(state=True) - context.view_layer.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_parent + d.z - self.t_dist_y * slope - self.z_parent = z - self.z - # 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.bargeboard_enable: - g.bargeboard(self, verts, faces, edges, matids, uvs) - - if self.fascia_enable: - g.fascia(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", 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", 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 bargeboard', 0), - ('BOTTOM', 'Bottom', 'Bottom with gutter', 1), - ('LINK', 'Side link', 'Side without 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 = 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.manipulable_disable(context) - self.from_points(pts) - self.manipulable_refresh = True - self.auto_update = True - if update_parent: - self.update_parent(context, o) - # print("update_points") - - def update_parent(self, context, o): - - d = archipack_roof.datablock(o.parent) - if d is not None: - o.parent.select_set(state=True) - context.view_layer.objects.active = o.parent - d.update(context, update_childs=False, update_hole=False) - o.parent.select_set(state=False) - context.view_layer.objects.active = o - # print("update_parent") - - -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() - if prop.boundary != "": - box.label(text="Auto Cutter:") - box.label(text=prop.boundary) - else: - box.operator('archipack.roof_cutter_manipulate', icon='VIEW_PAN') - 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='VIEW_PAN') - - 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='ADD') - row.operator("archipack.roof_preset", text="", icon='REMOVE').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="Covering", emboss=False) - else: - row.prop(prop, 'tile_expand', icon="TRIA_RIGHT", text="Covering", 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") - box.prop(prop, 'tile_size_x') - box.prop(prop, 'tile_size_y') - box.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") - box.prop(prop, 'tile_space_x') - box.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", emboss=False) - else: - row.prop(prop, 'hip_expand', icon="TRIA_RIGHT", text="Hip", emboss=False) - row.prop(prop, 'hip_enable') - if prop.hip_expand: - box.prop(prop, 'hip_model', text="") - box.prop(prop, 'hip_size_x') - box.prop(prop, 'hip_size_y') - box.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", emboss=False) - else: - row.prop(prop, 'beam_expand', icon="TRIA_RIGHT", text="Beam", 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", emboss=False) - else: - row.prop(prop, 'gutter_expand', icon="TRIA_RIGHT", text="Gutter", 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.fascia_expand: - row.prop(prop, 'fascia_expand', icon="TRIA_DOWN", text="Fascia", emboss=False) - else: - row.prop(prop, 'fascia_expand', icon="TRIA_RIGHT", text="Fascia", emboss=False) - row.prop(prop, 'fascia_enable') - if prop.fascia_expand: - box.prop(prop, 'fascia_altitude') - box.prop(prop, 'fascia_width') - box.prop(prop, 'fascia_height') - box.prop(prop, 'fascia_offset') - - box = layout.box() - row = box.row(align=True) - if prop.bargeboard_expand: - row.prop(prop, 'bargeboard_expand', icon="TRIA_DOWN", text="Bargeboard", emboss=False) - else: - row.prop(prop, 'bargeboard_expand', icon="TRIA_RIGHT", text="Bargeboard", emboss=False) - row.prop(prop, 'bargeboard_enable') - if prop.bargeboard_expand: - box.prop(prop, 'bargeboard_altitude') - box.prop(prop, 'bargeboard_width') - box.prop(prop, 'bargeboard_height') - box.prop(prop, 'bargeboard_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 - self.link_object_to_scene(context, o) - o.select_set(state=True) - context.view_layer.objects.active = o - self.add_material(o) - - # disable progress bar when - # background render thumbs - if not self.auto_manipulate: - d.quick_edit = False - - 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_set(state=True) - context.view_layer.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.strip()) - 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 - self.link_object_to_scene(context, o) - o.select_set(state=True) - context.view_layer.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_set(state=True) - context.view_layer.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(ArchipackCreateTool, 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(text="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 - self.link_object_to_scene(context, o) - o.select_set(state=True) - context.view_layer.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_set(state=True) - context.view_layer.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, window=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 - - # already a timer running - self.stop_timer(context) - - # prevent race conditions when already 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.strip()) - # print("delay update of %s" % (self.name)) - if o is not None: - selected = o.select_get() - o.select_set(state=True) - context.view_layer.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_set(state=selected) - context.view_layer.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', 'quick_edit', 'draft'] - - -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) |