Sun position: remove unused prop in HDRI mode
[blender-addons.git] / archipack / archipack_wall2.py
blob4469bc9a627135f49805b98532b3c718dd2758b7
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 # ----------------------------------------------------------
27 import bpy
28 import bmesh
30 import time
32 from bpy.types import Operator, PropertyGroup, Mesh, Panel
33 from bpy.props import (
34 FloatProperty, BoolProperty, IntProperty, StringProperty,
35 FloatVectorProperty, CollectionProperty, EnumProperty
37 from .bmesh_utils import BmeshEdit as bmed
38 from mathutils import Vector, Matrix
39 from mathutils.geometry import (
40 interpolate_bezier
42 from math import sin, cos, pi, atan2
43 from .archipack_manipulator import (
44 Manipulable, archipack_manipulator,
45 GlPolygon, GlPolyline,
46 GlLine, GlText, FeedbackPanel
48 from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool
49 from .archipack_2d import Line, Arc
50 from .archipack_snap import snap_point
51 from .archipack_keymaps import Keymaps
53 import logging
54 logger = logging.getLogger("archipack")
57 class Wall():
58 def __init__(self, wall_z, z, t, flip):
59 self.z = z
60 self.wall_z = wall_z
61 self.t = t
62 self.flip = flip
63 self.z_step = len(z)
65 def set_offset(self, offset, last=None):
66 """
67 Offset line and compute intersection point
68 between segments
69 """
70 self.line = self.make_offset(offset, last)
72 def get_z(self, t):
73 t0 = self.t[0]
74 z0 = self.z[0]
75 for i in range(1, self.z_step):
76 t1 = self.t[i]
77 z1 = self.z[i]
78 if t <= t1:
79 return z0 + (t - t0) / (t1 - t0) * (z1 - z0)
80 t0, z0 = t1, z1
81 return self.z[-1]
83 def make_faces(self, i, f, faces):
84 if i < self.n_step:
85 # 1 3 5 7
86 # 0 2 4 6
87 if self.flip:
88 faces.append((f + 2, f, f + 1, f + 3))
89 else:
90 faces.append((f, f + 2, f + 3, f + 1))
92 def p3d(self, verts, t):
93 x, y = self.lerp(t)
94 z = self.wall_z + self.get_z(t)
95 verts.append((x, y, 0))
96 verts.append((x, y, z))
98 def make_wall(self, i, verts, faces):
99 t = self.t_step[i]
100 f = len(verts)
101 self.p3d(verts, t)
102 self.make_faces(i, f, faces)
104 def make_hole(self, i, verts, z0):
105 t = self.t_step[i]
106 x, y = self.line.lerp(t)
107 verts.append((x, y, z0))
109 def straight_wall(self, a0, length, wall_z, z, t):
110 r = self.straight(length).rotate(a0)
111 return StraightWall(r.p, r.v, wall_z, z, t, self.flip)
113 def curved_wall(self, a0, da, radius, wall_z, z, t):
114 n = self.normal(1).rotate(a0).scale(radius)
115 if da < 0:
116 n.v = -n.v
117 a0 = n.angle
118 c = n.p - n.v
119 return CurvedWall(c, radius, a0, da, wall_z, z, t, self.flip)
122 class StraightWall(Wall, Line):
123 def __init__(self, p, v, wall_z, z, t, flip):
124 Line.__init__(self, p, v)
125 Wall.__init__(self, wall_z, z, t, flip)
127 def param_t(self, step_angle):
128 self.t_step = self.t
129 self.n_step = len(self.t) - 1
132 class CurvedWall(Wall, Arc):
133 def __init__(self, c, radius, a0, da, wall_z, z, t, flip):
134 Arc.__init__(self, c, radius, a0, da)
135 Wall.__init__(self, wall_z, z, t, flip)
137 def param_t(self, step_angle):
138 t_step, n_step = self.steps_by_angle(step_angle)
139 self.t_step = list(sorted([i * t_step for i in range(1, n_step)] + self.t))
140 self.n_step = len(self.t_step) - 1
143 class WallGenerator():
144 def __init__(self, parts):
145 self.last_type = 'NONE'
146 self.segs = []
147 self.parts = parts
148 self.faces_type = 'NONE'
149 self.closed = False
151 def set_offset(self, offset):
152 last = None
153 for i, seg in enumerate(self.segs):
154 seg.set_offset(offset, last)
155 last = seg.line
157 if self.closed:
158 w = self.segs[-1]
159 if len(self.segs) > 1:
160 w.line = w.make_offset(offset, self.segs[-2].line)
162 p1 = self.segs[0].line.p1
163 self.segs[0].line = self.segs[0].make_offset(offset, w.line)
164 self.segs[0].line.p1 = p1
166 def add_part(self, part, wall_z, flip):
168 # TODO:
169 # refactor this part (height manipulators)
170 manip_index = []
171 if len(self.segs) < 1:
172 s = None
173 z = [part.z[0]]
174 manip_index.append(0)
175 else:
176 s = self.segs[-1]
177 z = [s.z[-1]]
179 t_cur = 0
180 z_last = part.n_splits - 1
181 t = [0]
183 for i in range(part.n_splits):
184 t_try = t[-1] + part.t[i]
185 if t_try == t_cur:
186 continue
187 if t_try <= 1:
188 t_cur = t_try
189 t.append(t_cur)
190 z.append(part.z[i])
191 manip_index.append(i)
192 else:
193 z_last = i
194 break
196 if t_cur < 1:
197 t.append(1)
198 manip_index.append(z_last)
199 z.append(part.z[z_last])
201 # start a new wall
202 if s is None:
203 if part.type == 'S_WALL':
204 p = Vector((0, 0))
205 v = part.length * Vector((cos(part.a0), sin(part.a0)))
206 s = StraightWall(p, v, wall_z, z, t, flip)
207 elif part.type == 'C_WALL':
208 c = -part.radius * Vector((cos(part.a0), sin(part.a0)))
209 s = CurvedWall(c, part.radius, part.a0, part.da, wall_z, z, t, flip)
210 else:
211 if part.type == 'S_WALL':
212 s = s.straight_wall(part.a0, part.length, wall_z, z, t)
213 elif part.type == 'C_WALL':
214 s = s.curved_wall(part.a0, part.da, part.radius, wall_z, z, t)
216 self.segs.append(s)
217 self.last_type = part.type
219 return manip_index
221 def close(self, closed):
222 # Make last segment implicit closing one
223 if closed:
224 part = self.parts[-1]
225 w = self.segs[-1]
226 dp = self.segs[0].p0 - self.segs[-1].p0
227 if "C_" in part.type:
228 dw = (w.p1 - w.p0)
229 w.r = part.radius / dw.length * dp.length
230 # angle pt - p0 - angle p0 p1
231 da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x)
232 a0 = w.a0 + da
233 if a0 > pi:
234 a0 -= 2 * pi
235 if a0 < -pi:
236 a0 += 2 * pi
237 w.a0 = a0
238 else:
239 w.v = dp
241 def locate_manipulators(self, side):
243 for i, wall in enumerate(self.segs):
245 manipulators = self.parts[i].manipulators
247 p0 = wall.p0.to_3d()
248 p1 = wall.p1.to_3d()
250 # angle from last to current segment
251 if i > 0:
253 if i < len(self.segs) - 1:
254 manipulators[0].type_key = 'ANGLE'
255 else:
256 manipulators[0].type_key = 'DUMB_ANGLE'
258 v0 = self.segs[i - 1].straight(-side, 1).v.to_3d()
259 v1 = wall.straight(side, 0).v.to_3d()
260 manipulators[0].set_pts([p0, v0, v1])
262 if type(wall).__name__ == "StraightWall":
263 # segment length
264 manipulators[1].type_key = 'SIZE'
265 manipulators[1].prop1_name = "length"
266 manipulators[1].set_pts([p0, p1, (side, 0, 0)])
267 else:
268 # segment radius + angle
269 # scale to fix overlap with drag
270 v0 = side * (wall.p0 - wall.c).to_3d()
271 v1 = side * (wall.p1 - wall.c).to_3d()
272 scale = 1.0 + (0.5 / v0.length)
273 manipulators[1].type_key = 'ARC_ANGLE_RADIUS'
274 manipulators[1].prop1_name = "da"
275 manipulators[1].prop2_name = "radius"
276 manipulators[1].set_pts([wall.c.to_3d(), scale * v0, scale * v1])
278 # snap manipulator, don't change index !
279 manipulators[2].set_pts([p0, p1, (1, 0, 0)])
281 # dumb, segment index
282 z = Vector((0, 0, 0.75 * wall.wall_z))
283 manipulators[3].set_pts([p0 + z, p1 + z, (1, 0, 0)])
285 def make_wall(self, step_angle, flip, closed, verts, faces):
287 # swap manipulators so they always face outside
288 side = 1
289 if flip:
290 side = -1
292 # Make last segment implicit closing one
294 nb_segs = len(self.segs) - 1
295 if closed:
296 nb_segs += 1
298 for i, wall in enumerate(self.segs):
300 wall.param_t(step_angle)
301 if i < nb_segs:
302 for j in range(wall.n_step + 1):
303 wall.make_wall(j, verts, faces)
304 else:
305 # last segment
306 for j in range(wall.n_step):
307 continue
308 # print("%s" % (wall.n_step))
309 # wall.make_wall(j, verts, faces)
311 self.locate_manipulators(side)
313 def rotate(self, idx_from, a):
315 apply rotation to all following segs
317 self.segs[idx_from].rotate(a)
318 ca = cos(a)
319 sa = sin(a)
320 rM = Matrix([
321 [ca, -sa],
322 [sa, ca]
324 # rotation center
325 p0 = self.segs[idx_from].p0
326 for i in range(idx_from + 1, len(self.segs)):
327 seg = self.segs[i]
328 seg.rotate(a)
329 dp = rM @ (seg.p0 - p0)
330 seg.translate(dp)
332 def translate(self, idx_from, dp):
334 apply translation to all following segs
336 self.segs[idx_from].p1 += dp
337 for i in range(idx_from + 1, len(self.segs)):
338 self.segs[i].translate(dp)
340 def change_coordsys(self, fromTM, toTM):
342 move shape fromTM into toTM coordsys
344 dp = (toTM.inverted() @ fromTM.translation).to_2d()
345 da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d())
346 ca = cos(da)
347 sa = sin(da)
348 rM = Matrix([
349 [ca, -sa],
350 [sa, ca]
352 for s in self.segs:
353 tp = (rM @ s.p0) - s.p0 + dp
354 s.rotate(da)
355 s.translate(tp)
357 def draw(self, context):
358 for seg in self.segs:
359 seg.draw(context, render=False)
361 def debug(self, verts):
362 for wall in self.segs:
363 for i in range(33):
364 x, y = wall.lerp(i / 32)
365 verts.append((x, y, 0))
367 def make_surface(self, o, verts, height):
368 bm = bmesh.new()
369 for v in verts:
370 bm.verts.new(v)
371 bm.verts.ensure_lookup_table()
372 for i in range(1, len(verts)):
373 bm.edges.new((bm.verts[i - 1], bm.verts[i]))
374 bm.edges.new((bm.verts[-1], bm.verts[0]))
375 bm.edges.ensure_lookup_table()
376 bmesh.ops.contextual_create(bm, geom=bm.edges)
377 geom = bm.faces[:]
378 bmesh.ops.solidify(bm, geom=geom, thickness=height)
379 bm.to_mesh(o.data)
380 bm.free()
382 def make_hole(self, context, hole_obj, d):
384 offset = -0.5 * (1 - d.x_offset) * d.width
386 z0 = 0.1
387 self.set_offset(offset)
389 nb_segs = len(self.segs) - 1
390 if d.closed:
391 nb_segs += 1
393 verts = []
394 for i, wall in enumerate(self.segs):
395 wall.param_t(d.step_angle)
396 if i < nb_segs:
397 for j in range(wall.n_step + 1):
398 wall.make_hole(j, verts, -z0)
400 self.make_surface(hole_obj, verts, d.z + z0)
403 def update(self, context):
404 self.update(context)
407 def update_childs(self, context):
408 self.update(context, update_childs=True, manipulable_refresh=True)
411 def update_manipulators(self, context):
412 self.update(context, manipulable_refresh=True)
415 def update_t_part(self, context):
417 Make this wall a T child of parent wall
418 orient child so y points inside wall and x follow wall segment
419 set child a0 according
421 o = self.find_in_selection(context)
422 if o is not None:
424 # w is parent wall
425 w = context.scene.objects.get(self.t_part.strip())
426 wd = archipack_wall2.datablock(w)
428 if wd is not None:
429 og = self.get_generator()
430 self.setup_childs(o, og)
432 bpy.ops.object.select_all(action="DESELECT")
434 # 5 cases here:
435 # 1 No parents at all
436 # 2 o has parent
437 # 3 w has parent
438 # 4 o and w share same parent already
439 # 5 o and w doesn't share parent
440 link_to_parent = False
442 # when both walls do have a reference point, we may delete one of them
443 to_delete = None
445 # select childs and make parent reference point active
446 if w.parent is None:
447 # Either link to o.parent or create new parent
448 link_to_parent = True
449 if o.parent is None:
450 # create a reference point and make it active
451 x, y, z = w.bound_box[0]
452 context.scene.cursor.location = w.matrix_world @ Vector((x, y, z))
453 # fix issue #9
454 context.view_layer.objects.active = o
455 bpy.ops.archipack.reference_point()
456 o.select_set(state=True)
457 else:
458 context.view_layer.objects.active = o.parent
459 w.select_set(state=True)
460 else:
461 # w has parent
462 if o.parent is not w.parent:
463 link_to_parent = True
464 context.view_layer.objects.active = w.parent
465 o.select_set(state=True)
466 if o.parent is not None:
467 # store o.parent to delete it
468 to_delete = o.parent
469 for c in o.parent.children:
470 if c is not o:
471 c.hide_select = False
472 c.select_set(state=True)
474 parent = context.active_object
476 dmax = 2 * wd.width
478 wg = wd.get_generator()
480 otM = o.matrix_world
481 orM = Matrix([
482 otM[0].to_2d(),
483 otM[1].to_2d()
486 wtM = w.matrix_world
487 wrM = Matrix([
488 wtM[0].to_2d(),
489 wtM[1].to_2d()
492 # dir in absolute world coordsys
493 dir = orM @ og.segs[0].straight(1, 0).v
495 # pt in w coordsys
496 pos = otM.translation
497 pt = (wtM.inverted() @ pos).to_2d()
499 for wall_idx, wall in enumerate(wg.segs):
500 res, dist, t = wall.point_sur_segment(pt)
501 # outside is on the right side of the wall
502 # p1
503 # |-- x
504 # p0
506 # NOTE:
507 # rotation here is wrong when w has not parent while o has parent
509 if res and t > 0 and t < 1 and abs(dist) < dmax:
510 x = wrM @ wall.straight(1, t).v
511 y = wrM @ wall.normal(t).v.normalized()
512 self.parts[0].a0 = dir.angle_signed(x)
513 o.matrix_world = Matrix([
514 [x.x, -y.x, 0, pos.x],
515 [x.y, -y.y, 0, pos.y],
516 [0, 0, 1, pos.z],
517 [0, 0, 0, 1]
519 break
521 if link_to_parent and bpy.ops.archipack.parent_to_reference.poll():
522 bpy.ops.archipack.parent_to_reference('INVOKE_DEFAULT')
524 # update generator to take new rotation in account
525 # use this to relocate windows on wall after reparenting
526 g = self.get_generator()
527 self.relocate_childs(context, o, g)
529 # hide holes from select
530 for c in parent.children:
531 if "archipack_hybridhole" in c:
532 c.hide_select = True
534 # delete unneeded reference point
535 if to_delete is not None:
536 bpy.ops.object.select_all(action="DESELECT")
537 to_delete.select_set(state=True)
538 context.view_layer.objects.active = to_delete
539 if bpy.ops.object.delete.poll():
540 bpy.ops.object.delete(use_global=False)
542 elif self.t_part != "":
543 self.t_part = ""
545 self.restore_context(context)
548 def set_splits(self, value):
549 if self.n_splits != value:
550 self.auto_update = False
551 self._set_t(value)
552 self.auto_update = True
553 self.n_splits = value
554 return None
557 def get_splits(self):
558 return self.n_splits
561 def update_type(self, context):
563 d = self.find_datablock_in_selection(context)
565 if d is not None and d.auto_update:
567 d.auto_update = False
568 idx = 0
569 for i, part in enumerate(d.parts):
570 if part == self:
571 idx = i
572 break
573 a0 = 0
574 if idx > 0:
575 g = d.get_generator()
576 w0 = g.segs[idx - 1]
577 a0 = w0.straight(1).angle
578 if "C_" in self.type:
579 w = w0.straight_wall(self.a0, self.length, d.z, self.z, self.t)
580 else:
581 w = w0.curved_wall(self.a0, self.da, self.radius, d.z, self.z, self.t)
582 else:
583 if "C_" in self.type:
584 p = Vector((0, 0))
585 v = self.length * Vector((cos(self.a0), sin(self.a0)))
586 w = StraightWall(p, v, d.z, self.z, self.t, d.flip)
587 a0 = pi / 2
588 else:
589 c = -self.radius * Vector((cos(self.a0), sin(self.a0)))
590 w = CurvedWall(c, self.radius, self.a0, pi, d.z, self.z, self.t, d.flip)
592 # w0 - w - w1
593 if d.closed and idx == d.n_parts:
594 dp = - w.p0
595 else:
596 dp = w.p1 - w.p0
598 if "C_" in self.type:
599 self.radius = 0.5 * dp.length
600 self.da = pi
601 a0 = atan2(dp.y, dp.x) - pi / 2 - a0
602 else:
603 self.length = dp.length
604 a0 = atan2(dp.y, dp.x) - a0
606 if a0 > pi:
607 a0 -= 2 * pi
608 if a0 < -pi:
609 a0 += 2 * pi
610 self.a0 = a0
612 if idx + 1 < d.n_parts:
613 # adjust rotation of next part
614 part1 = d.parts[idx + 1]
615 if "C_" in self.type:
616 a0 = part1.a0 - pi / 2
617 else:
618 a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x)
620 if a0 > pi:
621 a0 -= 2 * pi
622 if a0 < -pi:
623 a0 += 2 * pi
624 part1.a0 = a0
626 d.auto_update = True
629 class archipack_wall2_part(PropertyGroup):
630 type : EnumProperty(
631 items=(
632 ('S_WALL', 'Straight', '', 0),
633 ('C_WALL', 'Curved', '', 1)
635 default='S_WALL',
636 update=update_type
638 length : FloatProperty(
639 name="Length",
640 min=0.01,
641 default=2.0,
642 unit='LENGTH', subtype='DISTANCE',
643 update=update
645 radius : FloatProperty(
646 name="Radius",
647 min=0.5,
648 default=0.7,
649 unit='LENGTH', subtype='DISTANCE',
650 update=update
652 a0 : FloatProperty(
653 name="Start angle",
654 min=-pi,
655 max=pi,
656 default=pi / 2,
657 subtype='ANGLE', unit='ROTATION',
658 update=update
660 da : FloatProperty(
661 name="Angle",
662 min=-pi,
663 max=pi,
664 default=pi / 2,
665 subtype='ANGLE', unit='ROTATION',
666 update=update
668 z : FloatVectorProperty(
669 name="Height",
670 default=[
671 0, 0, 0, 0, 0, 0, 0, 0,
672 0, 0, 0, 0, 0, 0, 0, 0,
673 0, 0, 0, 0, 0, 0, 0, 0,
674 0, 0, 0, 0, 0, 0, 0
676 size=31,
677 update=update
679 t : FloatVectorProperty(
680 name="Position",
681 min=0,
682 max=1,
683 default=[
684 1, 1, 1, 1, 1, 1, 1, 1,
685 1, 1, 1, 1, 1, 1, 1, 1,
686 1, 1, 1, 1, 1, 1, 1, 1,
687 1, 1, 1, 1, 1, 1, 1
689 size=31,
690 update=update
692 splits : IntProperty(
693 name="Splits",
694 default=1,
695 min=1,
696 max=31,
697 get=get_splits, set=set_splits
699 n_splits : IntProperty(
700 name="Splits",
701 default=1,
702 min=1,
703 max=31,
704 update=update
706 auto_update : BoolProperty(default=True)
707 manipulators : CollectionProperty(type=archipack_manipulator)
708 # ui related
709 expand : BoolProperty(default=False)
711 def _set_t(self, splits):
712 t = 1 / splits
713 for i in range(splits):
714 self.t[i] = t
716 def find_datablock_in_selection(self, context):
718 find witch selected object this instance belongs to
719 provide support for "copy to selected"
721 selected = context.selected_objects[:]
722 for o in selected:
723 props = archipack_wall2.datablock(o)
724 if props:
725 for part in props.parts:
726 if part == self:
727 return props
728 return None
730 def update(self, context, manipulable_refresh=False):
731 if not self.auto_update:
732 return
733 props = self.find_datablock_in_selection(context)
734 if props is not None:
735 props.update(context, manipulable_refresh)
737 def draw(self, layout, context, index):
739 row = layout.row(align=True)
740 if self.expand:
741 row.prop(self, 'expand', icon="TRIA_DOWN", text="Part " + str(index + 1), emboss=False)
742 else:
743 row.prop(self, 'expand', icon="TRIA_RIGHT", text="Part " + str(index + 1), emboss=False)
745 row.prop(self, "type", text="")
747 if self.expand:
748 row = layout.row(align=True)
749 row.operator("archipack.wall2_insert", text="Split").index = index
750 row.operator("archipack.wall2_remove", text="Remove").index = index
751 if self.type == 'C_WALL':
752 row = layout.row()
753 row.prop(self, "radius")
754 row = layout.row()
755 row.prop(self, "da")
756 else:
757 row = layout.row()
758 row.prop(self, "length")
759 row = layout.row()
760 row.prop(self, "a0")
761 row = layout.row()
762 row.prop(self, "splits")
763 for split in range(self.n_splits):
764 row = layout.row()
765 row.prop(self, "z", text="alt", index=split)
766 row.prop(self, "t", text="pos", index=split)
769 class archipack_wall2_child(PropertyGroup):
770 # Size Loc
771 # Delta Loc
772 manipulators : CollectionProperty(type=archipack_manipulator)
773 child_name : StringProperty()
774 wall_idx : IntProperty()
775 pos : FloatVectorProperty(subtype='XYZ')
776 flip : BoolProperty(default=False)
778 def get_child(self, context):
779 d = None
780 child = context.scene.objects.get(self.child_name.strip())
781 if child is not None and child.data is not None:
782 cd = child.data
783 if 'archipack_window' in cd:
784 d = cd.archipack_window[0]
785 elif 'archipack_door' in cd:
786 d = cd.archipack_door[0]
787 return child, d
790 class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
791 parts : CollectionProperty(type=archipack_wall2_part)
792 n_parts : IntProperty(
793 name="Parts",
794 min=1,
795 max=1024,
796 default=1, update=update_manipulators
798 step_angle : FloatProperty(
799 description="Curved parts segmentation",
800 name="Step angle",
801 min=1 / 180 * pi,
802 max=pi,
803 default=6 / 180 * pi,
804 subtype='ANGLE', unit='ROTATION',
805 update=update
807 width : FloatProperty(
808 name="Width",
809 min=0.01,
810 default=0.2,
811 unit='LENGTH', subtype='DISTANCE',
812 update=update
814 z : FloatProperty(
815 name='Height',
816 min=0.1,
817 default=2.7, precision=2,
818 unit='LENGTH', subtype='DISTANCE',
819 description='height', update=update,
821 x_offset : FloatProperty(
822 name="Offset",
823 min=-1, max=1,
824 default=-1, precision=2, step=1,
825 update=update
827 radius : FloatProperty(
828 name="Radius",
829 min=0.5,
830 default=0.7,
831 unit='LENGTH', subtype='DISTANCE',
832 update=update
834 da : FloatProperty(
835 name="Angle",
836 min=-pi,
837 max=pi,
838 default=pi / 2,
839 subtype='ANGLE', unit='ROTATION',
840 update=update
842 flip : BoolProperty(
843 name="Flip",
844 default=False,
845 update=update_childs
847 closed : BoolProperty(
848 default=False,
849 name="Close",
850 update=update_manipulators
852 auto_update : BoolProperty(
853 options={'SKIP_SAVE'},
854 default=True,
855 update=update_manipulators
857 realtime : BoolProperty(
858 options={'SKIP_SAVE'},
859 default=True,
860 name="Real Time",
861 description="Relocate childs in realtime"
863 # dumb manipulators to show sizes between childs
864 childs_manipulators : CollectionProperty(type=archipack_manipulator)
865 # store to manipulate windows and doors
866 childs : CollectionProperty(type=archipack_wall2_child)
867 t_part : StringProperty(
868 name="Parent wall",
869 description="This part will follow parent when set",
870 default="",
871 update=update_t_part
874 def insert_part(self, context, o, where):
875 self.manipulable_disable(context)
876 self.auto_update = False
877 # the part we do split
878 part_0 = self.parts[where]
879 part_0.length /= 2
880 part_0.da /= 2
881 self.parts.add()
882 part_1 = self.parts[len(self.parts) - 1]
883 part_1.type = part_0.type
884 part_1.length = part_0.length
885 part_1.da = part_0.da
886 part_1.a0 = 0
887 # move after current one
888 self.parts.move(len(self.parts) - 1, where + 1)
889 self.n_parts += 1
890 # re-eval childs location
891 g = self.get_generator()
892 self.setup_childs(o, g)
894 self.setup_manipulators()
895 self.auto_update = True
897 def add_part(self, context, length):
898 self.manipulable_disable(context)
899 self.auto_update = False
900 p = self.parts.add()
901 p.length = length
902 self.parts.move(len(self.parts) - 1, self.n_parts)
903 self.n_parts += 1
904 self.setup_manipulators()
905 self.auto_update = True
906 return self.parts[self.n_parts - 1]
908 def remove_part(self, context, o, where):
909 self.manipulable_disable(context)
910 self.auto_update = False
911 # preserve shape
912 # using generator
913 if where > 0:
914 g = self.get_generator()
915 w = g.segs[where - 1]
916 w.p1 = g.segs[where].p1
918 if where + 1 < self.n_parts:
919 self.parts[where + 1].a0 = g.segs[where + 1].delta_angle(w)
921 part = self.parts[where - 1]
923 if "C_" in part.type:
924 part.radius = w.r
925 else:
926 part.length = w.length
928 if where > 1:
929 part.a0 = w.delta_angle(g.segs[where - 2])
930 else:
931 part.a0 = w.straight(1, 0).angle
933 self.parts.remove(where)
934 self.n_parts -= 1
936 # re-eval child location
937 g = self.get_generator()
938 self.setup_childs(o, g)
940 # fix snap manipulators index
941 self.setup_manipulators()
942 self.auto_update = True
944 def get_generator(self):
945 # print("get_generator")
946 g = WallGenerator(self.parts)
947 for part in self.parts:
948 g.add_part(part, self.z, self.flip)
949 g.close(self.closed)
950 return g
952 def update_parts(self, o, update_childs=False):
953 # print("update_parts")
954 # remove rows
955 # NOTE:
956 # n_parts+1
957 # as last one is end point of last segment or closing one
958 row_change = False
959 for i in range(len(self.parts), self.n_parts + 1, -1):
960 row_change = True
961 self.parts.remove(i - 1)
963 # add rows
964 for i in range(len(self.parts), self.n_parts + 1):
965 row_change = True
966 self.parts.add()
968 self.setup_manipulators()
970 g = self.get_generator()
972 if o is not None and (row_change or update_childs):
973 self.setup_childs(o, g)
975 return g
977 def setup_manipulators(self):
979 if len(self.manipulators) == 0:
980 # make manipulators selectable
981 s = self.manipulators.add()
982 s.prop1_name = "width"
983 s = self.manipulators.add()
984 s.prop1_name = "n_parts"
985 s.type_key = 'COUNTER'
986 s = self.manipulators.add()
987 s.prop1_name = "z"
988 s.normal = (0, 1, 0)
990 if self.t_part != "" and len(self.manipulators) < 4:
991 s = self.manipulators.add()
992 s.prop1_name = "x"
993 s.type_key = 'DELTA_LOC'
995 for i in range(self.n_parts + 1):
996 p = self.parts[i]
997 n_manips = len(p.manipulators)
998 if n_manips < 1:
999 s = p.manipulators.add()
1000 s.type_key = "ANGLE"
1001 s.prop1_name = "a0"
1002 if n_manips < 2:
1003 s = p.manipulators.add()
1004 s.type_key = "SIZE"
1005 s.prop1_name = "length"
1006 if n_manips < 3:
1007 s = p.manipulators.add()
1008 s.type_key = 'WALL_SNAP'
1009 s.prop1_name = str(i)
1010 s.prop2_name = 'z'
1011 if n_manips < 4:
1012 s = p.manipulators.add()
1013 s.type_key = 'DUMB_STRING'
1014 s.prop1_name = str(i + 1)
1015 p.manipulators[2].prop1_name = str(i)
1016 p.manipulators[3].prop1_name = str(i + 1)
1018 def interpolate_bezier(self, pts, wM, p0, p1, resolution):
1019 if resolution == 0:
1020 pts.append(wM @ p0.co.to_3d())
1021 else:
1022 v = (p1.co - p0.co).normalized()
1023 d1 = (p0.handle_right - p0.co).normalized()
1024 d2 = (p1.co - p1.handle_left).normalized()
1025 if d1 == v and d2 == v:
1026 pts.append(wM @ p0.co.to_3d())
1027 else:
1028 seg = interpolate_bezier(wM @ p0.co,
1029 wM @ p0.handle_right,
1030 wM @ p1.handle_left,
1031 wM @ p1.co,
1032 resolution + 1)
1033 for i in range(resolution):
1034 pts.append(seg[i].to_3d())
1036 def is_cw(self, pts):
1037 p0 = pts[0]
1038 d = 0
1039 for p in pts[1:]:
1040 d += (p.x * p0.y - p.y * p0.x)
1041 p0 = p
1042 return d > 0
1044 def from_spline(self, wM, resolution, spline):
1045 pts = []
1046 if spline.type == 'POLY':
1047 pts = [wM @ p.co.to_3d() for p in spline.points]
1048 if spline.use_cyclic_u:
1049 pts.append(pts[0])
1050 elif spline.type == 'BEZIER':
1051 points = spline.bezier_points
1052 for i in range(1, len(points)):
1053 p0 = points[i - 1]
1054 p1 = points[i]
1055 self.interpolate_bezier(pts, wM, p0, p1, resolution)
1056 if spline.use_cyclic_u:
1057 p0 = points[-1]
1058 p1 = points[0]
1059 self.interpolate_bezier(pts, wM, p0, p1, resolution)
1060 pts.append(pts[0])
1061 else:
1062 pts.append(wM @ points[-1].co)
1064 if self.is_cw(pts):
1065 pts = list(reversed(pts))
1067 self.auto_update = False
1068 self.from_points(pts, spline.use_cyclic_u)
1069 self.auto_update = True
1071 def from_points(self, pts, closed):
1073 self.n_parts = len(pts) - 1
1075 if closed:
1076 self.n_parts -= 1
1078 self.update_parts(None)
1080 p0 = pts.pop(0)
1081 a0 = 0
1082 for i, p1 in enumerate(pts):
1083 dp = p1 - p0
1084 da = atan2(dp.y, dp.x) - a0
1085 if da > pi:
1086 da -= 2 * pi
1087 if da < -pi:
1088 da += 2 * pi
1089 if i >= len(self.parts):
1090 print("Too many pts for parts")
1091 break
1092 p = self.parts[i]
1093 p.length = dp.to_2d().length
1094 p.dz = dp.z
1095 p.a0 = da
1096 a0 += da
1097 p0 = p1
1099 self.closed = closed
1101 def reverse(self, context, o):
1103 g = self.get_generator()
1105 self.auto_update = False
1107 pts = [seg.p0.to_3d() for seg in g.segs]
1109 if not self.closed:
1110 g.segs.pop()
1112 g_segs = list(reversed(g.segs))
1114 last_seg = None
1116 for i, seg in enumerate(g_segs):
1118 s = seg.oposite
1119 if "Curved" in type(seg).__name__:
1120 self.parts[i].type = "C_WALL"
1121 self.parts[i].radius = s.r
1122 self.parts[i].da = s.da
1123 else:
1124 self.parts[i].type = "S_WALL"
1125 self.parts[i].length = s.length
1127 self.parts[i].a0 = s.delta_angle(last_seg)
1129 last_seg = s
1131 if self.closed:
1132 pts.append(pts[0])
1134 pts = list(reversed(pts))
1136 # location wont change for closed walls
1137 if not self.closed:
1138 dp = pts[0] - pts[-1]
1139 # pre-translate as dp is in local coordsys
1140 o.matrix_world = o.matrix_world @ Matrix([
1141 [1, 0, 0, dp.x],
1142 [0, 1, 0, dp.y],
1143 [0, 0, 1, 0],
1144 [0, 0, 0, 1],
1147 # self.from_points(pts, self.closed)
1149 g = self.get_generator()
1151 self.setup_childs(o, g)
1152 self.auto_update = True
1154 # flip does trigger relocate and keep childs orientation
1155 self.flip = not self.flip
1157 def update(self, context, manipulable_refresh=False, update_childs=False):
1159 o = self.find_in_selection(context, self.auto_update)
1161 if o is None:
1162 return
1164 if manipulable_refresh:
1165 # prevent crash by removing all manipulators refs to datablock before changes
1166 self.manipulable_disable(context)
1168 verts = []
1169 faces = []
1171 g = self.update_parts(o, update_childs)
1172 # print("make_wall")
1173 g.make_wall(self.step_angle, self.flip, self.closed, verts, faces)
1175 if self.closed:
1176 f = len(verts)
1177 if self.flip:
1178 faces.append((0, f - 2, f - 1, 1))
1179 else:
1180 faces.append((f - 2, 0, 1, f - 1))
1182 # print("buildmesh")
1183 bmed.buildmesh(context, o, verts, faces, matids=None, uvs=None, weld=True)
1185 side = 1
1186 if self.flip:
1187 side = -1
1188 # Width
1189 offset = side * (0.5 * self.x_offset) * self.width
1190 self.manipulators[0].set_pts([
1191 g.segs[0].sized_normal(0, offset + 0.5 * side * self.width).v.to_3d(),
1192 g.segs[0].sized_normal(0, offset - 0.5 * side * self.width).v.to_3d(),
1193 (-side, 0, 0)
1196 # Parts COUNTER
1197 self.manipulators[1].set_pts([g.segs[-2].lerp(1.1).to_3d(),
1198 g.segs[-2].lerp(1.1 + 0.5 / g.segs[-2].length).to_3d(),
1199 (-side, 0, 0)
1202 # Height
1203 self.manipulators[2].set_pts([
1204 (0, 0, 0),
1205 (0, 0, self.z),
1206 (-1, 0, 0)
1207 ], normal=g.segs[0].straight(side, 0).v.to_3d())
1209 if self.t_part != "":
1210 t = 0.3 / g.segs[0].length
1211 self.manipulators[3].set_pts([
1212 g.segs[0].sized_normal(t, 0.1).p1.to_3d(),
1213 g.segs[0].sized_normal(t, -0.1).p1.to_3d(),
1214 (1, 0, 0)
1217 if self.realtime:
1218 # update child location and size
1219 self.relocate_childs(context, o, g)
1220 # store gl points
1221 self.update_childs(context, o, g)
1222 else:
1223 bpy.ops.archipack.wall2_throttle_update(name=o.name)
1225 modif = o.modifiers.get('Wall')
1226 if modif is None:
1227 modif = o.modifiers.new('Wall', 'SOLIDIFY')
1228 modif.use_quality_normals = True
1229 modif.use_even_offset = True
1230 modif.material_offset_rim = 2
1231 modif.material_offset = 1
1233 modif.thickness = self.width
1234 modif.offset = self.x_offset
1236 if manipulable_refresh:
1237 # print("manipulable_refresh=True")
1238 self.manipulable_refresh = True
1240 self.restore_context(context)
1242 # manipulable children objects like windows and doors
1243 def child_partition(self, array, begin, end):
1244 pivot = begin
1245 for i in range(begin + 1, end + 1):
1246 # wall idx
1247 if array[i][1] < array[begin][1]:
1248 pivot += 1
1249 array[i], array[pivot] = array[pivot], array[i]
1250 # param t on the wall
1251 elif array[i][1] == array[begin][1] and array[i][4] <= array[begin][4]:
1252 pivot += 1
1253 array[i], array[pivot] = array[pivot], array[i]
1254 array[pivot], array[begin] = array[begin], array[pivot]
1255 return pivot
1257 def sort_child(self, array, begin=0, end=None):
1258 # print("sort_child")
1259 if end is None:
1260 end = len(array) - 1
1262 def _quicksort(array, begin, end):
1263 if begin >= end:
1264 return
1265 pivot = self.child_partition(array, begin, end)
1266 _quicksort(array, begin, pivot - 1)
1267 _quicksort(array, pivot + 1, end)
1268 return _quicksort(array, begin, end)
1270 def add_child(self, name, wall_idx, pos, flip):
1271 # print("add_child %s %s" % (name, wall_idx))
1272 c = self.childs.add()
1273 c.child_name = name
1274 c.wall_idx = wall_idx
1275 c.pos = pos
1276 c.flip = flip
1277 m = c.manipulators.add()
1278 m.type_key = 'DELTA_LOC'
1279 m.prop1_name = "x"
1280 m = c.manipulators.add()
1281 m.type_key = 'SNAP_SIZE_LOC'
1282 m.prop1_name = "x"
1283 m.prop2_name = "x"
1285 def setup_childs(self, o, g):
1287 Store childs
1288 create manipulators
1289 call after a boolean oop
1291 # tim = time.time()
1292 self.childs.clear()
1293 self.childs_manipulators.clear()
1294 if o.parent is None:
1295 return
1296 wall_with_childs = [0 for i in range(self.n_parts + 1)]
1297 relocate = []
1298 dmax = 2 * self.width
1300 wtM = o.matrix_world
1301 wrM = Matrix([
1302 wtM[0].to_2d(),
1303 wtM[1].to_2d()
1305 witM = wtM.inverted()
1307 for child in o.parent.children:
1308 # filter allowed childs
1309 cd = child.data
1310 wd = archipack_wall2.datablock(child)
1311 if (child != o and cd is not None and (
1312 'archipack_window' in cd or
1313 'archipack_door' in cd or (
1314 wd is not None and
1315 o.name in wd.t_part
1319 # setup on T linked walls
1320 if wd is not None:
1321 wg = wd.get_generator()
1322 wd.setup_childs(child, wg)
1324 ctM = child.matrix_world
1325 crM = Matrix([
1326 ctM[0].to_2d(),
1327 ctM[1].to_2d()
1330 # pt in w coordsys
1331 pos = ctM.translation
1332 pt = (witM @ pos).to_2d()
1334 for wall_idx, wall in enumerate(g.segs):
1335 # may be optimized with a bound check
1336 res, dist, t = wall.point_sur_segment(pt)
1337 # outside is on the right side of the wall
1338 # p1
1339 # |-- x
1340 # p0
1341 if res and t > 0 and t < 1 and abs(dist) < dmax:
1342 # dir in world coordsys
1343 dir = wrM @ wall.sized_normal(t, 1).v
1344 wall_with_childs[wall_idx] = 1
1345 m = self.childs_manipulators.add()
1346 m.type_key = 'DUMB_SIZE'
1347 # always make window points outside
1348 if "archipack_window" in cd:
1349 flip = self.flip
1350 else:
1351 dir_y = crM @ Vector((0, -1))
1352 # let door orient where user want
1353 flip = (dir_y - dir).length > 0.5
1354 # store z in wall space
1355 relocate.append((
1356 child.name,
1357 wall_idx,
1358 (t * wall.length, dist, (witM @ pos).z),
1359 flip,
1361 break
1363 self.sort_child(relocate)
1364 for child in relocate:
1365 name, wall_idx, pos, flip, t = child
1366 self.add_child(name, wall_idx, pos, flip)
1368 # add a dumb size from last child to end of wall segment
1369 for i in range(sum(wall_with_childs)):
1370 m = self.childs_manipulators.add()
1371 m.type_key = 'DUMB_SIZE'
1372 # print("setup_childs:%1.4f" % (time.time()-tim))
1374 def relocate_childs(self, context, o, g):
1376 Move and resize childs after wall edition
1378 # print("relocate_childs")
1379 # tim = time.time()
1380 w = -self.x_offset * self.width
1381 if self.flip:
1382 w = -w
1383 tM = o.matrix_world
1384 for child in self.childs:
1385 c, d = child.get_child(context)
1386 if c is None:
1387 continue
1388 t = child.pos.x / g.segs[child.wall_idx].length
1389 n = g.segs[child.wall_idx].sized_normal(t, 1)
1390 rx, ry = -n.v
1391 rx, ry = ry, -rx
1392 if child.flip:
1393 rx, ry = -rx, -ry
1395 if d is not None:
1396 # print("change flip:%s width:%s" % (d.flip != child.flip, d.y != self.width))
1397 if d.y != self.width or d.flip != child.flip:
1398 c.select_set(state=True)
1399 d.auto_update = False
1400 d.flip = child.flip
1401 d.y = self.width
1402 d.auto_update = True
1403 c.select_set(state=False)
1404 x, y = n.p - (0.5 * w * n.v)
1405 else:
1406 x, y = n.p - (child.pos.y * n.v)
1408 context.view_layer.objects.active = o
1409 # preTranslate
1410 c.matrix_world = tM @ Matrix([
1411 [rx, -ry, 0, x],
1412 [ry, rx, 0, y],
1413 [0, 0, 1, child.pos.z],
1414 [0, 0, 0, 1]
1417 # Update T linked wall's childs
1418 if archipack_wall2.filter(c):
1419 d = archipack_wall2.datablock(c)
1420 cg = d.get_generator()
1421 d.relocate_childs(context, c, cg)
1423 # print("relocate_childs:%1.4f" % (time.time()-tim))
1425 def update_childs(self, context, o, g):
1427 setup gl points for childs
1429 # print("update_childs")
1431 if o.parent is None:
1432 return
1434 # swap manipulators so they always face outside
1435 manip_side = 1
1436 if self.flip:
1437 manip_side = -1
1439 itM = o.matrix_world.inverted()
1440 m_idx = 0
1441 for wall_idx, wall in enumerate(g.segs):
1442 p0 = wall.lerp(0)
1443 wall_has_childs = False
1444 for child in self.childs:
1445 if child.wall_idx == wall_idx:
1446 c, d = child.get_child(context)
1447 if d is not None:
1448 # child is either a window or a door
1449 wall_has_childs = True
1450 dt = 0.5 * d.x / wall.length
1451 pt = (itM @ c.matrix_world.translation).to_2d()
1452 res, y, t = wall.point_sur_segment(pt)
1453 child.pos = (wall.length * t, y, child.pos.z)
1454 p1 = wall.lerp(t - dt)
1455 # dumb size between childs
1456 self.childs_manipulators[m_idx].set_pts([
1457 (p0.x, p0.y, 0),
1458 (p1.x, p1.y, 0),
1459 (manip_side * 0.5, 0, 0)])
1460 m_idx += 1
1461 x, y = 0.5 * d.x, -self.x_offset * 0.5 * d.y
1463 if child.flip:
1464 side = -manip_side
1465 else:
1466 side = manip_side
1468 # delta loc
1469 child.manipulators[0].set_pts([(-x, side * -y, 0), (x, side * -y, 0), (side, 0, 0)])
1470 # loc size
1471 child.manipulators[1].set_pts([
1472 (-x, side * -y, 0),
1473 (x, side * -y, 0),
1474 (0.5 * side, 0, 0)])
1475 p0 = wall.lerp(t + dt)
1476 p1 = wall.lerp(1)
1477 if wall_has_childs:
1478 # dub size after all childs
1479 self.childs_manipulators[m_idx].set_pts([
1480 (p0.x, p0.y, 0),
1481 (p1.x, p1.y, 0),
1482 (manip_side * 0.5, 0, 0)])
1483 m_idx += 1
1485 def manipulate_childs(self, context):
1487 setup child manipulators
1489 # print("manipulate_childs")
1490 n_parts = self.n_parts
1491 if self.closed:
1492 n_parts += 1
1494 for wall_idx in range(n_parts):
1495 for child in self.childs:
1496 if child.wall_idx == wall_idx:
1497 c, d = child.get_child(context)
1498 if d is not None:
1499 # delta loc
1500 self.manip_stack.append(child.manipulators[0].setup(context, c, d, self.manipulate_callback))
1501 # loc size
1502 self.manip_stack.append(child.manipulators[1].setup(context, c, d, self.manipulate_callback))
1504 def manipulate_callback(self, context, o=None, manipulator=None):
1505 found = False
1506 if o.parent is not None:
1507 for c in o.parent.children:
1508 if (archipack_wall2.datablock(c) == self):
1509 context.view_layer.objects.active = c
1510 found = True
1511 break
1512 if found:
1513 self.manipulable_manipulate(context, manipulator=manipulator)
1515 def manipulable_manipulate(self, context, event=None, manipulator=None):
1516 type_name = type(manipulator).__name__
1517 # print("manipulable_manipulate %s" % (type_name))
1518 if type_name in [
1519 'DeltaLocationManipulator',
1520 'SizeLocationManipulator',
1521 'SnapSizeLocationManipulator'
1523 # update manipulators pos of childs
1524 o = context.active_object
1525 if o.parent is None:
1526 return
1527 g = self.get_generator()
1528 itM = o.matrix_world.inverted() @ o.parent.matrix_world
1529 for child in self.childs:
1530 c, d = child.get_child(context)
1531 if d is not None:
1532 wall = g.segs[child.wall_idx]
1533 pt = (itM @ c.location).to_2d()
1534 res, d, t = wall.point_sur_segment(pt)
1535 child.pos = (t * wall.length, d, child.pos.z)
1536 # update childs manipulators
1537 self.update_childs(context, o, g)
1539 def manipulable_move_t_part(self, context, o=None, manipulator=None):
1541 Callback for t_parts childs
1543 type_name = type(manipulator).__name__
1544 # print("manipulable_manipulate %s" % (type_name))
1545 if type_name in [
1546 'DeltaLocationManipulator'
1548 # update manipulators pos of childs
1549 if archipack_wall2.datablock(o) != self:
1550 return
1551 g = self.get_generator()
1552 # update childs
1553 self.relocate_childs(context, o, g)
1555 def manipulable_release(self, context):
1557 Override with action to do on mouse release
1558 eg: big update
1560 return
1562 def manipulable_setup(self, context):
1563 # print("manipulable_setup")
1564 self.manipulable_disable(context)
1565 o = context.active_object
1567 # setup childs manipulators
1568 self.manipulate_childs(context)
1569 n_parts = self.n_parts
1570 if self.closed:
1571 n_parts += 1
1573 # update manipulators on version change
1574 self.setup_manipulators()
1576 for i, part in enumerate(self.parts):
1578 if i < n_parts:
1579 if i > 0:
1580 # start angle
1581 self.manip_stack.append(part.manipulators[0].setup(context, o, part))
1583 # length / radius + angle
1584 self.manip_stack.append(part.manipulators[1].setup(context, o, part))
1585 # segment index
1586 self.manip_stack.append(part.manipulators[3].setup(context, o, self))
1588 # snap point
1589 self.manip_stack.append(part.manipulators[2].setup(context, o, self))
1591 # height as per segment will be here when done
1593 # width + counter
1594 for m in self.manipulators:
1595 self.manip_stack.append(m.setup(context, o, self, self.manipulable_move_t_part))
1597 # dumb between childs
1598 for m in self.childs_manipulators:
1599 self.manip_stack.append(m.setup(context, o, self))
1601 def manipulable_exit(self, context):
1603 Override with action to do when modal exit
1605 return
1607 def manipulable_invoke(self, context):
1609 call this in operator invoke()
1611 # print("manipulable_invoke")
1612 if self.manipulate_mode:
1613 self.manipulable_disable(context)
1614 return False
1616 # self.manip_stack = []
1617 o = context.active_object
1618 g = self.get_generator()
1619 # setup childs manipulators
1620 self.setup_childs(o, g)
1621 # store gl points
1622 self.update_childs(context, o, g)
1623 # don't do anything ..
1624 # self.manipulable_release(context)
1625 # self.manipulate_mode = True
1626 self.manipulable_setup(context)
1627 self.manipulate_mode = True
1629 self._manipulable_invoke(context)
1631 return True
1633 def find_roof(self, context, o, g):
1634 tM = o.matrix_world
1635 up = Vector((0, 0, 1))
1636 for seg in g.segs:
1637 p = tM @ seg.p0.to_3d()
1638 p.z = 0.01
1639 # prevent self intersect
1640 o.hide_viewport = True
1641 res, pos, normal, face_index, r, matrix_world = context.scene.ray_cast(
1642 depsgraph=context.view_layer.depsgraph,
1643 origin=p,
1644 direction=up)
1646 o.hide_viewport = False
1647 # print("res:%s" % res)
1648 if res and r.data is not None and "archipack_roof" in r.data:
1649 return r, r.data.archipack_roof[0]
1651 return None, None
1654 # Update throttle (hack)
1655 # use 2 globals to store a timer and state of update_action
1656 # Use to update floor boolean on edit
1657 update_timer = None
1658 update_timer_updating = False
1659 throttle_delay = 0.5
1660 throttle_start = 0
1663 class ARCHIPACK_OT_wall2_throttle_update(Operator):
1664 bl_idname = "archipack.wall2_throttle_update"
1665 bl_label = "Update childs with a delay"
1667 name : StringProperty()
1669 def modal(self, context, event):
1670 global update_timer_updating
1671 if event.type == 'TIMER' and not update_timer_updating:
1672 # can't rely on TIMER event as another timer may run
1673 if time.time() - throttle_start > throttle_delay:
1674 update_timer_updating = True
1675 o = context.scene.objects.get(self.name.strip())
1676 if o is not None:
1677 m = o.modifiers.get("AutoBoolean")
1678 if m is not None:
1679 o.hide_viewport = False
1680 # o.display_type = 'TEXTURED'
1681 # m.show_viewport = True
1683 return self.cancel(context)
1684 return {'PASS_THROUGH'}
1686 def execute(self, context):
1687 global update_timer
1688 global update_timer_updating
1689 global throttle_delay
1690 global throttle_start
1691 if update_timer is not None:
1692 context.window_manager.event_timer_remove(update_timer)
1693 if update_timer_updating:
1694 return {'CANCELLED'}
1695 # reset update_timer so it only occurs once 0.1s after last action
1696 throttle_start = time.time()
1697 update_timer = context.window_manager.event_timer_add(throttle_delay, context.window)
1698 return {'CANCELLED'}
1699 throttle_start = time.time()
1700 update_timer_updating = False
1701 context.window_manager.modal_handler_add(self)
1702 update_timer = context.window_manager.event_timer_add(throttle_delay, context.window)
1703 return {'RUNNING_MODAL'}
1705 def cancel(self, context):
1706 global update_timer
1707 context.window_manager.event_timer_remove(update_timer)
1708 update_timer = None
1709 return {'CANCELLED'}
1712 class ARCHIPACK_PT_wall2(Panel):
1713 bl_idname = "ARCHIPACK_PT_wall2"
1714 bl_label = "Wall"
1715 bl_space_type = 'VIEW_3D'
1716 bl_region_type = 'UI'
1717 bl_category = 'Archipack'
1719 def draw(self, context):
1720 prop = archipack_wall2.datablock(context.object)
1721 if prop is None:
1722 return
1723 layout = self.layout
1724 row = layout.row(align=True)
1725 row.operator("archipack.wall2_manipulate", icon='VIEW_PAN')
1726 # row = layout.row(align=True)
1727 # row.prop(prop, 'realtime')
1728 box = layout.box()
1729 box.prop(prop, 'n_parts')
1730 box.prop(prop, 'step_angle')
1731 box.prop(prop, 'width')
1732 box.prop(prop, 'z')
1733 box.prop(prop, 'flip')
1734 box.prop(prop, 'x_offset')
1735 row = layout.row()
1736 row.prop(prop, "closed")
1737 row = layout.row()
1738 row.prop_search(prop, "t_part", context.scene, "objects", text="T link", icon='OBJECT_DATAMODE')
1739 layout.operator("archipack.wall2_reverse", icon='FILE_REFRESH')
1740 row = layout.row(align=True)
1741 row.operator("archipack.wall2_fit_roof")
1742 # row.operator("archipack.wall2_fit_roof", text="Inside").inside = True
1743 n_parts = prop.n_parts
1744 if prop.closed:
1745 n_parts += 1
1746 for i, part in enumerate(prop.parts):
1747 if i < n_parts:
1748 box = layout.box()
1749 part.draw(box, context, i)
1751 @classmethod
1752 def poll(cls, context):
1753 return archipack_wall2.filter(context.active_object)
1756 # ------------------------------------------------------------------
1757 # Define operator class to create object
1758 # ------------------------------------------------------------------
1761 class ARCHIPACK_OT_wall2(ArchipackCreateTool, Operator):
1762 bl_idname = "archipack.wall2"
1763 bl_label = "Wall"
1764 bl_description = "Create a Wall"
1765 bl_category = 'Archipack'
1766 bl_options = {'REGISTER', 'UNDO'}
1768 def create(self, context):
1769 m = bpy.data.meshes.new("Wall")
1770 o = bpy.data.objects.new("Wall", m)
1771 d = m.archipack_wall2.add()
1772 d.manipulable_selectable = True
1773 self.link_object_to_scene(context, o)
1774 o.select_set(state=True)
1775 # around 12 degree
1776 m.auto_smooth_angle = 0.20944
1777 context.view_layer.objects.active = o
1778 self.load_preset(d)
1779 self.add_material(o)
1780 return o
1782 def execute(self, context):
1783 if context.mode == "OBJECT":
1784 bpy.ops.object.select_all(action="DESELECT")
1785 o = self.create(context)
1786 o.location = bpy.context.scene.cursor.location
1787 o.select_set(state=True)
1788 context.view_layer.objects.active = o
1789 self.manipulate()
1790 return {'FINISHED'}
1791 else:
1792 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1793 return {'CANCELLED'}
1796 class ARCHIPACK_OT_wall2_from_curve(Operator):
1797 bl_idname = "archipack.wall2_from_curve"
1798 bl_label = "Wall curve"
1799 bl_description = "Create a wall from a curve"
1800 bl_category = 'Archipack'
1801 bl_options = {'REGISTER', 'UNDO'}
1803 auto_manipulate : BoolProperty(default=True)
1805 @classmethod
1806 def poll(self, context):
1807 return context.active_object is not None and context.active_object.type == 'CURVE'
1809 def create(self, context):
1810 curve = context.active_object
1811 for spline in curve.data.splines:
1812 bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate)
1813 o = context.view_layer.objects.active
1814 d = archipack_wall2.datablock(o)
1815 d.from_spline(curve.matrix_world, 12, spline)
1816 if spline.type == 'POLY':
1817 pt = spline.points[0].co
1818 elif spline.type == 'BEZIER':
1819 pt = spline.bezier_points[0].co
1820 else:
1821 pt = Vector((0, 0, 0))
1822 # pretranslate
1823 o.matrix_world = curve.matrix_world @ Matrix([
1824 [1, 0, 0, pt.x],
1825 [0, 1, 0, pt.y],
1826 [0, 0, 1, pt.z],
1827 [0, 0, 0, 1]
1829 return o
1831 # -----------------------------------------------------
1832 # Execute
1833 # -----------------------------------------------------
1834 def execute(self, context):
1835 if context.mode == "OBJECT":
1836 bpy.ops.object.select_all(action="DESELECT")
1837 o = self.create(context)
1838 if o is not None:
1839 o.select_set(state=True)
1840 context.view_layer.objects.active = o
1841 return {'FINISHED'}
1842 else:
1843 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1844 return {'CANCELLED'}
1847 class ARCHIPACK_OT_wall2_from_slab(Operator):
1848 bl_idname = "archipack.wall2_from_slab"
1849 bl_label = "->Wall"
1850 bl_description = "Create a wall from a slab"
1851 bl_category = 'Archipack'
1852 bl_options = {'REGISTER', 'UNDO'}
1854 auto_manipulate : BoolProperty(default=True)
1856 @classmethod
1857 def poll(self, context):
1858 o = context.active_object
1859 return o is not None and o.data is not None and 'archipack_slab' in o.data
1861 def create(self, context):
1862 slab = context.active_object
1863 wd = slab.data.archipack_slab[0]
1864 bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate)
1865 o = context.view_layer.objects.active
1866 d = archipack_wall2.datablock(o)
1867 d.auto_update = False
1868 d.parts.clear()
1869 d.n_parts = wd.n_parts - 1
1870 d.closed = True
1871 for part in wd.parts:
1872 p = d.parts.add()
1873 if "S_" in part.type:
1874 p.type = "S_WALL"
1875 else:
1876 p.type = "C_WALL"
1877 p.length = part.length
1878 p.radius = part.radius
1879 p.da = part.da
1880 p.a0 = part.a0
1881 o.select_set(state=True)
1882 context.view_layer.objects.active = o
1883 d.auto_update = True
1884 # pretranslate
1885 o.matrix_world = slab.matrix_world.copy()
1887 bpy.ops.object.select_all(action='DESELECT')
1888 # parenting childs to wall reference point
1889 if o.parent is None:
1890 x, y, z = o.bound_box[0]
1891 context.scene.cursor.location = o.matrix_world @ Vector((x, y, z))
1892 # fix issue #9
1893 context.view_layer.objects.active = o
1894 bpy.ops.archipack.reference_point()
1895 else:
1896 o.parent.select_set(state=True)
1897 context.view_layer.objects.active = o.parent
1898 o.select_set(state=True)
1899 slab.select_set(state=True)
1900 bpy.ops.archipack.parent_to_reference()
1901 o.parent.select_set(state=False)
1902 return o
1904 # -----------------------------------------------------
1905 # Execute
1906 # -----------------------------------------------------
1907 def execute(self, context):
1908 if context.mode == "OBJECT":
1909 bpy.ops.object.select_all(action="DESELECT")
1910 o = self.create(context)
1911 o.select_set(state=True)
1912 context.view_layer.objects.active = o
1913 return {'FINISHED'}
1914 else:
1915 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1916 return {'CANCELLED'}
1919 class ARCHIPACK_OT_wall2_fit_roof(Operator):
1920 bl_idname = "archipack.wall2_fit_roof"
1921 bl_label = "Fit roof"
1922 bl_description = "Fit roof"
1923 bl_category = 'Archipack'
1924 bl_options = {'REGISTER', 'UNDO'}
1926 inside : BoolProperty(default=False)
1928 @classmethod
1929 def poll(self, context):
1930 return archipack_wall2.filter(context.active_object)
1932 def execute(self, context):
1933 o = context.active_object
1934 d = archipack_wall2.datablock(o)
1935 g = d.get_generator()
1936 r, rd = d.find_roof(context, o, g)
1937 if rd is not None:
1938 d.setup_childs(o, g)
1939 rd.make_wall_fit(context, r, o, self.inside)
1940 return {'FINISHED'}
1942 # ------------------------------------------------------------------
1943 # Define operator class to draw a wall
1944 # ------------------------------------------------------------------
1947 class ARCHIPACK_OT_wall2_draw(ArchipackDrawTool, Operator):
1948 bl_idname = "archipack.wall2_draw"
1949 bl_label = "Draw a Wall"
1950 bl_description = "Create a wall by drawing its baseline in 3D view"
1951 bl_category = 'Archipack'
1953 o = None
1954 state = 'RUNNING'
1955 flag_create = False
1956 flag_next = False
1957 wall_part1 = None
1958 wall_line1 = None
1959 line = None
1960 label = None
1961 feedback = None
1962 takeloc = Vector((0, 0, 0))
1963 sel = []
1964 act = None
1966 # constraint to other wall and make a T child
1967 parent = None
1968 takemat = None
1970 @classmethod
1971 def poll(cls, context):
1972 return True
1974 def draw_callback(self, _self, context):
1975 self.feedback.draw(context)
1977 def sp_draw(self, sp, context):
1978 z = 2.7
1979 if self.state == 'CREATE':
1980 p0 = self.takeloc
1981 else:
1982 p0 = sp.takeloc
1984 p1 = sp.placeloc
1985 delta = p1 - p0
1986 # print("sp_draw state:%s delta:%s p0:%s p1:%s" % (self.state, delta.length, p0, p1))
1987 if delta.length == 0:
1988 return
1989 self.wall_part1.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))])
1990 self.wall_line1.set_pos([p0, p1, Vector((p1.x, p1.y, p1.z + z)), Vector((p0.x, p0.y, p0.z + z))])
1991 self.wall_part1.draw(context)
1992 self.wall_line1.draw(context)
1993 self.line.p = p0
1994 self.line.v = delta
1995 self.label.set_pos(context, self.line.length, self.line.lerp(0.5), self.line.v, normal=Vector((0, 0, 1)))
1996 self.label.draw(context)
1997 self.line.draw(context)
1999 def sp_callback(self, context, event, state, sp):
2000 logger.debug("ARCHIPACK_OT_wall2_draw.sp_callback event %s %s state:%s", event.type, event.value, state)
2002 if state == 'SUCCESS':
2004 if self.state == 'CREATE':
2005 takeloc = self.takeloc
2006 delta = sp.placeloc - self.takeloc
2007 else:
2008 takeloc = sp.takeloc
2009 delta = sp.delta
2011 old = context.object
2012 if self.o is None:
2013 bpy.ops.archipack.wall2(auto_manipulate=False)
2014 o = context.object
2015 o.location = takeloc
2016 self.o = o
2017 d = archipack_wall2.datablock(o)
2019 part = d.parts[0]
2020 part.length = delta.length
2021 else:
2022 o = self.o
2023 # select and make active
2024 o.select_set(state=True)
2025 context.view_layer.objects.active = o
2026 d = archipack_wall2.datablock(o)
2027 # Check for end close to start and close when applicable
2028 dp = sp.placeloc - o.location
2029 if dp.length < 0.01:
2030 d.closed = True
2031 self.state = 'CANCEL'
2032 return
2034 part = d.add_part(context, delta.length)
2036 # print("self.o :%s" % o.name)
2037 rM = o.matrix_world.inverted().to_3x3()
2038 g = d.get_generator()
2039 w = g.segs[-2]
2040 dp = rM @ delta
2041 da = atan2(dp.y, dp.x) - w.straight(1).angle
2042 a0 = part.a0 + da
2043 if a0 > pi:
2044 a0 -= 2 * pi
2045 if a0 < -pi:
2046 a0 += 2 * pi
2047 part.a0 = a0
2048 d.update(context)
2050 old.select_set(state=True)
2051 context.view_layer.objects.active = old
2052 self.flag_next = True
2053 context.area.tag_redraw()
2054 # print("feedback.on:%s" % self.feedback.on)
2056 self.state = state
2058 def sp_init(self, context, event, state, sp):
2059 # print("sp_init event %s %s %s" % (event.type, event.value, state))
2060 if state == 'SUCCESS':
2061 # point placed, check if a wall was under mouse
2062 res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event)
2063 if res:
2064 d = archipack_wall2.datablock(wall)
2065 if event.ctrl:
2066 # user snap, use direction as constraint
2067 tM.translation = sp.placeloc.copy()
2068 else:
2069 # without snap, use wall's bottom
2070 tM.translation -= y.normalized() * (0.5 * d.width)
2071 self.takeloc = tM.translation
2072 self.parent = wall.name
2073 self.takemat = tM
2074 else:
2075 self.takeloc = sp.placeloc.copy()
2077 self.state = 'RUNNING'
2078 # print("feedback.on:%s" % self.feedback.on)
2079 elif state == 'CANCEL':
2080 self.state = state
2081 return
2083 def ensure_ccw(self):
2085 Wall to slab expect wall vertex order to be ccw
2086 so reverse order here when needed
2088 d = archipack_wall2.datablock(self.o)
2089 g = d.get_generator(axis=False)
2090 pts = [seg.p0 for seg in g.segs]
2092 if d.closed:
2093 pts.append(pts[0])
2095 if d.is_cw(pts):
2096 d.x_offset = 1
2097 pts = list(reversed(pts))
2098 self.o.location += pts[0] - pts[-1]
2100 d.from_points(pts, d.closed)
2102 def modal(self, context, event):
2104 context.area.tag_redraw()
2105 if event.type in {'NONE', 'TIMER', 'TIMER_REPORT', 'EVT_TWEAK_L', 'WINDOW_DEACTIVATE'}:
2106 return {'PASS_THROUGH'}
2108 if self.keymap.check(event, self.keymap.delete):
2109 self.feedback.disable()
2110 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
2111 self.o = None
2112 return {'FINISHED', 'PASS_THROUGH'}
2114 if self.state == 'STARTING' and event.type not in {'ESC', 'RIGHTMOUSE'}:
2115 # wait for takeloc being visible when button is over horizon
2116 takeloc = self.mouse_to_plane(context, event)
2117 if takeloc is not None:
2118 logger.debug("ARCHIPACK_OT_wall2_draw.modal(STARTING) location:%s", takeloc)
2119 snap_point(takeloc=takeloc,
2120 callback=self.sp_init,
2121 constraint_axis=(True, True, False),
2122 release_confirm=True)
2123 return {'RUNNING_MODAL'}
2125 elif self.state == 'RUNNING':
2126 # print("RUNNING")
2127 logger.debug("ARCHIPACK_OT_wall2_draw.modal(RUNNING) location:%s", self.takeloc)
2128 self.state = 'CREATE'
2129 snap_point(takeloc=self.takeloc,
2130 draw=self.sp_draw,
2131 takemat=self.takemat,
2132 transform_orientation=context.scene.transform_orientation_slots[0].type,
2133 callback=self.sp_callback,
2134 constraint_axis=(True, True, False),
2135 release_confirm=self.max_style_draw_tool)
2136 return {'RUNNING_MODAL'}
2138 elif self.state != 'CANCEL' and event.type in {'C', 'c'}:
2140 logger.debug("ARCHIPACK_OT_wall2_draw.modal(%s) C pressed", self.state)
2141 self.feedback.disable()
2142 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
2144 o = self.o
2145 # select and make active
2146 o.select_set(state=True)
2147 context.view_layer.objects.active = o
2149 d = archipack_wall2.datablock(o)
2150 d.closed = True
2152 if bpy.ops.archipack.manipulate.poll():
2153 bpy.ops.archipack.manipulate('INVOKE_DEFAULT')
2155 return {'FINISHED'}
2157 elif self.state != 'CANCEL' and event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}:
2159 # print('LEFTMOUSE %s' % (event.value))
2160 self.feedback.instructions(context, "Draw a wall", "Click & Drag to add a segment", [
2161 ('ENTER', 'Add part'),
2162 ('BACK_SPACE', 'Remove part'),
2163 ('CTRL', 'Snap'),
2164 ('C', 'Close wall and exit'),
2165 ('MMBTN', 'Constraint to axis'),
2166 ('X Y', 'Constraint to axis'),
2167 ('RIGHTCLICK or ESC', 'exit')
2170 # press with max mode release with blender mode
2171 if self.max_style_draw_tool:
2172 evt_value = 'PRESS'
2173 else:
2174 evt_value = 'RELEASE'
2176 if event.value == evt_value:
2178 if self.flag_next:
2179 self.flag_next = False
2180 o = self.o
2182 # select and make active
2183 o.select_set(state=True)
2184 context.view_layer.objects.active = o
2186 d = archipack_wall2.datablock(o)
2187 g = d.get_generator()
2188 p0 = g.segs[-2].p0
2189 p1 = g.segs[-2].p1
2190 dp = p1 - p0
2191 takemat = o.matrix_world @ Matrix([
2192 [dp.x, dp.y, 0, p1.x],
2193 [dp.y, -dp.x, 0, p1.y],
2194 [0, 0, 1, 0],
2195 [0, 0, 0, 1]
2197 takeloc = o.matrix_world @ p1.to_3d()
2198 o.select_set(state=False)
2199 else:
2200 takemat = None
2201 takeloc = self.mouse_to_plane(context, event)
2203 if takeloc is not None:
2204 logger.debug("ARCHIPACK_OT_wall2_draw.modal(CREATE) location:%s", takeloc)
2206 snap_point(takeloc=takeloc,
2207 takemat=takemat,
2208 draw=self.sp_draw,
2209 callback=self.sp_callback,
2210 constraint_axis=(True, True, False),
2211 release_confirm=self.max_style_draw_tool)
2213 return {'RUNNING_MODAL'}
2215 if self.keymap.check(event, self.keymap.undo) or (
2216 event.type in {'BACK_SPACE'} and event.value == 'RELEASE'
2218 if self.o is not None:
2219 o = self.o
2221 # select and make active
2222 o.select_set(state=True)
2223 context.view_layer.objects.active = o
2224 d = archipack_wall2.datablock(o)
2225 if d.n_parts > 1:
2226 d.n_parts -= 1
2227 return {'RUNNING_MODAL'}
2229 if self.state == 'CANCEL' or (event.type in {'ESC', 'RIGHTMOUSE'} and
2230 event.value == 'RELEASE'):
2232 self.feedback.disable()
2233 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
2234 logger.debug("ARCHIPACK_OT_wall2_draw.modal(CANCEL) %s", event.type)
2235 if self.o is None:
2236 for o in self.sel:
2237 o.select_set(state=True)
2238 # select and make active
2239 if self.act is not None:
2240 self.act.select_set(state=True)
2241 context.view_layer.objects.active = self.act
2243 else:
2244 o = self.o
2245 o.select_set(state=True)
2246 context.view_layer.objects.active = o
2248 # remove last segment with blender mode
2249 d = archipack_wall2.datablock(o)
2250 if not self.max_style_draw_tool:
2251 if not d.closed and d.n_parts > 1:
2252 d.n_parts -= 1
2253 o.select_set(state=True)
2254 context.view_layer.objects.active = o
2255 # make T child
2256 if self.parent is not None:
2257 d.t_part = self.parent
2259 if bpy.ops.archipack.manipulate.poll():
2260 bpy.ops.archipack.manipulate('INVOKE_DEFAULT', object_name=o.name)
2262 return {'FINISHED'}
2264 return {'PASS_THROUGH'}
2266 def invoke(self, context, event):
2268 if context.mode == "OBJECT":
2269 prefs = context.preferences.addons[__name__.split('.')[0]].preferences
2270 self.max_style_draw_tool = prefs.max_style_draw_tool
2271 self.keymap = Keymaps(context)
2272 self.wall_part1 = GlPolygon((0.5, 0, 0, 0.2))
2273 self.wall_line1 = GlPolyline((0.5, 0, 0, 0.8))
2274 self.line = GlLine()
2275 self.label = GlText()
2276 self.feedback = FeedbackPanel()
2277 self.feedback.instructions(context, "Draw a wall", "Click & Drag to start", [
2278 ('CTRL', 'Snap'),
2279 ('MMBTN', 'Constraint to axis'),
2280 ('X Y', 'Constraint to axis'),
2281 ('SHIFT+CTRL+TAB', 'Switch snap mode'),
2282 ('RIGHTCLICK or ESC', 'exit without change')
2284 self.feedback.enable()
2285 args = (self, context)
2287 self.sel = context.selected_objects[:]
2288 self.act = context.active_object
2289 bpy.ops.object.select_all(action="DESELECT")
2291 self.state = 'STARTING'
2293 self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL')
2294 context.window_manager.modal_handler_add(self)
2295 return {'RUNNING_MODAL'}
2296 else:
2297 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
2298 return {'CANCELLED'}
2301 # ------------------------------------------------------------------
2302 # Define operator class to manage parts
2303 # ------------------------------------------------------------------
2306 class ARCHIPACK_OT_wall2_insert(Operator):
2307 bl_idname = "archipack.wall2_insert"
2308 bl_label = "Insert"
2309 bl_description = "Insert part"
2310 bl_category = 'Archipack'
2311 bl_options = {'REGISTER', 'UNDO'}
2312 index : IntProperty(default=0)
2314 def execute(self, context):
2315 if context.mode == "OBJECT":
2316 o = context.active_object
2317 d = archipack_wall2.datablock(o)
2318 if d is None:
2319 return {'CANCELLED'}
2320 d.insert_part(context, o, self.index)
2321 return {'FINISHED'}
2322 else:
2323 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
2324 return {'CANCELLED'}
2327 class ARCHIPACK_OT_wall2_remove(Operator):
2328 bl_idname = "archipack.wall2_remove"
2329 bl_label = "Remove"
2330 bl_description = "Remove part"
2331 bl_category = 'Archipack'
2332 bl_options = {'REGISTER', 'UNDO'}
2333 index : IntProperty(default=0)
2335 def execute(self, context):
2336 if context.mode == "OBJECT":
2337 o = context.active_object
2338 d = archipack_wall2.datablock(o)
2339 if d is None:
2340 return {'CANCELLED'}
2341 d.remove_part(context, o, self.index)
2342 return {'FINISHED'}
2343 else:
2344 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
2345 return {'CANCELLED'}
2348 class ARCHIPACK_OT_wall2_reverse(Operator):
2349 bl_idname = "archipack.wall2_reverse"
2350 bl_label = "Reverse"
2351 bl_description = "Reverse parts order"
2352 bl_category = 'Archipack'
2353 bl_options = {'REGISTER', 'UNDO'}
2355 def execute(self, context):
2356 if context.mode == "OBJECT":
2357 o = context.active_object
2358 d = archipack_wall2.datablock(o)
2359 if d is None:
2360 return {'CANCELLED'}
2361 d.reverse(context, o)
2362 return {'FINISHED'}
2363 else:
2364 self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
2365 return {'CANCELLED'}
2368 # ------------------------------------------------------------------
2369 # Define operator class to manipulate object
2370 # ------------------------------------------------------------------
2373 class ARCHIPACK_OT_wall2_manipulate(Operator):
2374 bl_idname = "archipack.wall2_manipulate"
2375 bl_label = "Manipulate"
2376 bl_description = "Manipulate"
2377 bl_options = {'REGISTER', 'UNDO'}
2379 @classmethod
2380 def poll(self, context):
2381 return archipack_wall2.filter(context.active_object)
2383 def invoke(self, context, event):
2384 d = archipack_wall2.datablock(context.active_object)
2385 d.manipulable_invoke(context)
2386 return {'FINISHED'}
2388 def execute(self, context):
2390 For use in boolean ops
2392 if archipack_wall2.filter(context.active_object):
2393 o = context.active_object
2394 d = archipack_wall2.datablock(o)
2395 g = d.get_generator()
2396 d.setup_childs(o, g)
2397 d.update_childs(context, o, g)
2398 d.update(context)
2399 o.select_set(state=True)
2400 context.view_layer.objects.active = o
2401 return {'FINISHED'}
2404 def register():
2405 bpy.utils.register_class(archipack_wall2_part)
2406 bpy.utils.register_class(archipack_wall2_child)
2407 bpy.utils.register_class(archipack_wall2)
2408 Mesh.archipack_wall2 = CollectionProperty(type=archipack_wall2)
2409 bpy.utils.register_class(ARCHIPACK_PT_wall2)
2410 bpy.utils.register_class(ARCHIPACK_OT_wall2)
2411 bpy.utils.register_class(ARCHIPACK_OT_wall2_draw)
2412 bpy.utils.register_class(ARCHIPACK_OT_wall2_insert)
2413 bpy.utils.register_class(ARCHIPACK_OT_wall2_remove)
2414 bpy.utils.register_class(ARCHIPACK_OT_wall2_reverse)
2415 bpy.utils.register_class(ARCHIPACK_OT_wall2_manipulate)
2416 bpy.utils.register_class(ARCHIPACK_OT_wall2_from_curve)
2417 bpy.utils.register_class(ARCHIPACK_OT_wall2_from_slab)
2418 bpy.utils.register_class(ARCHIPACK_OT_wall2_throttle_update)
2419 bpy.utils.register_class(ARCHIPACK_OT_wall2_fit_roof)
2422 def unregister():
2423 bpy.utils.unregister_class(archipack_wall2_part)
2424 bpy.utils.unregister_class(archipack_wall2_child)
2425 bpy.utils.unregister_class(archipack_wall2)
2426 del Mesh.archipack_wall2
2427 bpy.utils.unregister_class(ARCHIPACK_PT_wall2)
2428 bpy.utils.unregister_class(ARCHIPACK_OT_wall2)
2429 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_draw)
2430 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_insert)
2431 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_remove)
2432 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_reverse)
2433 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_manipulate)
2434 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_curve)
2435 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_from_slab)
2436 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_throttle_update)
2437 bpy.utils.unregister_class(ARCHIPACK_OT_wall2_fit_roof)