2438 lines
79 KiB
Python
2438 lines
79 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)
|
|
#
|
|
# ----------------------------------------------------------
|
|
import bpy
|
|
import bmesh
|
|
|
|
import time
|
|
|
|
from bpy.types import Operator, PropertyGroup, Mesh, Panel
|
|
from bpy.props import (
|
|
FloatProperty, BoolProperty, IntProperty, StringProperty,
|
|
FloatVectorProperty, CollectionProperty, EnumProperty
|
|
)
|
|
from .bmesh_utils import BmeshEdit as bmed
|
|
from mathutils import Vector, Matrix
|
|
from mathutils.geometry import (
|
|
interpolate_bezier
|
|
)
|
|
from math import sin, cos, pi, atan2
|
|
from .archipack_manipulator import (
|
|
Manipulable, archipack_manipulator,
|
|
GlPolygon, GlPolyline,
|
|
GlLine, GlText, FeedbackPanel
|
|
)
|
|
from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool
|
|
from .archipack_2d import Line, Arc
|
|
from .archipack_snap import snap_point
|
|
from .archipack_keymaps import Keymaps
|
|
|
|
import logging
|
|
logger = logging.getLogger("archipack")
|
|
|
|
|
|
class Wall():
|
|
def __init__(self, wall_z, z, t, flip):
|
|
self.z = z
|
|
self.wall_z = wall_z
|
|
self.t = t
|
|
self.flip = flip
|
|
self.z_step = len(z)
|
|
|
|
def set_offset(self, offset, last=None):
|
|
"""
|
|
Offset line and compute intersection point
|
|
between segments
|
|
"""
|
|
self.line = self.make_offset(offset, last)
|
|
|
|
def get_z(self, t):
|
|
t0 = self.t[0]
|
|
z0 = self.z[0]
|
|
for i in range(1, self.z_step):
|
|
t1 = self.t[i]
|
|
z1 = self.z[i]
|
|
if t <= t1:
|
|
return z0 + (t - t0) / (t1 - t0) * (z1 - z0)
|
|
t0, z0 = t1, z1
|
|
return self.z[-1]
|
|
|
|
def make_faces(self, i, f, faces):
|
|
if i < self.n_step:
|
|
# 1 3 5 7
|
|
# 0 2 4 6
|
|
if self.flip:
|
|
faces.append((f + 2, f, f + 1, f + 3))
|
|
else:
|
|
faces.append((f, f + 2, f + 3, f + 1))
|
|
|
|
def p3d(self, verts, t):
|
|
x, y = self.lerp(t)
|
|
z = self.wall_z + self.get_z(t)
|
|
verts.append((x, y, 0))
|
|
verts.append((x, y, z))
|
|
|
|
def make_wall(self, i, verts, faces):
|
|
t = self.t_step[i]
|
|
f = len(verts)
|
|
self.p3d(verts, t)
|
|
self.make_faces(i, f, faces)
|
|
|
|
def make_hole(self, i, verts, z0):
|
|
t = self.t_step[i]
|
|
x, y = self.line.lerp(t)
|
|
verts.append((x, y, z0))
|
|
|
|
def straight_wall(self, a0, length, wall_z, z, t):
|
|
r = self.straight(length).rotate(a0)
|
|
return StraightWall(r.p, r.v, wall_z, z, t, self.flip)
|
|
|
|
def curved_wall(self, a0, da, radius, wall_z, z, t):
|
|
n = self.normal(1).rotate(a0).scale(radius)
|
|
if da < 0:
|
|
n.v = -n.v
|
|
a0 = n.angle
|
|
c = n.p - n.v
|
|
return CurvedWall(c, radius, a0, da, wall_z, z, t, self.flip)
|
|
|
|
|
|
class StraightWall(Wall, Line):
|
|
def __init__(self, p, v, wall_z, z, t, flip):
|
|
Line.__init__(self, p, v)
|
|
Wall.__init__(self, wall_z, z, t, flip)
|
|
|
|
def param_t(self, step_angle):
|
|
self.t_step = self.t
|
|
self.n_step = len(self.t) - 1
|
|
|
|
|
|
class CurvedWall(Wall, Arc):
|
|
def __init__(self, c, radius, a0, da, wall_z, z, t, flip):
|
|
Arc.__init__(self, c, radius, a0, da)
|
|
Wall.__init__(self, wall_z, z, t, flip)
|
|
|
|
def param_t(self, step_angle):
|
|
t_step, n_step = self.steps_by_angle(step_angle)
|
|
self.t_step = list(sorted([i * t_step for i in range(1, n_step)] + self.t))
|
|
self.n_step = len(self.t_step) - 1
|
|
|
|
|
|
class WallGenerator():
|
|
def __init__(self, parts):
|
|
self.last_type = 'NONE'
|
|
self.segs = []
|
|
self.parts = parts
|
|
self.faces_type = 'NONE'
|
|
self.closed = False
|
|
|
|
def set_offset(self, offset):
|
|
last = None
|
|
for i, seg in enumerate(self.segs):
|
|
seg.set_offset(offset, last)
|
|
last = seg.line
|
|
|
|
if self.closed:
|
|
w = self.segs[-1]
|
|
if len(self.segs) > 1:
|
|
w.line = w.make_offset(offset, self.segs[-2].line)
|
|
|
|
p1 = self.segs[0].line.p1
|
|
self.segs[0].line = self.segs[0].make_offset(offset, w.line)
|
|
self.segs[0].line.p1 = p1
|
|
|
|
def add_part(self, part, wall_z, flip):
|
|
|
|
# TODO:
|
|
# refactor this part (height manipulators)
|
|
manip_index = []
|
|
if len(self.segs) < 1:
|
|
s = None
|
|
z = [part.z[0]]
|
|
manip_index.append(0)
|
|
else:
|
|
s = self.segs[-1]
|
|
z = [s.z[-1]]
|
|
|
|
t_cur = 0
|
|
z_last = part.n_splits - 1
|
|
t = [0]
|
|
|
|
for i in range(part.n_splits):
|
|
t_try = t[-1] + part.t[i]
|
|
if t_try == t_cur:
|
|
continue
|
|
if t_try <= 1:
|
|
t_cur = t_try
|
|
t.append(t_cur)
|
|
z.append(part.z[i])
|
|
manip_index.append(i)
|
|
else:
|
|
z_last = i
|
|
break
|
|
|
|
if t_cur < 1:
|
|
t.append(1)
|
|
manip_index.append(z_last)
|
|
z.append(part.z[z_last])
|
|
|
|
# start a new wall
|
|
if s is None:
|
|
if part.type == 'S_WALL':
|
|
p = Vector((0, 0))
|
|
v = part.length * Vector((cos(part.a0), sin(part.a0)))
|
|
s = StraightWall(p, v, wall_z, z, t, flip)
|
|
elif part.type == 'C_WALL':
|
|
c = -part.radius * Vector((cos(part.a0), sin(part.a0)))
|
|
s = CurvedWall(c, part.radius, part.a0, part.da, wall_z, z, t, flip)
|
|
else:
|
|
if part.type == 'S_WALL':
|
|
s = s.straight_wall(part.a0, part.length, wall_z, z, t)
|
|
elif part.type == 'C_WALL':
|
|
s = s.curved_wall(part.a0, part.da, part.radius, wall_z, z, t)
|
|
|
|
self.segs.append(s)
|
|
self.last_type = part.type
|
|
|
|
return manip_index
|
|
|
|
def close(self, closed):
|
|
# Make last segment implicit closing one
|
|
if closed:
|
|
part = self.parts[-1]
|
|
w = self.segs[-1]
|
|
dp = self.segs[0].p0 - self.segs[-1].p0
|
|
if "C_" in part.type:
|
|
dw = (w.p1 - w.p0)
|
|
w.r = part.radius / dw.length * dp.length
|
|
# angle pt - p0 - angle p0 p1
|
|
da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x)
|
|
a0 = w.a0 + da
|
|
if a0 > pi:
|
|
a0 -= 2 * pi
|
|
if a0 < -pi:
|
|
a0 += 2 * pi
|
|
w.a0 = a0
|
|
else:
|
|
w.v = dp
|
|
|
|
def locate_manipulators(self, side):
|
|
|
|
for i, wall in enumerate(self.segs):
|
|
|
|
manipulators = self.parts[i].manipulators
|
|
|
|
p0 = wall.p0.to_3d()
|
|
p1 = wall.p1.to_3d()
|
|
|
|
# angle from last to current segment
|
|
if i > 0:
|
|
|
|
if i < len(self.segs) - 1:
|
|
manipulators[0].type_key = 'ANGLE'
|
|
else:
|
|
manipulators[0].type_key = 'DUMB_ANGLE'
|
|
|
|
v0 = self.segs[i - 1].straight(-side, 1).v.to_3d()
|
|
v1 = wall.straight(side, 0).v.to_3d()
|
|
manipulators[0].set_pts([p0, v0, v1])
|
|
|
|
if type(wall).__name__ == "StraightWall":
|
|
# segment length
|
|
manipulators[1].type_key = 'SIZE'
|
|
manipulators[1].prop1_name = "length"
|
|
manipulators[1].set_pts([p0, p1, (side, 0, 0)])
|
|
else:
|
|
# segment radius + angle
|
|
# scale to fix overlap with drag
|
|
v0 = side * (wall.p0 - wall.c).to_3d()
|
|
v1 = side * (wall.p1 - wall.c).to_3d()
|
|
scale = 1.0 + (0.5 / v0.length)
|
|
manipulators[1].type_key = 'ARC_ANGLE_RADIUS'
|
|
manipulators[1].prop1_name = "da"
|
|
manipulators[1].prop2_name = "radius"
|
|
manipulators[1].set_pts([wall.c.to_3d(), scale * v0, scale * v1])
|
|
|
|
# snap manipulator, don't change index !
|
|
manipulators[2].set_pts([p0, p1, (1, 0, 0)])
|
|
|
|
# dumb, segment index
|
|
z = Vector((0, 0, 0.75 * wall.wall_z))
|
|
manipulators[3].set_pts([p0 + z, p1 + z, (1, 0, 0)])
|
|
|
|
def make_wall(self, step_angle, flip, closed, verts, faces):
|
|
|
|
# swap manipulators so they always face outside
|
|
side = 1
|
|
if flip:
|
|
side = -1
|
|
|
|
# Make last segment implicit closing one
|
|
|
|
nb_segs = len(self.segs) - 1
|
|
if closed:
|
|
nb_segs += 1
|
|
|
|
for i, wall in enumerate(self.segs):
|
|
|
|
wall.param_t(step_angle)
|
|
if i < nb_segs:
|
|
for j in range(wall.n_step + 1):
|
|
wall.make_wall(j, verts, faces)
|
|
else:
|
|
# last segment
|
|
for j in range(wall.n_step):
|
|
continue
|
|
# print("%s" % (wall.n_step))
|
|
# wall.make_wall(j, verts, faces)
|
|
|
|
self.locate_manipulators(side)
|
|
|
|
def rotate(self, idx_from, a):
|
|
"""
|
|
apply rotation to all following segs
|
|
"""
|
|
self.segs[idx_from].rotate(a)
|
|
ca = cos(a)
|
|
sa = sin(a)
|
|
rM = Matrix([
|
|
[ca, -sa],
|
|
[sa, ca]
|
|
])
|
|
# rotation center
|
|
p0 = self.segs[idx_from].p0
|
|
for i in range(idx_from + 1, len(self.segs)):
|
|
seg = self.segs[i]
|
|
seg.rotate(a)
|
|
dp = rM @ (seg.p0 - p0)
|
|
seg.translate(dp)
|
|
|
|
def translate(self, idx_from, dp):
|
|
"""
|
|
apply translation to all following segs
|
|
"""
|
|
self.segs[idx_from].p1 += dp
|
|
for i in range(idx_from + 1, len(self.segs)):
|
|
self.segs[i].translate(dp)
|
|
|
|
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 draw(self, context):
|
|
for seg in self.segs:
|
|
seg.draw(context, render=False)
|
|
|
|
def debug(self, verts):
|
|
for wall in self.segs:
|
|
for i in range(33):
|
|
x, y = wall.lerp(i / 32)
|
|
verts.append((x, y, 0))
|
|
|
|
def make_surface(self, o, verts, height):
|
|
bm = bmesh.new()
|
|
for v in verts:
|
|
bm.verts.new(v)
|
|
bm.verts.ensure_lookup_table()
|
|
for i in range(1, len(verts)):
|
|
bm.edges.new((bm.verts[i - 1], bm.verts[i]))
|
|
bm.edges.new((bm.verts[-1], bm.verts[0]))
|
|
bm.edges.ensure_lookup_table()
|
|
bmesh.ops.contextual_create(bm, geom=bm.edges)
|
|
geom = bm.faces[:]
|
|
bmesh.ops.solidify(bm, geom=geom, thickness=height)
|
|
bm.to_mesh(o.data)
|
|
bm.free()
|
|
|
|
def make_hole(self, context, hole_obj, d):
|
|
|
|
offset = -0.5 * (1 - d.x_offset) * d.width
|
|
|
|
z0 = 0.1
|
|
self.set_offset(offset)
|
|
|
|
nb_segs = len(self.segs) - 1
|
|
if d.closed:
|
|
nb_segs += 1
|
|
|
|
verts = []
|
|
for i, wall in enumerate(self.segs):
|
|
wall.param_t(d.step_angle)
|
|
if i < nb_segs:
|
|
for j in range(wall.n_step + 1):
|
|
wall.make_hole(j, verts, -z0)
|
|
|
|
self.make_surface(hole_obj, verts, d.z + z0)
|
|
|
|
|
|
def update(self, context):
|
|
self.update(context)
|
|
|
|
|
|
def update_childs(self, context):
|
|
self.update(context, update_childs=True, manipulable_refresh=True)
|
|
|
|
|
|
def update_manipulators(self, context):
|
|
self.update(context, manipulable_refresh=True)
|
|
|
|
|
|
def update_t_part(self, context):
|
|
"""
|
|
Make this wall a T child of parent wall
|
|
orient child so y points inside wall and x follow wall segment
|
|
set child a0 according
|
|
"""
|
|
o = self.find_in_selection(context)
|
|
if o is not None:
|
|
|
|
# w is parent wall
|
|
w = context.scene.objects.get(self.t_part.strip())
|
|
wd = archipack_wall2.datablock(w)
|
|
|
|
if wd is not None:
|
|
og = self.get_generator()
|
|
self.setup_childs(o, og)
|
|
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
|
|
# 5 cases here:
|
|
# 1 No parents at all
|
|
# 2 o has parent
|
|
# 3 w has parent
|
|
# 4 o and w share same parent already
|
|
# 5 o and w doesn't share parent
|
|
link_to_parent = False
|
|
|
|
# when both walls do have a reference point, we may delete one of them
|
|
to_delete = None
|
|
|
|
# select childs and make parent reference point active
|
|
if w.parent is None:
|
|
# Either link to o.parent or create new parent
|
|
link_to_parent = True
|
|
if o.parent is None:
|
|
# create a reference point and make it active
|
|
x, y, z = w.bound_box[0]
|
|
context.scene.cursor.location = w.matrix_world @ Vector((x, y, z))
|
|
# fix issue #9
|
|
context.view_layer.objects.active = o
|
|
bpy.ops.archipack.reference_point()
|
|
o.select_set(state=True)
|
|
else:
|
|
context.view_layer.objects.active = o.parent
|
|
w.select_set(state=True)
|
|
else:
|
|
# w has parent
|
|
if o.parent is not w.parent:
|
|
link_to_parent = True
|
|
context.view_layer.objects.active = w.parent
|
|
o.select_set(state=True)
|
|
if o.parent is not None:
|
|
# store o.parent to delete it
|
|
to_delete = o.parent
|
|
for c in o.parent.children:
|
|
if c is not o:
|
|
c.hide_select = False
|
|
c.select_set(state=True)
|
|
|
|
parent = context.active_object
|
|
|
|
dmax = 2 * wd.width
|
|
|
|
wg = wd.get_generator()
|
|
|
|
otM = o.matrix_world
|
|
orM = Matrix([
|
|
otM[0].to_2d(),
|
|
otM[1].to_2d()
|
|
])
|
|
|
|
wtM = w.matrix_world
|
|
wrM = Matrix([
|
|
wtM[0].to_2d(),
|
|
wtM[1].to_2d()
|
|
])
|
|
|
|
# dir in absolute world coordsys
|
|
dir = orM @ og.segs[0].straight(1, 0).v
|
|
|
|
# pt in w coordsys
|
|
pos = otM.translation
|
|
pt = (wtM.inverted() @ pos).to_2d()
|
|
|
|
for wall_idx, wall in enumerate(wg.segs):
|
|
res, dist, t = wall.point_sur_segment(pt)
|
|
# outside is on the right side of the wall
|
|
# p1
|
|
# |-- x
|
|
# p0
|
|
|
|
# NOTE:
|
|
# rotation here is wrong when w has not parent while o has parent
|
|
|
|
if res and t > 0 and t < 1 and abs(dist) < dmax:
|
|
x = wrM @ wall.straight(1, t).v
|
|
y = wrM @ wall.normal(t).v.normalized()
|
|
self.parts[0].a0 = dir.angle_signed(x)
|
|
o.matrix_world = Matrix([
|
|
[x.x, -y.x, 0, pos.x],
|
|
[x.y, -y.y, 0, pos.y],
|
|
[0, 0, 1, pos.z],
|
|
[0, 0, 0, 1]
|
|
])
|
|
break
|
|
|
|
if link_to_parent and bpy.ops.archipack.parent_to_reference.poll():
|
|
bpy.ops.archipack.parent_to_reference('INVOKE_DEFAULT')
|
|
|
|
# update generator to take new rotation in account
|
|
# use this to relocate windows on wall after reparenting
|
|
g = self.get_generator()
|
|
self.relocate_childs(context, o, g)
|
|
|
|
# hide holes from select
|
|
for c in parent.children:
|
|
if "archipack_hybridhole" in c:
|
|
c.hide_select = True
|
|
|
|
# delete unneeded reference point
|
|
if to_delete is not None:
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
to_delete.select_set(state=True)
|
|
context.view_layer.objects.active = to_delete
|
|
if bpy.ops.object.delete.poll():
|
|
bpy.ops.object.delete(use_global=False)
|
|
|
|
elif self.t_part != "":
|
|
self.t_part = ""
|
|
|
|
self.restore_context(context)
|
|
|
|
|
|
def set_splits(self, value):
|
|
if self.n_splits != value:
|
|
self.auto_update = False
|
|
self._set_t(value)
|
|
self.auto_update = True
|
|
self.n_splits = value
|
|
return None
|
|
|
|
|
|
def get_splits(self):
|
|
return self.n_splits
|
|
|
|
|
|
def update_type(self, context):
|
|
|
|
d = self.find_datablock_in_selection(context)
|
|
|
|
if d is not None and d.auto_update:
|
|
|
|
d.auto_update = False
|
|
idx = 0
|
|
for i, part in enumerate(d.parts):
|
|
if part == self:
|
|
idx = i
|
|
break
|
|
a0 = 0
|
|
if idx > 0:
|
|
g = d.get_generator()
|
|
w0 = g.segs[idx - 1]
|
|
a0 = w0.straight(1).angle
|
|
if "C_" in self.type:
|
|
w = w0.straight_wall(self.a0, self.length, d.z, self.z, self.t)
|
|
else:
|
|
w = w0.curved_wall(self.a0, self.da, self.radius, d.z, self.z, self.t)
|
|
else:
|
|
if "C_" in self.type:
|
|
p = Vector((0, 0))
|
|
v = self.length * Vector((cos(self.a0), sin(self.a0)))
|
|
w = StraightWall(p, v, d.z, self.z, self.t, d.flip)
|
|
a0 = pi / 2
|
|
else:
|
|
c = -self.radius * Vector((cos(self.a0), sin(self.a0)))
|
|
w = CurvedWall(c, self.radius, self.a0, pi, d.z, self.z, self.t, d.flip)
|
|
|
|
# w0 - w - w1
|
|
if d.closed and idx == d.n_parts:
|
|
dp = - w.p0
|
|
else:
|
|
dp = w.p1 - w.p0
|
|
|
|
if "C_" in self.type:
|
|
self.radius = 0.5 * dp.length
|
|
self.da = pi
|
|
a0 = atan2(dp.y, dp.x) - pi / 2 - a0
|
|
else:
|
|
self.length = dp.length
|
|
a0 = atan2(dp.y, dp.x) - a0
|
|
|
|
if a0 > pi:
|
|
a0 -= 2 * pi
|
|
if a0 < -pi:
|
|
a0 += 2 * pi
|
|
self.a0 = a0
|
|
|
|
if idx + 1 < d.n_parts:
|
|
# adjust rotation of next part
|
|
part1 = d.parts[idx + 1]
|
|
if "C_" in self.type:
|
|
a0 = part1.a0 - pi / 2
|
|
else:
|
|
a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x)
|
|
|
|
if a0 > pi:
|
|
a0 -= 2 * pi
|
|
if a0 < -pi:
|
|
a0 += 2 * pi
|
|
part1.a0 = a0
|
|
|
|
d.auto_update = True
|
|
|
|
|
|
class archipack_wall2_part(PropertyGroup):
|
|
type : EnumProperty(
|
|
items=(
|
|
('S_WALL', 'Straight', '', 0),
|
|
('C_WALL', 'Curved', '', 1)
|
|
),
|
|
default='S_WALL',
|
|
update=update_type
|
|
)
|
|
length : FloatProperty(
|
|
name="Length",
|
|
min=0.01,
|
|
default=2.0,
|
|
unit='LENGTH', subtype='DISTANCE',
|
|
update=update
|
|
)
|
|
radius : FloatProperty(
|
|
name="Radius",
|
|
min=0.5,
|
|
default=0.7,
|
|
unit='LENGTH', subtype='DISTANCE',
|
|
update=update
|
|
)
|
|
a0 : FloatProperty(
|
|
name="Start angle",
|
|
min=-pi,
|
|
max=pi,
|
|
default=pi / 2,
|
|
subtype='ANGLE', unit='ROTATION',
|
|
update=update
|
|
)
|
|
da : FloatProperty(
|
|
name="Angle",
|
|
min=-pi,
|
|
max=pi,
|
|
default=pi / 2,
|
|
subtype='ANGLE', unit='ROTATION',
|
|
update=update
|
|
)
|
|
z : FloatVectorProperty(
|
|
name="Height",
|
|
default=[
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0
|
|
],
|
|
size=31,
|
|
update=update
|
|
)
|
|
t : FloatVectorProperty(
|
|
name="Position",
|
|
min=0,
|
|
max=1,
|
|
default=[
|
|
1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1
|
|
],
|
|
size=31,
|
|
update=update
|
|
)
|
|
splits : IntProperty(
|
|
name="Splits",
|
|
default=1,
|
|
min=1,
|
|
max=31,
|
|
get=get_splits, set=set_splits
|
|
)
|
|
n_splits : IntProperty(
|
|
name="Splits",
|
|
default=1,
|
|
min=1,
|
|
max=31,
|
|
update=update
|
|
)
|
|
auto_update : BoolProperty(default=True)
|
|
manipulators : CollectionProperty(type=archipack_manipulator)
|
|
# ui related
|
|
expand : BoolProperty(default=False)
|
|
|
|
def _set_t(self, splits):
|
|
t = 1 / splits
|
|
for i in range(splits):
|
|
self.t[i] = t
|
|
|
|
def find_datablock_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:
|
|
props = archipack_wall2.datablock(o)
|
|
if props:
|
|
for part in props.parts:
|
|
if part == self:
|
|
return props
|
|
return None
|
|
|
|
def update(self, context, manipulable_refresh=False):
|
|
if not self.auto_update:
|
|
return
|
|
props = self.find_datablock_in_selection(context)
|
|
if props is not None:
|
|
props.update(context, manipulable_refresh)
|
|
|
|
def draw(self, layout, context, index):
|
|
|
|
row = layout.row(align=True)
|
|
if self.expand:
|
|
row.prop(self, 'expand', icon="TRIA_DOWN", text="Part " + str(index + 1), emboss=False)
|
|
else:
|
|
row.prop(self, 'expand', icon="TRIA_RIGHT", text="Part " + str(index + 1), emboss=False)
|
|
|
|
row.prop(self, "type", text="")
|
|
|
|
if self.expand:
|
|
row = layout.row(align=True)
|
|
row.operator("archipack.wall2_insert", text="Split").index = index
|
|
row.operator("archipack.wall2_remove", text="Remove").index = index
|
|
if self.type == 'C_WALL':
|
|
row = layout.row()
|
|
row.prop(self, "radius")
|
|
row = layout.row()
|
|
row.prop(self, "da")
|
|
else:
|
|
row = layout.row()
|
|
row.prop(self, "length")
|
|
row = layout.row()
|
|
row.prop(self, "a0")
|
|
row = layout.row()
|
|
row.prop(self, "splits")
|
|
for split in range(self.n_splits):
|
|
row = layout.row()
|
|
row.prop(self, "z", text="alt", index=split)
|
|
row.prop(self, "t", text="pos", index=split)
|
|
|
|
|
|
class archipack_wall2_child(PropertyGroup):
|
|
# Size Loc
|
|
# Delta Loc
|
|
manipulators : CollectionProperty(type=archipack_manipulator)
|
|
child_name : StringProperty()
|
|
wall_idx : IntProperty()
|
|
pos : FloatVectorProperty(subtype='XYZ')
|
|
flip : BoolProperty(default=False)
|
|
|
|
def get_child(self, context):
|
|
d = None
|
|
child = context.scene.objects.get(self.child_name.strip())
|
|
if child is not None and child.data is not None:
|
|
cd = child.data
|
|
if 'archipack_window' in cd:
|
|
d = cd.archipack_window[0]
|
|
elif 'archipack_door' in cd:
|
|
d = cd.archipack_door[0]
|
|
return child, d
|
|
|
|
|
|
class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
|
|
parts : CollectionProperty(type=archipack_wall2_part)
|
|
n_parts : IntProperty(
|
|
name="Parts",
|
|
min=1,
|
|
max=1024,
|
|
default=1, update=update_manipulators
|
|
)
|
|
step_angle : FloatProperty(
|
|
description="Curved parts segmentation",
|
|
name="Step angle",
|
|
min=1 / 180 * pi,
|
|
max=pi,
|
|
default=6 / 180 * pi,
|
|
subtype='ANGLE', unit='ROTATION',
|
|
update=update
|
|
)
|
|
width : FloatProperty(
|
|
name="Width",
|
|
min=0.01,
|
|
default=0.2,
|
|
unit='LENGTH', subtype='DISTANCE',
|
|
update=update
|
|
)
|
|
z : FloatProperty(
|
|
name='Height',
|
|
min=0.1,
|
|
default=2.7, precision=2,
|
|
unit='LENGTH', subtype='DISTANCE',
|
|
description='height', update=update,
|
|
)
|
|
x_offset : FloatProperty(
|
|
name="Offset",
|
|
min=-1, max=1,
|
|
default=-1, precision=2, step=1,
|
|
update=update
|
|
)
|
|
radius : FloatProperty(
|
|
name="Radius",
|
|
min=0.5,
|
|
default=0.7,
|
|
unit='LENGTH', subtype='DISTANCE',
|
|
update=update
|
|
)
|
|
da : FloatProperty(
|
|
name="Angle",
|
|
min=-pi,
|
|
max=pi,
|
|
default=pi / 2,
|
|
subtype='ANGLE', unit='ROTATION',
|
|
update=update
|
|
)
|
|
flip : BoolProperty(
|
|
name="Flip",
|
|
default=False,
|
|
update=update_childs
|
|
)
|
|
closed : BoolProperty(
|
|
default=False,
|
|
name="Close",
|
|
update=update_manipulators
|
|
)
|
|
auto_update : BoolProperty(
|
|
options={'SKIP_SAVE'},
|
|
default=True,
|
|
update=update_manipulators
|
|
)
|
|
realtime : BoolProperty(
|
|
options={'SKIP_SAVE'},
|
|
default=True,
|
|
name="Real Time",
|
|
description="Relocate childs in realtime"
|
|
)
|
|
# dumb manipulators to show sizes between childs
|
|
childs_manipulators : CollectionProperty(type=archipack_manipulator)
|
|
# store to manipulate windows and doors
|
|
childs : CollectionProperty(type=archipack_wall2_child)
|
|
t_part : StringProperty(
|
|
name="Parent wall",
|
|
description="This part will follow parent when set",
|
|
default="",
|
|
update=update_t_part
|
|
)
|
|
|
|
def insert_part(self, context, o, where):
|
|
self.manipulable_disable(context)
|
|
self.auto_update = False
|
|
# the part we do split
|
|
part_0 = self.parts[where]
|
|
part_0.length /= 2
|
|
part_0.da /= 2
|
|
self.parts.add()
|
|
part_1 = self.parts[len(self.parts) - 1]
|
|
part_1.type = part_0.type
|
|
part_1.length = part_0.length
|
|
part_1.da = part_0.da
|
|
part_1.a0 = 0
|
|
# move after current one
|
|
self.parts.move(len(self.parts) - 1, where + 1)
|
|
self.n_parts += 1
|
|
# re-eval childs location
|
|
g = self.get_generator()
|
|
self.setup_childs(o, g)
|
|
|
|
self.setup_manipulators()
|
|
self.auto_update = True
|
|
|
|
def add_part(self, context, length):
|
|
self.manipulable_disable(context)
|
|
self.auto_update = False
|
|
p = self.parts.add()
|
|
p.length = length
|
|
self.parts.move(len(self.parts) - 1, self.n_parts)
|
|
self.n_parts += 1
|
|
self.setup_manipulators()
|
|
self.auto_update = True
|
|
return self.parts[self.n_parts - 1]
|
|
|
|
def remove_part(self, context, o, where):
|
|
self.manipulable_disable(context)
|
|
self.auto_update = False
|
|
# preserve shape
|
|
# using generator
|
|
if where > 0:
|
|
g = self.get_generator()
|
|
w = g.segs[where - 1]
|
|
w.p1 = g.segs[where].p1
|
|
|
|
if where + 1 < self.n_parts:
|
|
self.parts[where + 1].a0 = g.segs[where + 1].delta_angle(w)
|
|
|
|
part = self.parts[where - 1]
|
|
|
|
if "C_" in part.type:
|
|
part.radius = w.r
|
|
else:
|
|
part.length = w.length
|
|
|
|
if where > 1:
|
|
part.a0 = w.delta_angle(g.segs[where - 2])
|
|
else:
|
|
part.a0 = w.straight(1, 0).angle
|
|
|
|
self.parts.remove(where)
|
|
self.n_parts -= 1
|
|
|
|
# re-eval child location
|
|
g = self.get_generator()
|
|
self.setup_childs(o, g)
|
|
|
|
# fix snap manipulators index
|
|
self.setup_manipulators()
|
|
self.auto_update = True
|
|
|
|
def get_generator(self):
|
|
# print("get_generator")
|
|
g = WallGenerator(self.parts)
|
|
for part in self.parts:
|
|
g.add_part(part, self.z, self.flip)
|
|
g.close(self.closed)
|
|
return g
|
|
|
|
def update_parts(self, o, update_childs=False):
|
|
# print("update_parts")
|
|
# remove rows
|
|
# NOTE:
|
|
# n_parts+1
|
|
# as last one is end point of last segment or closing one
|
|
row_change = False
|
|
for i in range(len(self.parts), self.n_parts + 1, -1):
|
|
row_change = True
|
|
self.parts.remove(i - 1)
|
|
|
|
# add rows
|
|
for i in range(len(self.parts), self.n_parts + 1):
|
|
row_change = True
|
|
self.parts.add()
|
|
|
|
self.setup_manipulators()
|
|
|
|
g = self.get_generator()
|
|
|
|
if o is not None and (row_change or update_childs):
|
|
self.setup_childs(o, g)
|
|
|
|
return g
|
|
|
|
def setup_manipulators(self):
|
|
|
|
if len(self.manipulators) == 0:
|
|
# make manipulators selectable
|
|
s = self.manipulators.add()
|
|
s.prop1_name = "width"
|
|
s = self.manipulators.add()
|
|
s.prop1_name = "n_parts"
|
|
s.type_key = 'COUNTER'
|
|
s = self.manipulators.add()
|
|
s.prop1_name = "z"
|
|
s.normal = (0, 1, 0)
|
|
|
|
if self.t_part != "" and len(self.manipulators) < 4:
|
|
s = self.manipulators.add()
|
|
s.prop1_name = "x"
|
|
s.type_key = 'DELTA_LOC'
|
|
|
|
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)
|
|
p.manipulators[2].prop1_name = str(i)
|
|
p.manipulators[3].prop1_name = str(i + 1)
|
|
|
|
def interpolate_bezier(self, pts, wM, p0, p1, resolution):
|
|
if resolution == 0:
|
|
pts.append(wM @ p0.co.to_3d())
|
|
else:
|
|
v = (p1.co - p0.co).normalized()
|
|
d1 = (p0.handle_right - p0.co).normalized()
|
|
d2 = (p1.co - p1.handle_left).normalized()
|
|
if d1 == v and d2 == v:
|
|
pts.append(wM @ p0.co.to_3d())
|
|
else:
|
|
seg = interpolate_bezier(wM @ p0.co,
|
|
wM @ p0.handle_right,
|
|
wM @ p1.handle_left,
|
|
wM @ p1.co,
|
|
resolution + 1)
|
|
for i in range(resolution):
|
|
pts.append(seg[i].to_3d())
|
|
|
|
def is_cw(self, pts):
|
|
p0 = pts[0]
|
|
d = 0
|
|
for p in pts[1:]:
|
|
d += (p.x * p0.y - p.y * p0.x)
|
|
p0 = p
|
|
return d > 0
|
|
|
|
def from_spline(self, wM, resolution, spline):
|
|
pts = []
|
|
if spline.type == 'POLY':
|
|
pts = [wM @ p.co.to_3d() for p in spline.points]
|
|
if spline.use_cyclic_u:
|
|
pts.append(pts[0])
|
|
elif spline.type == 'BEZIER':
|
|
points = spline.bezier_points
|
|
for i in range(1, len(points)):
|
|
p0 = points[i - 1]
|
|
p1 = points[i]
|
|
self.interpolate_bezier(pts, wM, p0, p1, resolution)
|
|
if spline.use_cyclic_u:
|
|
p0 = points[-1]
|
|
p1 = points[0]
|
|
self.interpolate_bezier(pts, wM, p0, p1, resolution)
|
|
pts.append(pts[0])
|
|
else:
|
|
pts.append(wM @ points[-1].co)
|
|
|
|
if self.is_cw(pts):
|
|
pts = list(reversed(pts))
|
|
|
|
self.auto_update = False
|
|
self.from_points(pts, spline.use_cyclic_u)
|
|
self.auto_update = True
|
|
|
|
def from_points(self, pts, closed):
|
|
|
|
self.n_parts = len(pts) - 1
|
|
|
|
if closed:
|
|
self.n_parts -= 1
|
|
|
|
self.update_parts(None)
|
|
|
|
p0 = pts.pop(0)
|
|
a0 = 0
|
|
for i, p1 in enumerate(pts):
|
|
dp = p1 - p0
|
|
da = atan2(dp.y, dp.x) - a0
|
|
if da > pi:
|
|
da -= 2 * pi
|
|
if da < -pi:
|
|
da += 2 * pi
|
|
if i >= len(self.parts):
|
|
print("Too many pts for parts")
|
|
break
|
|
p = self.parts[i]
|
|
p.length = dp.to_2d().length
|
|
p.dz = dp.z
|
|
p.a0 = da
|
|
a0 += da
|
|
p0 = p1
|
|
|
|
self.closed = closed
|
|
|
|
def reverse(self, context, o):
|
|
|
|
g = self.get_generator()
|
|
|
|
self.auto_update = False
|
|
|
|
pts = [seg.p0.to_3d() for seg in g.segs]
|
|
|
|
if not self.closed:
|
|
g.segs.pop()
|
|
|
|
g_segs = list(reversed(g.segs))
|
|
|
|
last_seg = None
|
|
|
|
for i, seg in enumerate(g_segs):
|
|
|
|
s = seg.oposite
|
|
if "Curved" in type(seg).__name__:
|
|
self.parts[i].type = "C_WALL"
|
|
self.parts[i].radius = s.r
|
|
self.parts[i].da = s.da
|
|
else:
|
|
self.parts[i].type = "S_WALL"
|
|
self.parts[i].length = s.length
|
|
|
|
self.parts[i].a0 = s.delta_angle(last_seg)
|
|
|
|
last_seg = s
|
|
|
|
if self.closed:
|
|
pts.append(pts[0])
|
|
|
|
pts = list(reversed(pts))
|
|
|
|
# location wont change for closed walls
|
|
if not self.closed:
|
|
dp = pts[0] - pts[-1]
|
|
# pre-translate as dp is in local coordsys
|
|
o.matrix_world = o.matrix_world @ Matrix([
|
|
[1, 0, 0, dp.x],
|
|
[0, 1, 0, dp.y],
|
|
[0, 0, 1, 0],
|
|
[0, 0, 0, 1],
|
|
])
|
|
|
|
# self.from_points(pts, self.closed)
|
|
|
|
g = self.get_generator()
|
|
|
|
self.setup_childs(o, g)
|
|
self.auto_update = True
|
|
|
|
# flip does trigger relocate and keep childs orientation
|
|
self.flip = not self.flip
|
|
|
|
def update(self, context, manipulable_refresh=False, update_childs=False):
|
|
|
|
o = self.find_in_selection(context, self.auto_update)
|
|
|
|
if o is None:
|
|
return
|
|
|
|
if manipulable_refresh:
|
|
# prevent crash by removing all manipulators refs to datablock before changes
|
|
self.manipulable_disable(context)
|
|
|
|
verts = []
|
|
faces = []
|
|
|
|
g = self.update_parts(o, update_childs)
|
|
# print("make_wall")
|
|
g.make_wall(self.step_angle, self.flip, self.closed, verts, faces)
|
|
|
|
if self.closed:
|
|
f = len(verts)
|
|
if self.flip:
|
|
faces.append((0, f - 2, f - 1, 1))
|
|
else:
|
|
faces.append((f - 2, 0, 1, f - 1))
|
|
|
|
# print("buildmesh")
|
|
bmed.buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=True)
|
|
|
|
side = 1
|
|
if self.flip:
|
|
side = -1
|
|
# Width
|
|
offset = side * (0.5 * self.x_offset) * self.width
|
|
self.manipulators[0].set_pts([
|
|
g.segs[0].sized_normal(0, offset + 0.5 * side * self.width).v.to_3d(),
|
|
g.segs[0].sized_normal(0, offset - 0.5 * side * self.width).v.to_3d(),
|
|
(-side, 0, 0)
|
|
])
|
|
|
|
# Parts COUNTER
|
|
self.manipulators[1].set_pts([g.segs[-2].lerp(1.1).to_3d(),
|
|
g.segs[-2].lerp(1.1 + 0.5 / g.segs[-2].length).to_3d(),
|
|
(-side, 0, 0)
|
|
])
|
|
|
|
# Height
|
|
self.manipulators[2].set_pts([
|
|
(0, 0, 0),
|
|
(0, 0, self.z),
|
|
(-1, 0, 0)
|
|
], normal=g.segs[0].straight(side, 0).v.to_3d())
|
|
|
|
if self.t_part != "":
|
|
t = 0.3 / g.segs[0].length
|
|
self.manipulators[3].set_pts([
|
|
g.segs[0].sized_normal(t, 0.1).p1.to_3d(),
|
|
g.segs[0].sized_normal(t, -0.1).p1.to_3d(),
|
|
(1, 0, 0)
|
|
])
|
|
|
|
if self.realtime:
|
|
# update child location and size
|
|
self.relocate_childs(context, o, g)
|
|
# store gl points
|
|
self.update_childs(context, o, g)
|
|
else:
|
|
bpy.ops.archipack.wall2_throttle_update(name=o.name)
|
|
|
|
modif = o.modifiers.get('Wall')
|
|
if modif is None:
|
|
modif = o.modifiers.new('Wall', 'SOLIDIFY')
|
|
modif.use_quality_normals = True
|
|
modif.use_even_offset = True
|
|
modif.material_offset_rim = 2
|
|
modif.material_offset = 1
|
|
|
|
modif.thickness = self.width
|
|
modif.offset = self.x_offset
|
|
|
|
if manipulable_refresh:
|
|
# print("manipulable_refresh=True")
|
|
self.manipulable_refresh = True
|
|
|
|
self.restore_context(context)
|
|
|
|
# manipulable children objects like windows and doors
|
|
def child_partition(self, array, begin, end):
|
|
pivot = begin
|
|
for i in range(begin + 1, end + 1):
|
|
# wall idx
|
|
if array[i][1] < array[begin][1]:
|
|
pivot += 1
|
|
array[i], array[pivot] = array[pivot], array[i]
|
|
# param t on the wall
|
|
elif array[i][1] == array[begin][1] and array[i][4] <= array[begin][4]:
|
|
pivot += 1
|
|
array[i], array[pivot] = array[pivot], array[i]
|
|
array[pivot], array[begin] = array[begin], array[pivot]
|
|
return pivot
|
|
|
|
def sort_child(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.child_partition(array, begin, end)
|
|
_quicksort(array, begin, pivot - 1)
|
|
_quicksort(array, pivot + 1, end)
|
|
return _quicksort(array, begin, end)
|
|
|
|
def add_child(self, name, wall_idx, pos, flip):
|
|
# print("add_child %s %s" % (name, wall_idx))
|
|
c = self.childs.add()
|
|
c.child_name = name
|
|
c.wall_idx = wall_idx
|
|
c.pos = pos
|
|
c.flip = flip
|
|
m = c.manipulators.add()
|
|
m.type_key = 'DELTA_LOC'
|
|
m.prop1_name = "x"
|
|
m = c.manipulators.add()
|
|
m.type_key = 'SNAP_SIZE_LOC'
|
|
m.prop1_name = "x"
|
|
m.prop2_name = "x"
|
|
|
|
def setup_childs(self, o, g):
|
|
"""
|
|
Store childs
|
|
create manipulators
|
|
call after a boolean oop
|
|
"""
|
|
# tim = time.time()
|
|
self.childs.clear()
|
|
self.childs_manipulators.clear()
|
|
if o.parent is None:
|
|
return
|
|
wall_with_childs = [0 for i in range(self.n_parts + 1)]
|
|
relocate = []
|
|
dmax = 2 * self.width
|
|
|
|
wtM = o.matrix_world
|
|
wrM = Matrix([
|
|
wtM[0].to_2d(),
|
|
wtM[1].to_2d()
|
|
])
|
|
witM = wtM.inverted()
|
|
|
|
for child in o.parent.children:
|
|
# filter allowed childs
|
|
cd = child.data
|
|
wd = archipack_wall2.datablock(child)
|
|
if (child != o and cd is not None and (
|
|
'archipack_window' in cd or
|
|
'archipack_door' in cd or (
|
|
wd is not None and
|
|
o.name in wd.t_part
|
|
)
|
|
)):
|
|
|
|
# setup on T linked walls
|
|
if wd is not None:
|
|
wg = wd.get_generator()
|
|
wd.setup_childs(child, wg)
|
|
|
|
ctM = child.matrix_world
|
|
crM = Matrix([
|
|
ctM[0].to_2d(),
|
|
ctM[1].to_2d()
|
|
])
|
|
|
|
# pt in w coordsys
|
|
pos = ctM.translation
|
|
pt = (witM @ pos).to_2d()
|
|
|
|
for wall_idx, wall in enumerate(g.segs):
|
|
# may be optimized with a bound check
|
|
res, dist, t = wall.point_sur_segment(pt)
|
|
# outside is on the right side of the wall
|
|
# p1
|
|
# |-- x
|
|
# p0
|
|
if res and t > 0 and t < 1 and abs(dist) < dmax:
|
|
# dir in world coordsys
|
|
dir = wrM @ wall.sized_normal(t, 1).v
|
|
wall_with_childs[wall_idx] = 1
|
|
m = self.childs_manipulators.add()
|
|
m.type_key = 'DUMB_SIZE'
|
|
# always make window points outside
|
|
if "archipack_window" in cd:
|
|
flip = self.flip
|
|
else:
|
|
dir_y = crM @ Vector((0, -1))
|
|
# let door orient where user want
|
|
flip = (dir_y - dir).length > 0.5
|
|
# store z in wall space
|
|
relocate.append((
|
|
child.name,
|
|
wall_idx,
|
|
(t * wall.length, dist, (witM @ pos).z),
|
|
flip,
|
|
t))
|
|
break
|
|
|
|
self.sort_child(relocate)
|
|
for child in relocate:
|
|
name, wall_idx, pos, flip, t = child
|
|
self.add_child(name, wall_idx, pos, flip)
|
|
|
|
# add a dumb size from last child to end of wall segment
|
|
for i in range(sum(wall_with_childs)):
|
|
m = self.childs_manipulators.add()
|
|
m.type_key = 'DUMB_SIZE'
|
|
# print("setup_childs:%1.4f" % (time.time()-tim))
|
|
|
|
def relocate_childs(self, context, o, g):
|
|
"""
|
|
Move and resize childs after wall edition
|
|
"""
|
|
# print("relocate_childs")
|
|
# tim = time.time()
|
|
w = -self.x_offset * self.width
|
|
if self.flip:
|
|
w = -w
|
|
tM = o.matrix_world
|
|
for child in self.childs:
|
|
c, d = child.get_child(context)
|
|
if c is None:
|
|
continue
|
|
t = child.pos.x / g.segs[child.wall_idx].length
|
|
n = g.segs[child.wall_idx].sized_normal(t, 1)
|
|
rx, ry = -n.v
|
|
rx, ry = ry, -rx
|
|
if child.flip:
|
|
rx, ry = -rx, -ry
|
|
|
|
if d is not None:
|
|
# print("change flip:%s width:%s" % (d.flip != child.flip, d.y != self.width))
|
|
if d.y != self.width or d.flip != child.flip:
|
|
c.select_set(state=True)
|
|
d.auto_update = False
|
|
d.flip = child.flip
|
|
d.y = self.width
|
|
d.auto_update = True
|
|
c.select_set(state=False)
|
|
x, y = n.p - (0.5 * w * n.v)
|
|
else:
|
|
x, y = n.p - (child.pos.y * n.v)
|
|
|
|
context.view_layer.objects.active = o
|
|
# preTranslate
|
|
c.matrix_world = tM @ Matrix([
|
|
[rx, -ry, 0, x],
|
|
[ry, rx, 0, y],
|
|
[0, 0, 1, child.pos.z],
|
|
[0, 0, 0, 1]
|
|
])
|
|
|
|
# Update T linked wall's childs
|
|
if archipack_wall2.filter(c):
|
|
d = archipack_wall2.datablock(c)
|
|
cg = d.get_generator()
|
|
d.relocate_childs(context, c, cg)
|
|
|
|
# print("relocate_childs:%1.4f" % (time.time()-tim))
|
|
|
|
def update_childs(self, context, o, g):
|
|
"""
|
|
setup gl points for childs
|
|
"""
|
|
# print("update_childs")
|
|
|
|
if o.parent is None:
|
|
return
|
|
|
|
# swap manipulators so they always face outside
|
|
manip_side = 1
|
|
if self.flip:
|
|
manip_side = -1
|
|
|
|
itM = o.matrix_world.inverted()
|
|
m_idx = 0
|
|
for wall_idx, wall in enumerate(g.segs):
|
|
p0 = wall.lerp(0)
|
|
wall_has_childs = False
|
|
for child in self.childs:
|
|
if child.wall_idx == wall_idx:
|
|
c, d = child.get_child(context)
|
|
if d is not None:
|
|
# child is either a window or a door
|
|
wall_has_childs = True
|
|
dt = 0.5 * d.x / wall.length
|
|
pt = (itM @ c.matrix_world.translation).to_2d()
|
|
res, y, t = wall.point_sur_segment(pt)
|
|
child.pos = (wall.length * t, y, child.pos.z)
|
|
p1 = wall.lerp(t - dt)
|
|
# dumb size between childs
|
|
self.childs_manipulators[m_idx].set_pts([
|
|
(p0.x, p0.y, 0),
|
|
(p1.x, p1.y, 0),
|
|
(manip_side * 0.5, 0, 0)])
|
|
m_idx += 1
|
|
x, y = 0.5 * d.x, -self.x_offset * 0.5 * d.y
|
|
|
|
if child.flip:
|
|
side = -manip_side
|
|
else:
|
|
side = manip_side
|
|
|
|
# delta loc
|
|
child.manipulators[0].set_pts([(-x, side * -y, 0), (x, side * -y, 0), (side, 0, 0)])
|
|
# loc size
|
|
child.manipulators[1].set_pts([
|
|
(-x, side * -y, 0),
|
|
(x, side * -y, 0),
|
|
(0.5 * side, 0, 0)])
|
|
p0 = wall.lerp(t + dt)
|
|
p1 = wall.lerp(1)
|
|
if wall_has_childs:
|
|
# dub size after all childs
|
|
self.childs_manipulators[m_idx].set_pts([
|
|
(p0.x, p0.y, 0),
|
|
(p1.x, p1.y, 0),
|
|
(manip_side * 0.5, 0, 0)])
|
|
m_idx += 1
|
|
|
|
def manipulate_childs(self, context):
|
|
"""
|
|
setup child manipulators
|
|
"""
|
|
# print("manipulate_childs")
|
|
n_parts = self.n_parts
|
|
if self.closed:
|
|
n_parts += 1
|
|
|
|
for wall_idx in range(n_parts):
|
|
for child in self.childs:
|
|
if child.wall_idx == wall_idx:
|
|
c, d = child.get_child(context)
|
|
if d is not None:
|
|
# delta loc
|
|
self.manip_stack.append(child.manipulators[0].setup(context, c, d, self.manipulate_callback))
|
|
# loc size
|
|
self.manip_stack.append(child.manipulators[1].setup(context, c, d, self.manipulate_callback))
|
|
|
|
def manipulate_callback(self, context, o=None, manipulator=None):
|
|
found = False
|
|
if o.parent is not None:
|
|
for c in o.parent.children:
|
|
if (archipack_wall2.datablock(c) == self):
|
|
context.view_layer.objects.active = c
|
|
found = True
|
|
break
|
|
if found:
|
|
self.manipulable_manipulate(context, manipulator=manipulator)
|
|
|
|
def manipulable_manipulate(self, context, event=None, manipulator=None):
|
|
type_name = type(manipulator).__name__
|
|
# print("manipulable_manipulate %s" % (type_name))
|
|
if type_name in [
|
|
'DeltaLocationManipulator',
|
|
'SizeLocationManipulator',
|
|
'SnapSizeLocationManipulator'
|
|
]:
|
|
# update manipulators pos of childs
|
|
o = context.active_object
|
|
if o.parent is None:
|
|
return
|
|
g = self.get_generator()
|
|
itM = o.matrix_world.inverted() @ o.parent.matrix_world
|
|
for child in self.childs:
|
|
c, d = child.get_child(context)
|
|
if d is not None:
|
|
wall = g.segs[child.wall_idx]
|
|
pt = (itM @ c.location).to_2d()
|
|
res, d, t = wall.point_sur_segment(pt)
|
|
child.pos = (t * wall.length, d, child.pos.z)
|
|
# update childs manipulators
|
|
self.update_childs(context, o, g)
|
|
|
|
def manipulable_move_t_part(self, context, o=None, manipulator=None):
|
|
"""
|
|
Callback for t_parts childs
|
|
"""
|
|
type_name = type(manipulator).__name__
|
|
# print("manipulable_manipulate %s" % (type_name))
|
|
if type_name in [
|
|
'DeltaLocationManipulator'
|
|
]:
|
|
# update manipulators pos of childs
|
|
if archipack_wall2.datablock(o) != self:
|
|
return
|
|
g = self.get_generator()
|
|
# update childs
|
|
self.relocate_childs(context, o, g)
|
|
|
|
def manipulable_release(self, context):
|
|
"""
|
|
Override with action to do on mouse release
|
|
eg: big update
|
|
"""
|
|
return
|
|
|
|
def manipulable_setup(self, context):
|
|
# print("manipulable_setup")
|
|
self.manipulable_disable(context)
|
|
o = context.active_object
|
|
|
|
# setup childs manipulators
|
|
self.manipulate_childs(context)
|
|
n_parts = self.n_parts
|
|
if self.closed:
|
|
n_parts += 1
|
|
|
|
# update manipulators on version change
|
|
self.setup_manipulators()
|
|
|
|
for i, part in enumerate(self.parts):
|
|
|
|
if i < n_parts:
|
|
if i > 0:
|
|
# start angle
|
|
self.manip_stack.append(part.manipulators[0].setup(context, o, part))
|
|
|
|
# length / radius + angle
|
|
self.manip_stack.append(part.manipulators[1].setup(context, o, part))
|
|
# segment index
|
|
self.manip_stack.append(part.manipulators[3].setup(context, o, self))
|
|
|
|
# snap point
|
|
self.manip_stack.append(part.manipulators[2].setup(context, o, self))
|
|
|
|
# height as per segment will be here when done
|
|
|
|
# width + counter
|
|
for m in self.manipulators:
|
|
self.manip_stack.append(m.setup(context, o, self, self.manipulable_move_t_part))
|
|
|
|
# dumb between childs
|
|
for m in self.childs_manipulators:
|
|
self.manip_stack.append(m.setup(context, o, self))
|
|
|
|
def manipulable_exit(self, context):
|
|
"""
|
|
Override with action to do when modal exit
|
|
"""
|
|
return
|
|
|
|
def manipulable_invoke(self, context):
|
|
"""
|
|
call this in operator invoke()
|
|
"""
|
|
# print("manipulable_invoke")
|
|
if self.manipulate_mode:
|
|
self.manipulable_disable(context)
|
|
return False
|
|
|
|
# self.manip_stack = []
|
|
o = context.active_object
|
|
g = self.get_generator()
|
|
# setup childs manipulators
|
|
self.setup_childs(o, g)
|
|
# store gl points
|
|
self.update_childs(context, o, g)
|
|
# don't do anything ..
|
|
# self.manipulable_release(context)
|
|
# self.manipulate_mode = True
|
|
self.manipulable_setup(context)
|
|
self.manipulate_mode = True
|
|
|
|
self._manipulable_invoke(context)
|
|
|
|
return True
|
|
|
|
def find_roof(self, context, o, g):
|
|
tM = o.matrix_world
|
|
up = Vector((0, 0, 1))
|
|
for seg in g.segs:
|
|
p = tM @ seg.p0.to_3d()
|
|
p.z = 0.01
|
|
# prevent self intersect
|
|
o.hide_viewport = True
|
|
res, pos, normal, face_index, r, matrix_world = context.scene.ray_cast(
|
|
depsgraph=context.view_layer.depsgraph,
|
|
origin=p,
|
|
direction=up)
|
|
|
|
o.hide_viewport = False
|
|
# print("res:%s" % res)
|
|
if res and r.data is not None and "archipack_roof" in r.data:
|
|
return r, r.data.archipack_roof[0]
|
|
|
|
return None, None
|
|
|
|
|
|
# Update throttle (hack)
|
|
# use 2 globals to store a timer and state of update_action
|
|
# Use to update floor boolean on edit
|
|
update_timer = None
|
|
update_timer_updating = False
|
|
throttle_delay = 0.5
|
|
throttle_start = 0
|
|
|
|
|
|
class ARCHIPACK_OT_wall2_throttle_update(Operator):
|
|
bl_idname = "archipack.wall2_throttle_update"
|
|
bl_label = "Update childs with a delay"
|
|
|
|
name : StringProperty()
|
|
|
|
def modal(self, context, event):
|
|
global update_timer_updating
|
|
if event.type == 'TIMER' and not update_timer_updating:
|
|
# can't rely on TIMER event as another timer may run
|
|
if time.time() - throttle_start > throttle_delay:
|
|
update_timer_updating = True
|
|
o = context.scene.objects.get(self.name.strip())
|
|
if o is not None:
|
|
m = o.modifiers.get("AutoBoolean")
|
|
if m is not None:
|
|
o.hide_viewport = False
|
|
# o.display_type = 'TEXTURED'
|
|
# m.show_viewport = True
|
|
|
|
return self.cancel(context)
|
|
return {'PASS_THROUGH'}
|
|
|
|
def execute(self, context):
|
|
global update_timer
|
|
global update_timer_updating
|
|
global throttle_delay
|
|
global throttle_start
|
|
if update_timer is not None:
|
|
context.window_manager.event_timer_remove(update_timer)
|
|
if update_timer_updating:
|
|
return {'CANCELLED'}
|
|
# reset update_timer so it only occurs once 0.1s after last action
|
|
throttle_start = time.time()
|
|
update_timer = context.window_manager.event_timer_add(throttle_delay, context.window)
|
|
return {'CANCELLED'}
|
|
throttle_start = time.time()
|
|
update_timer_updating = False
|
|
context.window_manager.modal_handler_add(self)
|
|
update_timer = context.window_manager.event_timer_add(throttle_delay, context.window)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
def cancel(self, context):
|
|
global update_timer
|
|
context.window_manager.event_timer_remove(update_timer)
|
|
update_timer = None
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_PT_wall2(Panel):
|
|
bl_idname = "ARCHIPACK_PT_wall2"
|
|
bl_label = "Wall"
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = 'Archipack'
|
|
|
|
def draw(self, context):
|
|
prop = archipack_wall2.datablock(context.object)
|
|
if prop is None:
|
|
return
|
|
layout = self.layout
|
|
row = layout.row(align=True)
|
|
row.operator("archipack.wall2_manipulate", icon='VIEW_PAN')
|
|
# row = layout.row(align=True)
|
|
# row.prop(prop, 'realtime')
|
|
box = layout.box()
|
|
box.prop(prop, 'n_parts')
|
|
box.prop(prop, 'step_angle')
|
|
box.prop(prop, 'width')
|
|
box.prop(prop, 'z')
|
|
box.prop(prop, 'flip')
|
|
box.prop(prop, 'x_offset')
|
|
row = layout.row()
|
|
row.prop(prop, "closed")
|
|
row = layout.row()
|
|
row.prop_search(prop, "t_part", context.scene, "objects", text="T link", icon='OBJECT_DATAMODE')
|
|
layout.operator("archipack.wall2_reverse", icon='FILE_REFRESH')
|
|
row = layout.row(align=True)
|
|
row.operator("archipack.wall2_fit_roof")
|
|
# row.operator("archipack.wall2_fit_roof", text="Inside").inside = True
|
|
n_parts = prop.n_parts
|
|
if prop.closed:
|
|
n_parts += 1
|
|
for i, part in enumerate(prop.parts):
|
|
if i < n_parts:
|
|
box = layout.box()
|
|
part.draw(box, context, i)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return archipack_wall2.filter(context.active_object)
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define operator class to create object
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
class ARCHIPACK_OT_wall2(ArchipackCreateTool, Operator):
|
|
bl_idname = "archipack.wall2"
|
|
bl_label = "Wall"
|
|
bl_description = "Create a Wall"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def create(self, context):
|
|
m = bpy.data.meshes.new("Wall")
|
|
o = bpy.data.objects.new("Wall", m)
|
|
d = m.archipack_wall2.add()
|
|
d.manipulable_selectable = True
|
|
self.link_object_to_scene(context, o)
|
|
o.select_set(state=True)
|
|
# around 12 degree
|
|
m.auto_smooth_angle = 0.20944
|
|
context.view_layer.objects.active = o
|
|
self.load_preset(d)
|
|
self.add_material(o)
|
|
return o
|
|
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
o = self.create(context)
|
|
o.location = bpy.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_wall2_from_curve(Operator):
|
|
bl_idname = "archipack.wall2_from_curve"
|
|
bl_label = "Wall curve"
|
|
bl_description = "Create a wall 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 create(self, context):
|
|
curve = context.active_object
|
|
for spline in curve.data.splines:
|
|
bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate)
|
|
o = context.view_layer.objects.active
|
|
d = archipack_wall2.datablock(o)
|
|
d.from_spline(curve.matrix_world, 12, spline)
|
|
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]
|
|
])
|
|
return o
|
|
|
|
# -----------------------------------------------------
|
|
# Execute
|
|
# -----------------------------------------------------
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
o = self.create(context)
|
|
if o is not None:
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_OT_wall2_from_slab(Operator):
|
|
bl_idname = "archipack.wall2_from_slab"
|
|
bl_label = "->Wall"
|
|
bl_description = "Create a wall from a slab"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
auto_manipulate : BoolProperty(default=True)
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
o = context.active_object
|
|
return o is not None and o.data is not None and 'archipack_slab' in o.data
|
|
|
|
def create(self, context):
|
|
slab = context.active_object
|
|
wd = slab.data.archipack_slab[0]
|
|
bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate)
|
|
o = context.view_layer.objects.active
|
|
d = archipack_wall2.datablock(o)
|
|
d.auto_update = False
|
|
d.parts.clear()
|
|
d.n_parts = wd.n_parts - 1
|
|
d.closed = True
|
|
for part in wd.parts:
|
|
p = d.parts.add()
|
|
if "S_" in part.type:
|
|
p.type = "S_WALL"
|
|
else:
|
|
p.type = "C_WALL"
|
|
p.length = part.length
|
|
p.radius = part.radius
|
|
p.da = part.da
|
|
p.a0 = part.a0
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
d.auto_update = True
|
|
# pretranslate
|
|
o.matrix_world = slab.matrix_world.copy()
|
|
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
# parenting childs to wall reference point
|
|
if o.parent is None:
|
|
x, y, z = o.bound_box[0]
|
|
context.scene.cursor.location = o.matrix_world @ Vector((x, y, z))
|
|
# fix issue #9
|
|
context.view_layer.objects.active = o
|
|
bpy.ops.archipack.reference_point()
|
|
else:
|
|
o.parent.select_set(state=True)
|
|
context.view_layer.objects.active = o.parent
|
|
o.select_set(state=True)
|
|
slab.select_set(state=True)
|
|
bpy.ops.archipack.parent_to_reference()
|
|
o.parent.select_set(state=False)
|
|
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
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_OT_wall2_fit_roof(Operator):
|
|
bl_idname = "archipack.wall2_fit_roof"
|
|
bl_label = "Fit roof"
|
|
bl_description = "Fit roof"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
inside : BoolProperty(default=False)
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return archipack_wall2.filter(context.active_object)
|
|
|
|
def execute(self, context):
|
|
o = context.active_object
|
|
d = archipack_wall2.datablock(o)
|
|
g = d.get_generator()
|
|
r, rd = d.find_roof(context, o, g)
|
|
if rd is not None:
|
|
d.setup_childs(o, g)
|
|
rd.make_wall_fit(context, r, o, self.inside)
|
|
return {'FINISHED'}
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define operator class to draw a wall
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
class ARCHIPACK_OT_wall2_draw(ArchipackDrawTool, Operator):
|
|
bl_idname = "archipack.wall2_draw"
|
|
bl_label = "Draw a Wall"
|
|
bl_description = "Create a wall by drawing its baseline in 3D view"
|
|
bl_category = 'Archipack'
|
|
|
|
o = None
|
|
state = 'RUNNING'
|
|
flag_create = False
|
|
flag_next = False
|
|
wall_part1 = None
|
|
wall_line1 = None
|
|
line = None
|
|
label = None
|
|
feedback = None
|
|
takeloc = Vector((0, 0, 0))
|
|
sel = []
|
|
act = None
|
|
|
|
# constraint to other wall and make a T child
|
|
parent = None
|
|
takemat = None
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return True
|
|
|
|
def draw_callback(self, _self, context):
|
|
self.feedback.draw(context)
|
|
|
|
def sp_draw(self, sp, context):
|
|
z = 2.7
|
|
if self.state == 'CREATE':
|
|
p0 = self.takeloc
|
|
else:
|
|
p0 = sp.takeloc
|
|
|
|
p1 = sp.placeloc
|
|
delta = p1 - p0
|
|
# print("sp_draw state:%s delta:%s p0:%s p1:%s" % (self.state, delta.length, p0, p1))
|
|
if delta.length == 0:
|
|
return
|
|
self.wall_part1.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))])
|
|
self.wall_line1.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))])
|
|
self.wall_part1.draw(context)
|
|
self.wall_line1.draw(context)
|
|
self.line.p = p0
|
|
self.line.v = delta
|
|
self.label.set_pos(context, self.line.length, self.line.lerp(0.5), self.line.v, normal=Vector((0, 0, 1)))
|
|
self.label.draw(context)
|
|
self.line.draw(context)
|
|
|
|
def sp_callback(self, context, event, state, sp):
|
|
logger.debug("ARCHIPACK_OT_wall2_draw.sp_callback event %s %s state:%s", event.type, event.value, state)
|
|
|
|
if state == 'SUCCESS':
|
|
|
|
if self.state == 'CREATE':
|
|
takeloc = self.takeloc
|
|
delta = sp.placeloc - self.takeloc
|
|
else:
|
|
takeloc = sp.takeloc
|
|
delta = sp.delta
|
|
|
|
old = context.object
|
|
if self.o is None:
|
|
bpy.ops.archipack.wall2(auto_manipulate=False)
|
|
o = context.object
|
|
o.location = takeloc
|
|
self.o = o
|
|
d = archipack_wall2.datablock(o)
|
|
|
|
part = d.parts[0]
|
|
part.length = delta.length
|
|
else:
|
|
o = self.o
|
|
# select and make active
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
d = archipack_wall2.datablock(o)
|
|
# Check for end close to start and close when applicable
|
|
dp = sp.placeloc - o.location
|
|
if dp.length < 0.01:
|
|
d.closed = True
|
|
self.state = 'CANCEL'
|
|
return
|
|
|
|
part = d.add_part(context, delta.length)
|
|
|
|
# print("self.o :%s" % o.name)
|
|
rM = o.matrix_world.inverted().to_3x3()
|
|
g = d.get_generator()
|
|
w = g.segs[-2]
|
|
dp = rM @ delta
|
|
da = atan2(dp.y, dp.x) - w.straight(1).angle
|
|
a0 = part.a0 + da
|
|
if a0 > pi:
|
|
a0 -= 2 * pi
|
|
if a0 < -pi:
|
|
a0 += 2 * pi
|
|
part.a0 = a0
|
|
d.update(context)
|
|
|
|
old.select_set(state=True)
|
|
context.view_layer.objects.active = old
|
|
self.flag_next = True
|
|
context.area.tag_redraw()
|
|
# print("feedback.on:%s" % self.feedback.on)
|
|
|
|
self.state = state
|
|
|
|
def sp_init(self, context, event, state, sp):
|
|
# print("sp_init event %s %s %s" % (event.type, event.value, state))
|
|
if state == 'SUCCESS':
|
|
# point placed, check if a wall was under mouse
|
|
res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event)
|
|
if res:
|
|
d = archipack_wall2.datablock(wall)
|
|
if event.ctrl:
|
|
# user snap, use direction as constraint
|
|
tM.translation = sp.placeloc.copy()
|
|
else:
|
|
# without snap, use wall's bottom
|
|
tM.translation -= y.normalized() * (0.5 * d.width)
|
|
self.takeloc = tM.translation
|
|
self.parent = wall.name
|
|
self.takemat = tM
|
|
else:
|
|
self.takeloc = sp.placeloc.copy()
|
|
|
|
self.state = 'RUNNING'
|
|
# print("feedback.on:%s" % self.feedback.on)
|
|
elif state == 'CANCEL':
|
|
self.state = state
|
|
return
|
|
|
|
def ensure_ccw(self):
|
|
"""
|
|
Wall to slab expect wall vertex order to be ccw
|
|
so reverse order here when needed
|
|
"""
|
|
d = archipack_wall2.datablock(self.o)
|
|
g = d.get_generator(axis=False)
|
|
pts = [seg.p0 for seg in g.segs]
|
|
|
|
if d.closed:
|
|
pts.append(pts[0])
|
|
|
|
if d.is_cw(pts):
|
|
d.x_offset = 1
|
|
pts = list(reversed(pts))
|
|
self.o.location += pts[0] - pts[-1]
|
|
|
|
d.from_points(pts, d.closed)
|
|
|
|
def modal(self, context, event):
|
|
|
|
context.area.tag_redraw()
|
|
if event.type in {'NONE', 'TIMER', 'TIMER_REPORT', 'EVT_TWEAK_L', 'WINDOW_DEACTIVATE'}:
|
|
return {'PASS_THROUGH'}
|
|
|
|
if self.keymap.check(event, self.keymap.delete):
|
|
self.feedback.disable()
|
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
|
self.o = None
|
|
return {'FINISHED', 'PASS_THROUGH'}
|
|
|
|
if self.state == 'STARTING' and event.type not in {'ESC', 'RIGHTMOUSE'}:
|
|
# wait for takeloc being visible when button is over horizon
|
|
takeloc = self.mouse_to_plane(context, event)
|
|
if takeloc is not None:
|
|
logger.debug("ARCHIPACK_OT_wall2_draw.modal(STARTING) location:%s", takeloc)
|
|
snap_point(takeloc=takeloc,
|
|
callback=self.sp_init,
|
|
constraint_axis=(True, True, False),
|
|
release_confirm=True)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
elif self.state == 'RUNNING':
|
|
# print("RUNNING")
|
|
logger.debug("ARCHIPACK_OT_wall2_draw.modal(RUNNING) location:%s", self.takeloc)
|
|
self.state = 'CREATE'
|
|
snap_point(takeloc=self.takeloc,
|
|
draw=self.sp_draw,
|
|
takemat=self.takemat,
|
|
transform_orientation=context.scene.transform_orientation_slots[0].type,
|
|
callback=self.sp_callback,
|
|
constraint_axis=(True, True, False),
|
|
release_confirm=self.max_style_draw_tool)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
elif self.state != 'CANCEL' and event.type in {'C', 'c'}:
|
|
|
|
logger.debug("ARCHIPACK_OT_wall2_draw.modal(%s) C pressed", self.state)
|
|
self.feedback.disable()
|
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
|
|
|
o = self.o
|
|
# select and make active
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
|
|
d = archipack_wall2.datablock(o)
|
|
d.closed = True
|
|
|
|
if bpy.ops.archipack.manipulate.poll():
|
|
bpy.ops.archipack.manipulate('INVOKE_DEFAULT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
elif self.state != 'CANCEL' and event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}:
|
|
|
|
# print('LEFTMOUSE %s' % (event.value))
|
|
self.feedback.instructions(context, "Draw a wall", "Click & Drag to add a segment", [
|
|
('ENTER', 'Add part'),
|
|
('BACK_SPACE', 'Remove part'),
|
|
('CTRL', 'Snap'),
|
|
('C', 'Close wall and exit'),
|
|
('MMBTN', 'Constraint to axis'),
|
|
('X Y', 'Constraint to axis'),
|
|
('RIGHTCLICK or ESC', 'exit')
|
|
])
|
|
|
|
# press with max mode release with blender mode
|
|
if self.max_style_draw_tool:
|
|
evt_value = 'PRESS'
|
|
else:
|
|
evt_value = 'RELEASE'
|
|
|
|
if event.value == evt_value:
|
|
|
|
if self.flag_next:
|
|
self.flag_next = False
|
|
o = self.o
|
|
|
|
# select and make active
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
|
|
d = archipack_wall2.datablock(o)
|
|
g = d.get_generator()
|
|
p0 = g.segs[-2].p0
|
|
p1 = g.segs[-2].p1
|
|
dp = p1 - p0
|
|
takemat = o.matrix_world @ Matrix([
|
|
[dp.x, dp.y, 0, p1.x],
|
|
[dp.y, -dp.x, 0, p1.y],
|
|
[0, 0, 1, 0],
|
|
[0, 0, 0, 1]
|
|
])
|
|
takeloc = o.matrix_world @ p1.to_3d()
|
|
o.select_set(state=False)
|
|
else:
|
|
takemat = None
|
|
takeloc = self.mouse_to_plane(context, event)
|
|
|
|
if takeloc is not None:
|
|
logger.debug("ARCHIPACK_OT_wall2_draw.modal(CREATE) location:%s", takeloc)
|
|
|
|
snap_point(takeloc=takeloc,
|
|
takemat=takemat,
|
|
draw=self.sp_draw,
|
|
callback=self.sp_callback,
|
|
constraint_axis=(True, True, False),
|
|
release_confirm=self.max_style_draw_tool)
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
if self.keymap.check(event, self.keymap.undo) or (
|
|
event.type in {'BACK_SPACE'} and event.value == 'RELEASE'
|
|
):
|
|
if self.o is not None:
|
|
o = self.o
|
|
|
|
# select and make active
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
d = archipack_wall2.datablock(o)
|
|
if d.n_parts > 1:
|
|
d.n_parts -= 1
|
|
return {'RUNNING_MODAL'}
|
|
|
|
if self.state == 'CANCEL' or (event.type in {'ESC', 'RIGHTMOUSE'} and
|
|
event.value == 'RELEASE'):
|
|
|
|
self.feedback.disable()
|
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
|
logger.debug("ARCHIPACK_OT_wall2_draw.modal(CANCEL) %s", event.type)
|
|
if self.o is None:
|
|
for o in self.sel:
|
|
o.select_set(state=True)
|
|
# select and make active
|
|
if self.act is not None:
|
|
self.act.select_set(state=True)
|
|
context.view_layer.objects.active = self.act
|
|
|
|
else:
|
|
o = self.o
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
|
|
# remove last segment with blender mode
|
|
d = archipack_wall2.datablock(o)
|
|
if not self.max_style_draw_tool:
|
|
if not d.closed and d.n_parts > 1:
|
|
d.n_parts -= 1
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
# make T child
|
|
if self.parent is not None:
|
|
d.t_part = self.parent
|
|
|
|
if bpy.ops.archipack.manipulate.poll():
|
|
bpy.ops.archipack.manipulate('INVOKE_DEFAULT', object_name=o.name)
|
|
|
|
return {'FINISHED'}
|
|
|
|
return {'PASS_THROUGH'}
|
|
|
|
def invoke(self, context, event):
|
|
|
|
if context.mode == "OBJECT":
|
|
prefs = context.preferences.addons[__name__.split('.')[0]].preferences
|
|
self.max_style_draw_tool = prefs.max_style_draw_tool
|
|
self.keymap = Keymaps(context)
|
|
self.wall_part1 = GlPolygon((0.5, 0, 0, 0.2))
|
|
self.wall_line1 = GlPolyline((0.5, 0, 0, 0.8))
|
|
self.line = GlLine()
|
|
self.label = GlText()
|
|
self.feedback = FeedbackPanel()
|
|
self.feedback.instructions(context, "Draw a wall", "Click & Drag to start", [
|
|
('CTRL', 'Snap'),
|
|
('MMBTN', 'Constraint to axis'),
|
|
('X Y', 'Constraint to axis'),
|
|
('SHIFT+CTRL+TAB', 'Switch snap mode'),
|
|
('RIGHTCLICK or ESC', 'exit without change')
|
|
])
|
|
self.feedback.enable()
|
|
args = (self, context)
|
|
|
|
self.sel = context.selected_objects[:]
|
|
self.act = context.active_object
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
|
|
self.state = 'STARTING'
|
|
|
|
self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL')
|
|
context.window_manager.modal_handler_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define operator class to manage parts
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
class ARCHIPACK_OT_wall2_insert(Operator):
|
|
bl_idname = "archipack.wall2_insert"
|
|
bl_label = "Insert"
|
|
bl_description = "Insert part"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
index : IntProperty(default=0)
|
|
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
o = context.active_object
|
|
d = archipack_wall2.datablock(o)
|
|
if d is None:
|
|
return {'CANCELLED'}
|
|
d.insert_part(context, o, self.index)
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_OT_wall2_remove(Operator):
|
|
bl_idname = "archipack.wall2_remove"
|
|
bl_label = "Remove"
|
|
bl_description = "Remove part"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
index : IntProperty(default=0)
|
|
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
o = context.active_object
|
|
d = archipack_wall2.datablock(o)
|
|
if d is None:
|
|
return {'CANCELLED'}
|
|
d.remove_part(context, o, self.index)
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class ARCHIPACK_OT_wall2_reverse(Operator):
|
|
bl_idname = "archipack.wall2_reverse"
|
|
bl_label = "Reverse"
|
|
bl_description = "Reverse parts order"
|
|
bl_category = 'Archipack'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
if context.mode == "OBJECT":
|
|
o = context.active_object
|
|
d = archipack_wall2.datablock(o)
|
|
if d is None:
|
|
return {'CANCELLED'}
|
|
d.reverse(context, o)
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define operator class to manipulate object
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
class ARCHIPACK_OT_wall2_manipulate(Operator):
|
|
bl_idname = "archipack.wall2_manipulate"
|
|
bl_label = "Manipulate"
|
|
bl_description = "Manipulate"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return archipack_wall2.filter(context.active_object)
|
|
|
|
def invoke(self, context, event):
|
|
d = archipack_wall2.datablock(context.active_object)
|
|
d.manipulable_invoke(context)
|
|
return {'FINISHED'}
|
|
|
|
def execute(self, context):
|
|
"""
|
|
For use in boolean ops
|
|
"""
|
|
if archipack_wall2.filter(context.active_object):
|
|
o = context.active_object
|
|
d = archipack_wall2.datablock(o)
|
|
g = d.get_generator()
|
|
d.setup_childs(o, g)
|
|
d.update_childs(context, o, g)
|
|
d.update(context)
|
|
o.select_set(state=True)
|
|
context.view_layer.objects.active = o
|
|
return {'FINISHED'}
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(archipack_wall2_part)
|
|
bpy.utils.register_class(archipack_wall2_child)
|
|
bpy.utils.register_class(archipack_wall2)
|
|
Mesh.archipack_wall2 = CollectionProperty(type=archipack_wall2)
|
|
bpy.utils.register_class(ARCHIPACK_PT_wall2)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2_draw)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2_insert)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2_remove)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2_reverse)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2_manipulate)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2_from_curve)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2_from_slab)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2_throttle_update)
|
|
bpy.utils.register_class(ARCHIPACK_OT_wall2_fit_roof)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_class(archipack_wall2_part)
|
|
bpy.utils.unregister_class(archipack_wall2_child)
|
|
bpy.utils.unregister_class(archipack_wall2)
|
|
del Mesh.archipack_wall2
|
|
bpy.utils.unregister_class(ARCHIPACK_PT_wall2)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2_draw)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2_insert)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2_remove)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2_reverse)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2_manipulate)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_curve)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_slab)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2_throttle_update)
|
|
bpy.utils.unregister_class(ARCHIPACK_OT_wall2_fit_roof)
|