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 #####
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
26 # ----------------------------------------------------------
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 (
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
54 logger
= logging
.getLogger("archipack")
58 def __init__(self
, wall_z
, z
, t
, flip
):
65 def set_offset(self
, offset
, last
=None):
67 Offset line and compute intersection point
70 self
.line
= self
.make_offset(offset
, last
)
75 for i
in range(1, self
.z_step
):
79 return z0
+ (t
- t0
) / (t1
- t0
) * (z1
- z0
)
83 def make_faces(self
, i
, f
, faces
):
88 faces
.append((f
+ 2, f
, f
+ 1, f
+ 3))
90 faces
.append((f
, f
+ 2, f
+ 3, f
+ 1))
92 def p3d(self
, verts
, 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
):
102 self
.make_faces(i
, f
, faces
)
104 def make_hole(self
, i
, verts
, z0
):
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
)
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
):
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'
148 self
.faces_type
= 'NONE'
151 def set_offset(self
, offset
):
153 for i
, seg
in enumerate(self
.segs
):
154 seg
.set_offset(offset
, last
)
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
):
169 # refactor this part (height manipulators)
171 if len(self
.segs
) < 1:
174 manip_index
.append(0)
180 z_last
= part
.n_splits
- 1
183 for i
in range(part
.n_splits
):
184 t_try
= t
[-1] + part
.t
[i
]
191 manip_index
.append(i
)
198 manip_index
.append(z_last
)
199 z
.append(part
.z
[z_last
])
203 if part
.type == 'S_WALL':
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
)
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
)
217 self
.last_type
= part
.type
221 def close(self
, closed
):
222 # Make last segment implicit closing one
224 part
= self
.parts
[-1]
226 dp
= self
.segs
[0].p0
- self
.segs
[-1].p0
227 if "C_" in part
.type:
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
)
241 def locate_manipulators(self
, side
):
243 for i
, wall
in enumerate(self
.segs
):
245 manipulators
= self
.parts
[i
].manipulators
250 # angle from last to current segment
253 if i
< len(self
.segs
) - 1:
254 manipulators
[0].type_key
= 'ANGLE'
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":
264 manipulators
[1].type_key
= 'SIZE'
265 manipulators
[1].prop1_name
= "length"
266 manipulators
[1].set_pts([p0
, p1
, (side
, 0, 0)])
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
292 # Make last segment implicit closing one
294 nb_segs
= len(self
.segs
) - 1
298 for i
, wall
in enumerate(self
.segs
):
300 wall
.param_t(step_angle
)
302 for j
in range(wall
.n_step
+ 1):
303 wall
.make_wall(j
, verts
, faces
)
306 for j
in range(wall
.n_step
):
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
)
325 p0
= self
.segs
[idx_from
].p0
326 for i
in range(idx_from
+ 1, len(self
.segs
)):
329 dp
= rM
@ (seg
.p0
- p0
)
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())
353 tp
= (rM
@ s
.p0
) - s
.p0
+ dp
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
:
364 x
, y
= wall
.lerp(i
/ 32)
365 verts
.append((x
, y
, 0))
367 def make_surface(self
, o
, verts
, height
):
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
)
378 bmesh
.ops
.solidify(bm
, geom
=geom
, thickness
=height
)
382 def make_hole(self
, context
, hole_obj
, d
):
384 offset
= -0.5 * (1 - d
.x_offset
) * d
.width
387 self
.set_offset(offset
)
389 nb_segs
= len(self
.segs
) - 1
394 for i
, wall
in enumerate(self
.segs
):
395 wall
.param_t(d
.step_angle
)
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
):
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
)
425 w
= context
.scene
.objects
.get(self
.t_part
.strip())
426 wd
= archipack_wall2
.datablock(w
)
429 og
= self
.get_generator()
430 self
.setup_childs(o
, og
)
432 bpy
.ops
.object.select_all(action
="DESELECT")
435 # 1 No parents at all
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
445 # select childs and make parent reference point active
447 # Either link to o.parent or create new parent
448 link_to_parent
= True
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
))
454 context
.view_layer
.objects
.active
= o
455 bpy
.ops
.archipack
.reference_point()
456 o
.select_set(state
=True)
458 context
.view_layer
.objects
.active
= o
.parent
459 w
.select_set(state
=True)
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
469 for c
in o
.parent
.children
:
471 c
.hide_select
= False
472 c
.select_set(state
=True)
474 parent
= context
.active_object
478 wg
= wd
.get_generator()
492 # dir in absolute world coordsys
493 dir = orM
@ og
.segs
[0].straight(1, 0).v
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
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
],
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
:
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
!= "":
545 self
.restore_context(context
)
548 def set_splits(self
, value
):
549 if self
.n_splits
!= value
:
550 self
.auto_update
= False
552 self
.auto_update
= True
553 self
.n_splits
= value
557 def get_splits(self
):
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
569 for i
, part
in enumerate(d
.parts
):
575 g
= d
.get_generator()
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
)
581 w
= w0
.curved_wall(self
.a0
, self
.da
, self
.radius
, d
.z
, self
.z
, self
.t
)
583 if "C_" in self
.type:
585 v
= self
.length
* Vector((cos(self
.a0
), sin(self
.a0
)))
586 w
= StraightWall(p
, v
, d
.z
, self
.z
, self
.t
, d
.flip
)
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
)
593 if d
.closed
and idx
== d
.n_parts
:
598 if "C_" in self
.type:
599 self
.radius
= 0.5 * dp
.length
601 a0
= atan2(dp
.y
, dp
.x
) - pi
/ 2 - a0
603 self
.length
= dp
.length
604 a0
= atan2(dp
.y
, dp
.x
) - 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
618 a0
= part1
.a0
+ w
.straight(1).angle
- atan2(dp
.y
, dp
.x
)
629 class archipack_wall2_part(PropertyGroup
):
632 ('S_WALL', 'Straight', '', 0),
633 ('C_WALL', 'Curved', '', 1)
638 length
: FloatProperty(
642 unit
='LENGTH', subtype
='DISTANCE',
645 radius
: FloatProperty(
649 unit
='LENGTH', subtype
='DISTANCE',
657 subtype
='ANGLE', unit
='ROTATION',
665 subtype
='ANGLE', unit
='ROTATION',
668 z
: FloatVectorProperty(
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,
679 t
: FloatVectorProperty(
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,
692 splits
: IntProperty(
697 get
=get_splits
, set=set_splits
699 n_splits
: IntProperty(
706 auto_update
: BoolProperty(default
=True)
707 manipulators
: CollectionProperty(type=archipack_manipulator
)
709 expand
: BoolProperty(default
=False)
711 def _set_t(self
, splits
):
713 for i
in range(splits
):
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
[:]
723 props
= archipack_wall2
.datablock(o
)
725 for part
in props
.parts
:
730 def update(self
, context
, manipulable_refresh
=False):
731 if not self
.auto_update
:
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)
741 row
.prop(self
, 'expand', icon
="TRIA_DOWN", text
="Part " + str(index
+ 1), emboss
=False)
743 row
.prop(self
, 'expand', icon
="TRIA_RIGHT", text
="Part " + str(index
+ 1), emboss
=False)
745 row
.prop(self
, "type", text
="")
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':
753 row
.prop(self
, "radius")
758 row
.prop(self
, "length")
762 row
.prop(self
, "splits")
763 for split
in range(self
.n_splits
):
765 row
.prop(self
, "z", text
="alt", index
=split
)
766 row
.prop(self
, "t", text
="pos", index
=split
)
769 class archipack_wall2_child(PropertyGroup
):
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
):
780 child
= context
.scene
.objects
.get(self
.child_name
.strip())
781 if child
is not None and child
.data
is not None:
783 if 'archipack_window' in cd
:
784 d
= cd
.archipack_window
[0]
785 elif 'archipack_door' in cd
:
786 d
= cd
.archipack_door
[0]
790 class archipack_wall2(ArchipackObject
, Manipulable
, PropertyGroup
):
791 parts
: CollectionProperty(type=archipack_wall2_part
)
792 n_parts
: IntProperty(
796 default
=1, update
=update_manipulators
798 step_angle
: FloatProperty(
799 description
="Curved parts segmentation",
803 default
=6 / 180 * pi
,
804 subtype
='ANGLE', unit
='ROTATION',
807 width
: FloatProperty(
811 unit
='LENGTH', subtype
='DISTANCE',
817 default
=2.7, precision
=2,
818 unit
='LENGTH', subtype
='DISTANCE',
819 description
='height', update
=update
,
821 x_offset
: FloatProperty(
824 default
=-1, precision
=2, step
=1,
827 radius
: FloatProperty(
831 unit
='LENGTH', subtype
='DISTANCE',
839 subtype
='ANGLE', unit
='ROTATION',
847 closed
: BoolProperty(
850 update
=update_manipulators
852 auto_update
: BoolProperty(
853 options
={'SKIP_SAVE'},
855 update
=update_manipulators
857 realtime
: BoolProperty(
858 options
={'SKIP_SAVE'},
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(
869 description
="This part will follow parent when set",
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
]
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
887 # move after current one
888 self
.parts
.move(len(self
.parts
) - 1, where
+ 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
902 self
.parts
.move(len(self
.parts
) - 1, self
.n_parts
)
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
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:
926 part
.length
= w
.length
929 part
.a0
= w
.delta_angle(g
.segs
[where
- 2])
931 part
.a0
= w
.straight(1, 0).angle
933 self
.parts
.remove(where
)
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
)
952 def update_parts(self
, o
, update_childs
=False):
953 # print("update_parts")
957 # as last one is end point of last segment or closing one
959 for i
in range(len(self
.parts
), self
.n_parts
+ 1, -1):
961 self
.parts
.remove(i
- 1)
964 for i
in range(len(self
.parts
), self
.n_parts
+ 1):
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
)
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()
990 if self
.t_part
!= "" and len(self
.manipulators
) < 4:
991 s
= self
.manipulators
.add()
993 s
.type_key
= 'DELTA_LOC'
995 for i
in range(self
.n_parts
+ 1):
997 n_manips
= len(p
.manipulators
)
999 s
= p
.manipulators
.add()
1000 s
.type_key
= "ANGLE"
1003 s
= p
.manipulators
.add()
1005 s
.prop1_name
= "length"
1007 s
= p
.manipulators
.add()
1008 s
.type_key
= 'WALL_SNAP'
1009 s
.prop1_name
= str(i
)
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
):
1020 pts
.append(wM
@ p0
.co
.to_3d())
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())
1028 seg
= interpolate_bezier(wM
@ p0
.co
,
1029 wM
@ p0
.handle_right
,
1030 wM
@ p1
.handle_left
,
1033 for i
in range(resolution
):
1034 pts
.append(seg
[i
].to_3d())
1036 def is_cw(self
, pts
):
1040 d
+= (p
.x
* p0
.y
- p
.y
* p0
.x
)
1044 def from_spline(self
, wM
, resolution
, spline
):
1046 if spline
.type == 'POLY':
1047 pts
= [wM
@ p
.co
.to_3d() for p
in spline
.points
]
1048 if spline
.use_cyclic_u
:
1050 elif spline
.type == 'BEZIER':
1051 points
= spline
.bezier_points
1052 for i
in range(1, len(points
)):
1055 self
.interpolate_bezier(pts
, wM
, p0
, p1
, resolution
)
1056 if spline
.use_cyclic_u
:
1059 self
.interpolate_bezier(pts
, wM
, p0
, p1
, resolution
)
1062 pts
.append(wM
@ points
[-1].co
)
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
1078 self
.update_parts(None)
1082 for i
, p1
in enumerate(pts
):
1084 da
= atan2(dp
.y
, dp
.x
) - a0
1089 if i
>= len(self
.parts
):
1090 print("Too many pts for parts")
1093 p
.length
= dp
.to_2d().length
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
]
1112 g_segs
= list(reversed(g
.segs
))
1116 for i
, seg
in enumerate(g_segs
):
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
1124 self
.parts
[i
].type = "S_WALL"
1125 self
.parts
[i
].length
= s
.length
1127 self
.parts
[i
].a0
= s
.delta_angle(last_seg
)
1134 pts
= list(reversed(pts
))
1136 # location wont change for closed walls
1138 dp
= pts
[0] - pts
[-1]
1139 # pre-translate as dp is in local coordsys
1140 o
.matrix_world
= o
.matrix_world
@ Matrix([
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
)
1164 if manipulable_refresh
:
1165 # prevent crash by removing all manipulators refs to datablock before changes
1166 self
.manipulable_disable(context
)
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
)
1178 faces
.append((0, f
- 2, f
- 1, 1))
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)
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(),
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(),
1203 self
.manipulators
[2].set_pts([
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(),
1218 # update child location and size
1219 self
.relocate_childs(context
, o
, g
)
1221 self
.update_childs(context
, o
, g
)
1223 bpy
.ops
.archipack
.wall2_throttle_update(name
=o
.name
)
1225 modif
= o
.modifiers
.get('Wall')
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
):
1245 for i
in range(begin
+ 1, end
+ 1):
1247 if array
[i
][1] < array
[begin
][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]:
1253 array
[i
], array
[pivot
] = array
[pivot
], array
[i
]
1254 array
[pivot
], array
[begin
] = array
[begin
], array
[pivot
]
1257 def sort_child(self
, array
, begin
=0, end
=None):
1258 # print("sort_child")
1260 end
= len(array
) - 1
1262 def _quicksort(array
, begin
, end
):
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()
1274 c
.wall_idx
= wall_idx
1277 m
= c
.manipulators
.add()
1278 m
.type_key
= 'DELTA_LOC'
1280 m
= c
.manipulators
.add()
1281 m
.type_key
= 'SNAP_SIZE_LOC'
1285 def setup_childs(self
, o
, g
):
1289 call after a boolean oop
1293 self
.childs_manipulators
.clear()
1294 if o
.parent
is None:
1296 wall_with_childs
= [0 for i
in range(self
.n_parts
+ 1)]
1298 dmax
= 2 * self
.width
1300 wtM
= o
.matrix_world
1305 witM
= wtM
.inverted()
1307 for child
in o
.parent
.children
:
1308 # filter allowed childs
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 (
1319 # setup on T linked walls
1321 wg
= wd
.get_generator()
1322 wd
.setup_childs(child
, wg
)
1324 ctM
= child
.matrix_world
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
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
:
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
1358 (t
* wall
.length
, dist
, (witM
@ pos
).z
),
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")
1380 w
= -self
.x_offset
* self
.width
1384 for child
in self
.childs
:
1385 c
, d
= child
.get_child(context
)
1388 t
= child
.pos
.x
/ g
.segs
[child
.wall_idx
].length
1389 n
= g
.segs
[child
.wall_idx
].sized_normal(t
, 1)
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
1402 d
.auto_update
= True
1403 c
.select_set(state
=False)
1404 x
, y
= n
.p
- (0.5 * w
* n
.v
)
1406 x
, y
= n
.p
- (child
.pos
.y
* n
.v
)
1408 context
.view_layer
.objects
.active
= o
1410 c
.matrix_world
= tM
@ Matrix([
1413 [0, 0, 1, child
.pos
.z
],
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:
1434 # swap manipulators so they always face outside
1439 itM
= o
.matrix_world
.inverted()
1441 for wall_idx
, wall
in enumerate(g
.segs
):
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
)
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([
1459 (manip_side
* 0.5, 0, 0)])
1461 x
, y
= 0.5 * d
.x
, -self
.x_offset
* 0.5 * d
.y
1469 child
.manipulators
[0].set_pts([(-x
, side
* -y
, 0), (x
, side
* -y
, 0), (side
, 0, 0)])
1471 child
.manipulators
[1].set_pts([
1474 (0.5 * side
, 0, 0)])
1475 p0
= wall
.lerp(t
+ dt
)
1478 # dub size after all childs
1479 self
.childs_manipulators
[m_idx
].set_pts([
1482 (manip_side
* 0.5, 0, 0)])
1485 def manipulate_childs(self
, context
):
1487 setup child manipulators
1489 # print("manipulate_childs")
1490 n_parts
= self
.n_parts
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
)
1500 self
.manip_stack
.append(child
.manipulators
[0].setup(context
, c
, d
, self
.manipulate_callback
))
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):
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
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))
1519 'DeltaLocationManipulator',
1520 'SizeLocationManipulator',
1521 'SnapSizeLocationManipulator'
1523 # update manipulators pos of childs
1524 o
= context
.active_object
1525 if o
.parent
is None:
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
)
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))
1546 'DeltaLocationManipulator'
1548 # update manipulators pos of childs
1549 if archipack_wall2
.datablock(o
) != self
:
1551 g
= self
.get_generator()
1553 self
.relocate_childs(context
, o
, g
)
1555 def manipulable_release(self
, context
):
1557 Override with action to do on mouse release
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
1573 # update manipulators on version change
1574 self
.setup_manipulators()
1576 for i
, part
in enumerate(self
.parts
):
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
))
1586 self
.manip_stack
.append(part
.manipulators
[3].setup(context
, o
, self
))
1589 self
.manip_stack
.append(part
.manipulators
[2].setup(context
, o
, self
))
1591 # height as per segment will be here when done
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
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
)
1616 # self.manip_stack = []
1617 o
= context
.active_object
1618 g
= self
.get_generator()
1619 # setup childs manipulators
1620 self
.setup_childs(o
, g
)
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
)
1633 def find_roof(self
, context
, o
, g
):
1635 up
= Vector((0, 0, 1))
1637 p
= tM
@ seg
.p0
.to_3d()
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
,
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]
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
1658 update_timer_updating
= False
1659 throttle_delay
= 0.5
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())
1677 m
= o
.modifiers
.get("AutoBoolean")
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
):
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
):
1707 context
.window_manager
.event_timer_remove(update_timer
)
1709 return {'CANCELLED'}
1712 class ARCHIPACK_PT_wall2(Panel
):
1713 bl_idname
= "ARCHIPACK_PT_wall2"
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)
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')
1729 box
.prop(prop
, 'n_parts')
1730 box
.prop(prop
, 'step_angle')
1731 box
.prop(prop
, 'width')
1733 box
.prop(prop
, 'flip')
1734 box
.prop(prop
, 'x_offset')
1736 row
.prop(prop
, "closed")
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
1746 for i
, part
in enumerate(prop
.parts
):
1749 part
.draw(box
, context
, i
)
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"
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)
1776 m
.auto_smooth_angle
= 0.20944
1777 context
.view_layer
.objects
.active
= o
1779 self
.add_material(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
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)
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
1821 pt
= Vector((0, 0, 0))
1823 o
.matrix_world
= curve
.matrix_world
@ Matrix([
1831 # -----------------------------------------------------
1833 # -----------------------------------------------------
1834 def execute(self
, context
):
1835 if context
.mode
== "OBJECT":
1836 bpy
.ops
.object.select_all(action
="DESELECT")
1837 o
= self
.create(context
)
1839 o
.select_set(state
=True)
1840 context
.view_layer
.objects
.active
= o
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"
1850 bl_description
= "Create a wall from a slab"
1851 bl_category
= 'Archipack'
1852 bl_options
= {'REGISTER', 'UNDO'}
1854 auto_manipulate
: BoolProperty(default
=True)
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
1869 d
.n_parts
= wd
.n_parts
- 1
1871 for part
in wd
.parts
:
1873 if "S_" in part
.type:
1877 p
.length
= part
.length
1878 p
.radius
= part
.radius
1881 o
.select_set(state
=True)
1882 context
.view_layer
.objects
.active
= o
1883 d
.auto_update
= True
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
))
1893 context
.view_layer
.objects
.active
= o
1894 bpy
.ops
.archipack
.reference_point()
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)
1904 # -----------------------------------------------------
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
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)
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
)
1938 d
.setup_childs(o
, g
)
1939 rd
.make_wall_fit(context
, r
, o
, self
.inside
)
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'
1962 takeloc
= Vector((0, 0, 0))
1966 # constraint to other wall and make a T child
1971 def poll(cls
, context
):
1974 def draw_callback(self
, _self
, context
):
1975 self
.feedback
.draw(context
)
1977 def sp_draw(self
, sp
, context
):
1979 if self
.state
== 'CREATE':
1986 # print("sp_draw state:%s delta:%s p0:%s p1:%s" % (self.state, delta.length, p0, p1))
1987 if delta
.length
== 0:
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
)
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
2008 takeloc
= sp
.takeloc
2011 old
= context
.object
2013 bpy
.ops
.archipack
.wall2(auto_manipulate
=False)
2015 o
.location
= takeloc
2017 d
= archipack_wall2
.datablock(o
)
2020 part
.length
= delta
.length
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:
2031 self
.state
= 'CANCEL'
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()
2041 da
= atan2(dp
.y
, dp
.x
) - w
.straight(1).angle
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)
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
)
2064 d
= archipack_wall2
.datablock(wall
)
2066 # user snap, use direction as constraint
2067 tM
.translation
= sp
.placeloc
.copy()
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
2075 self
.takeloc
= sp
.placeloc
.copy()
2077 self
.state
= 'RUNNING'
2078 # print("feedback.on:%s" % self.feedback.on)
2079 elif state
== 'CANCEL':
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
]
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')
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':
2127 logger
.debug("ARCHIPACK_OT_wall2_draw.modal(RUNNING) location:%s", self
.takeloc
)
2128 self
.state
= 'CREATE'
2129 snap_point(takeloc
=self
.takeloc
,
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')
2145 # select and make active
2146 o
.select_set(state
=True)
2147 context
.view_layer
.objects
.active
= o
2149 d
= archipack_wall2
.datablock(o
)
2152 if bpy
.ops
.archipack
.manipulate
.poll():
2153 bpy
.ops
.archipack
.manipulate('INVOKE_DEFAULT')
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'),
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
:
2174 evt_value
= 'RELEASE'
2176 if event
.value
== evt_value
:
2179 self
.flag_next
= False
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()
2191 takemat
= o
.matrix_world
@ Matrix([
2192 [dp
.x
, dp
.y
, 0, p1
.x
],
2193 [dp
.y
, -dp
.x
, 0, p1
.y
],
2197 takeloc
= o
.matrix_world
@ p1
.to_3d()
2198 o
.select_set(state
=False)
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
,
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:
2221 # select and make active
2222 o
.select_set(state
=True)
2223 context
.view_layer
.objects
.active
= o
2224 d
= archipack_wall2
.datablock(o
)
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)
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
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:
2253 o
.select_set(state
=True)
2254 context
.view_layer
.objects
.active
= o
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
)
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", [
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'}
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"
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
)
2319 return {'CANCELLED'}
2320 d
.insert_part(context
, o
, self
.index
)
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"
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
)
2340 return {'CANCELLED'}
2341 d
.remove_part(context
, o
, self
.index
)
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
)
2360 return {'CANCELLED'}
2361 d
.reverse(context
, o
)
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'}
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
)
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
)
2399 o
.select_set(state
=True)
2400 context
.view_layer
.objects
.active
= o
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
)
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
)