Fix mesh_snap_utilities_line running without key-maps available
[blender-addons.git] / archipack / archipack_door.py
blob4d8b61ca094ff5f57b8227d41c2e0c814903de8a
1 # -*- coding:utf-8 -*-
3 # ##### BEGIN GPL LICENSE BLOCK #####
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 # ##### END GPL LICENSE BLOCK #####
21 # <pep8 compliant>
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
26 # ----------------------------------------------------------
28 # noinspection PyUnresolvedReferences
29 import bpy
30 # noinspection PyUnresolvedReferences
31 from bpy.types import Operator, PropertyGroup, Mesh, Panel
32 from bpy.props import (
33 FloatProperty, IntProperty, CollectionProperty,
34 EnumProperty, BoolProperty, StringProperty
36 from mathutils import Vector
37 # door component objects (panels, handles ..)
38 from .bmesh_utils import BmeshEdit as bmed
39 from .panel import Panel as DoorPanel
40 from .archipack_handle import create_handle, door_handle_horizontal_01
41 from .archipack_manipulator import Manipulable
42 from .archipack_preset import ArchipackPreset, PresetMenuOperator
43 from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool, ArchipackCollectionManager
44 from .archipack_gl import FeedbackPanel
45 from .archipack_keymaps import Keymaps
48 SPACING = 0.005
49 BATTUE = 0.01
50 BOTTOM_HOLE_MARGIN = 0.001
51 FRONT_HOLE_MARGIN = 0.1
54 def update(self, context):
55 self.update(context)
58 def update_childs(self, context):
59 self.update(context, childs_only=True)
62 class archipack_door_panel(ArchipackObject, PropertyGroup):
63 x : FloatProperty(
64 name='Width',
65 min=0.25,
66 default=100.0, precision=2,
67 unit='LENGTH', subtype='DISTANCE',
68 description='Width'
70 y : FloatProperty(
71 name='Depth',
72 min=0.001,
73 default=0.02, precision=2,
74 unit='LENGTH', subtype='DISTANCE',
75 description='depth'
77 z : FloatProperty(
78 name='Height',
79 min=0.1,
80 default=2.0, precision=2,
81 unit='LENGTH', subtype='DISTANCE',
82 description='height'
84 direction : IntProperty(
85 name="Direction",
86 min=0,
87 max=1,
88 description="open direction"
90 model : IntProperty(
91 name="Model",
92 min=0,
93 max=3,
94 default=0,
95 description="Model"
97 chanfer : FloatProperty(
98 name='Bevel',
99 min=0.001,
100 default=0.005, precision=3,
101 unit='LENGTH', subtype='DISTANCE',
102 description='chanfer'
104 panel_spacing : FloatProperty(
105 name='Spacing',
106 min=0.001,
107 default=0.1, precision=2,
108 unit='LENGTH', subtype='DISTANCE',
109 description='distance between panels'
111 panel_bottom : FloatProperty(
112 name='Bottom',
113 min=0.0,
114 default=0.0, precision=2,
115 unit='LENGTH', subtype='DISTANCE',
116 description='distance from bottom'
118 panel_border : FloatProperty(
119 name='Border',
120 min=0.001,
121 default=0.2, precision=2,
122 unit='LENGTH', subtype='DISTANCE',
123 description='distance from border'
125 panels_x : IntProperty(
126 name="# h",
127 min=1,
128 max=50,
129 default=1,
130 description="panels h"
132 panels_y : IntProperty(
133 name="# v",
134 min=1,
135 max=50,
136 default=1,
137 description="panels v"
139 panels_distrib : EnumProperty(
140 name='distribution',
141 items=(
142 ('REGULAR', 'Regular', '', 0),
143 ('ONE_THIRD', '1/3 2/3', '', 1)
145 default='REGULAR'
147 handle : EnumProperty(
148 name='Shape',
149 items=(
150 ('NONE', 'No handle', '', 0),
151 ('BOTH', 'Inside and outside', '', 1)
153 default='BOTH'
156 @property
157 def panels(self):
159 # subdivide side to weld panels
160 subdiv_x = self.panels_x - 1
162 if self.panels_distrib == 'REGULAR':
163 subdiv_y = self.panels_y - 1
164 else:
165 subdiv_y = 2
167 # __ y0
168 # |__ y1
169 # x0 x1
170 y0 = -self.y
171 y1 = 0
172 x0 = 0
173 x1 = max(0.001, self.panel_border - 0.5 * self.panel_spacing)
175 side = DoorPanel(
176 False, # profil closed
177 [1, 0, 0, 1], # x index
178 [x0, x1],
179 [y0, y0, y1, y1],
180 [0, 1, 1, 1], # material index
181 closed_path=True, #
182 subdiv_x=subdiv_x,
183 subdiv_y=subdiv_y
186 face = None
187 back = None
189 if self.model == 1:
190 # / y2-y3
191 # __/ y1-y0
192 # x2 x3
193 x2 = 0.5 * self.panel_spacing
194 x3 = x2 + self.chanfer
195 y2 = y1 + self.chanfer
196 y3 = y0 - self.chanfer
198 face = DoorPanel(
199 False, # profil closed
200 [0, 1, 2], # x index
201 [0, x2, x3],
202 [y1, y1, y2],
203 [1, 1, 1], # material index
204 side_cap_front=2, # cap index
205 closed_path=True
208 back = DoorPanel(
209 False, # profil closed
210 [0, 1, 2], # x index
211 [x3, x2, 0],
212 [y3, y0, y0],
213 [0, 0, 0], # material index
214 side_cap_back=0, # cap index
215 closed_path=True
218 elif self.model == 2:
219 # / y2-y3
220 # ___ _____/ y1-y0
221 # \ /
222 # \/ y4-y5
223 # 0 x2 x4 x5 x6 x3
224 x2 = 0.5 * self.panel_spacing
225 x4 = x2 + self.chanfer
226 x5 = x4 + self.chanfer
227 x6 = x5 + 4 * self.chanfer
228 x3 = x6 + self.chanfer
229 y2 = y1 - self.chanfer
230 y4 = y1 + self.chanfer
231 y3 = y0 + self.chanfer
232 y5 = y0 - self.chanfer
233 face = DoorPanel(
234 False, # profil closed
235 [0, 1, 2, 3, 4, 5], # x index
236 [0, x2, x4, x5, x6, x3],
237 [y1, y1, y4, y1, y1, y2],
238 [1, 1, 1, 1, 1, 1], # material index
239 side_cap_front=5, # cap index
240 closed_path=True
243 back = DoorPanel(
244 False, # profil closed
245 [0, 1, 2, 3, 4, 5], # x index
246 [x3, x6, x5, x4, x2, 0],
247 [y3, y0, y0, y5, y0, y0],
248 [0, 0, 0, 0, 0, 0], # material index
249 side_cap_back=0, # cap index
250 closed_path=True
253 elif self.model == 3:
254 # _____ y2-y3
255 # / \ y4-y5
256 # __/ y1-y0
257 # 0 x2 x3 x4 x5
258 x2 = 0.5 * self.panel_spacing
259 x3 = x2 + self.chanfer
260 x4 = x3 + 4 * self.chanfer
261 x5 = x4 + 2 * self.chanfer
262 y2 = y1 - self.chanfer
263 y3 = y0 + self.chanfer
264 y4 = y2 + self.chanfer
265 y5 = y3 - self.chanfer
266 face = DoorPanel(
267 False, # profil closed
268 [0, 1, 2, 3, 4], # x index
269 [0, x2, x3, x4, x5],
270 [y1, y1, y2, y2, y4],
271 [1, 1, 1, 1, 1], # material index
272 side_cap_front=4, # cap index
273 closed_path=True
276 back = DoorPanel(
277 False, # profil closed
278 [0, 1, 2, 3, 4], # x index
279 [x5, x4, x3, x2, 0],
280 [y5, y3, y3, y0, y0],
281 [0, 0, 0, 0, 0], # material index
282 side_cap_back=0, # cap index
283 closed_path=True
286 else:
287 side.side_cap_front = 3
288 side.side_cap_back = 0
290 return side, face, back
292 @property
293 def verts(self):
294 if self.panels_distrib == 'REGULAR':
295 subdiv_y = self.panels_y - 1
296 else:
297 subdiv_y = 2
299 radius = Vector((0.8, 0.5, 0))
300 center = Vector((0, self.z - radius.x, 0))
302 if self.direction == 0:
303 pivot = 1
304 else:
305 pivot = -1
307 path_type = 'RECTANGLE'
308 curve_steps = 16
309 side, face, back = self.panels
311 x1 = max(0.001, self.panel_border - 0.5 * self.panel_spacing)
312 bottom_z = self.panel_bottom
313 shape_z = [0, bottom_z, bottom_z, 0]
314 origin = Vector((-pivot * 0.5 * self.x, 0, 0))
315 offset = Vector((0, 0, 0))
316 size = Vector((self.x, self.z, 0))
317 verts = side.vertices(curve_steps, offset, center, origin,
318 size, radius, 0, pivot, shape_z=shape_z, path_type=path_type)
319 if face is not None:
320 p_radius = radius.copy()
321 p_radius.x -= x1
322 p_radius.y -= x1
323 if self.panels_distrib == 'REGULAR':
324 p_size = Vector(((self.x - 2 * x1) / self.panels_x,
325 (self.z - 2 * x1 - bottom_z) / self.panels_y, 0))
326 for i in range(self.panels_x):
327 for j in range(self.panels_y):
328 if j < subdiv_y:
329 shape = 'RECTANGLE'
330 else:
331 shape = path_type
332 offset = Vector(((pivot * 0.5 * self.x) + p_size.x * (i + 0.5) - 0.5 * size.x + x1,
333 bottom_z + p_size.y * j + x1, 0))
334 origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0))
335 verts += face.vertices(curve_steps, offset, center, origin,
336 p_size, p_radius, 0, 0, shape_z=None, path_type=shape)
337 if back is not None:
338 verts += back.vertices(curve_steps, offset, center, origin,
339 p_size, p_radius, 0, 0, shape_z=None, path_type=shape)
340 else:
341 ####################################
342 # Ratio vertical panels 1/3 - 2/3
343 ####################################
344 p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / 3, 0))
345 p_size_2x = Vector((p_size.x, p_size.y * 2, 0))
346 for i in range(self.panels_x):
347 j = 0
348 offset = Vector(((pivot * 0.5 * self.x) + p_size.x * (i + 0.5) - 0.5 * size.x + x1,
349 bottom_z + p_size.y * j + x1, 0))
350 origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0))
351 shape = 'RECTANGLE'
352 face.subdiv_y = 0
353 verts += face.vertices(curve_steps, offset, center, origin,
354 p_size, p_radius, 0, 0, shape_z=None, path_type=shape)
355 if back is not None:
356 back.subdiv_y = 0
357 verts += back.vertices(curve_steps, offset, center, origin,
358 p_size, p_radius, 0, 0, shape_z=None, path_type=shape)
359 j = 1
360 offset = Vector(((pivot * 0.5 * self.x) + p_size.x * (i + 0.5) - 0.5 * size.x + x1,
361 bottom_z + p_size.y * j + x1, 0))
362 origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1,
363 bottom_z + p_size.y * j + x1, 0))
364 shape = path_type
365 face.subdiv_y = 1
366 verts += face.vertices(curve_steps, offset, center, origin,
367 p_size_2x, p_radius, 0, 0, shape_z=None, path_type=path_type)
368 if back is not None:
369 back.subdiv_y = 1
370 verts += back.vertices(curve_steps, offset, center, origin,
371 p_size_2x, p_radius, 0, 0, shape_z=None, path_type=path_type)
373 return verts
375 @property
376 def faces(self):
377 if self.panels_distrib == 'REGULAR':
378 subdiv_y = self.panels_y - 1
379 else:
380 subdiv_y = 2
382 path_type = 'RECTANGLE'
383 curve_steps = 16
384 side, face, back = self.panels
386 faces = side.faces(curve_steps, path_type=path_type)
387 faces_offset = side.n_verts(curve_steps, path_type=path_type)
389 if face is not None:
390 if self.panels_distrib == 'REGULAR':
391 for i in range(self.panels_x):
392 for j in range(self.panels_y):
393 if j < subdiv_y:
394 shape = 'RECTANGLE'
395 else:
396 shape = path_type
397 faces += face.faces(curve_steps, path_type=shape, offset=faces_offset)
398 faces_offset += face.n_verts(curve_steps, path_type=shape)
399 if back is not None:
400 faces += back.faces(curve_steps, path_type=shape, offset=faces_offset)
401 faces_offset += back.n_verts(curve_steps, path_type=shape)
402 else:
403 ####################################
404 # Ratio vertical panels 1/3 - 2/3
405 ####################################
406 for i in range(self.panels_x):
407 j = 0
408 shape = 'RECTANGLE'
409 face.subdiv_y = 0
410 faces += face.faces(curve_steps, path_type=shape, offset=faces_offset)
411 faces_offset += face.n_verts(curve_steps, path_type=shape)
412 if back is not None:
413 back.subdiv_y = 0
414 faces += back.faces(curve_steps, path_type=shape, offset=faces_offset)
415 faces_offset += back.n_verts(curve_steps, path_type=shape)
416 j = 1
417 shape = path_type
418 face.subdiv_y = 1
419 faces += face.faces(curve_steps, path_type=path_type, offset=faces_offset)
420 faces_offset += face.n_verts(curve_steps, path_type=path_type)
421 if back is not None:
422 back.subdiv_y = 1
423 faces += back.faces(curve_steps, path_type=path_type, offset=faces_offset)
424 faces_offset += back.n_verts(curve_steps, path_type=path_type)
426 return faces
428 @property
429 def uvs(self):
430 if self.panels_distrib == 'REGULAR':
431 subdiv_y = self.panels_y - 1
432 else:
433 subdiv_y = 2
435 radius = Vector((0.8, 0.5, 0))
436 center = Vector((0, self.z - radius.x, 0))
438 if self.direction == 0:
439 pivot = 1
440 else:
441 pivot = -1
443 path_type = 'RECTANGLE'
444 curve_steps = 16
445 side, face, back = self.panels
447 x1 = max(0.001, self.panel_border - 0.5 * self.panel_spacing)
448 bottom_z = self.panel_bottom
449 origin = Vector((-pivot * 0.5 * self.x, 0, 0))
450 size = Vector((self.x, self.z, 0))
451 uvs = side.uv(curve_steps, center, origin, size, radius, 0, pivot, 0, self.panel_border, path_type=path_type)
452 if face is not None:
453 p_radius = radius.copy()
454 p_radius.x -= x1
455 p_radius.y -= x1
456 if self.panels_distrib == 'REGULAR':
457 p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / self.panels_y, 0))
458 for i in range(self.panels_x):
459 for j in range(self.panels_y):
460 if j < subdiv_y:
461 shape = 'RECTANGLE'
462 else:
463 shape = path_type
464 origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0))
465 uvs += face.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape)
466 if back is not None:
467 uvs += back.uv(curve_steps, center, origin,
468 p_size, p_radius, 0, 0, 0, 0, path_type=shape)
469 else:
470 ####################################
471 # Ratio vertical panels 1/3 - 2/3
472 ####################################
473 p_size = Vector(((self.x - 2 * x1) / self.panels_x, (self.z - 2 * x1 - bottom_z) / 3, 0))
474 p_size_2x = Vector((p_size.x, p_size.y * 2, 0))
475 for i in range(self.panels_x):
476 j = 0
477 origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0))
478 shape = 'RECTANGLE'
479 face.subdiv_y = 0
480 uvs += face.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape)
481 if back is not None:
482 back.subdiv_y = 0
483 uvs += back.uv(curve_steps, center, origin, p_size, p_radius, 0, 0, 0, 0, path_type=shape)
484 j = 1
485 origin = Vector((p_size.x * (i + 0.5) - 0.5 * size.x + x1, bottom_z + p_size.y * j + x1, 0))
486 shape = path_type
487 face.subdiv_y = 1
488 uvs += face.uv(curve_steps, center, origin, p_size_2x, p_radius, 0, 0, 0, 0, path_type=path_type)
489 if back is not None:
490 back.subdiv_y = 1
491 uvs += back.uv(curve_steps, center, origin,
492 p_size_2x, p_radius, 0, 0, 0, 0, path_type=path_type)
493 return uvs
495 @property
496 def matids(self):
497 if self.panels_distrib == 'REGULAR':
498 subdiv_y = self.panels_y - 1
499 else:
500 subdiv_y = 2
502 path_type = 'RECTANGLE'
503 curve_steps = 16
504 side, face, back = self.panels
506 mat = side.mat(curve_steps, 1, 0, path_type=path_type)
508 if face is not None:
509 if self.panels_distrib == 'REGULAR':
510 for i in range(self.panels_x):
511 for j in range(self.panels_y):
512 if j < subdiv_y:
513 shape = 'RECTANGLE'
514 else:
515 shape = path_type
516 mat += face.mat(curve_steps, 1, 1, path_type=shape)
517 if back is not None:
518 mat += back.mat(curve_steps, 0, 0, path_type=shape)
519 else:
520 ####################################
521 # Ratio vertical panels 1/3 - 2/3
522 ####################################
523 for i in range(self.panels_x):
524 j = 0
525 shape = 'RECTANGLE'
526 face.subdiv_y = 0
527 mat += face.mat(curve_steps, 1, 1, path_type=shape)
528 if back is not None:
529 back.subdiv_y = 0
530 mat += back.mat(curve_steps, 0, 0, path_type=shape)
531 j = 1
532 shape = path_type
533 face.subdiv_y = 1
534 mat += face.mat(curve_steps, 1, 1, path_type=shape)
535 if back is not None:
536 back.subdiv_y = 1
537 mat += back.mat(curve_steps, 0, 0, path_type=shape)
538 return mat
540 def find_handle(self, o):
541 for child in o.children:
542 if 'archipack_handle' in child:
543 return child
544 return None
546 def update_handle(self, context, o):
547 handle = self.find_handle(o)
548 if handle is None:
549 m = bpy.data.meshes.new("Handle")
550 handle = create_handle(context, o, m)
552 verts, faces = door_handle_horizontal_01(self.direction, 1)
553 b_verts, b_faces = door_handle_horizontal_01(self.direction, 0, offset=len(verts))
554 b_verts = [(v[0], v[1] - self.y, v[2]) for v in b_verts]
555 handle_y = 0.07
556 handle.location = ((1 - self.direction * 2) * (self.x - handle_y), 0, 0.5 * self.z)
557 bmed.buildmesh(context, handle, verts + b_verts, faces + b_faces)
559 def remove_handle(self, context, o):
560 handle = self.find_handle(o)
561 if handle is not None:
562 self.unlink_object_from_scene(handle)
563 bpy.data.objects.remove(handle, do_unlink=True)
565 def update(self, context):
566 o = self.find_in_selection(context)
568 if o is None:
569 return
571 bmed.buildmesh(context, o, self.verts, self.faces, matids=self.matids, uvs=self.uvs, weld=True)
573 if self.handle == 'NONE':
574 self.remove_handle(context, o)
575 else:
576 self.update_handle(context, o)
578 self.restore_context(context)
581 class ARCHIPACK_PT_door_panel(Panel):
582 bl_idname = "ARCHIPACK_PT_door_panel"
583 bl_label = "Door"
584 bl_space_type = 'VIEW_3D'
585 bl_region_type = 'UI'
586 # bl_context = 'object'
587 bl_category = 'Archipack'
589 @classmethod
590 def poll(cls, context):
591 return archipack_door_panel.filter(context.active_object)
593 def draw(self, context):
594 layout = self.layout
595 layout.operator("archipack.select_parent")
598 # ------------------------------------------------------------------
599 # Define operator class to create object
600 # ------------------------------------------------------------------
603 class ARCHIPACK_OT_door_panel(ArchipackCollectionManager, Operator):
604 bl_idname = "archipack.door_panel"
605 bl_label = "Door model 1"
606 bl_description = "Door model 1"
607 bl_category = 'Archipack'
608 bl_options = {'REGISTER', 'UNDO'}
609 x : FloatProperty(
610 name='Width',
611 min=0.1,
612 default=0.80, precision=2,
613 unit='LENGTH', subtype='DISTANCE',
614 description='Width'
616 z : FloatProperty(
617 name='Height',
618 min=0.1,
619 default=2.0, precision=2,
620 unit='LENGTH', subtype='DISTANCE',
621 description='height'
623 y : FloatProperty(
624 name='Depth',
625 min=0.001,
626 default=0.02, precision=2,
627 unit='LENGTH', subtype='DISTANCE',
628 description='Depth'
630 direction : IntProperty(
631 name="Direction",
632 min=0,
633 max=1,
634 description="open direction"
636 model : IntProperty(
637 name="Model",
638 min=0,
639 max=3,
640 description="panel type"
642 chanfer : FloatProperty(
643 name='Bevel',
644 min=0.001,
645 default=0.005, precision=3,
646 unit='LENGTH', subtype='DISTANCE',
647 description='chanfer'
649 panel_spacing : FloatProperty(
650 name='Spacing',
651 min=0.001,
652 default=0.1, precision=2,
653 unit='LENGTH', subtype='DISTANCE',
654 description='distance between panels'
656 panel_bottom : FloatProperty(
657 name='Bottom',
658 min=0.0,
659 default=0.0, precision=2,
660 unit='LENGTH', subtype='DISTANCE',
661 description='distance from bottom'
663 panel_border : FloatProperty(
664 name='Border',
665 min=0.001,
666 default=0.2, precision=2,
667 unit='LENGTH', subtype='DISTANCE',
668 description='distance from border'
670 panels_x : IntProperty(
671 name="# h",
672 min=1,
673 max=50,
674 default=1,
675 description="panels h"
677 panels_y : IntProperty(
678 name="# v",
679 min=1,
680 max=50,
681 default=1,
682 description="panels v"
684 panels_distrib : EnumProperty(
685 name='Distribution',
686 items=(
687 ('REGULAR', 'Regular', '', 0),
688 ('ONE_THIRD', '1/3 2/3', '', 1)
690 default='REGULAR'
692 handle : EnumProperty(
693 name='Shape',
694 items=(
695 ('NONE', 'No handle', '', 0),
696 ('BOTH', 'Inside and outside', '', 1)
698 default='BOTH'
700 material : StringProperty(
701 default=""
704 def draw(self, context):
705 layout = self.layout
706 row = layout.row()
707 row.label(text="Use Properties panel (N) to define parms", icon='INFO')
709 def create(self, context):
711 expose only basic params in operator
712 use object property for other params
714 m = bpy.data.meshes.new("Panel")
715 o = bpy.data.objects.new("Panel", m)
716 d = m.archipack_door_panel.add()
717 d.x = self.x
718 d.y = self.y
719 d.z = self.z
720 d.model = self.model
721 d.direction = self.direction
722 d.chanfer = self.chanfer
723 d.panel_border = self.panel_border
724 d.panel_bottom = self.panel_bottom
725 d.panel_spacing = self.panel_spacing
726 d.panels_distrib = self.panels_distrib
727 d.panels_x = self.panels_x
728 d.panels_y = self.panels_y
729 d.handle = self.handle
730 self.link_object_to_scene(context, o)
731 o.lock_location[0] = True
732 o.lock_location[1] = True
733 o.lock_location[2] = True
734 o.lock_rotation[0] = True
735 o.lock_rotation[1] = True
736 o.lock_scale[0] = True
737 o.lock_scale[1] = True
738 o.lock_scale[2] = True
739 o.select_set(state=True)
740 context.view_layer.objects.active = o
741 m = o.archipack_material.add()
742 m.category = "door"
743 m.material = self.material
744 d.update(context)
745 # MaterialUtils.add_door_materials(o)
746 o.lock_rotation[0] = True
747 o.lock_rotation[1] = True
748 return o
750 def execute(self, context):
751 if context.mode == "OBJECT":
752 bpy.ops.object.select_all(action="DESELECT")
753 o = self.create(context)
754 o.select_set(state=True)
755 context.view_layer.objects.active = o
756 return {'FINISHED'}
757 else:
758 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
759 return {'CANCELLED'}
762 class ARCHIPACK_OT_select_parent(Operator):
763 bl_idname = "archipack.select_parent"
764 bl_label = "Edit parameters"
765 bl_description = "Edit parameters located on parent"
766 bl_category = 'Archipack'
767 bl_options = {'REGISTER', 'UNDO'}
769 def draw(self, context):
770 layout = self.layout
771 row = layout.row()
772 row.label(text="Use Properties panel (N) to define parms", icon='INFO')
774 def execute(self, context):
775 if context.mode == "OBJECT":
776 if context.active_object is not None and context.active_object.parent is not None:
777 bpy.ops.object.select_all(action="DESELECT")
778 context.active_object.parent.select_set(state=True)
779 context.view_layer.objects.active = context.active_object.parent
780 return {'FINISHED'}
781 else:
782 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
783 return {'CANCELLED'}
786 class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
788 The frame is the door main object
789 parent parametric object
790 create/remove/update her own childs
792 x : FloatProperty(
793 name='Width',
794 min=0.25,
795 default=100.0, precision=2, step=1,
796 unit='LENGTH', subtype='DISTANCE',
797 description='Width', update=update,
799 y : FloatProperty(
800 name='Depth',
801 min=0.1,
802 default=0.20, precision=2, step=1,
803 unit='LENGTH', subtype='DISTANCE',
804 description='Depth', update=update,
806 z : FloatProperty(
807 name='Height',
808 min=0.1,
809 default=2.0, precision=2, step=1,
810 unit='LENGTH', subtype='DISTANCE',
811 description='height', update=update,
813 frame_x : FloatProperty(
814 name='Width',
815 min=0,
816 default=0.1, precision=2, step=1,
817 unit='LENGTH', subtype='DISTANCE',
818 description='frame width', update=update,
820 frame_y : FloatProperty(
821 name='Depth',
822 default=0.03, precision=2, step=1,
823 unit='LENGTH', subtype='DISTANCE',
824 description='frame depth', update=update,
826 direction : IntProperty(
827 name="Direction",
828 min=0,
829 max=1,
830 description="open direction", update=update,
832 door_y : FloatProperty(
833 name='Depth',
834 min=0.001,
835 default=0.02, precision=2, step=1,
836 unit='LENGTH', subtype='DISTANCE',
837 description='depth', update=update,
839 door_offset : FloatProperty(
840 name='Offset',
841 min=0,
842 default=0, precision=2, step=1,
843 unit='LENGTH', subtype='DISTANCE',
844 description='offset', update=update,
846 model : IntProperty(
847 name="Model",
848 min=0,
849 max=3,
850 default=0,
851 description="Model", update=update,
853 n_panels : IntProperty(
854 name="Panels",
855 min=1,
856 max=2,
857 default=1,
858 description="number of panels", update=update
860 chanfer : FloatProperty(
861 name='Bevel',
862 min=0.001,
863 default=0.005, precision=3, step=0.01,
864 unit='LENGTH', subtype='DISTANCE',
865 description='chanfer', update=update_childs,
867 panel_spacing : FloatProperty(
868 name='Spacing',
869 min=0.001,
870 default=0.1, precision=2, step=1,
871 unit='LENGTH', subtype='DISTANCE',
872 description='distance between panels', update=update_childs,
874 panel_bottom : FloatProperty(
875 name='Bottom',
876 min=0.0,
877 default=0.0, precision=2, step=1,
878 unit='LENGTH', subtype='DISTANCE',
879 description='distance from bottom', update=update_childs,
881 panel_border : FloatProperty(
882 name='Border',
883 min=0.001,
884 default=0.2, precision=2, step=1,
885 unit='LENGTH', subtype='DISTANCE',
886 description='distance from border', update=update_childs,
888 panels_x : IntProperty(
889 name="# h",
890 min=1,
891 max=50,
892 default=1,
893 description="panels h", update=update_childs,
895 panels_y : IntProperty(
896 name="# v",
897 min=1,
898 max=50,
899 default=1,
900 description="panels v", update=update_childs,
902 panels_distrib : EnumProperty(
903 name='Distribution',
904 items=(
905 ('REGULAR', 'Regular', '', 0),
906 ('ONE_THIRD', '1/3 2/3', '', 1)
908 default='REGULAR', update=update_childs,
910 handle : EnumProperty(
911 name='Handle',
912 items=(
913 ('NONE', 'No handle', '', 0),
914 ('BOTH', 'Inside and outside', '', 1)
916 default='BOTH', update=update_childs,
918 hole_margin : FloatProperty(
919 name='Hole margin',
920 min=0.0,
921 default=0.1, precision=2, step=1,
922 unit='LENGTH', subtype='DISTANCE',
923 description='how much hole surround wall'
925 flip : BoolProperty(
926 default=False,
927 update=update,
928 description='flip outside and outside material of hole'
930 auto_update : BoolProperty(
931 options={'SKIP_SAVE'},
932 default=True,
933 update=update
936 @property
937 def frame(self):
940 # _____ y0
941 # | |___ y1
942 # x | y3
943 # | |
944 # |_________| y2
946 # x2 x1 x0
947 x0 = 0
948 x1 = -BATTUE
949 x2 = -self.frame_x
950 y0 = max(0.25 * self.door_y + 0.0005, self.y / 2 + self.frame_y)
951 y1 = max(y0 - 0.5 * self.door_y - self.door_offset, -y0 + 0.001)
952 y2 = -y0
953 y3 = 0
954 return DoorPanel(
955 True, # closed
956 [0, 0, 0, 1, 1, 2, 2], # x index
957 [x2, x1, x0],
958 [y2, y3, y0, y0, y1, y1, y2],
959 [0, 1, 1, 1, 1, 0, 0], # material index
960 closed_path=False
963 @property
964 def hole(self):
966 # _____ y0
968 # x y2
970 # |_____ y1
972 # x0
973 x0 = 0
974 y0 = self.y / 2 + self.hole_margin
975 y1 = -y0
976 y2 = 0
977 outside_mat = 0
978 inside_mat = 1
979 if self.flip:
980 outside_mat, inside_mat = inside_mat, outside_mat
981 return DoorPanel(
982 False, # closed
983 [0, 0, 0], # x index
984 [x0],
985 [y1, y2, y0],
986 [outside_mat, inside_mat, inside_mat], # material index
987 closed_path=True,
988 side_cap_front=2,
989 side_cap_back=0 # cap index
992 @property
993 def verts(self):
994 # door inner space
995 v = Vector((0, 0, 0))
996 size = Vector((self.x, self.z, self.y))
997 return self.frame.vertices(16, v, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE')
999 @property
1000 def faces(self):
1001 return self.frame.faces(16, path_type='RECTANGLE')
1003 @property
1004 def matids(self):
1005 return self.frame.mat(16, 0, 0, path_type='RECTANGLE')
1007 @property
1008 def uvs(self):
1009 v = Vector((0, 0, 0))
1010 size = Vector((self.x, self.z, self.y))
1011 return self.frame.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE')
1013 def setup_manipulators(self):
1014 if len(self.manipulators) == 3:
1015 return
1016 s = self.manipulators.add()
1017 s.prop1_name = "x"
1018 s.prop2_name = "x"
1019 s.type_key = "SNAP_SIZE_LOC"
1020 s = self.manipulators.add()
1021 s.prop1_name = "y"
1022 s.prop2_name = "y"
1023 s.type_key = "SNAP_SIZE_LOC"
1024 s = self.manipulators.add()
1025 s.prop1_name = "z"
1026 s.normal = Vector((0, 1, 0))
1028 def remove_childs(self, context, o, to_remove):
1029 for child in o.children:
1030 if to_remove < 1:
1031 return
1032 if archipack_door_panel.filter(child):
1033 self.remove_handle(context, child)
1034 to_remove -= 1
1035 self.unlink_object_from_scene(child)
1036 bpy.data.objects.remove(child, do_unlink=True)
1038 def remove_handle(self, context, o):
1039 handle = self.find_handle(o)
1040 if handle is not None:
1041 self.unlink_object_from_scene(handle)
1042 bpy.data.objects.remove(handle, do_unlink=True)
1044 def create_childs(self, context, o):
1046 n_childs = 0
1047 for child in o.children:
1048 if archipack_door_panel.filter(child):
1049 n_childs += 1
1051 # remove child
1052 if n_childs > self.n_panels:
1053 self.remove_childs(context, o, n_childs - self.n_panels)
1055 if n_childs < 1:
1056 # create one door panel
1057 bpy.ops.archipack.door_panel(
1058 x=self.x,
1059 z=self.z,
1060 door_y=self.door_y,
1061 n_panels=self.n_panels,
1062 direction=self.direction,
1063 material=o.archipack_material[0].material
1065 child = context.active_object
1066 child.parent = o
1067 child.matrix_world = o.matrix_world.copy()
1068 location = self.x / 2 + BATTUE - SPACING
1069 if self.direction == 0:
1070 location = -location
1071 child.location.x = location
1072 child.location.y = self.door_y
1074 if self.n_panels == 2 and n_childs < 2:
1075 # create 2nth door panel
1076 bpy.ops.archipack.door_panel(
1077 x=self.x,
1078 z=self.z,
1079 door_y=self.door_y,
1080 n_panels=self.n_panels,
1081 direction=1 - self.direction,
1082 material=o.archipack_material[0].material
1084 child = context.active_object
1086 child.parent = o
1087 child.matrix_world = o.matrix_world.copy()
1088 location = self.x / 2 + BATTUE - SPACING
1089 if self.direction == 1:
1090 location = -location
1091 child.location.x = location
1092 child.location.y = self.door_y
1094 def find_handle(self, o):
1095 for handle in o.children:
1096 if 'archipack_handle' in handle:
1097 return handle
1098 return None
1100 def get_childs_panels(self, context, o):
1101 return [child for child in o.children if archipack_door_panel.filter(child)]
1103 def _synch_childs(self, context, o, linked, childs):
1105 sub synch childs nodes of linked object
1107 # remove childs not found on source
1108 l_childs = self.get_childs_panels(context, linked)
1109 c_names = [c.data.name for c in childs]
1110 for c in l_childs:
1111 try:
1112 id = c_names.index(c.data.name)
1113 except:
1114 self.remove_handle(context, c)
1115 self.unlink_object_from_scene(c)
1116 bpy.data.objects.remove(c, do_unlink=True)
1118 # children ordering may not be the same, so get the right l_childs order
1119 l_childs = self.get_childs_panels(context, linked)
1120 l_names = [c.data.name for c in l_childs]
1121 order = []
1122 for c in childs:
1123 try:
1124 id = l_names.index(c.data.name)
1125 except:
1126 id = -1
1127 order.append(id)
1129 # add missing childs and update other ones
1130 for i, child in enumerate(childs):
1131 if order[i] < 0:
1132 p = bpy.data.objects.new("DoorPanel", child.data)
1133 self.link_object_to_scene(context, p)
1134 p.lock_location[0] = True
1135 p.lock_location[1] = True
1136 p.lock_location[2] = True
1137 p.lock_rotation[0] = True
1138 p.lock_rotation[1] = True
1139 p.lock_scale[0] = True
1140 p.lock_scale[1] = True
1141 p.lock_scale[2] = True
1142 p.parent = linked
1143 p.matrix_world = linked.matrix_world.copy()
1144 m = p.archipack_material.add()
1145 m.category = 'door'
1146 m.material = o.archipack_material[0].material
1147 else:
1148 p = l_childs[order[i]]
1150 p.location = child.location.copy()
1152 # update handle
1153 handle = self.find_handle(child)
1154 h = self.find_handle(p)
1155 if handle is not None:
1156 if h is None:
1157 h = create_handle(context, p, handle.data)
1158 # MaterialUtils.add_handle_materials(h)
1159 h.location = handle.location.copy()
1160 elif h is not None:
1161 self.unlink_object_from_scene(h)
1162 bpy.data.objects.remove(h, do_unlink=True)
1164 def _synch_hole(self, context, linked, hole):
1165 l_hole = self.find_hole(linked)
1166 if l_hole is None:
1167 l_hole = bpy.data.objects.new("hole", hole.data)
1168 l_hole['archipack_hole'] = True
1169 self.link_object_to_scene(context, l_hole)
1170 l_hole.parent = linked
1171 l_hole.matrix_world = linked.matrix_world.copy()
1172 l_hole.location = hole.location.copy()
1173 else:
1174 l_hole.data = hole.data
1176 def synch_childs(self, context, o):
1178 synch childs nodes of linked objects
1180 bpy.ops.object.select_all(action='DESELECT')
1181 o.select_set(state=True)
1182 context.view_layer.objects.active = o
1183 childs = self.get_childs_panels(context, o)
1184 hole = self.find_hole(o)
1185 bpy.ops.object.select_linked(type='OBDATA')
1186 for linked in context.selected_objects:
1187 if linked != o:
1188 self._synch_childs(context, o, linked, childs)
1189 if hole is not None:
1190 self._synch_hole(context, linked, hole)
1192 def update_childs(self, context, o):
1194 pass params to childrens
1196 childs = self.get_childs_panels(context, o)
1197 n_childs = len(childs)
1198 self.remove_childs(context, o, n_childs - self.n_panels)
1200 childs = self.get_childs_panels(context, o)
1201 n_childs = len(childs)
1202 child_n = 0
1204 # location_y = self.y / 2 + self.frame_y - SPACING
1205 # location_y = min(max(self.door_offset, - location_y), location_y) + self.door_y
1207 location_y = max(0.25 * self.door_y + 0.0005, self.y / 2 + self.frame_y)
1208 location_y = max(location_y - self.door_offset + 0.5 * self.door_y, -location_y + self.door_y + 0.001)
1210 x = self.x / self.n_panels + (3 - self.n_panels) * (BATTUE - SPACING)
1211 y = self.door_y
1212 z = self.z + BATTUE - SPACING
1214 if self.n_panels < 2:
1215 direction = self.direction
1216 else:
1217 direction = 0
1219 for panel in range(self.n_panels):
1220 child_n += 1
1222 if child_n == 1:
1223 handle = self.handle
1224 else:
1225 handle = 'NONE'
1227 if child_n > 1:
1228 direction = 1 - direction
1230 location_x = (2 * direction - 1) * (self.x / 2 + BATTUE - SPACING)
1232 if child_n > n_childs:
1233 bpy.ops.archipack.door_panel(
1234 x=x,
1235 y=y,
1236 z=z,
1237 model=self.model,
1238 direction=direction,
1239 chanfer=self.chanfer,
1240 panel_border=self.panel_border,
1241 panel_bottom=self.panel_bottom,
1242 panel_spacing=self.panel_spacing,
1243 panels_distrib=self.panels_distrib,
1244 panels_x=self.panels_x,
1245 panels_y=self.panels_y,
1246 handle=handle,
1247 material=o.archipack_material[0].material
1249 child = context.active_object
1250 # parenting at 0, 0, 0 before set object matrix_world
1251 # so location remains local from frame
1252 child.parent = o
1253 child.matrix_world = o.matrix_world.copy()
1254 else:
1255 child = childs[child_n - 1]
1256 child.select_set(state=True)
1257 context.view_layer.objects.active = child
1258 props = archipack_door_panel.datablock(child)
1259 if props is not None:
1260 props.x = x
1261 props.y = y
1262 props.z = z
1263 props.model = self.model
1264 props.direction = direction
1265 props.chanfer = self.chanfer
1266 props.panel_border = self.panel_border
1267 props.panel_bottom = self.panel_bottom
1268 props.panel_spacing = self.panel_spacing
1269 props.panels_distrib = self.panels_distrib
1270 props.panels_x = self.panels_x
1271 props.panels_y = self.panels_y
1272 props.handle = handle
1273 props.update(context)
1274 child.location = Vector((location_x, location_y, 0))
1276 def update(self, context, childs_only=False):
1278 # support for "copy to selected"
1279 o = self.find_in_selection(context, self.auto_update)
1281 if o is None:
1282 return
1284 self.setup_manipulators()
1286 if childs_only is False:
1287 bmed.buildmesh(context, o, self.verts, self.faces, self.matids, self.uvs)
1289 self.update_childs(context, o)
1291 if childs_only is False and self.find_hole(o) is not None:
1292 self.interactive_hole(context, o)
1294 # support for instances childs, update at object level
1295 self.synch_childs(context, o)
1297 # setup 3d points for gl manipulators
1298 x, y = 0.5 * self.x, 0.5 * self.y
1299 self.manipulators[0].set_pts([(-x, -y, 0), (x, -y, 0), (1, 0, 0)])
1300 self.manipulators[1].set_pts([(-x, -y, 0), (-x, y, 0), (-1, 0, 0)])
1301 self.manipulators[2].set_pts([(x, -y, 0), (x, -y, self.z), (-1, 0, 0)])
1303 # restore context
1304 self.restore_context(context)
1306 def find_hole(self, o):
1307 for child in o.children:
1308 if 'archipack_hole' in child:
1309 return child
1310 return None
1312 def interactive_hole(self, context, o):
1313 hole_obj = self.find_hole(o)
1314 if hole_obj is None:
1315 m = bpy.data.meshes.new("hole")
1316 hole_obj = bpy.data.objects.new("hole", m)
1317 self.link_object_to_scene(context, hole_obj)
1318 hole_obj['archipack_hole'] = True
1319 hole_obj.parent = o
1320 hole_obj.matrix_world = o.matrix_world.copy()
1322 hole_obj.data.materials.clear()
1323 for mat in o.data.materials:
1324 hole_obj.data.materials.append(mat)
1326 hole = self.hole
1327 v = Vector((0, 0, 0))
1328 offset = Vector((0, -0.001, 0))
1329 size = Vector((self.x + 2 * self.frame_x, self.z + self.frame_x + 0.001, self.y))
1330 verts = hole.vertices(16, offset, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE')
1331 faces = hole.faces(16, path_type='RECTANGLE')
1332 matids = hole.mat(16, 0, 1, path_type='RECTANGLE')
1333 uvs = hole.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE')
1334 bmed.buildmesh(context, hole_obj, verts, faces, matids=matids, uvs=uvs)
1335 return hole_obj
1337 def robust_hole(self, context, tM):
1338 hole = self.hole
1339 m = bpy.data.meshes.new("hole")
1340 o = bpy.data.objects.new("hole", m)
1341 o['archipack_robusthole'] = True
1342 self.link_object_to_scene(context, o)
1343 v = Vector((0, 0, 0))
1344 offset = Vector((0, -0.001, 0))
1345 size = Vector((self.x + 2 * self.frame_x, self.z + self.frame_x + 0.001, self.y))
1346 verts = hole.vertices(16, offset, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE')
1347 verts = [tM @ Vector(v) for v in verts]
1348 faces = hole.faces(16, path_type='RECTANGLE')
1349 matids = hole.mat(16, 0, 1, path_type='RECTANGLE')
1350 uvs = hole.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE')
1351 bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs)
1353 o.select_set(state=True)
1354 context.view_layer.objects.active = o
1355 return o
1358 class ARCHIPACK_PT_door(Panel):
1359 bl_idname = "ARCHIPACK_PT_door"
1360 bl_label = "Door"
1361 bl_space_type = 'VIEW_3D'
1362 bl_region_type = 'UI'
1363 bl_category = 'Archipack'
1365 @classmethod
1366 def poll(cls, context):
1367 return archipack_door.filter(context.active_object)
1369 def draw(self, context):
1370 o = context.active_object
1371 if not archipack_door.filter(o):
1372 return
1373 layout = self.layout
1374 layout.operator('archipack.door_manipulate', icon='VIEW_PAN')
1375 props = archipack_door.datablock(o)
1376 row = layout.row(align=True)
1377 row.operator('archipack.door', text="Refresh", icon='FILE_REFRESH').mode = 'REFRESH'
1378 if o.data.users > 1:
1379 row.operator('archipack.door', text="Make unique", icon='UNLINKED').mode = 'UNIQUE'
1380 row.operator('archipack.door', text="Delete", icon='ERROR').mode = 'DELETE'
1381 box = layout.box()
1382 # box.label(text="Styles")
1383 row = box.row(align=True)
1384 row.operator("archipack.door_preset_menu", text=bpy.types.ARCHIPACK_OT_door_preset_menu.bl_label)
1385 row.operator("archipack.door_preset", text="", icon='ADD')
1386 row.operator("archipack.door_preset", text="", icon='REMOVE').remove_active = True
1387 row = layout.row()
1388 box = row.box()
1389 box.label(text="Size")
1390 box.prop(props, 'x')
1391 box.prop(props, 'y')
1392 box.prop(props, 'z')
1393 box.prop(props, 'door_offset')
1394 row = layout.row()
1395 box = row.box()
1396 row = box.row()
1397 row.label(text="Door")
1398 box.prop(props, 'direction')
1399 box.prop(props, 'n_panels')
1400 box.prop(props, 'door_y')
1401 box.prop(props, 'handle')
1402 row = layout.row()
1403 box = row.box()
1404 row = box.row()
1405 row.label(text="Frame")
1406 row = box.row(align=True)
1407 row.prop(props, 'frame_x')
1408 row.prop(props, 'frame_y')
1409 row = layout.row()
1410 box = row.box()
1411 row = box.row()
1412 row.label(text="Panels")
1413 box.prop(props, 'model')
1414 if props.model > 0:
1415 box.prop(props, 'panels_distrib', text="")
1416 row = box.row(align=True)
1417 row.prop(props, 'panels_x')
1418 if props.panels_distrib == 'REGULAR':
1419 row.prop(props, 'panels_y')
1420 box.prop(props, 'panel_bottom')
1421 box.prop(props, 'panel_spacing')
1422 box.prop(props, 'panel_border')
1423 box.prop(props, 'chanfer')
1426 # ------------------------------------------------------------------
1427 # Define operator class to create object
1428 # ------------------------------------------------------------------
1431 class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
1432 bl_idname = "archipack.door"
1433 bl_label = "Door"
1434 bl_description = "Door"
1435 bl_category = 'Archipack'
1436 bl_options = {'REGISTER', 'UNDO'}
1437 x : FloatProperty(
1438 name='width',
1439 min=0.1,
1440 default=0.80, precision=2,
1441 unit='LENGTH', subtype='DISTANCE',
1442 description='Width'
1444 y : FloatProperty(
1445 name='depth',
1446 min=0.1,
1447 default=0.20, precision=2,
1448 unit='LENGTH', subtype='DISTANCE',
1449 description='Depth'
1451 z : FloatProperty(
1452 name='height',
1453 min=0.1,
1454 default=2.0, precision=2,
1455 unit='LENGTH', subtype='DISTANCE',
1456 description='height'
1458 direction : IntProperty(
1459 name="direction",
1460 min=0,
1461 max=1,
1462 description="open direction"
1464 n_panels : IntProperty(
1465 name="panels",
1466 min=1,
1467 max=2,
1468 default=1,
1469 description="number of panels"
1471 chanfer : FloatProperty(
1472 name='chanfer',
1473 min=0.001,
1474 default=0.005, precision=3,
1475 unit='LENGTH', subtype='DISTANCE',
1476 description='chanfer'
1478 panel_spacing : FloatProperty(
1479 name='spacing',
1480 min=0.001,
1481 default=0.1, precision=2,
1482 unit='LENGTH', subtype='DISTANCE',
1483 description='distance between panels'
1485 panel_bottom : FloatProperty(
1486 name='bottom',
1487 default=0.0, precision=2,
1488 unit='LENGTH', subtype='DISTANCE',
1489 description='distance from bottom'
1491 panel_border : FloatProperty(
1492 name='border',
1493 min=0.001,
1494 default=0.2, precision=2,
1495 unit='LENGTH', subtype='DISTANCE',
1496 description='distance from border'
1498 panels_x : IntProperty(
1499 name="panels h",
1500 min=1,
1501 max=50,
1502 default=1,
1503 description="panels h"
1505 panels_y : IntProperty(
1506 name="panels v",
1507 min=1,
1508 max=50,
1509 default=1,
1510 description="panels v"
1512 panels_distrib : EnumProperty(
1513 name='distribution',
1514 items=(
1515 ('REGULAR', 'Regular', '', 0),
1516 ('ONE_THIRD', '1/3 2/3', '', 1)
1518 default='REGULAR'
1520 handle : EnumProperty(
1521 name='Shape',
1522 items=(
1523 ('NONE', 'No handle', '', 0),
1524 ('BOTH', 'Inside and outside', '', 1)
1526 default='BOTH'
1528 mode : EnumProperty(
1529 items=(
1530 ('CREATE', 'Create', '', 0),
1531 ('DELETE', 'Delete', '', 1),
1532 ('REFRESH', 'Refresh', '', 2),
1533 ('UNIQUE', 'Make unique', '', 3),
1535 default='CREATE'
1538 def create(self, context):
1540 expose only basic params in operator
1541 use object property for other params
1543 m = bpy.data.meshes.new("Door")
1544 o = bpy.data.objects.new("Door", m)
1545 d = m.archipack_door.add()
1546 d.x = self.x
1547 d.y = self.y
1548 d.z = self.z
1549 d.direction = self.direction
1550 d.n_panels = self.n_panels
1551 d.chanfer = self.chanfer
1552 d.panel_border = self.panel_border
1553 d.panel_bottom = self.panel_bottom
1554 d.panel_spacing = self.panel_spacing
1555 d.panels_distrib = self.panels_distrib
1556 d.panels_x = self.panels_x
1557 d.panels_y = self.panels_y
1558 d.handle = self.handle
1559 self.link_object_to_scene(context, o)
1560 o.select_set(state=True)
1561 context.view_layer.objects.active = o
1562 self.add_material(o)
1563 self.load_preset(d)
1564 o.select_set(state=True)
1565 context.view_layer.objects.active = o
1566 return o
1568 def delete(self, context):
1569 o = context.active_object
1570 if archipack_door.filter(o):
1571 bpy.ops.archipack.disable_manipulate()
1572 for child in o.children:
1573 if 'archipack_hole' in child:
1574 self.unlink_object_from_scene(child)
1575 bpy.data.objects.remove(child, do_unlink=True)
1576 elif child.data is not None and 'archipack_door_panel' in child.data:
1577 for handle in child.children:
1578 if 'archipack_handle' in handle:
1579 self.unlink_object_from_scene(handle)
1580 bpy.data.objects.remove(handle, do_unlink=True)
1581 self.unlink_object_from_scene(child)
1582 bpy.data.objects.remove(child, do_unlink=True)
1583 self.unlink_object_from_scene(o)
1584 bpy.data.objects.remove(o, do_unlink=True)
1586 def update(self, context):
1587 o = context.active_object
1588 d = archipack_door.datablock(o)
1589 if d is not None:
1590 d.update(context)
1591 bpy.ops.object.select_linked(type='OBDATA')
1592 for linked in context.selected_objects:
1593 if linked != o:
1594 archipack_door.datablock(linked).update(context)
1595 bpy.ops.object.select_all(action="DESELECT")
1596 o.select_set(state=True)
1597 context.view_layer.objects.active = o
1599 def unique(self, context):
1600 act = context.active_object
1601 sel = context.selected_objects[:]
1602 bpy.ops.object.select_all(action="DESELECT")
1603 for o in sel:
1604 if archipack_door.filter(o):
1605 o.select_set(state=True)
1606 for child in o.children:
1607 if 'archipack_hole' in child or (child.data is not None and
1608 'archipack_door_panel' in child.data):
1609 child.hide_select = False
1610 child.select_set(state=True)
1611 if len(context.selected_objects) > 0:
1612 bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True,
1613 obdata=True, material=False, texture=False, animation=False)
1614 for child in context.selected_objects:
1615 if 'archipack_hole' in child:
1616 child.hide_select = True
1617 bpy.ops.object.select_all(action="DESELECT")
1618 context.view_layer.objects.active = act
1619 for o in sel:
1620 o.select_set(state=True)
1622 def execute(self, context):
1623 if context.mode == "OBJECT":
1624 if self.mode == 'CREATE':
1625 bpy.ops.object.select_all(action="DESELECT")
1626 o = self.create(context)
1627 o.location = bpy.context.scene.cursor.location
1628 o.select_set(state=True)
1629 context.view_layer.objects.active = o
1630 self.manipulate()
1631 elif self.mode == 'DELETE':
1632 self.delete(context)
1633 elif self.mode == 'REFRESH':
1634 self.update(context)
1635 elif self.mode == 'UNIQUE':
1636 self.unique(context)
1637 return {'FINISHED'}
1638 else:
1639 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1640 return {'CANCELLED'}
1643 class ARCHIPACK_OT_door_draw(ArchipackDrawTool, Operator):
1644 bl_idname = "archipack.door_draw"
1645 bl_label = "Draw Doors"
1646 bl_description = "Draw Doors over walls"
1647 bl_category = 'Archipack'
1648 bl_options = {'REGISTER', 'UNDO'}
1650 filepath : StringProperty(default="")
1651 feedback = None
1652 stack = []
1653 object_name = ""
1655 @classmethod
1656 def poll(cls, context):
1657 return True
1659 def draw(self, context):
1660 layout = self.layout
1661 row = layout.row()
1662 row.label(text="Use Properties panel (N) to define parms", icon='INFO')
1664 def draw_callback(self, _self, context):
1665 self.feedback.draw(context)
1667 def add_object(self, context, event):
1668 o = context.active_object
1669 bpy.ops.object.select_all(action="DESELECT")
1671 if archipack_door.filter(o):
1673 o.select_set(state=True)
1674 context.view_layer.objects.active = o
1676 if event.shift:
1677 bpy.ops.archipack.door(mode="UNIQUE")
1679 new_w = o.copy()
1680 new_w.data = o.data
1681 self.link_object_to_scene(context, new_w)
1682 # instance subs
1683 for child in o.children:
1684 if "archipack_hole" not in child:
1685 new_c = child.copy()
1686 new_c.data = child.data
1687 new_c.parent = new_w
1688 self.link_object_to_scene(context, new_c)
1689 # dup handle if any
1690 for c in child.children:
1691 new_h = c.copy()
1692 new_h.data = c.data
1693 new_h.parent = new_c
1694 self.link_object_to_scene(context, new_h)
1696 o = new_w
1697 o.select_set(state=True)
1698 context.view_layer.objects.active = o
1700 else:
1701 bpy.ops.archipack.door(auto_manipulate=False, filepath=self.filepath)
1702 o = context.active_object
1704 self.object_name = o.name
1706 bpy.ops.archipack.generate_hole('INVOKE_DEFAULT')
1707 o.select_set(state=True)
1708 context.view_layer.objects.active = o
1710 def modal(self, context, event):
1712 context.area.tag_redraw()
1713 o = context.scene.objects.get(self.object_name.strip())
1714 if o is None:
1715 return {'FINISHED'}
1717 d = archipack_door.datablock(o)
1718 hole = None
1720 if d is not None:
1721 hole = d.find_hole(o)
1723 # hide hole from raycast
1724 if hole is not None:
1725 o.hide_viewport = True
1726 hole.hide_viewport = True
1728 res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event)
1730 if hole is not None:
1731 o.hide_viewport = False
1732 hole.hide_viewport = False
1734 if res and d is not None:
1735 o.matrix_world = tM
1736 if d.y != wall.data.archipack_wall2[0].width:
1737 d.y = wall.data.archipack_wall2[0].width
1739 if event.value == 'PRESS':
1741 if event.type in {'C'}:
1742 bpy.ops.archipack.door(mode='DELETE')
1743 self.feedback.disable()
1744 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
1745 bpy.ops.archipack.door_preset_menu(
1746 'INVOKE_DEFAULT',
1747 preset_operator="archipack.door_draw")
1748 return {'FINISHED'}
1750 if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}:
1751 if wall is not None:
1752 o.select_set(state=True)
1753 context.view_layer.objects.active = wall
1754 wall.select_set(state=True)
1755 if bpy.ops.archipack.single_boolean.poll():
1756 bpy.ops.archipack.single_boolean()
1757 wall.select_set(state=False)
1758 # o must be a door here
1759 if d is not None:
1760 context.view_layer.objects.active = o
1761 self.stack.append(o)
1762 self.add_object(context, event)
1763 context.active_object.matrix_world = tM
1764 return {'RUNNING_MODAL'}
1766 # prevent selection of other object
1767 if event.type in {'RIGHTMOUSE'}:
1768 return {'RUNNING_MODAL'}
1770 if self.keymap.check(event, self.keymap.undo) or (
1771 event.type in {'BACK_SPACE'} and event.value == 'RELEASE'
1773 if len(self.stack) > 0:
1774 last = self.stack.pop()
1775 context.view_layer.objects.active = last
1776 bpy.ops.archipack.door(mode="DELETE")
1777 context.view_layer.objects.active = o
1778 return {'RUNNING_MODAL'}
1780 if event.value == 'RELEASE':
1782 if event.type in {'ESC', 'RIGHTMOUSE'}:
1783 bpy.ops.archipack.door(mode='DELETE')
1784 self.feedback.disable()
1785 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
1786 return {'FINISHED'}
1788 return {'PASS_THROUGH'}
1790 def invoke(self, context, event):
1792 if context.mode == "OBJECT":
1793 o = None
1794 self.stack = []
1795 self.keymap = Keymaps(context)
1796 # exit manipulate_mode if any
1797 bpy.ops.archipack.disable_manipulate()
1798 # invoke with alt pressed will use current object as basis for linked copy
1799 if self.filepath == '' and archipack_door.filter(context.active_object):
1800 o = context.active_object
1801 context.view_layer.objects.active = None
1802 bpy.ops.object.select_all(action="DESELECT")
1803 if o is not None:
1804 o.select_set(state=True)
1805 context.view_layer.objects.active = o
1806 self.add_object(context, event)
1807 self.feedback = FeedbackPanel()
1808 self.feedback.instructions(context, "Draw a door", "Click & Drag over a wall", [
1809 ('LEFTCLICK, RET, SPACE, ENTER', 'Create a door'),
1810 ('BACKSPACE, CTRL+Z', 'undo last'),
1811 ('C', 'Choose another door'),
1812 ('SHIFT', 'Make independent copy'),
1813 ('RIGHTCLICK or ESC', 'exit')
1815 self.feedback.enable()
1816 args = (self, context)
1818 self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL')
1819 context.window_manager.modal_handler_add(self)
1820 return {'RUNNING_MODAL'}
1821 else:
1822 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1823 return {'CANCELLED'}
1826 # ------------------------------------------------------------------
1827 # Define operator class to manipulate object
1828 # ------------------------------------------------------------------
1831 class ARCHIPACK_OT_door_manipulate(Operator):
1832 bl_idname = "archipack.door_manipulate"
1833 bl_label = "Manipulate"
1834 bl_description = "Manipulate"
1835 bl_options = {'REGISTER', 'UNDO'}
1837 @classmethod
1838 def poll(self, context):
1839 return archipack_door.filter(context.active_object)
1841 def invoke(self, context, event):
1842 d = archipack_door.datablock(context.active_object)
1843 d.manipulable_invoke(context)
1844 return {'FINISHED'}
1847 # ------------------------------------------------------------------
1848 # Define operator class to load / save presets
1849 # ------------------------------------------------------------------
1852 class ARCHIPACK_OT_door_preset_menu(PresetMenuOperator, Operator):
1853 bl_description = "Show Doors presets"
1854 bl_idname = "archipack.door_preset_menu"
1855 bl_label = "Door Presets"
1856 preset_subdir = "archipack_door"
1859 class ARCHIPACK_OT_door_preset(ArchipackPreset, Operator):
1860 """Add a Door Preset"""
1861 bl_idname = "archipack.door_preset"
1862 bl_label = "Add Door Preset"
1863 preset_menu = "ARCHIPACK_OT_door_preset_menu"
1865 @property
1866 def blacklist(self):
1867 return ['manipulators']
1870 def register():
1871 bpy.utils.register_class(archipack_door_panel)
1872 Mesh.archipack_door_panel = CollectionProperty(type=archipack_door_panel)
1873 bpy.utils.register_class(ARCHIPACK_PT_door_panel)
1874 bpy.utils.register_class(ARCHIPACK_OT_door_panel)
1875 bpy.utils.register_class(ARCHIPACK_OT_select_parent)
1876 bpy.utils.register_class(archipack_door)
1877 Mesh.archipack_door = CollectionProperty(type=archipack_door)
1878 bpy.utils.register_class(ARCHIPACK_OT_door_preset_menu)
1879 bpy.utils.register_class(ARCHIPACK_PT_door)
1880 bpy.utils.register_class(ARCHIPACK_OT_door)
1881 bpy.utils.register_class(ARCHIPACK_OT_door_preset)
1882 bpy.utils.register_class(ARCHIPACK_OT_door_draw)
1883 bpy.utils.register_class(ARCHIPACK_OT_door_manipulate)
1886 def unregister():
1887 bpy.utils.unregister_class(archipack_door_panel)
1888 del Mesh.archipack_door_panel
1889 bpy.utils.unregister_class(ARCHIPACK_PT_door_panel)
1890 bpy.utils.unregister_class(ARCHIPACK_OT_door_panel)
1891 bpy.utils.unregister_class(ARCHIPACK_OT_select_parent)
1892 bpy.utils.unregister_class(archipack_door)
1893 del Mesh.archipack_door
1894 bpy.utils.unregister_class(ARCHIPACK_OT_door_preset_menu)
1895 bpy.utils.unregister_class(ARCHIPACK_PT_door)
1896 bpy.utils.unregister_class(ARCHIPACK_OT_door)
1897 bpy.utils.unregister_class(ARCHIPACK_OT_door_preset)
1898 bpy.utils.unregister_class(ARCHIPACK_OT_door_draw)
1899 bpy.utils.unregister_class(ARCHIPACK_OT_door_manipulate)