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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Dinges <blender@dingto.org>2021-11-23 11:24:55 +0300
committerThomas Dinges <blender@dingto.org>2021-11-23 11:24:55 +0300
commitd7517a6f2a69071eab53c02a645f7651ccfffd45 (patch)
tree7a496b8ada3e764b7e6c438e30d8c2be49fb95cf /archipack/archipack_roof.py
parent162cba016c8c11bcebea4d8d3cf80da9faf4ce76 (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.py5413
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)