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_window.py | |
parent | 5638a8783502138500912061dde0e8ee476d7fca (diff) |
archipack: T52120 release to official
Diffstat (limited to 'archipack/archipack_window.py')
-rw-r--r-- | archipack/archipack_window.py | 2098 |
1 files changed, 2098 insertions, 0 deletions
diff --git a/archipack/archipack_window.py b/archipack/archipack_window.py new file mode 100644 index 00000000..2be55947 --- /dev/null +++ b/archipack/archipack_window.py @@ -0,0 +1,2098 @@ +# -*- coding:utf-8 -*- + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# +# ---------------------------------------------------------- +# noinspection PyUnresolvedReferences +import bpy +# noinspection PyUnresolvedReferences +from bpy.types import Operator, PropertyGroup, Mesh, Panel +from bpy.props import ( + FloatProperty, IntProperty, BoolProperty, BoolVectorProperty, + CollectionProperty, FloatVectorProperty, EnumProperty, StringProperty +) +from mathutils import Vector +from math import tan, sqrt +from .bmesh_utils import BmeshEdit as bmed +from .panel import Panel as WindowPanel +from .materialutils import MaterialUtils +from .archipack_handle import create_handle, window_handle_vertical_01, window_handle_vertical_02 +# from .archipack_door_panel import ARCHIPACK_OT_select_parent +from .archipack_manipulator import Manipulable +from .archipack_preset import ArchipackPreset, PresetMenuOperator +from .archipack_gl import FeedbackPanel +from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool +from .archipack_keymaps import Keymaps + + +def update(self, context): + self.update(context) + + +def update_childs(self, context): + self.update(context, childs_only=True) + + +def set_cols(self, value): + if self.n_cols != value: + self.auto_update = False + self._set_width(value) + self.auto_update = True + self.n_cols = value + return None + + +def get_cols(self): + return self.n_cols + + +class archipack_window_panelrow(PropertyGroup): + width = FloatVectorProperty( + name="width", + min=0.5, + max=100.0, + default=[ + 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50 + ], + size=31, + update=update + ) + fixed = BoolVectorProperty( + name="Fixed", + default=[ + False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False + ], + size=32, + update=update + ) + cols = IntProperty( + name="panels", + description="number of panels getter and setter, to avoid infinite recursion", + min=1, + max=32, + default=2, + get=get_cols, set=set_cols + ) + n_cols = IntProperty( + name="panels", + description="store number of panels, internal use only to avoid infinite recursion", + min=1, + max=32, + default=2, + update=update + ) + height = FloatProperty( + name="Height", + min=0.1, + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + update=update + ) + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + name="auto_update", + description="disable auto update to avoid infinite recursion", + default=True + ) + + def get_row(self, x, y): + size = [Vector((x * self.width[w] / 100, y, 0)) for w in range(self.cols - 1)] + sum_x = sum([s.x for s in size]) + size.append(Vector((x - sum_x, y, 0))) + origin = [] + pivot = [] + ttl = 0 + xh = x / 2 + n_center = len(size) / 2 + for i, sx in enumerate(size): + ttl += sx.x + if i < n_center: + # pivot left + origin.append(Vector((ttl - xh - sx.x, 0))) + pivot.append(1) + else: + # pivot right + origin.append(Vector((ttl - xh, 0))) + pivot.append(-1) + return size, origin, pivot + + def _set_width(self, cols): + width = 100 / cols + for i in range(cols - 1): + self.width[i] = width + + 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_window.datablock(o) + if props: + for row in props.rows: + if row == self: + return props + return None + + def update(self, context): + if self.auto_update: + props = self.find_datablock_in_selection(context) + if props is not None: + props.update(context, childs_only=False) + + def draw(self, layout, context, last_row): + # store parent at runtime to trigger update on parent + row = layout.row() + row.prop(self, "cols") + row = layout.row() + if not last_row: + row.prop(self, "height") + for i in range(self.cols - 1): + row = layout.row() + row.prop(self, "width", text="col " + str(i + 1), index=i) + row.prop(self, "fixed", text="fixed", index=i) + row = layout.row() + row.label(text="col " + str(self.cols)) + row.prop(self, "fixed", text="fixed", index=(self.cols - 1)) + + +class archipack_window_panel(ArchipackObject, PropertyGroup): + center = FloatVectorProperty( + subtype='XYZ' + ) + origin = FloatVectorProperty( + subtype='XYZ' + ) + size = FloatVectorProperty( + subtype='XYZ' + ) + radius = FloatVectorProperty( + subtype='XYZ' + ) + angle_y = FloatProperty( + name='angle', + unit='ROTATION', + subtype='ANGLE', + min=-1.5, max=1.5, + default=0, precision=2, + description='angle' + ) + frame_y = FloatProperty( + name='Depth', + min=0, + default=0.06, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='frame depth' + ) + frame_x = FloatProperty( + name='Width', + min=0, + default=0.06, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='frame width' + ) + curve_steps = IntProperty( + name="curve steps", + min=1, + max=128, + default=1 + ) + shape = EnumProperty( + name='Shape', + items=( + ('RECTANGLE', 'Rectangle', '', 0), + ('ROUND', 'Top Round', '', 1), + ('ELLIPSIS', 'Top elliptic', '', 2), + ('QUADRI', 'Top oblique', '', 3), + ('CIRCLE', 'Full circle', '', 4) + ), + default='RECTANGLE' + ) + pivot = FloatProperty( + name='pivot', + min=-1, max=1, + default=-1, precision=2, + description='pivot' + ) + side_material = IntProperty( + name="side material", + min=0, + max=2, + default=0 + ) + handle = EnumProperty( + name='Shape', + items=( + ('NONE', 'No handle', '', 0), + ('INSIDE', 'Inside', '', 1), + ('BOTH', 'Inside and outside', '', 2) + ), + default='NONE' + ) + handle_model = IntProperty( + name="handle model", + default=1, + min=1, + max=2 + ) + handle_altitude = FloatProperty( + name='handle altitude', + min=0, + default=0.2, precision=2, + unit='LENGTH', subtype='DISTANCE', + description='handle altitude' + ) + fixed = BoolProperty( + name="Fixed", + default=False + ) + + @property + def window(self): + verre = 0.005 + chanfer = 0.004 + x0 = 0 + x1 = self.frame_x + x2 = 0.75 * self.frame_x + x3 = chanfer + y0 = -self.frame_y + y1 = 0 + y2 = -0.5 * self.frame_y + y3 = -chanfer + y4 = chanfer - self.frame_y + + if self.fixed: + # profil carre avec support pour verre + # p ______ y1 + # / | y3 + # | |___ + # x |___ y2 verre + # | | y4 + # \______| y0 + # x0 x3 x1 + # + x1 = 0.5 * self.frame_x + y1 = -0.45 * self.frame_y + y3 = y1 - chanfer + y4 = chanfer + y0 + y2 = (y0 + y2) / 2 + return WindowPanel( + True, # closed + [1, 0, 0, 0, 1, 2, 2, 2, 2], # x index + [x0, x3, x1], + [y0, y4, y2, y3, y1, y1, y2 + verre, y2 - verre, y0], + [0, 0, 1, 1, 1, 1, 0, 0, 0], # materials + side_cap_front=6, + side_cap_back=7 # cap index + ) + else: + # profil avec chanfrein et joint et support pour verre + # p ____ y1 inside + # / |_ y3 + # | |___ + # x |___ y2 verre + # | _| y4 + # \____| y0 + # x0 x3 x2 x1 outside + if self.side_material == 0: + materials = [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0] + elif self.side_material == 1: + # rail window exterior + materials = [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0] + else: + # rail window interior + materials = [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0] + return WindowPanel( + True, # closed shape + [1, 0, 0, 0, 1, 2, 2, 3, 3, 3, 3, 2, 2], # x index + [x0, x3, x2, x1], # unique x positions + [y0, y4, y2, y3, y1, y1, y3, y3, y2 + verre, y2 - verre, y4, y4, y0], + materials, # materials + side_cap_front=8, + side_cap_back=9 # cap index + ) + + @property + def verts(self): + offset = Vector((0, 0, 0)) + return self.window.vertices(self.curve_steps, offset, self.center, self.origin, self.size, + self.radius, self.angle_y, self.pivot, shape_z=None, path_type=self.shape) + + @property + def faces(self): + return self.window.faces(self.curve_steps, path_type=self.shape) + + @property + def matids(self): + return self.window.mat(self.curve_steps, 2, 2, path_type=self.shape) + + @property + def uvs(self): + return self.window.uv(self.curve_steps, self.center, self.origin, self.size, + self.radius, self.angle_y, self.pivot, 0, self.frame_x, path_type=self.shape) + + def find_handle(self, o): + for child in o.children: + if 'archipack_handle' in child: + return child + return None + + def update_handle(self, context, o): + handle = self.find_handle(o) + if handle is None: + m = bpy.data.meshes.new("Handle") + handle = create_handle(context, o, m) + MaterialUtils.add_handle_materials(handle) + if self.handle_model == 1: + verts, faces = window_handle_vertical_01(1) + else: + verts, faces = window_handle_vertical_02(1) + handle.location = (self.pivot * (self.size.x - 0.4 * self.frame_x), 0, self.handle_altitude) + bmed.buildmesh(context, handle, verts, faces) + + def remove_handle(self, context, o): + handle = self.find_handle(o) + if handle is not None: + context.scene.objects.unlink(handle) + bpy.data.objects.remove(handle, do_unlink=True) + + def update(self, context): + + o = self.find_in_selection(context) + + if o is None: + return + + # update handle, dosent care of instances as window will do + if self.handle == 'NONE': + self.remove_handle(context, o) + else: + self.update_handle(context, o) + + bmed.buildmesh(context, o, self.verts, self.faces, self.matids, self.uvs) + + self.restore_context(context) + + +class archipack_window(ArchipackObject, Manipulable, PropertyGroup): + x = FloatProperty( + name='width', + min=0.25, + default=100.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Width', update=update + ) + y = FloatProperty( + name='depth', + min=0.1, + default=0.20, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Depth', update=update, + ) + z = FloatProperty( + name='height', + min=0.1, + default=1.2, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='height', update=update, + ) + angle_y = FloatProperty( + name='angle', + unit='ROTATION', + subtype='ANGLE', + min=-1.5, max=1.5, + default=0, precision=2, + description='angle', update=update, + ) + radius = FloatProperty( + name='radius', + min=0.1, + default=2.5, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='radius', update=update, + ) + elipsis_b = FloatProperty( + name='ellipsis', + min=0.1, + default=0.5, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='ellipsis vertical size', update=update, + ) + altitude = FloatProperty( + name='altitude', + default=1.0, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='altitude', update=update, + ) + offset = FloatProperty( + name='offset', + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='offset', update=update, + ) + frame_y = FloatProperty( + name='Depth', + min=0, + default=0.06, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame depth', update=update, + ) + frame_x = FloatProperty( + name='Width', + min=0, + default=0.06, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame width', update=update, + ) + out_frame = BoolProperty( + name="Out frame", + default=False, update=update, + ) + out_frame_y = FloatProperty( + name='Side depth', + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame side depth', update=update, + ) + out_frame_y2 = FloatProperty( + name='Front depth', + min=0.001, + default=0.02, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame front depth', update=update, + ) + out_frame_x = FloatProperty( + name='Front Width', + min=0.0, + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='frame width set to 0 disable front frame', update=update, + ) + out_frame_offset = FloatProperty( + name='offset', + min=0.0, + default=0.0, precision=3, step=0.1, + unit='LENGTH', subtype='DISTANCE', + description='frame offset', update=update, + ) + out_tablet_enable = BoolProperty( + name="Out tablet", + default=True, update=update, + ) + out_tablet_x = FloatProperty( + name='Width', + min=0.0, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet width', update=update, + ) + out_tablet_y = FloatProperty( + name='Depth', + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet depth', update=update, + ) + out_tablet_z = FloatProperty( + name='Height', + min=0.001, + default=0.03, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet height', update=update, + ) + in_tablet_enable = BoolProperty( + name="In tablet", + default=True, update=update, + ) + in_tablet_x = FloatProperty( + name='Width', + min=0.0, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet width', update=update, + ) + in_tablet_y = FloatProperty( + name='Depth', + min=0.001, + default=0.04, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet depth', update=update, + ) + in_tablet_z = FloatProperty( + name='Height', + min=0.001, + default=0.03, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='tablet height', update=update, + ) + blind_enable = BoolProperty( + name="Blind", + default=False, update=update, + ) + blind_y = FloatProperty( + name='Depth', + min=0.001, + default=0.002, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Store depth', update=update, + ) + blind_z = FloatProperty( + name='Height', + min=0.001, + default=0.03, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='Store height', update=update, + ) + blind_open = FloatProperty( + name='Open', + min=0.0, max=100, + default=80, precision=1, + subtype='PERCENTAGE', + description='Store open', update=update, + ) + rows = CollectionProperty(type=archipack_window_panelrow) + n_rows = IntProperty( + name="number of rows", + min=1, + max=32, + default=1, update=update, + ) + curve_steps = IntProperty( + name="curve steps", + min=6, + max=128, + default=16, update=update, + ) + hole_outside_mat = IntProperty( + name="Outside", + min=0, + max=128, + default=0, update=update, + ) + hole_inside_mat = IntProperty( + name="Inside", + min=0, + max=128, + default=1, update=update, + ) + window_shape = EnumProperty( + name='Shape', + items=( + ('RECTANGLE', 'Rectangle', '', 0), + ('ROUND', 'Top Round', '', 1), + ('ELLIPSIS', 'Top elliptic', '', 2), + ('QUADRI', 'Top oblique', '', 3), + ('CIRCLE', 'Full circle', '', 4) + ), + default='RECTANGLE', update=update, + ) + window_type = EnumProperty( + name='Type', + items=( + ('FLAT', 'Flat window', '', 0), + ('RAIL', 'Rail window', '', 1) + ), + default='FLAT', update=update, + ) + warning = BoolProperty( + name="warning", + default=False + ) + handle_enable = BoolProperty( + name='handle', + default=True, update=update_childs, + ) + handle_altitude = FloatProperty( + name="altitude", + min=0, + default=1.4, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='handle altitude', update=update_childs, + ) + hole_margin = FloatProperty( + name='hole margin', + min=0.0, + default=0.1, precision=2, step=1, + unit='LENGTH', subtype='DISTANCE', + description='how much hole surround wall' + ) + flip = BoolProperty( + default=False, + update=update, + description='flip outside and outside material of hole' + ) + # layout related + display_detail = BoolProperty( + options={'SKIP_SAVE'}, + default=False + ) + display_panels = BoolProperty( + options={'SKIP_SAVE'}, + default=True + ) + display_materials = BoolProperty( + options={'SKIP_SAVE'}, + default=True + ) + + auto_update = BoolProperty( + options={'SKIP_SAVE'}, + default=True, + update=update + ) + + @property + def shape(self): + if self.window_type == 'RAIL': + return 'RECTANGLE' + else: + return self.window_shape + + @property + def window(self): + # Flat window frame profil + # ___ y1 + # | |__ + # | | y2 + # |______| y0 + # + x0 = 0 + x1 = -x0 - self.frame_x + x2 = x0 + 0.5 * self.frame_x + y0 = 0.5 * self.y - self.offset + y2 = y0 + 0.5 * self.frame_y + + if self.window_type == 'FLAT': + y1 = y0 + self.frame_y + return WindowPanel( + True, # closed + [0, 0, 1, 1, 2, 2], # x index + [x1, x0, x2], + [y0, y1, y1, y2, y2, y0], + [1, 1, 1, 1, 0, 0] # material index + ) + else: + # Rail window frame profil + # ________ y1 + # | __| y5 + # | |__ y4 + # | __| y3 + # | |____ + # | | y2 + # |__________| y0 + # -x1 0 x3 x2 + x2 = x0 + 0.35 * self.frame_y + x3 = x0 + 0.2 * self.frame_x + y1 = y0 + 2.55 * self.frame_y + y3 = y0 + 1.45 * self.frame_y + y4 = y0 + 1.55 * self.frame_y + y5 = y0 + 2.45 * self.frame_y + + return WindowPanel( + True, # closed + [0, 0, 2, 2, 1, 1, 2, 2, 1, 1, 3, 3], # x index + [x1, x0, x3, x2], + [y0, y1, y1, y5, y5, y4, y4, y3, y3, y2, y2, y0], + [1, 1, 1, 3, 1, 3, 3, 3, 0, 3, 0, 0] # material index + ) + + @property + def hole(self): + # profil percement ____ + # _____ y_inside vertical ___| x1 + # | + # |__ y0 outside ___ + # |___ y_outside |____ x1-shape_z inside + # -x1 x0 + y0 = 0.5 * self.y - self.offset + x1 = self.frame_x # sur-largeur percement interieur + y_inside = 0.5 * self.y + self.hole_margin # outside wall + + if self.out_frame is False: + x0 = 0 + else: + x0 = -min(self.frame_x - 0.001, self.out_frame_y + self.out_frame_offset) + + outside_mat = self.hole_outside_mat + inside_mat = self.hole_inside_mat + # if self.flip: + # outside_mat, inside_mat = inside_mat, outside_mat + + y_outside = -y_inside # inside wall + + return WindowPanel( + False, # closed + [1, 1, 0, 0], # x index + [-x1, x0], + [y_outside, y0, y0, y_inside], + [outside_mat, outside_mat, inside_mat], # material index + side_cap_front=3, # cap index + side_cap_back=0 + ) + + @property + def frame(self): + # profil cadre + # ___ y0 + # __| | + # | | y2 + # |______| y1 + # x1 x2 x0 + y2 = -0.5 * self.y + y0 = 0.5 * self.y - self.offset + y1 = y2 - self.out_frame_y2 + x0 = 0 # -min(self.frame_x - 0.001, self.out_frame_offset) + x1 = x0 - self.out_frame_x + x2 = x0 - self.out_frame_y + # y = depth + # x = width + if self.out_frame_x <= self.out_frame_y: + if self.out_frame_x == 0: + pts_y = [y2, y0, y0, y2] + else: + pts_y = [y1, y0, y0, y1] + return WindowPanel( + True, # closed profil + [0, 0, 1, 1], # x index + [x2, x0], + pts_y, + [0, 0, 0, 0], # material index + closed_path=bool(self.shape == 'CIRCLE') # closed path + ) + else: + return WindowPanel( + True, # closed profil + [0, 0, 1, 1, 2, 2], # x index + [x1, x2, x0], + [y1, y2, y2, y0, y0, y1], + [0, 0, 0, 0, 0, 0], # material index + closed_path=bool(self.shape == 'CIRCLE') # closed path + ) + + @property + def out_tablet(self): + # profil tablette + # __ y0 + # | | y2 + # | / y3 + # |_| y1 + # x0 x2 x1 + y0 = 0.001 + 0.5 * self.y - self.offset + y1 = -0.5 * self.y - self.out_tablet_y + y2 = y0 - 0.01 + y3 = y2 - 0.04 + x2 = 0 + x0 = x2 - self.out_tablet_z + x1 = 0.3 * self.frame_x + # y = depth + # x = width1 + return WindowPanel( + True, # closed profil + [1, 1, 2, 2, 0, 0], # x index + [x0, x2, x1], + [y1, y3, y2, y0, y0, y1], + [4, 3, 3, 4, 4, 4], # material index + closed_path=False # closed path + ) + + @property + def in_tablet(self): + # profil tablette + # __ y0 + # | | + # | | + # |__| y1 + # x0 x1 + y0 = 0.5 * self.y + self.frame_y - self.offset + y1 = 0.5 * self.y + self.in_tablet_y + if self.window_type == 'RAIL': + y0 += 1.55 * self.frame_y + y1 += 1.55 * self.frame_y + x0 = -self.frame_x + x1 = min(x0 + self.in_tablet_z, x0 + self.frame_x - 0.001) + # y = depth + # x = width1 + return WindowPanel( + True, # closed profil + [0, 0, 1, 1], # x index + [x0, x1], + [y1, y0, y0, y1], + [1, 1, 1, 1], # material index + closed_path=False # closed path + ) + + @property + def blind(self): + # profil blind + # y0 + # | / | / | / + # y1 + # xn x1 x0 + dx = self.z / self.blind_z + nx = int(self.z / dx) + y0 = -0.5 * self.offset + # -0.5 * self.y + 0.5 * (0.5 * self.y - self.offset) + # 0.5 * (-0.5 * self.y-0.5 * self.offset) + y1 = y0 + self.blind_y + nx = int(self.z / self.blind_z) + dx = self.z / nx + open = 1.0 - self.blind_open / 100 + return WindowPanel( + False, # profil closed + [int((i + (i % 2)) / 2) for i in range(2 * nx)], # x index + [self.z - (dx * i * open) for i in range(nx + 1)], # x + [[y1, y0][i % 2] for i in range(2 * nx)], # + [5 for i in range(2 * nx - 1)], # material index + closed_path=False # + ) + + @property + def verts(self): + center, origin, size, radius = self.get_radius() + is_not_circle = self.shape != 'CIRCLE' + offset = Vector((0, self.altitude, 0)) + verts = self.window.vertices(self.curve_steps, offset, center, origin, + size, radius, self.angle_y, 0, shape_z=None, path_type=self.shape) + if self.out_frame: + verts += self.frame.vertices(self.curve_steps, offset, center, origin, + size, radius, self.angle_y, 0, shape_z=None, path_type=self.shape) + if is_not_circle and self.out_tablet_enable: + verts += self.out_tablet.vertices(self.curve_steps, offset, center, origin, + Vector((size.x + 2 * self.out_tablet_x, size.y, size.z)), + radius, self.angle_y, 0, shape_z=None, path_type='HORIZONTAL') + if is_not_circle and self.in_tablet_enable: + verts += self.in_tablet.vertices(self.curve_steps, offset, center, origin, + Vector((size.x + 2 * (self.frame_x + self.in_tablet_x), size.y, size.z)), + radius, self.angle_y, 0, shape_z=None, path_type='HORIZONTAL') + if is_not_circle and self.blind_enable: + verts += self.blind.vertices(self.curve_steps, offset, center, origin, + Vector((-size.x, 0, 0)), radius, 0, 0, shape_z=None, path_type='HORIZONTAL') + return verts + + @property + def faces(self): + window = self.window + faces = window.faces(self.curve_steps, path_type=self.shape) + verts_offset = window.n_verts(self.curve_steps, path_type=self.shape) + is_not_circle = self.shape != 'CIRCLE' + if self.out_frame: + frame = self.frame + faces += frame.faces(self.curve_steps, path_type=self.shape, offset=verts_offset) + verts_offset += frame.n_verts(self.curve_steps, path_type=self.shape) + if is_not_circle and self.out_tablet_enable: + tablet = self.out_tablet + faces += tablet.faces(self.curve_steps, path_type='HORIZONTAL', offset=verts_offset) + verts_offset += tablet.n_verts(self.curve_steps, path_type='HORIZONTAL') + if is_not_circle and self.in_tablet_enable: + tablet = self.in_tablet + faces += tablet.faces(self.curve_steps, path_type='HORIZONTAL', offset=verts_offset) + verts_offset += tablet.n_verts(self.curve_steps, path_type='HORIZONTAL') + if is_not_circle and self.blind_enable: + blind = self.blind + faces += blind.faces(self.curve_steps, path_type='HORIZONTAL', offset=verts_offset) + verts_offset += blind.n_verts(self.curve_steps, path_type='HORIZONTAL') + + return faces + + @property + def matids(self): + mat = self.window.mat(self.curve_steps, 2, 2, path_type=self.shape) + is_not_circle = self.shape != 'CIRCLE' + if self.out_frame: + mat += self.frame.mat(self.curve_steps, 0, 0, path_type=self.shape) + if is_not_circle and self.out_tablet_enable: + mat += self.out_tablet.mat(self.curve_steps, 0, 0, path_type='HORIZONTAL') + if is_not_circle and self.in_tablet_enable: + mat += self.in_tablet.mat(self.curve_steps, 0, 0, path_type='HORIZONTAL') + if is_not_circle and self.blind_enable: + mat += self.blind.mat(self.curve_steps, 0, 0, path_type='HORIZONTAL') + return mat + + @property + def uvs(self): + center, origin, size, radius = self.get_radius() + uvs = self.window.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type=self.shape) + is_not_circle = self.shape != 'CIRCLE' + if self.out_frame: + uvs += self.frame.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type=self.shape) + if is_not_circle and self.out_tablet_enable: + uvs += self.out_tablet.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type='HORIZONTAL') + if is_not_circle and self.in_tablet_enable: + uvs += self.in_tablet.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type='HORIZONTAL') + if is_not_circle and self.blind_enable: + uvs += self.blind.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type='HORIZONTAL') + return uvs + + def setup_manipulators(self): + if len(self.manipulators) == 4: + return + s = self.manipulators.add() + s.prop1_name = "x" + s.prop2_name = "x" + s.type_key = "SNAP_SIZE_LOC" + s = self.manipulators.add() + s.prop1_name = "y" + s.prop2_name = "y" + s.type_key = "SNAP_SIZE_LOC" + s = self.manipulators.add() + s.prop1_name = "z" + s.normal = Vector((0, 1, 0)) + s = self.manipulators.add() + s.prop1_name = "altitude" + s.normal = Vector((0, 1, 0)) + + def remove_childs(self, context, o, to_remove): + for child in o.children: + if to_remove < 1: + return + if archipack_window_panel.filter(child): + to_remove -= 1 + self.remove_handle(context, child) + context.scene.objects.unlink(child) + bpy.data.objects.remove(child, do_unlink=True) + + def remove_handle(self, context, o): + handle = self.find_handle(o) + if handle is not None: + context.scene.objects.unlink(handle) + bpy.data.objects.remove(handle, do_unlink=True) + + def update_rows(self, context, o): + # remove rows + for i in range(len(self.rows), self.n_rows, -1): + self.rows.remove(i - 1) + + # add rows + for i in range(len(self.rows), self.n_rows): + self.rows.add() + + # wanted childs + if self.shape == 'CIRCLE': + w_childs = 1 + elif self.window_type == 'RAIL': + w_childs = self.rows[0].cols + else: + w_childs = sum([row.cols for row in self.rows]) + + # real childs + childs = self.get_childs_panels(context, o) + n_childs = len(childs) + + # remove child + if n_childs > w_childs: + self.remove_childs(context, o, n_childs - w_childs) + + def get_childs_panels(self, context, o): + return [child for child in o.children if archipack_window_panel.filter(child)] + + def adjust_size_and_origin(self, size, origin, pivot, materials): + if len(size) > 1: + size[0].x += 0.5 * self.frame_x + size[-1].x += 0.5 * self.frame_x + for i in range(1, len(size) - 1): + size[i].x += 0.5 * self.frame_x + origin[i].x += -0.25 * self.frame_x * pivot[i] + for i, o in enumerate(origin): + o.y = (1 - (i % 2)) * self.frame_y + for i, o in enumerate(origin): + materials[i] = (1 - (i % 2)) + 1 + + def find_handle(self, o): + for handle in o.children: + if 'archipack_handle' in handle: + return handle + return None + + def _synch_childs(self, context, o, linked, childs): + """ + sub synch childs nodes of linked object + """ + # remove childs not found on source + l_childs = self.get_childs_panels(context, linked) + c_names = [c.data.name for c in childs] + for c in l_childs: + try: + id = c_names.index(c.data.name) + except: + self.remove_handle(context, c) + context.scene.objects.unlink(c) + bpy.data.objects.remove(c, do_unlink=True) + + # children ordering may not be the same, so get the right l_childs order + l_childs = self.get_childs_panels(context, linked) + l_names = [c.data.name for c in l_childs] + order = [] + for c in childs: + try: + id = l_names.index(c.data.name) + except: + id = -1 + order.append(id) + + # add missing childs and update other ones + for i, child in enumerate(childs): + if order[i] < 0: + p = bpy.data.objects.new("Panel", child.data) + context.scene.objects.link(p) + p.lock_location[0] = True + p.lock_location[1] = True + p.lock_location[2] = True + p.lock_rotation[1] = True + p.lock_scale[0] = True + p.lock_scale[1] = True + p.lock_scale[2] = True + p.parent = linked + p.matrix_world = linked.matrix_world.copy() + + else: + p = l_childs[order[i]] + + # update handle + handle = self.find_handle(child) + h = self.find_handle(p) + if handle is not None: + if h is None: + h = create_handle(context, p, handle.data) + MaterialUtils.add_handle_materials(h) + h.location = handle.location.copy() + elif h is not None: + context.scene.objects.unlink(h) + bpy.data.objects.remove(h, do_unlink=True) + + p.location = child.location.copy() + + def _synch_hole(self, context, linked, hole): + l_hole = self.find_hole(linked) + if l_hole is None: + l_hole = bpy.data.objects.new("hole", hole.data) + l_hole['archipack_hole'] = True + context.scene.objects.link(l_hole) + l_hole.parent = linked + l_hole.matrix_world = linked.matrix_world.copy() + l_hole.location = hole.location.copy() + else: + l_hole.data = hole.data + + def synch_childs(self, context, o): + """ + synch childs nodes of linked objects + """ + bpy.ops.object.select_all(action='DESELECT') + o.select = True + context.scene.objects.active = o + childs = self.get_childs_panels(context, o) + hole = self.find_hole(o) + bpy.ops.object.select_linked(type='OBDATA') + for linked in context.selected_objects: + if linked != o: + self._synch_childs(context, o, linked, childs) + if hole is not None: + self._synch_hole(context, linked, hole) + + def update_childs(self, context, o): + """ + pass params to childrens + """ + self.update_rows(context, o) + childs = self.get_childs_panels(context, o) + n_childs = len(childs) + child_n = 0 + row_n = 0 + location_y = 0.5 * self.y - self.offset + 0.5 * self.frame_y + center, origin, size, radius = self.get_radius() + offset = Vector((0, 0)) + handle = 'NONE' + if self.shape != 'CIRCLE': + if self.handle_enable: + if self.z > 1.8: + handle = 'BOTH' + else: + handle = 'INSIDE' + is_circle = False + else: + is_circle = True + + if self.window_type == 'RAIL': + handle_model = 2 + else: + handle_model = 1 + + for row in self.rows: + row_n += 1 + if row_n < self.n_rows and not is_circle and self.window_type != 'RAIL': + z = row.height + shape = 'RECTANGLE' + else: + z = max(2 * self.frame_x + 0.001, self.z - offset.y) + shape = self.shape + + self.warning = bool(z > self.z - offset.y) + if self.warning: + break + size, origin, pivot = row.get_row(self.x, z) + # side materials + materials = [0 for i in range(row.cols)] + + handle_altitude = min( + max(4 * self.frame_x, self.handle_altitude - offset.y - self.altitude), + z - 4 * self.frame_x + ) + + if self.window_type == 'RAIL': + self.adjust_size_and_origin(size, origin, pivot, materials) + + for panel in range(row.cols): + child_n += 1 + + if row.fixed[panel]: + enable_handle = 'NONE' + else: + enable_handle = handle + + if child_n > n_childs: + bpy.ops.archipack.window_panel( + center=center, + origin=Vector((origin[panel].x, offset.y, 0)), + size=size[panel], + radius=radius, + pivot=pivot[panel], + shape=shape, + fixed=row.fixed[panel], + handle=enable_handle, + handle_model=handle_model, + handle_altitude=handle_altitude, + curve_steps=self.curve_steps, + side_material=materials[panel], + frame_x=self.frame_x, + frame_y=self.frame_y, + angle_y=self.angle_y, + ) + child = context.active_object + # parenting at 0, 0, 0 before set object matrix_world + # so location remains local from frame + child.parent = o + child.matrix_world = o.matrix_world.copy() + else: + child = childs[child_n - 1] + child.select = True + context.scene.objects.active = child + props = archipack_window_panel.datablock(child) + if props is not None: + props.origin = Vector((origin[panel].x, offset.y, 0)) + props.center = center + props.radius = radius + props.size = size[panel] + props.pivot = pivot[panel] + props.shape = shape + props.fixed = row.fixed[panel] + props.handle = enable_handle + props.handle_model = handle_model + props.handle_altitude = handle_altitude + props.side_material = materials[panel] + props.curve_steps = self.curve_steps + props.frame_x = self.frame_x + props.frame_y = self.frame_y + props.angle_y = self.angle_y + props.update(context) + # location y + frame width. frame depends on choosen profile (fixed or not) + # update linked childs location too + child.location = Vector((origin[panel].x, origin[panel].y + location_y + self.frame_y, + self.altitude + offset.y)) + + if not row.fixed[panel]: + handle = 'NONE' + + # only one single panel allowed for circle + if is_circle: + return + + # only one single row allowed for rail window + if self.window_type == 'RAIL': + return + offset.y += row.height + + def _get_tri_radius(self): + return Vector((0, self.y, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((self.x, 0, 0)) + + def _get_quad_radius(self): + fx_z = self.z / self.x + center_y = min(self.x / (self.x - self.frame_x) * self.z - self.frame_x * (1 + sqrt(1 + fx_z * fx_z)), + abs(tan(self.angle_y) * (self.x))) + if self.angle_y < 0: + center_x = 0.5 * self.x + else: + center_x = -0.5 * self.x + return Vector((center_x, center_y, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((self.x, 0, 0)) + + def _get_round_radius(self): + """ + bound radius to available space + return center, origin, size, radius + """ + x = 0.5 * self.x - self.frame_x + # minimum space available + y = self.z - sum([row.height for row in self.rows[:self.n_rows - 1]]) - 2 * self.frame_x + y = min(y, x) + # minimum radius inside + r = y + x * (x - (y * y / x)) / (2 * y) + radius = max(self.radius, 0.001 + self.frame_x + r) + return Vector((0, self.z - radius, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((radius, 0, 0)) + + def _get_circle_radius(self): + """ + return center, origin, size, radius + """ + return Vector((0, 0.5 * self.x, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((0.5 * self.x, 0, 0)) + + def _get_ellipsis_radius(self): + """ + return center, origin, size, radius + """ + y = self.z - sum([row.height for row in self.rows[:self.n_rows - 1]]) + radius_b = max(0, 0.001 - 2 * self.frame_x + min(y, self.elipsis_b)) + return Vector((0, self.z - radius_b, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((self.x / 2, radius_b, 0)) + + def get_radius(self): + """ + return center, origin, size, radius + """ + if self.shape == 'ROUND': + return self._get_round_radius() + elif self.shape == 'ELLIPSIS': + return self._get_ellipsis_radius() + elif self.shape == 'CIRCLE': + return self._get_circle_radius() + elif self.shape == 'QUADRI': + return self._get_quad_radius() + elif self.shape in ['TRIANGLE', 'PENTAGON']: + return self._get_tri_radius() + else: + return Vector((0, 0, 0)), Vector((0, 0, 0)), \ + Vector((self.x, self.z, 0)), Vector((0, 0, 0)) + + def update(self, context, childs_only=False): + # support for "copy to selected" + o = self.find_in_selection(context, self.auto_update) + + if o is None: + return + + self.setup_manipulators() + + if childs_only is False: + bmed.buildmesh(context, o, self.verts, self.faces, self.matids, self.uvs) + + self.update_childs(context, o) + + # update hole + if childs_only is False and self.find_hole(o) is not None: + self.interactive_hole(context, o) + + # support for instances childs, update at object level + self.synch_childs(context, o) + + # store 3d points for gl manipulators + x, y = 0.5 * self.x, 0.5 * self.y + self.manipulators[0].set_pts([(-x, -y, 0), (x, -y, 0), (1, 0, 0)]) + self.manipulators[1].set_pts([(-x, -y, 0), (-x, y, 0), (-1, 0, 0)]) + self.manipulators[2].set_pts([(x, -y, self.altitude), (x, -y, self.altitude + self.z), (-1, 0, 0)]) + self.manipulators[3].set_pts([(x, -y, 0), (x, -y, self.altitude), (-1, 0, 0)]) + + # restore context + self.restore_context(context) + + def find_hole(self, o): + for child in o.children: + if 'archipack_hole' in child: + return child + return None + + def interactive_hole(self, context, o): + hole_obj = self.find_hole(o) + + if hole_obj is None: + m = bpy.data.meshes.new("hole") + hole_obj = bpy.data.objects.new("hole", m) + context.scene.objects.link(hole_obj) + hole_obj['archipack_hole'] = True + hole_obj.parent = o + hole_obj.matrix_world = o.matrix_world.copy() + MaterialUtils.add_wall2_materials(hole_obj) + + hole = self.hole + center, origin, size, radius = self.get_radius() + + if self.out_frame is False: + x0 = 0 + else: + x0 = min(self.frame_x - 0.001, self.out_frame_y + self.out_frame_offset) + + if self.out_tablet_enable: + x0 -= min(self.frame_x - 0.001, self.out_tablet_z) + shape_z = [0, x0] + + verts = hole.vertices(self.curve_steps, Vector((0, self.altitude, 0)), center, origin, size, radius, + self.angle_y, 0, shape_z=shape_z, path_type=self.shape) + + faces = hole.faces(self.curve_steps, path_type=self.shape) + + matids = hole.mat(self.curve_steps, 2, 2, path_type=self.shape) + + uvs = hole.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type=self.shape) + + bmed.buildmesh(context, hole_obj, verts, faces, matids=matids, uvs=uvs) + return hole_obj + + def robust_hole(self, context, tM): + hole = self.hole + center, origin, size, radius = self.get_radius() + + if self.out_frame is False: + x0 = 0 + else: + x0 = min(self.frame_x - 0.001, self.out_frame_y + self.out_frame_offset) + + if self.out_tablet_enable: + x0 -= min(self.frame_x - 0.001, self.out_tablet_z) + shape_z = [0, x0] + + m = bpy.data.meshes.new("hole") + o = bpy.data.objects.new("hole", m) + o['archipack_robusthole'] = True + context.scene.objects.link(o) + verts = hole.vertices(self.curve_steps, Vector((0, self.altitude, 0)), center, origin, size, radius, + self.angle_y, 0, shape_z=shape_z, path_type=self.shape) + + verts = [tM * Vector(v) for v in verts] + + faces = hole.faces(self.curve_steps, path_type=self.shape) + + matids = hole.mat(self.curve_steps, 2, 2, path_type=self.shape) + + uvs = hole.uv(self.curve_steps, center, origin, size, radius, + self.angle_y, 0, 0, self.frame_x, path_type=self.shape) + + bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs) + MaterialUtils.add_wall2_materials(o) + o.select = True + context.scene.objects.active = o + return o + + +class ARCHIPACK_PT_window(Panel): + bl_idname = "ARCHIPACK_PT_window" + bl_label = "Window" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + # bl_context = 'object' + bl_category = 'ArchiPack' + + # layout related + display_detail = BoolProperty( + default=False + ) + display_panels = BoolProperty( + default=True + ) + + @classmethod + def poll(cls, context): + return archipack_window.filter(context.active_object) + + def draw(self, context): + o = context.active_object + prop = archipack_window.datablock(o) + if prop is None: + return + layout = self.layout + layout.operator('archipack.window_manipulate', icon='HAND') + row = layout.row(align=True) + row.operator('archipack.window', text="Refresh", icon='FILE_REFRESH').mode = 'REFRESH' + if o.data.users > 1: + row.operator('archipack.window', text="Make unique", icon='UNLINKED').mode = 'UNIQUE' + row.operator('archipack.window', text="Delete", icon='ERROR').mode = 'DELETE' + box = layout.box() + # box.label(text="Styles") + row = box.row(align=True) + row.operator("archipack.window_preset_menu", text=bpy.types.ARCHIPACK_OT_window_preset_menu.bl_label) + row.operator("archipack.window_preset", text="", icon='ZOOMIN') + row.operator("archipack.window_preset", text="", icon='ZOOMOUT').remove_active = True + box = layout.box() + box.prop(prop, 'window_type') + box.prop(prop, 'x') + box.prop(prop, 'y') + if prop.window_shape != 'CIRCLE': + box.prop(prop, 'z') + if prop.warning: + box.label(text="Insufficient height", icon='ERROR') + box.prop(prop, 'altitude') + box.prop(prop, 'offset') + + if prop.window_type == 'FLAT': + box = layout.box() + box.prop(prop, 'window_shape') + if prop.window_shape in ['ROUND', 'CIRCLE', 'ELLIPSIS']: + box.prop(prop, 'curve_steps') + if prop.window_shape in ['ROUND']: + box.prop(prop, 'radius') + elif prop.window_shape == 'ELLIPSIS': + box.prop(prop, 'elipsis_b') + elif prop.window_shape == 'QUADRI': + box.prop(prop, 'angle_y') + + row = layout.row(align=True) + if prop.display_detail: + row.prop(prop, "display_detail", icon="TRIA_DOWN", icon_only=True, text="Components", emboss=False) + else: + row.prop(prop, "display_detail", icon="TRIA_RIGHT", icon_only=True, text="Components", emboss=False) + + if prop.display_detail: + box = layout.box() + box.label("Frame") + box.prop(prop, 'frame_x') + box.prop(prop, 'frame_y') + if prop.window_shape != 'CIRCLE': + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'handle_enable') + if prop.handle_enable: + box.prop(prop, 'handle_altitude') + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'out_frame') + if prop.out_frame: + box.prop(prop, 'out_frame_x') + box.prop(prop, 'out_frame_y2') + box.prop(prop, 'out_frame_y') + box.prop(prop, 'out_frame_offset') + if prop.window_shape != 'CIRCLE': + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'out_tablet_enable') + if prop.out_tablet_enable: + box.prop(prop, 'out_tablet_x') + box.prop(prop, 'out_tablet_y') + box.prop(prop, 'out_tablet_z') + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'in_tablet_enable') + if prop.in_tablet_enable: + box.prop(prop, 'in_tablet_x') + box.prop(prop, 'in_tablet_y') + box.prop(prop, 'in_tablet_z') + box = layout.box() + row = box.row(align=True) + row.prop(prop, 'blind_enable') + if prop.blind_enable: + box.prop(prop, 'blind_open') + box.prop(prop, 'blind_y') + box.prop(prop, 'blind_z') + if prop.window_shape != 'CIRCLE': + row = layout.row() + if prop.display_panels: + row.prop(prop, "display_panels", icon="TRIA_DOWN", icon_only=True, text="Rows", emboss=False) + else: + row.prop(prop, "display_panels", icon="TRIA_RIGHT", icon_only=True, text="Rows", emboss=False) + + if prop.display_panels: + if prop.window_type != 'RAIL': + row = layout.row() + row.prop(prop, 'n_rows') + last_row = prop.n_rows - 1 + for i, row in enumerate(prop.rows): + box = layout.box() + box.label(text="Row " + str(i + 1)) + row.draw(box, context, i == last_row) + else: + box = layout.box() + row = prop.rows[0] + row.draw(box, context, True) + + row = layout.row(align=True) + if prop.display_materials: + row.prop(prop, "display_materials", icon="TRIA_DOWN", icon_only=True, text="Materials", emboss=False) + else: + row.prop(prop, "display_materials", icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False) + if prop.display_materials: + box = layout.box() + box.label("Hole") + box.prop(prop, 'hole_inside_mat') + box.prop(prop, 'hole_outside_mat') + + +class ARCHIPACK_PT_window_panel(Panel): + bl_idname = "ARCHIPACK_PT_window_panel" + bl_label = "Window panel" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'ArchiPack' + + @classmethod + def poll(cls, context): + return archipack_window_panel.filter(context.active_object) + + def draw(self, context): + layout = self.layout + layout.operator("archipack.select_parent") + + +# ------------------------------------------------------------------ +# Define operator class to create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): + bl_idname = "archipack.window" + bl_label = "Window" + bl_description = "Window" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + x = FloatProperty( + name='width', + min=0.1, max=10000, + default=2.0, precision=2, + description='Width' + ) + y = FloatProperty( + name='depth', + min=0.1, max=10000, + default=0.20, precision=2, + description='Depth' + ) + z = FloatProperty( + name='height', + min=0.1, max=10000, + default=1.2, precision=2, + description='height' + ) + altitude = FloatProperty( + name='altitude', + min=0.0, max=10000, + default=1.0, precision=2, + description='altitude' + ) + mode = EnumProperty( + items=( + ('CREATE', 'Create', '', 0), + ('DELETE', 'Delete', '', 1), + ('REFRESH', 'Refresh', '', 2), + ('UNIQUE', 'Make unique', '', 3), + ), + default='CREATE' + ) + # auto_manipulate = BoolProperty(default=True) + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + m = bpy.data.meshes.new("Window") + o = bpy.data.objects.new("Window", m) + d = m.archipack_window.add() + d.x = self.x + d.y = self.y + d.z = self.z + d.altitude = self.altitude + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + self.load_preset(d) + self.add_material(o) + # select frame + o.select = True + context.scene.objects.active = o + return o + + def delete(self, context): + o = context.active_object + if archipack_window.filter(o): + bpy.ops.archipack.disable_manipulate() + for child in o.children: + if 'archipack_hole' in child: + context.scene.objects.unlink(child) + bpy.data.objects.remove(child, do_unlink=True) + elif child.data is not None and 'archipack_window_panel' in child.data: + for handle in child.children: + if 'archipack_handle' in handle: + context.scene.objects.unlink(handle) + bpy.data.objects.remove(handle, do_unlink=True) + context.scene.objects.unlink(child) + bpy.data.objects.remove(child, do_unlink=True) + context.scene.objects.unlink(o) + bpy.data.objects.remove(o, do_unlink=True) + + def update(self, context): + o = context.active_object + d = archipack_window.datablock(o) + if d is not None: + d.update(context) + bpy.ops.object.select_linked(type='OBDATA') + for linked in context.selected_objects: + if linked != o: + archipack_window.datablock(linked).update(context) + bpy.ops.object.select_all(action="DESELECT") + o.select = True + context.scene.objects.active = o + + def unique(self, context): + act = context.active_object + sel = [o for o in context.selected_objects] + bpy.ops.object.select_all(action="DESELECT") + for o in sel: + if archipack_window.filter(o): + o.select = True + for child in o.children: + if 'archipack_hole' in child or ( + child.data is not None and + 'archipack_window_panel' in child.data): + child.hide_select = False + child.select = True + if len(context.selected_objects) > 0: + bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, + obdata=True, material=False, texture=False, animation=False) + for child in context.selected_objects: + if 'archipack_hole' in child: + child.hide_select = True + bpy.ops.object.select_all(action="DESELECT") + context.scene.objects.active = act + for o in sel: + o.select = True + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if context.mode == "OBJECT": + if self.mode == 'CREATE': + 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() + elif self.mode == 'DELETE': + self.delete(context) + elif self.mode == 'REFRESH': + self.update(context) + elif self.mode == 'UNIQUE': + self.unique(context) + return {'FINISHED'} + else: + self.report({'WARNING'}, "Archipack: Option only valid in Object mode") + return {'CANCELLED'} + + +class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): + bl_idname = "archipack.window_draw" + bl_label = "Draw Windows" + bl_description = "Draw Windows over walls" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + + filepath = StringProperty(default="") + feedback = None + stack = [] + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def draw_callback(self, _self, context): + self.feedback.draw(context) + + def add_object(self, context, event): + o = context.active_object + bpy.ops.object.select_all(action="DESELECT") + + if archipack_window.filter(o): + + o.select = True + context.scene.objects.active = o + + if event.shift: + bpy.ops.archipack.window(mode="UNIQUE") + + new_w = o.copy() + new_w.data = o.data + context.scene.objects.link(new_w) + + o = new_w + o.select = True + context.scene.objects.active = o + + # synch subs from parent instance + bpy.ops.archipack.window(mode="REFRESH") + + else: + bpy.ops.archipack.window(auto_manipulate=False, filepath=self.filepath) + o = context.active_object + + bpy.ops.archipack.generate_hole('INVOKE_DEFAULT') + o.select = True + context.scene.objects.active = o + + def modal(self, context, event): + + context.area.tag_redraw() + o = context.active_object + d = archipack_window.datablock(o) + hole = None + if d is not None: + hole = d.find_hole(o) + + # hide hole from raycast + if hole is not None: + o.hide = True + hole.hide = True + + res, tM, wall, y = self.mouse_hover_wall(context, event) + + if hole is not None: + o.hide = False + hole.hide = False + + if res and d is not None: + o.matrix_world = tM + if d.y != wall.data.archipack_wall2[0].width: + d.y = wall.data.archipack_wall2[0].width + + if event.value == 'PRESS': + if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: + if wall is not None: + context.scene.objects.active = wall + wall.select = True + if bpy.ops.archipack.single_boolean.poll(): + bpy.ops.archipack.single_boolean() + wall.select = False + # o must be a window here + if d is not None: + context.scene.objects.active = o + self.stack.append(o) + self.add_object(context, event) + context.active_object.matrix_world = tM + return {'RUNNING_MODAL'} + # prevent selection of other object + if event.type in {'RIGHTMOUSE'}: + return {'RUNNING_MODAL'} + + if self.keymap.check(event, self.keymap.undo) or ( + event.type in {'BACK_SPACE'} and event.value == 'RELEASE' + ): + if len(self.stack) > 0: + last = self.stack.pop() + context.scene.objects.active = last + bpy.ops.archipack.window(mode="DELETE") + context.scene.objects.active = o + return {'RUNNING_MODAL'} + + if event.value == 'RELEASE': + + if event.type in {'ESC', 'RIGHTMOUSE'}: + bpy.ops.archipack.window(mode='DELETE') + self.feedback.disable() + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + return {'FINISHED'} + + return {'PASS_THROUGH'} + + def invoke(self, context, event): + + if context.mode == "OBJECT": + o = None + self.stack = [] + self.keymap = Keymaps(context) + # exit manipulate_mode if any + bpy.ops.archipack.disable_manipulate() + # invoke with shift pressed will use current object as basis for linked copy + if self.filepath == '' and archipack_window.filter(context.active_object): + o = context.active_object + context.scene.objects.active = None + bpy.ops.object.select_all(action="DESELECT") + if o is not None: + o.select = True + context.scene.objects.active = o + self.add_object(context, event) + self.feedback = FeedbackPanel() + self.feedback.instructions(context, "Draw a window", "Click & Drag over a wall", [ + ('LEFTCLICK, RET, SPACE, ENTER', 'Create a window'), + ('BACKSPACE, CTRL+Z', 'undo last'), + ('SHIFT', 'Make independant copy'), + ('RIGHTCLICK or ESC', 'exit') + ]) + self.feedback.enable() + args = (self, context) + + 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 create object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_window_panel(Operator): + bl_idname = "archipack.window_panel" + bl_label = "Window panel" + bl_description = "Window panel" + bl_category = 'Archipack' + bl_options = {'REGISTER', 'UNDO'} + center = FloatVectorProperty( + subtype='XYZ' + ) + origin = FloatVectorProperty( + subtype='XYZ' + ) + size = FloatVectorProperty( + subtype='XYZ' + ) + radius = FloatVectorProperty( + subtype='XYZ' + ) + angle_y = FloatProperty( + name='angle', + unit='ROTATION', + subtype='ANGLE', + min=-1.5, max=1.5, + default=0, precision=2, + description='angle' + ) + frame_y = FloatProperty( + name='Depth', + min=0, max=100, + default=0.06, precision=2, + description='frame depth' + ) + frame_x = FloatProperty( + name='Width', + min=0, max=100, + default=0.06, precision=2, + description='frame width' + ) + curve_steps = IntProperty( + name="curve steps", + min=1, + max=128, + default=16 + ) + shape = EnumProperty( + name='Shape', + items=( + ('RECTANGLE', 'Rectangle', '', 0), + ('ROUND', 'Top Round', '', 1), + ('ELLIPSIS', 'Top Elliptic', '', 2), + ('QUADRI', 'Top oblique', '', 3), + ('CIRCLE', 'Full circle', '', 4) + ), + default='RECTANGLE' + ) + pivot = FloatProperty( + name='pivot', + min=-1, max=1, + default=-1, precision=2, + description='pivot' + ) + side_material = IntProperty( + name="side material", + min=0, + max=2, + default=0 + ) + handle = EnumProperty( + name='Handle', + items=( + ('NONE', 'No handle', '', 0), + ('INSIDE', 'Inside', '', 1), + ('BOTH', 'Inside and outside', '', 2) + ), + default='NONE' + ) + handle_model = IntProperty( + name="handle model", + default=1, + min=1, + max=2 + ) + handle_altitude = FloatProperty( + name='handle altitude', + min=0, max=1000, + default=0.2, precision=2, + description='handle altitude' + ) + fixed = BoolProperty( + name="Fixed", + default=False + ) + + def draw(self, context): + layout = self.layout + row = layout.row() + row.label("Use Properties panel (N) to define parms", icon='INFO') + + def create(self, context): + m = bpy.data.meshes.new("Window Panel") + o = bpy.data.objects.new("Window Panel", m) + d = m.archipack_window_panel.add() + d.center = self.center + d.origin = self.origin + d.size = self.size + d.radius = self.radius + d.frame_y = self.frame_y + d.frame_x = self.frame_x + d.curve_steps = self.curve_steps + d.shape = self.shape + d.fixed = self.fixed + d.pivot = self.pivot + d.angle_y = self.angle_y + d.side_material = self.side_material + d.handle = self.handle + d.handle_model = self.handle_model + d.handle_altitude = self.handle_altitude + context.scene.objects.link(o) + o.select = True + context.scene.objects.active = o + o.lock_location[0] = True + o.lock_location[1] = True + o.lock_location[2] = True + o.lock_rotation[1] = True + o.lock_scale[0] = True + o.lock_scale[1] = True + o.lock_scale[2] = True + d.update(context) + MaterialUtils.add_window_materials(o) + return o + + def execute(self, context): + if context.mode == "OBJECT": + 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 manipulate object +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_window_manipulate(Operator): + bl_idname = "archipack.window_manipulate" + bl_label = "Manipulate" + bl_description = "Manipulate" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(self, context): + return archipack_window.filter(context.active_object) + + def invoke(self, context, event): + d = archipack_window.datablock(context.active_object) + d.manipulable_invoke(context) + return {'FINISHED'} + +# ------------------------------------------------------------------ +# Define operator class to load / save presets +# ------------------------------------------------------------------ + + +class ARCHIPACK_OT_window_preset_menu(PresetMenuOperator, Operator): + bl_idname = "archipack.window_preset_menu" + bl_label = "Window Presets" + preset_subdir = "archipack_window" + + +class ARCHIPACK_OT_window_preset(ArchipackPreset, Operator): + """Add a Window Preset""" + bl_idname = "archipack.window_preset" + bl_label = "Add Window Preset" + preset_menu = "ARCHIPACK_OT_window_preset_menu" + + @property + def blacklist(self): + # 'x', 'y', 'z', 'altitude', 'window_shape' + return ['manipulators'] + + +def register(): + bpy.utils.register_class(archipack_window_panelrow) + bpy.utils.register_class(archipack_window_panel) + Mesh.archipack_window_panel = CollectionProperty(type=archipack_window_panel) + bpy.utils.register_class(ARCHIPACK_PT_window_panel) + bpy.utils.register_class(ARCHIPACK_OT_window_panel) + bpy.utils.register_class(archipack_window) + Mesh.archipack_window = CollectionProperty(type=archipack_window) + bpy.utils.register_class(ARCHIPACK_OT_window_preset_menu) + bpy.utils.register_class(ARCHIPACK_PT_window) + bpy.utils.register_class(ARCHIPACK_OT_window) + bpy.utils.register_class(ARCHIPACK_OT_window_preset) + bpy.utils.register_class(ARCHIPACK_OT_window_draw) + bpy.utils.register_class(ARCHIPACK_OT_window_manipulate) + + +def unregister(): + bpy.utils.unregister_class(archipack_window_panelrow) + bpy.utils.unregister_class(archipack_window_panel) + bpy.utils.unregister_class(ARCHIPACK_PT_window_panel) + del Mesh.archipack_window_panel + bpy.utils.unregister_class(ARCHIPACK_OT_window_panel) + bpy.utils.unregister_class(archipack_window) + del Mesh.archipack_window + bpy.utils.unregister_class(ARCHIPACK_OT_window_preset_menu) + bpy.utils.unregister_class(ARCHIPACK_PT_window) + bpy.utils.unregister_class(ARCHIPACK_OT_window) + bpy.utils.unregister_class(ARCHIPACK_OT_window_preset) + bpy.utils.unregister_class(ARCHIPACK_OT_window_draw) + bpy.utils.unregister_class(ARCHIPACK_OT_window_manipulate) |