# -*- 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, CollectionProperty, EnumProperty, BoolProperty, StringProperty ) from mathutils import Vector # door component objects (panels, handles ..) from .bmesh_utils import BmeshEdit as bmed from .panel import Panel as DoorPanel from .archipack_handle import create_handle, door_handle_horizontal_01 from .archipack_manipulator import Manipulable from .archipack_preset import ArchipackPreset, PresetMenuOperator from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool, ArchipackCollectionManager from .archipack_gl import FeedbackPanel from .archipack_keymaps import Keymaps SPACING = 0.005 BATTUE = 0.01 BOTTOM_HOLE_MARGIN = 0.001 FRONT_HOLE_MARGIN = 0.1 def update(self, context): self.update(context) def update_childs(self, context): self.update(context, childs_only=True) class archipack_door_panel(ArchipackObject, PropertyGroup): x : FloatProperty( name='Width', min=0.25, default=100.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='Width' ) y : FloatProperty( name='Depth', min=0.001, default=0.02, precision=2, unit='LENGTH', subtype='DISTANCE', description='depth' ) z : FloatProperty( name='Height', min=0.1, default=2.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='height' ) direction : IntProperty( name="Direction", min=0, max=1, description="open direction" ) model : IntProperty( name="Model", min=0, max=3, default=0, description="Model" ) chanfer : FloatProperty( name='Bevel', min=0.001, default=0.005, precision=3, unit='LENGTH', subtype='DISTANCE', description='chanfer' ) panel_spacing : FloatProperty( name='Spacing', min=0.001, default=0.1, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance between panels' ) panel_bottom : FloatProperty( name='Bottom', min=0.0, default=0.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from bottom' ) panel_border : FloatProperty( name='Border', min=0.001, default=0.2, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from border' ) panels_x : IntProperty( name="# h", min=1, max=50, default=1, description="panels h" ) panels_y : IntProperty( name="# v", min=1, max=50, default=1, description="panels v" ) panels_distrib : EnumProperty( name='distribution', items=( ('REGULAR', 'Regular', '', 0), ('ONE_THIRD', '1/3 2/3', '', 1) ), default='REGULAR' ) handle : EnumProperty( name='Shape', items=( ('NONE', 'No handle', '', 0), ('BOTH', 'Inside and outside', '', 1) ), default='BOTH' ) @property def panels(self): # subdivide side to weld panels subdiv_x = self.panels_x - 1 if self.panels_distrib == 'REGULAR': subdiv_y = self.panels_y - 1 else: subdiv_y = 2 # __ y0 # |__ y1 # x0 x1 y0 = -self.y y1 = 0 x0 = 0 x1 = max(0.001, self.panel_border - 0.5 * self.panel_spacing) side = DoorPanel( False, # profil closed [1, 0, 0, 1], # x index [x0, x1], [y0, y0, y1, y1], [0, 1, 1, 1], # material index closed_path=True, # subdiv_x=subdiv_x, subdiv_y=subdiv_y ) face = None back = None if self.model == 1: # / y2-y3 # __/ y1-y0 # x2 x3 x2 = 0.5 * self.panel_spacing x3 = x2 + self.chanfer y2 = y1 + self.chanfer y3 = y0 - self.chanfer face = DoorPanel( False, # profil closed [0, 1, 2], # x index [0, x2, x3], [y1, y1, y2], [1, 1, 1], # material index side_cap_front=2, # cap index closed_path=True ) back = DoorPanel( False, # profil closed [0, 1, 2], # x index [x3, x2, 0], [y3, y0, y0], [0, 0, 0], # material index side_cap_back=0, # cap index closed_path=True ) elif self.model == 2: # / y2-y3 # ___ _____/ y1-y0 # \ / # \/ y4-y5 # 0 x2 x4 x5 x6 x3 x2 = 0.5 * self.panel_spacing x4 = x2 + self.chanfer x5 = x4 + self.chanfer x6 = x5 + 4 * self.chanfer x3 = x6 + self.chanfer y2 = y1 - self.chanfer y4 = y1 + self.chanfer y3 = y0 + self.chanfer y5 = y0 - self.chanfer face = DoorPanel( False, # profil closed [0, 1, 2, 3, 4, 5], # x index [0, x2, x4, x5, x6, x3], [y1, y1, y4, y1, y1, y2], [1, 1, 1, 1, 1, 1], # material index side_cap_front=5, # cap index closed_path=True ) back = DoorPanel( False, # profil closed [0, 1, 2, 3, 4, 5], # x index [x3, x6, x5, x4, x2, 0], [y3, y0, y0, y5, y0, y0], [0, 0, 0, 0, 0, 0], # material index side_cap_back=0, # cap index closed_path=True ) elif self.model == 3: # _____ y2-y3 # / \ y4-y5 # __/ y1-y0 # 0 x2 x3 x4 x5 x2 = 0.5 * self.panel_spacing x3 = x2 + self.chanfer x4 = x3 + 4 * self.chanfer x5 = x4 + 2 * self.chanfer y2 = y1 - self.chanfer y3 = y0 + self.chanfer y4 = y2 + self.chanfer y5 = y3 - self.chanfer face = DoorPanel( False, # profil closed [0, 1, 2, 3, 4], # x index [0, x2, x3, x4, x5], [y1, y1, y2, y2, y4], [1, 1, 1, 1, 1], # material index side_cap_front=4, # cap index closed_path=True ) back = DoorPanel( False, # profil closed [0, 1, 2, 3, 4], # x index [x5, x4, x3, x2, 0], [y5, y3, y3, y0, y0], [0, 0, 0, 0, 0], # material index side_cap_back=0, # cap index closed_path=True ) else: side.side_cap_front = 3 side.side_cap_back = 0 return side, face, back @property def verts(self): if self.panels_distrib == 'REGULAR': subdiv_y = self.panels_y - 1 else: subdiv_y = 2 radius = Vector((0.8, 0.5, 0)) center = Vector((0, self.z - radius.x, 0)) if self.direction == 0: pivot = 1 else: pivot = -1 path_type = 'RECTANGLE' curve_steps = 16 side, face, back = self.panels x1 = max(0.001, self.panel_border - 0.5 * self.panel_spacing) bottom_z = self.panel_bottom shape_z = [0, bottom_z, bottom_z, 0] origin = Vector((-pivot * 0.5 * self.x, 0, 0)) offset = Vector((0, 0, 0)) size = Vector((self.x, self.z, 0)) verts = side.vertices(curve_steps, offset, center, origin, size, radius, 0, pivot, shape_z=shape_z, path_type=path_type) if face is not None: p_radius = radius.copy() p_radius.x -= x1 p_radius.y -= x1 if self.panels_distrib == 'REGULAR': p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / self.panels_y, 0)) for i in range(self.panels_x): for j in range(self.panels_y): if j < subdiv_y: shape = 'RECTANGLE' else: shape = path_type offset = Vector(((pivot * 0.5 * self.x) + p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) verts += face.vertices(curve_steps, offset, center, origin, p_size, p_radius, 0, 0, shape_z=None, path_type=shape) if back is not None: verts += back.vertices(curve_steps, offset, center, origin, p_size, p_radius, 0, 0, shape_z=None, path_type=shape) else: #################################### # Ratio vertical panels 1/3 - 2/3 #################################### p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / 3, 0)) p_size_2x = Vector((p_size.x, p_size.y * 2, 0)) for i in range(self.panels_x): j = 0 offset = Vector(((pivot * 0.5 * self.x) + p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) shape = 'RECTANGLE' face.subdiv_y = 0 verts += face.vertices(curve_steps, offset, center, origin, p_size, p_radius, 0, 0, shape_z=None, path_type=shape) if back is not None: back.subdiv_y = 0 verts += back.vertices(curve_steps, offset, center, origin, p_size, p_radius, 0, 0, shape_z=None, path_type=shape) j = 1 offset = Vector(((pivot * 0.5 * self.x) + p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) shape = path_type face.subdiv_y = 1 verts += face.vertices(curve_steps, offset, center, origin, p_size_2x, p_radius, 0, 0, shape_z=None, path_type=path_type) if back is not None: back.subdiv_y = 1 verts += back.vertices(curve_steps, offset, center, origin, p_size_2x, p_radius, 0, 0, shape_z=None, path_type=path_type) return verts @property def faces(self): if self.panels_distrib == 'REGULAR': subdiv_y = self.panels_y - 1 else: subdiv_y = 2 path_type = 'RECTANGLE' curve_steps = 16 side, face, back = self.panels faces = side.faces(curve_steps, path_type=path_type) faces_offset = side.n_verts(curve_steps, path_type=path_type) if face is not None: if self.panels_distrib == 'REGULAR': for i in range(self.panels_x): for j in range(self.panels_y): if j < subdiv_y: shape = 'RECTANGLE' else: shape = path_type faces += face.faces(curve_steps, path_type=shape, offset=faces_offset) faces_offset += face.n_verts(curve_steps, path_type=shape) if back is not None: faces += back.faces(curve_steps, path_type=shape, offset=faces_offset) faces_offset += back.n_verts(curve_steps, path_type=shape) else: #################################### # Ratio vertical panels 1/3 - 2/3 #################################### for i in range(self.panels_x): j = 0 shape = 'RECTANGLE' face.subdiv_y = 0 faces += face.faces(curve_steps, path_type=shape, offset=faces_offset) faces_offset += face.n_verts(curve_steps, path_type=shape) if back is not None: back.subdiv_y = 0 faces += back.faces(curve_steps, path_type=shape, offset=faces_offset) faces_offset += back.n_verts(curve_steps, path_type=shape) j = 1 shape = path_type face.subdiv_y = 1 faces += face.faces(curve_steps, path_type=path_type, offset=faces_offset) faces_offset += face.n_verts(curve_steps, path_type=path_type) if back is not None: back.subdiv_y = 1 faces += back.faces(curve_steps, path_type=path_type, offset=faces_offset) faces_offset += back.n_verts(curve_steps, path_type=path_type) return faces @property def uvs(self): if self.panels_distrib == 'REGULAR': subdiv_y = self.panels_y - 1 else: subdiv_y = 2 radius = Vector((0.8, 0.5, 0)) center = Vector((0, self.z - radius.x, 0)) if self.direction == 0: pivot = 1 else: pivot = -1 path_type = 'RECTANGLE' curve_steps = 16 side, face, back = self.panels x1 = max(0.001, self.panel_border - 0.5 * self.panel_spacing) bottom_z = self.panel_bottom origin = Vector((-pivot * 0.5 * self.x, 0, 0)) size = Vector((self.x, self.z, 0)) uvs = side.uv(curve_steps, center, origin, size, radius, 0, pivot, 0, self.panel_border, path_type=path_type) if face is not None: p_radius = radius.copy() p_radius.x -= x1 p_radius.y -= x1 if self.panels_distrib == 'REGULAR': p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / self.panels_y, 0)) for i in range(self.panels_x): for j in range(self.panels_y): if j < subdiv_y: shape = 'RECTANGLE' else: shape = path_type origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) uvs += face.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape) if back is not None: uvs += back.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape) else: #################################### # Ratio vertical panels 1/3 - 2/3 #################################### p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / 3, 0)) p_size_2x = Vector((p_size.x, p_size.y * 2, 0)) for i in range(self.panels_x): j = 0 origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) shape = 'RECTANGLE' face.subdiv_y = 0 uvs += face.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape) if back is not None: back.subdiv_y = 0 uvs += back.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape) j = 1 origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0)) shape = path_type face.subdiv_y = 1 uvs += face.uv(curve_steps, center, origin, p_size_2x, p_radius, 0, 0, 0, 0, path_type=path_type) if back is not None: back.subdiv_y = 1 uvs += back.uv(curve_steps, center, origin, p_size_2x, p_radius, 0, 0, 0, 0, path_type=path_type) return uvs @property def matids(self): if self.panels_distrib == 'REGULAR': subdiv_y = self.panels_y - 1 else: subdiv_y = 2 path_type = 'RECTANGLE' curve_steps = 16 side, face, back = self.panels mat = side.mat(curve_steps, 1, 0, path_type=path_type) if face is not None: if self.panels_distrib == 'REGULAR': for i in range(self.panels_x): for j in range(self.panels_y): if j < subdiv_y: shape = 'RECTANGLE' else: shape = path_type mat += face.mat(curve_steps, 1, 1, path_type=shape) if back is not None: mat += back.mat(curve_steps, 0, 0, path_type=shape) else: #################################### # Ratio vertical panels 1/3 - 2/3 #################################### for i in range(self.panels_x): j = 0 shape = 'RECTANGLE' face.subdiv_y = 0 mat += face.mat(curve_steps, 1, 1, path_type=shape) if back is not None: back.subdiv_y = 0 mat += back.mat(curve_steps, 0, 0, path_type=shape) j = 1 shape = path_type face.subdiv_y = 1 mat += face.mat(curve_steps, 1, 1, path_type=shape) if back is not None: back.subdiv_y = 1 mat += back.mat(curve_steps, 0, 0, path_type=shape) return mat 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) verts, faces = door_handle_horizontal_01(self.direction, 1) b_verts, b_faces = door_handle_horizontal_01(self.direction, 0, offset=len(verts)) b_verts = [(v[0], v[1] - self.y, v[2]) for v in b_verts] handle_y = 0.07 handle.location = ((1 - self.direction * 2) * (self.x - handle_y), 0, 0.5 * self.z) bmed.buildmesh(context, handle, verts + b_verts, faces + b_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 bmed.buildmesh(context, o, self.verts, self.faces, matids=self.matids, uvs=self.uvs, weld=True) if self.handle == 'NONE': self.remove_handle(context, o) else: self.update_handle(context, o) self.restore_context(context) class ARCHIPACK_PT_door_panel(Panel): bl_idname = "ARCHIPACK_PT_door_panel" bl_label = "Door" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' # bl_context = 'object' bl_category = 'Archipack' @classmethod def poll(cls, context): return archipack_door_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_door_panel(ArchipackCollectionManager, Operator): bl_idname = "archipack.door_panel" bl_label = "Door model 1" bl_description = "Door model 1" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} x : FloatProperty( name='Width', min=0.1, default=0.80, precision=2, unit='LENGTH', subtype='DISTANCE', description='Width' ) z : FloatProperty( name='Height', min=0.1, default=2.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='height' ) y : FloatProperty( name='Depth', min=0.001, default=0.02, precision=2, unit='LENGTH', subtype='DISTANCE', description='Depth' ) direction : IntProperty( name="Direction", min=0, max=1, description="open direction" ) model : IntProperty( name="Model", min=0, max=3, description="panel type" ) chanfer : FloatProperty( name='Bevel', min=0.001, default=0.005, precision=3, unit='LENGTH', subtype='DISTANCE', description='chanfer' ) panel_spacing : FloatProperty( name='Spacing', min=0.001, default=0.1, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance between panels' ) panel_bottom : FloatProperty( name='Bottom', min=0.0, default=0.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from bottom' ) panel_border : FloatProperty( name='Border', min=0.001, default=0.2, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from border' ) panels_x : IntProperty( name="# h", min=1, max=50, default=1, description="panels h" ) panels_y : IntProperty( name="# v", min=1, max=50, default=1, description="panels v" ) panels_distrib : EnumProperty( name='Distribution', items=( ('REGULAR', 'Regular', '', 0), ('ONE_THIRD', '1/3 2/3', '', 1) ), default='REGULAR' ) handle : EnumProperty( name='Shape', items=( ('NONE', 'No handle', '', 0), ('BOTH', 'Inside and outside', '', 1) ), default='BOTH' ) material : StringProperty( default="" ) 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): """ expose only basic params in operator use object property for other params """ m = bpy.data.meshes.new("Panel") o = bpy.data.objects.new("Panel", m) d = m.archipack_door_panel.add() d.x = self.x d.y = self.y d.z = self.z d.model = self.model d.direction = self.direction d.chanfer = self.chanfer d.panel_border = self.panel_border d.panel_bottom = self.panel_bottom d.panel_spacing = self.panel_spacing d.panels_distrib = self.panels_distrib d.panels_x = self.panels_x d.panels_y = self.panels_y d.handle = self.handle self.link_object_to_scene(context, o) o.lock_location[0] = True o.lock_location[1] = True o.lock_location[2] = True o.lock_rotation[0] = True o.lock_rotation[1] = True o.lock_scale[0] = True o.lock_scale[1] = True o.lock_scale[2] = True o.select_set(state=True) context.view_layer.objects.active = o m = o.archipack_material.add() m.category = "door" m.material = self.material d.update(context) # MaterialUtils.add_door_materials(o) o.lock_rotation[0] = True o.lock_rotation[1] = True return o def execute(self, context): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) o.select_set(state=True) context.view_layer.objects.active = o return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") return {'CANCELLED'} class ARCHIPACK_OT_select_parent(Operator): bl_idname = "archipack.select_parent" bl_label = "Edit parameters" bl_description = "Edit parameters located on parent" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} def draw(self, context): layout = self.layout row = layout.row() row.label(text="Use Properties panel (N) to define parms", icon='INFO') def execute(self, context): if context.mode == "OBJECT": if context.active_object is not None and context.active_object.parent is not None: bpy.ops.object.select_all(action="DESELECT") context.active_object.parent.select_set(state=True) context.view_layer.objects.active = context.active_object.parent return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") return {'CANCELLED'} class archipack_door(ArchipackObject, Manipulable, PropertyGroup): """ The frame is the door main object parent parametric object create/remove/update her own childs """ 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=2.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='height', update=update, ) frame_x : FloatProperty( name='Width', min=0, default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='frame width', update=update, ) frame_y : FloatProperty( name='Depth', default=0.03, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='frame depth', update=update, ) direction : IntProperty( name="Direction", min=0, max=1, description="open direction", update=update, ) door_y : FloatProperty( name='Depth', min=0.001, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='depth', update=update, ) door_offset : FloatProperty( name='Offset', min=0, default=0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='offset', update=update, ) model : IntProperty( name="Model", min=0, max=3, default=0, description="Model", update=update, ) n_panels : IntProperty( name="Panels", min=1, max=2, default=1, description="number of panels", update=update ) chanfer : FloatProperty( name='Bevel', min=0.001, default=0.005, precision=3, step=0.01, unit='LENGTH', subtype='DISTANCE', description='chanfer', update=update_childs, ) panel_spacing : FloatProperty( name='Spacing', min=0.001, default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='distance between panels', update=update_childs, ) panel_bottom : FloatProperty( name='Bottom', min=0.0, default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='distance from bottom', update=update_childs, ) panel_border : FloatProperty( name='Border', min=0.001, default=0.2, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='distance from border', update=update_childs, ) panels_x : IntProperty( name="# h", min=1, max=50, default=1, description="panels h", update=update_childs, ) panels_y : IntProperty( name="# v", min=1, max=50, default=1, description="panels v", update=update_childs, ) panels_distrib : EnumProperty( name='Distribution', items=( ('REGULAR', 'Regular', '', 0), ('ONE_THIRD', '1/3 2/3', '', 1) ), default='REGULAR', update=update_childs, ) handle : EnumProperty( name='Handle', items=( ('NONE', 'No handle', '', 0), ('BOTH', 'Inside and outside', '', 1) ), default='BOTH', 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' ) auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update ) @property def frame(self): # # _____ y0 # | |___ y1 # x | y3 # | | # |_________| y2 # # x2 x1 x0 x0 = 0 x1 = -BATTUE x2 = -self.frame_x y0 = max(0.25 * self.door_y + 0.0005, self.y / 2 + self.frame_y) y1 = max(y0 - 0.5 * self.door_y - self.door_offset, -y0 + 0.001) y2 = -y0 y3 = 0 return DoorPanel( True, # closed [0, 0, 0, 1, 1, 2, 2], # x index [x2, x1, x0], [y2, y3, y0, y0, y1, y1, y2], [0, 1, 1, 1, 1, 0, 0], # material index closed_path=False ) @property def hole(self): # # _____ y0 # | # x y2 # | # |_____ y1 # # x0 x0 = 0 y0 = self.y / 2 + self.hole_margin y1 = -y0 y2 = 0 outside_mat = 0 inside_mat = 1 if self.flip: outside_mat, inside_mat = inside_mat, outside_mat return DoorPanel( False, # closed [0, 0, 0], # x index [x0], [y1, y2, y0], [outside_mat, inside_mat, inside_mat], # material index closed_path=True, side_cap_front=2, side_cap_back=0 # cap index ) @property def verts(self): # door inner space v = Vector((0, 0, 0)) size = Vector((self.x, self.z, self.y)) return self.frame.vertices(16, v, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE') @property def faces(self): return self.frame.faces(16, path_type='RECTANGLE') @property def matids(self): return self.frame.mat(16, 0, 0, path_type='RECTANGLE') @property def uvs(self): v = Vector((0, 0, 0)) size = Vector((self.x, self.z, self.y)) return self.frame.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE') def setup_manipulators(self): if len(self.manipulators) == 3: 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)) def remove_childs(self, context, o, to_remove): for child in o.children: if to_remove < 1: return if archipack_door_panel.filter(child): self.remove_handle(context, child) to_remove -= 1 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 create_childs(self, context, o): n_childs = 0 for child in o.children: if archipack_door_panel.filter(child): n_childs += 1 # remove child if n_childs > self.n_panels: self.remove_childs(context, o, n_childs - self.n_panels) if n_childs < 1: # create one door panel bpy.ops.archipack.door_panel( x=self.x, z=self.z, door_y=self.door_y, n_panels=self.n_panels, direction=self.direction, material=o.archipack_material[0].material ) child = context.active_object child.parent = o child.matrix_world = o.matrix_world.copy() location = self.x / 2 + BATTUE - SPACING if self.direction == 0: location = -location child.location.x = location child.location.y = self.door_y if self.n_panels == 2 and n_childs < 2: # create 2nth door panel bpy.ops.archipack.door_panel( x=self.x, z=self.z, door_y=self.door_y, n_panels=self.n_panels, direction=1 - self.direction, material=o.archipack_material[0].material ) child = context.active_object child.parent = o child.matrix_world = o.matrix_world.copy() location = self.x / 2 + BATTUE - SPACING if self.direction == 1: location = -location child.location.x = location child.location.y = self.door_y def find_handle(self, o): for handle in o.children: if 'archipack_handle' in handle: return handle return None def get_childs_panels(self, context, o): return [child for child in o.children if archipack_door_panel.filter(child)] 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("DoorPanel", 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[0] = 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 = 'door' m.material = o.archipack_material[0].material else: p = l_childs[order[i]] p.location = child.location.copy() # 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: self.unlink_object_from_scene(h) bpy.data.objects.remove(h, do_unlink=True) 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) 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_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 """ childs = self.get_childs_panels(context, o) n_childs = len(childs) self.remove_childs(context, o, n_childs - self.n_panels) childs = self.get_childs_panels(context, o) n_childs = len(childs) child_n = 0 # location_y = self.y / 2 + self.frame_y - SPACING # location_y = min(max(self.door_offset, - location_y), location_y) + self.door_y location_y = max(0.25 * self.door_y + 0.0005, self.y / 2 + self.frame_y) location_y = max(location_y - self.door_offset + 0.5 * self.door_y, -location_y + self.door_y + 0.001) x = self.x / self.n_panels + (3 - self.n_panels) * (BATTUE - SPACING) y = self.door_y z = self.z + BATTUE - SPACING if self.n_panels < 2: direction = self.direction else: direction = 0 for panel in range(self.n_panels): child_n += 1 if child_n == 1: handle = self.handle else: handle = 'NONE' if child_n > 1: direction = 1 - direction location_x = (2 * direction - 1) * (self.x / 2 + BATTUE - SPACING) if child_n > n_childs: bpy.ops.archipack.door_panel( x=x, y=y, z=z, model=self.model, direction=direction, chanfer=self.chanfer, panel_border=self.panel_border, panel_bottom=self.panel_bottom, panel_spacing=self.panel_spacing, panels_distrib=self.panels_distrib, panels_x=self.panels_x, panels_y=self.panels_y, handle=handle, 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_door_panel.datablock(child) if props is not None: props.x = x props.y = y props.z = z props.model = self.model props.direction = direction props.chanfer = self.chanfer props.panel_border = self.panel_border props.panel_bottom = self.panel_bottom props.panel_spacing = self.panel_spacing props.panels_distrib = self.panels_distrib props.panels_x = self.panels_x props.panels_y = self.panels_y props.handle = handle props.update(context) child.location = Vector((location_x, location_y, 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) 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) # setup 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, 0), (x, -y, self.z), (-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) hole = self.hole v = Vector((0, 0, 0)) offset = Vector((0, -0.001, 0)) size = Vector((self.x + 2 * self.frame_x, self.z + self.frame_x + 0.001, self.y)) verts = hole.vertices(16, offset, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE') faces = hole.faces(16, path_type='RECTANGLE') matids = hole.mat(16, 0, 1, path_type='RECTANGLE') uvs = hole.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE') bmed.buildmesh(context, hole_obj, verts, faces, matids=matids, uvs=uvs) return hole_obj def robust_hole(self, context, tM): hole = self.hole m = bpy.data.meshes.new("hole") o = bpy.data.objects.new("hole", m) o['archipack_robusthole'] = True self.link_object_to_scene(context, o) v = Vector((0, 0, 0)) offset = Vector((0, -0.001, 0)) size = Vector((self.x + 2 * self.frame_x, self.z + self.frame_x + 0.001, self.y)) verts = hole.vertices(16, offset, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE') verts = [tM @ Vector(v) for v in verts] faces = hole.faces(16, path_type='RECTANGLE') matids = hole.mat(16, 0, 1, path_type='RECTANGLE') uvs = hole.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE') bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs) o.select_set(state=True) context.view_layer.objects.active = o return o class ARCHIPACK_PT_door(Panel): bl_idname = "ARCHIPACK_PT_door" bl_label = "Door" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Archipack' @classmethod def poll(cls, context): return archipack_door.filter(context.active_object) def draw(self, context): o = context.active_object if not archipack_door.filter(o): return layout = self.layout layout.operator('archipack.door_manipulate', icon='VIEW_PAN') props = archipack_door.datablock(o) row = layout.row(align=True) row.operator('archipack.door', text="Refresh", icon='FILE_REFRESH').mode = 'REFRESH' if o.data.users > 1: row.operator('archipack.door', text="Make unique", icon='UNLINKED').mode = 'UNIQUE' row.operator('archipack.door', text="Delete", icon='ERROR').mode = 'DELETE' box = layout.box() # box.label(text="Styles") row = box.row(align=True) row.operator("archipack.door_preset_menu", text=bpy.types.ARCHIPACK_OT_door_preset_menu.bl_label) row.operator("archipack.door_preset", text="", icon='ADD') row.operator("archipack.door_preset", text="", icon='REMOVE').remove_active = True row = layout.row() box = row.box() box.label(text="Size") box.prop(props, 'x') box.prop(props, 'y') box.prop(props, 'z') box.prop(props, 'door_offset') row = layout.row() box = row.box() row = box.row() row.label(text="Door") box.prop(props, 'direction') box.prop(props, 'n_panels') box.prop(props, 'door_y') box.prop(props, 'handle') row = layout.row() box = row.box() row = box.row() row.label(text="Frame") row = box.row(align=True) row.prop(props, 'frame_x') row.prop(props, 'frame_y') row = layout.row() box = row.box() row = box.row() row.label(text="Panels") box.prop(props, 'model') if props.model > 0: box.prop(props, 'panels_distrib', text="") row = box.row(align=True) row.prop(props, 'panels_x') if props.panels_distrib == 'REGULAR': row.prop(props, 'panels_y') box.prop(props, 'panel_bottom') box.prop(props, 'panel_spacing') box.prop(props, 'panel_border') box.prop(props, 'chanfer') # ------------------------------------------------------------------ # Define operator class to create object # ------------------------------------------------------------------ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): bl_idname = "archipack.door" bl_label = "Door" bl_description = "Door" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} x : FloatProperty( name='width', min=0.1, default=0.80, precision=2, unit='LENGTH', subtype='DISTANCE', description='Width' ) y : FloatProperty( name='depth', min=0.1, default=0.20, precision=2, unit='LENGTH', subtype='DISTANCE', description='Depth' ) z : FloatProperty( name='height', min=0.1, default=2.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='height' ) direction : IntProperty( name="direction", min=0, max=1, description="open direction" ) n_panels : IntProperty( name="panels", min=1, max=2, default=1, description="number of panels" ) chanfer : FloatProperty( name='chanfer', min=0.001, default=0.005, precision=3, unit='LENGTH', subtype='DISTANCE', description='chanfer' ) panel_spacing : FloatProperty( name='spacing', min=0.001, default=0.1, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance between panels' ) panel_bottom : FloatProperty( name='bottom', default=0.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from bottom' ) panel_border : FloatProperty( name='border', min=0.001, default=0.2, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from border' ) panels_x : IntProperty( name="panels h", min=1, max=50, default=1, description="panels h" ) panels_y : IntProperty( name="panels v", min=1, max=50, default=1, description="panels v" ) panels_distrib : EnumProperty( name='distribution', items=( ('REGULAR', 'Regular', '', 0), ('ONE_THIRD', '1/3 2/3', '', 1) ), default='REGULAR' ) handle : EnumProperty( name='Shape', items=( ('NONE', 'No handle', '', 0), ('BOTH', 'Inside and outside', '', 1) ), default='BOTH' ) mode : EnumProperty( items=( ('CREATE', 'Create', '', 0), ('DELETE', 'Delete', '', 1), ('REFRESH', 'Refresh', '', 2), ('UNIQUE', 'Make unique', '', 3), ), default='CREATE' ) def create(self, context): """ expose only basic params in operator use object property for other params """ m = bpy.data.meshes.new("Door") o = bpy.data.objects.new("Door", m) d = m.archipack_door.add() d.x = self.x d.y = self.y d.z = self.z d.direction = self.direction d.n_panels = self.n_panels d.chanfer = self.chanfer d.panel_border = self.panel_border d.panel_bottom = self.panel_bottom d.panel_spacing = self.panel_spacing d.panels_distrib = self.panels_distrib d.panels_x = self.panels_x d.panels_y = self.panels_y d.handle = self.handle 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) o.select_set(state=True) context.view_layer.objects.active = o return o def delete(self, context): o = context.active_object if archipack_door.filter(o): bpy.ops.archipack.disable_manipulate() for child in o.children: if '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_door_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_door.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_door.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_door.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_door_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) 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_door_draw(ArchipackDrawTool, Operator): bl_idname = "archipack.door_draw" bl_label = "Draw Doors" bl_description = "Draw Doors 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_door.filter(o): o.select_set(state=True) context.view_layer.objects.active = o if event.shift: bpy.ops.archipack.door(mode="UNIQUE") new_w = o.copy() new_w.data = o.data self.link_object_to_scene(context, new_w) # instance subs 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.door(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_door.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.door(mode='DELETE') self.feedback.disable() bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') bpy.ops.archipack.door_preset_menu( 'INVOKE_DEFAULT', preset_operator="archipack.door_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 door 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.door(mode="DELETE") context.view_layer.objects.active = o return {'RUNNING_MODAL'} if event.value == 'RELEASE': if event.type in {'ESC', 'RIGHTMOUSE'}: bpy.ops.archipack.door(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 alt pressed will use current object as basis for linked copy if self.filepath == '' and archipack_door.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 door", "Click & Drag over a wall", [ ('LEFTCLICK, RET, SPACE, ENTER', 'Create a door'), ('BACKSPACE, CTRL+Z', 'undo last'), ('C', 'Choose another door'), ('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'} # ------------------------------------------------------------------ # Define operator class to manipulate object # ------------------------------------------------------------------ class ARCHIPACK_OT_door_manipulate(Operator): bl_idname = "archipack.door_manipulate" bl_label = "Manipulate" bl_description = "Manipulate" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(self, context): return archipack_door.filter(context.active_object) def invoke(self, context, event): d = archipack_door.datablock(context.active_object) d.manipulable_invoke(context) return {'FINISHED'} # ------------------------------------------------------------------ # Define operator class to load / save presets # ------------------------------------------------------------------ class ARCHIPACK_OT_door_preset_menu(PresetMenuOperator, Operator): bl_description = "Show Doors presets" bl_idname = "archipack.door_preset_menu" bl_label = "Door Presets" preset_subdir = "archipack_door" class ARCHIPACK_OT_door_preset(ArchipackPreset, Operator): """Add a Door Preset""" bl_idname = "archipack.door_preset" bl_label = "Add Door Preset" preset_menu = "ARCHIPACK_OT_door_preset_menu" @property def blacklist(self): return ['manipulators'] def register(): bpy.utils.register_class(archipack_door_panel) Mesh.archipack_door_panel = CollectionProperty(type=archipack_door_panel) bpy.utils.register_class(ARCHIPACK_PT_door_panel) bpy.utils.register_class(ARCHIPACK_OT_door_panel) bpy.utils.register_class(ARCHIPACK_OT_select_parent) bpy.utils.register_class(archipack_door) Mesh.archipack_door = CollectionProperty(type=archipack_door) bpy.utils.register_class(ARCHIPACK_OT_door_preset_menu) bpy.utils.register_class(ARCHIPACK_PT_door) bpy.utils.register_class(ARCHIPACK_OT_door) bpy.utils.register_class(ARCHIPACK_OT_door_preset) bpy.utils.register_class(ARCHIPACK_OT_door_draw) bpy.utils.register_class(ARCHIPACK_OT_door_manipulate) def unregister(): bpy.utils.unregister_class(archipack_door_panel) del Mesh.archipack_door_panel bpy.utils.unregister_class(ARCHIPACK_PT_door_panel) bpy.utils.unregister_class(ARCHIPACK_OT_door_panel) bpy.utils.unregister_class(ARCHIPACK_OT_select_parent) bpy.utils.unregister_class(archipack_door) del Mesh.archipack_door bpy.utils.unregister_class(ARCHIPACK_OT_door_preset_menu) bpy.utils.unregister_class(ARCHIPACK_PT_door) bpy.utils.unregister_class(ARCHIPACK_OT_door) bpy.utils.unregister_class(ARCHIPACK_OT_door_preset) bpy.utils.unregister_class(ARCHIPACK_OT_door_draw) bpy.utils.unregister_class(ARCHIPACK_OT_door_manipulate)