# -*- 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 ##### # # ---------------------------------------------------------- # 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, Matrix from math import tan, sqrt from .bmesh_utils import BmeshEdit as bmed from .panel import Panel as WindowPanel 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, ArchipackDrawTool, ArchipackCollectionManager from .archipack_keymaps import Keymaps def update(self, context): self.update(context) def update_childs(self, context): self.update(context, childs_only=True) def update_portal(self, context): self.update_portal(context) 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 = 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 ) enable_glass : BoolProperty( name="Enable glass", default=True ) @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 side_cap_front = -1 side_cap_back = -1 if self.enable_glass: side_cap_front = 6 side_cap_back = 7 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=side_cap_front, side_cap_back=side_cap_back # 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] side_cap_front = -1 side_cap_back = -1 if self.enable_glass: side_cap_front = 8 side_cap_back = 9 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=side_cap_front, side_cap_back=side_cap_back # 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: self.unlink_object_from_scene(handle) bpy.data.objects.remove(handle, do_unlink=True) def update(self, context): o = self.find_in_selection(context) if o is None: return 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="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, ) enable_glass : BoolProperty( name="Enable glass", default=True, update=update ) warning : BoolProperty( options={'SKIP_SAVE'}, 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 ) portal : BoolProperty( default=False, name="Portal", description="Generate a portal", 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 find_portal(self, o): for child in o.children: if child.type == 'LIGHT': return child return None def update_portal(self, context, o): lamp = self.find_portal(o) if self.portal: if lamp is None: bpy.ops.object.light_add(type='AREA') lamp = context.active_object lamp.name = "Portal" lamp.parent = o d = lamp.data d.cycles.is_portal = True d.shape = 'RECTANGLE' d.size = self.x d.size_y = self.z tM = Matrix([ [1, 0, 0, 0], [0, 0, -1, -0.5 * self.y], [0, 1, 0, 0.5 * self.z + self.altitude], [0, 0, 0, 1] ]) lamp.matrix_world = o.matrix_world @ tM elif lamp is not None: d = lamp.data self.unlink_object_from_scene(lamp) bpy.data.objects.remove(lamp) bpy.data.lights.remove(d) context.view_layer.objects.active = o 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) self.unlink_object_from_scene(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: self.unlink_object_from_scene(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_portal(self, context, o, linked, childs): # update portal dl = archipack_window.datablock(linked) dl.update_portal(context, linked) 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) self.unlink_object_from_scene(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) self.link_object_to_scene(context, 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() m = p.archipack_material.add() m.category = 'window' m.material = o.archipack_material[0].material 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) h.location = handle.location.copy() elif h is not None: self.unlink_object_from_scene(h) bpy.data.objects.remove(h, do_unlink=True) p.location = child.location.copy() # restore context context.view_layer.objects.active = o 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 self.link_object_to_scene(context, l_hole) for mat in hole.data.materials: l_hole.data.materials.append(mat) 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_set(state=True) context.view_layer.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_portal(context, o, linked, childs) 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, enable_glass=self.enable_glass, material=o.archipack_material[0].material ) 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_set(state=True) context.view_layer.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.enable_glass = self.enable_glass props.update(context) # location y + frame width. frame depends on chosen 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_portal(context, o) 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) self.link_object_to_scene(context, hole_obj) hole_obj['archipack_hole'] = True hole_obj.parent = o hole_obj.matrix_world = o.matrix_world.copy() """ hole_obj.data.materials.clear() for mat in o.data.materials: hole_obj.data.materials.append(mat) # 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 self.link_object_to_scene(context, 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_set(state=True) context.view_layer.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='VIEW_PAN') 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='ADD') row.operator("archipack.window_preset", text="", icon='REMOVE').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", text="Components", emboss=False) else: row.prop(prop, "display_detail", icon="TRIA_RIGHT", text="Components", emboss=False) if prop.display_detail: box = layout.box() box.prop(prop, 'enable_glass') box = layout.box() box.label(text="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", text="Rows", emboss=False) else: row.prop(prop, "display_panels", icon="TRIA_RIGHT", 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", text="Materials", emboss=False) else: row.prop(prop, "display_materials", icon="TRIA_RIGHT", text="Materials", emboss=False) if prop.display_materials: box = layout.box() box.label(text="Hole") box.prop(prop, 'hole_inside_mat') box.prop(prop, 'hole_outside_mat') layout.prop(prop, 'portal', icon="LIGHT_AREA") 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(text="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 self.link_object_to_scene(context, o) o.select_set(state=True) context.view_layer.objects.active = o self.add_material(o) self.load_preset(d) # select frame o.select_set(state=True) context.view_layer.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 child.type == 'LIGHT': d = child.data self.unlink_object_from_scene(child) bpy.data.objects.remove(child) bpy.data.lights.remove(d) elif 'archipack_hole' in child: self.unlink_object_from_scene(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: self.unlink_object_from_scene(handle) bpy.data.objects.remove(handle, do_unlink=True) self.unlink_object_from_scene(child) bpy.data.objects.remove(child, do_unlink=True) self.unlink_object_from_scene(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_set(state=True) context.view_layer.objects.active = o def unique(self, context): act = context.active_object sel = context.selected_objects[:] bpy.ops.object.select_all(action="DESELECT") for o in sel: if archipack_window.filter(o): o.select_set(state=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_set(state=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.view_layer.objects.active = act for o in sel: o.select_set(state=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_set(state=True) context.view_layer.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(ArchipackDrawTool, 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 = [] object_name = "" @classmethod def poll(cls, context): return True def draw(self, context): layout = self.layout row = layout.row() row.label(text="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_set(state=True) context.view_layer.objects.active = o if event.shift: bpy.ops.archipack.window(mode="UNIQUE") # instance subs new_w = o.copy() new_w.data = o.data self.link_object_to_scene(context, new_w) for child in o.children: if "archipack_hole" not in child: new_c = child.copy() new_c.data = child.data new_c.parent = new_w self.link_object_to_scene(context, new_c) # dup handle if any for c in child.children: new_h = c.copy() new_h.data = c.data new_h.parent = new_c self.link_object_to_scene(context, new_h) o = new_w o.select_set(state=True) context.view_layer.objects.active = o else: bpy.ops.archipack.window(auto_manipulate=False, filepath=self.filepath) o = context.active_object self.object_name = o.name bpy.ops.archipack.generate_hole('INVOKE_DEFAULT') o.select_set(state=True) context.view_layer.objects.active = o def modal(self, context, event): context.area.tag_redraw() o = context.scene.objects.get(self.object_name.strip()) if o is None: return {'FINISHED'} 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_viewport = True hole.hide_viewport = True res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event) if hole is not None: o.hide_viewport = False hole.hide_viewport = 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 {'C'}: bpy.ops.archipack.window(mode='DELETE') self.feedback.disable() bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') bpy.ops.archipack.window_preset_menu('INVOKE_DEFAULT', preset_operator="archipack.window_draw") return {'FINISHED'} if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: if wall is not None: o.select_set(state=True) context.view_layer.objects.active = wall wall.select_set(state=True) if bpy.ops.archipack.single_boolean.poll(): bpy.ops.archipack.single_boolean() wall.select_set(state=False) # o must be a window here if d is not None: context.view_layer.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.view_layer.objects.active = last bpy.ops.archipack.window(mode="DELETE") context.view_layer.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.view_layer.objects.active = None bpy.ops.object.select_all(action="DESELECT") if o is not None: o.select_set(state=True) context.view_layer.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'), ('C', 'Choose another window'), ('SHIFT', 'Make independent 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'} class ARCHIPACK_OT_window_portals(Operator): bl_idname = "archipack.window_portals" bl_label = "Portals" bl_description = "Create portal for each window" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(self, context): return True def invoke(self, context, event): for o in context.scene.objects: d = archipack_window.datablock(o) if d is not None: d.update_portal(context) return {'FINISHED'} # ------------------------------------------------------------------ # Define operator class to create object # ------------------------------------------------------------------ class ARCHIPACK_OT_window_panel(ArchipackCollectionManager, 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 ) material : StringProperty( name="material", default="" ) enable_glass : BoolProperty( name="Enable glass", default=True ) def draw(self, context): layout = self.layout row = layout.row() row.label(text="Use Properties panel (N) to define parms", icon='INFO') def create(self, context): 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 d.enable_glass = self.enable_glass self.link_object_to_scene(context, o) o.select_set(state=True) context.view_layer.objects.active = o m = o.archipack_material.add() m.category = "window" m.material = self.material 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) return o def execute(self, context): if context.mode == "OBJECT": o = self.create(context) o.select_set(state=True) context.view_layer.objects.active = o return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") return {'CANCELLED'} # ------------------------------------------------------------------ # 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_description = "Show Window Presets" 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) bpy.utils.register_class(ARCHIPACK_OT_window_portals) 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) bpy.utils.unregister_class(ARCHIPACK_OT_window_portals)