diff options
author | Stephen Leger <stephen@3dservices.ch> | 2017-07-22 14:25:28 +0300 |
---|---|---|
committer | Stephen Leger <stephen@3dservices.ch> | 2017-07-22 14:26:04 +0300 |
commit | c1ab9b4b9c6c0226f8d7789b92efda9b0f33cfd1 (patch) | |
tree | 37d5a97c758fa9af48d1dfb5428edd72072d882a /archipack/archipack_wall2.py | |
parent | 5638a8783502138500912061dde0e8ee476d7fca (diff) |
archipack: T52120 release to official
Diffstat (limited to 'archipack/archipack_wall2.py')
-rw-r--r-- | archipack/archipack_wall2.py | 2220 |
1 files changed, 2220 insertions, 0 deletions
diff --git a/archipack/archipack_wall2.py b/archipack/archipack_wall2.py new file mode 100644 index 00000000..4944f59f --- /dev/null +++ b/archipack/archipack_wall2.py @@ -0,0 +1,2220 @@ +# -*- 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 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, ArchpackDrawTool +from .archipack_2d import Line, Arc +from .archipack_snap import snap_point +from .archipack_keymaps import Keymaps + + +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 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 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 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 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): + + 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, dont 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)]) + + 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) + + 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 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 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) + 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 allready + # 5 o and w dosent 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.scene.objects.active = o + bpy.ops.archipack.reference_point() + o.select = True + else: + context.scene.objects.active = o.parent + w.select = True + else: + # w has parent + if o.parent is not w.parent: + link_to_parent = True + context.scene.objects.active = w.parent + o.select = 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 = 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 = True + context.scene.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: + g = WallGenerator(None) + g.add_part(self, d.z, d.flip) + w = g.segs[0] + # w0 - w - w1 + 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", + min=0, + 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 = [o for o in 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", icon_only=True, text="Part " + str(index + 1), emboss=False) + else: + row.prop(self, 'expand', icon="TRIA_RIGHT", icon_only=True, 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) + 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="x 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(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="RealTime", + 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() + pts = [seg.p0.to_3d() for seg in g.segs] + + if self.closed: + pts.append(pts[0]) + + pts = list(reversed(pts)) + self.auto_update = False + + # 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 = True + d.auto_update = False + d.flip = child.flip + d.y = self.width + d.auto_update = True + c.select = False + x, y = n.p - (0.5 * w * n.v) + else: + x, y = n.p - (child.pos.y * n.v) + + context.scene.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.scene.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): + 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) + # dont do anything .. + # self.manipulable_release(context) + # self.manipulate_mode = True + self.manipulable_setup(context) + self.manipulate_mode = True + + self._manipulable_invoke(context) + + return True + + +# Update throttle (smell hack here) +# use 2 globals to store a timer and state of update_action +# NO MORE USING THIS PART, kept as it as it may be usefull in some cases +update_timer = None +update_timer_updating = False + + +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: + update_timer_updating = True + o = context.scene.objects.get(self.name) + # print("delay update of %s" % (self.name)) + if o is not None: + o.select = True + context.scene.objects.active = o + d = o.data.archipack_wall2[0] + g = d.get_generator() + # update child location and size + d.relocate_childs(context, o, g) + # store gl points + d.update_childs(context, o, g) + return self.cancel(context) + return {'PASS_THROUGH'} + + def execute(self, context): + global update_timer + global update_timer_updating + if update_timer is not None: + if update_timer_updating: + return {'CANCELLED'} + # reset update_timer so it only occurs once 0.1s after last action + context.window_manager.event_timer_remove(update_timer) + update_timer = context.window_manager.event_timer_add(0.1, context.window) + return {'CANCELLED'} + update_timer_updating = False + context.window_manager.modal_handler_add(self) + update_timer = context.window_manager.event_timer_add(0.1, 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='HAND') + # 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') + row = layout.row() + row.operator("archipack.wall2_reverse", icon='FILE_REFRESH') + 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 + context.scene.objects.link(o) + o.select = True + # around 12 degree + m.auto_smooth_angle = 0.20944 + context.scene.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 = True + context.scene.objects.active = o + self.manipulate() + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_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.scene.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 = True + context.scene.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.scene.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 = True + context.scene.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.scene.objects.active = o + bpy.ops.archipack.reference_point() + else: + o.parent.select = True + context.scene.objects.active = o.parent + o.select = True + slab.select = True + bpy.ops.archipack.parent_to_reference() + o.parent.select = 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 = True + context.scene.objects.active = o + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +# ------------------------------------------------------------------ +# Define operator class to draw a wall +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): + bl_idname = "archipack.wall2_draw" + bl_label = "Draw a Wall" + bl_description = "Draw a Wall" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + 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 + + max_style_draw_tool = False + + @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): + # print("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.active_object + if self.o is None: + bpy.ops.archipack.wall2(auto_manipulate=False) + o = context.active_object + o.location = takeloc + self.o = o + d = archipack_wall2.datablock(o) + part = d.parts[0] + part.length = delta.length + else: + o = self.o + o.select = True + context.scene.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 + + context.scene.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, y = 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() + pts = [seg.p0.to_3d() 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() + # print("modal event %s %s" % (event.type, event.value)) + if event.type == 'NONE': + return {'PASS_THROUGH'} + + if self.state == 'STARTING': + takeloc = self.mouse_to_plane(context, event) + # wait for takeloc being visible when button is over horizon + rv3d = context.region_data + viewinv = rv3d.view_matrix.inverted() + if (takeloc * viewinv).z < 0: + # print("STARTING") + # when user press draw button + snap_point(takeloc=takeloc, + callback=self.sp_init, + # transform_orientation=context.space_data.transform_orientation, + constraint_axis=(True, True, False), + release_confirm=True) + return {'RUNNING_MODAL'} + + elif self.state == 'RUNNING': + # print("RUNNING") + # when user start drawing + + # release confirm = False on blender mode + # release confirm = True on max mode + self.state = 'CREATE' + snap_point(takeloc=self.takeloc, + draw=self.sp_draw, + takemat=self.takemat, + transform_orientation=context.space_data.transform_orientation, + 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 {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: + + # print('LEFTMOUSE %s' % (event.value)) + self.feedback.instructions(context, "Draw a wall", "Click & Drag to add a segment", [ + ('CTRL', 'Snap'), + ('MMBTN', 'Constraint to axis'), + ('X Y', 'Constraint to axis'), + ('BACK_SPACE', 'Remove part'), + ('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 + o.select = True + context.scene.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 = False + else: + takeloc = self.mouse_to_plane(context, event) + takemat = None + + 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 + o.select = True + context.scene.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') + + if self.o is None: + context.scene.objects.active = self.act + for o in self.sel: + o.select = True + else: + self.o.select = True + context.scene.objects.active = self.o + d = archipack_wall2.datablock(self.o) + + # remove last segment with blender mode + if not self.max_style_draw_tool: + if not d.closed and d.n_parts > 1: + d.n_parts -= 1 + + self.o.select = True + context.scene.objects.active = self.o + # make T child + if self.parent is not None: + d.t_part = self.parent + + if bpy.ops.archipack.wall2_manipulate.poll(): + bpy.ops.archipack.wall2_manipulate('INVOKE_DEFAULT') + + return {'FINISHED'} + + return {'PASS_THROUGH'} + + def invoke(self, context, event): + + if context.mode == "OBJECT": + prefs = context.user_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 = [o for o in 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 = True + context.scene.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) + + +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) |