5414 lines
182 KiB
Python
5414 lines
182 KiB
Python
# -*- 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)
|