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 # ----------------------------------------------------------
27 # noinspection PyUnresolvedReferences
29 # noinspection PyUnresolvedReferences
30 from bpy
.types
import Operator
, PropertyGroup
, Mesh
, Panel
31 from bpy
.props
import (
32 FloatProperty
, BoolProperty
, IntProperty
,
33 StringProperty
, EnumProperty
,
37 from mathutils
import Vector
, Matrix
38 from mathutils
.geometry
import interpolate_bezier
39 from math
import sin
, cos
, pi
, atan2
40 from .archipack_manipulator
import Manipulable
, archipack_manipulator
41 from .archipack_object
import ArchipackCreateTool
, ArchipackObject
42 from .archipack_2d
import Line
, Arc
43 from .archipack_cutter
import (
44 CutAblePolygon
, CutAbleGenerator
,
53 # self.colour_inactive = (1, 1, 1, 1)
56 def set_offset(self
, offset
, last
=None):
58 Offset line and compute intersection point
61 self
.line
= self
.make_offset(offset
, last
)
63 def straight_slab(self
, a0
, length
):
64 s
= self
.straight(length
).rotate(a0
)
65 return StraightSlab(s
.p
, s
.v
)
67 def curved_slab(self
, a0
, da
, radius
):
68 n
= self
.normal(1).rotate(a0
).scale(radius
)
73 return CurvedSlab(c
, radius
, a0
, da
)
76 class StraightSlab(Slab
, Line
):
78 def __init__(self
, p
, v
):
79 Line
.__init
__(self
, p
, v
)
83 class CurvedSlab(Slab
, Arc
):
85 def __init__(self
, c
, radius
, a0
, da
):
86 Arc
.__init
__(self
, c
, radius
, a0
, da
)
90 class SlabGenerator(CutAblePolygon
, CutAbleGenerator
):
92 def __init__(self
, parts
):
99 def add_part(self
, part
):
101 if len(self
.segs
) < 1:
107 if part
.type == 'S_SEG':
109 v
= part
.length
* Vector((cos(part
.a0
), sin(part
.a0
)))
110 s
= StraightSlab(p
, v
)
111 elif part
.type == 'C_SEG':
112 c
= -part
.radius
* Vector((cos(part
.a0
), sin(part
.a0
)))
113 s
= CurvedSlab(c
, part
.radius
, part
.a0
, part
.da
)
115 if part
.type == 'S_SEG':
116 s
= s
.straight_slab(part
.a0
, part
.length
)
117 elif part
.type == 'C_SEG':
118 s
= s
.curved_slab(part
.a0
, part
.da
, part
.radius
)
121 self
.last_type
= part
.type
123 def set_offset(self
):
125 for i
, seg
in enumerate(self
.segs
):
126 seg
.set_offset(self
.parts
[i
].offset
, last
)
129 def close(self
, closed
):
130 # Make last segment implicit closing one
132 part
= self
.parts
[-1]
134 dp
= self
.segs
[0].p0
- self
.segs
[-1].p0
135 if "C_" in part
.type:
137 w
.r
= part
.radius
/ dw
.length
* dp
.length
138 # angle pt - p0 - angle p0 p1
139 da
= atan2(dp
.y
, dp
.x
) - atan2(dw
.y
, dw
.x
)
149 if len(self
.segs
) > 1:
150 w
.line
= w
.make_offset(self
.parts
[-1].offset
, self
.segs
[-2].line
)
152 p1
= self
.segs
[0].line
.p1
153 self
.segs
[0].line
= self
.segs
[0].make_offset(self
.parts
[0].offset
, w
.line
)
154 self
.segs
[0].line
.p1
= p1
156 def locate_manipulators(self
):
160 for i
, f
in enumerate(self
.segs
):
162 manipulators
= self
.parts
[i
].manipulators
165 # angle from last to current segment
167 v0
= self
.segs
[i
- 1].straight(-1, 1).v
.to_3d()
168 v1
= f
.straight(1, 0).v
.to_3d()
169 manipulators
[0].set_pts([p0
, v0
, v1
])
171 if type(f
).__name
__ == "StraightSlab":
173 manipulators
[1].type_key
= 'SIZE'
174 manipulators
[1].prop1_name
= "length"
175 manipulators
[1].set_pts([p0
, p1
, (1, 0, 0)])
177 # segment radius + angle
178 v0
= (f
.p0
- f
.c
).to_3d()
179 v1
= (f
.p1
- f
.c
).to_3d()
180 manipulators
[1].type_key
= 'ARC_ANGLE_RADIUS'
181 manipulators
[1].prop1_name
= "da"
182 manipulators
[1].prop2_name
= "radius"
183 manipulators
[1].set_pts([f
.c
.to_3d(), v0
, v1
])
185 # snap manipulator, don't change index !
186 manipulators
[2].set_pts([p0
, p1
, (1, 0, 0)])
188 manipulators
[3].set_pts([p0
, p1
, (1, 0, 0)])
190 def get_verts(self
, verts
):
192 if "Curved" in type(s
).__name
__:
194 # x, y = slab.line.lerp(i / 16)
195 verts
.append(s
.lerp(i
/ 16).to_3d())
198 verts
.append(s
.p0
.to_3d())
201 x, y = slab.line.lerp(i / 32)
202 verts.append((x, y, 0))
205 def rotate(self
, idx_from
, a
):
207 apply rotation to all following segs
209 self
.segs
[idx_from
].rotate(a
)
217 p0
= self
.segs
[idx_from
].p0
218 for i
in range(idx_from
+ 1, len(self
.segs
)):
222 # rotate delta from rotation center to segment start
223 dp
= rM
@ (seg
.p0
- p0
)
226 def translate(self
, idx_from
, dp
):
228 apply translation to all following segs
230 self
.segs
[idx_from
].p1
+= dp
231 for i
in range(idx_from
+ 1, len(self
.segs
)):
232 self
.segs
[i
].translate(dp
)
234 def draw(self
, context
):
236 draw generator using gl
238 for seg
in self
.segs
:
239 seg
.draw(context
, render
=False)
242 x_size
= [s
.p0
.x
for s
in self
.segs
]
243 self
.xsize
= max(x_size
) - min(x_size
)
245 def cut(self
, context
, o
):
247 either external or holes cuts
251 self
.as_lines(step_angle
=0.0502)
252 # self.segs = [s.line for s in self.segs]
255 d
= archipack_slab_cutter
.datablock(b
)
257 g
= d
.ensure_direction()
258 g
.change_coordsys(b
.matrix_world
, o
.matrix_world
)
261 def slab(self
, context
, o
, d
):
264 self
.get_verts(verts
)
271 bm
.verts
.ensure_lookup_table()
272 for i
in range(1, len(verts
)):
273 bm
.edges
.new((bm
.verts
[i
- 1], bm
.verts
[i
]))
274 bm
.edges
.new((bm
.verts
[-1], bm
.verts
[0]))
275 bm
.edges
.ensure_lookup_table()
276 bmesh
.ops
.contextual_create(bm
, geom
=bm
.edges
)
278 self
.cut_holes(bm
, self
)
280 bmesh
.ops
.dissolve_limit(bm
,
282 use_dissolve_boundaries
=False,
285 delimit
={'MATERIAL'})
290 # verts = bm.verts[:]
291 # bmesh.ops.solidify(bm, geom=geom, thickness=d.z)
294 # bmed.bmesh_join(context, o, [bm], normal_update=True)
296 bpy
.ops
.object.mode_set(mode
='OBJECT')
299 def update(self
, context
):
303 def update_manipulators(self
, context
):
304 self
.update(context
, manipulable_refresh
=True)
307 def update_path(self
, context
):
308 self
.update_path(context
)
312 ('0', 'Ceiling', '', 0),
313 ('1', 'White', '', 1),
314 ('2', 'Concrete', '', 2),
315 ('3', 'Wood', '', 3),
316 ('4', 'Metal', '', 4),
317 ('5', 'Glass', '', 5)
321 class archipack_slab_material(PropertyGroup
):
322 index
: EnumProperty(
323 items
=materials_enum
,
328 def find_in_selection(self
, context
):
330 find witch selected object this instance belongs to
331 provide support for "copy to selected"
333 selected
= context
.selected_objects
[:]
335 props
= archipack_slab
.datablock(o
)
337 for part
in props
.rail_mat
:
342 def update(self
, context
):
343 props
= self
.find_in_selection(context
)
344 if props
is not None:
345 props
.update(context
)
348 class archipack_slab_child(PropertyGroup
):
350 Store child fences to be able to sync
352 child_name
: StringProperty()
355 def get_child(self
, context
):
357 child
= context
.scene
.objects
.get(self
.child_name
.strip())
358 if child
is not None and child
.data
is not None:
359 if 'archipack_fence' in child
.data
:
360 d
= child
.data
.archipack_fence
[0]
364 def update_type(self
, context
):
366 d
= self
.find_in_selection(context
)
368 if d
is not None and d
.auto_update
:
370 d
.auto_update
= False
373 for i
, part
in enumerate(d
.parts
):
381 g
= d
.get_generator()
383 a0
= w0
.straight(1).angle
384 if "C_" in self
.type:
385 w
= w0
.straight_slab(part
.a0
, part
.length
)
387 w
= w0
.curved_slab(part
.a0
, part
.da
, part
.radius
)
389 if "C_" in self
.type:
391 v
= self
.length
* Vector((cos(self
.a0
), sin(self
.a0
)))
392 w
= StraightSlab(p
, v
)
395 c
= -self
.radius
* Vector((cos(self
.a0
), sin(self
.a0
)))
396 w
= CurvedSlab(c
, self
.radius
, self
.a0
, pi
)
399 if idx
+ 1 == d
.n_parts
:
404 if "C_" in self
.type:
405 part
.radius
= 0.5 * dp
.length
407 a0
= atan2(dp
.y
, dp
.x
) - pi
/ 2 - a0
409 part
.length
= dp
.length
410 a0
= atan2(dp
.y
, dp
.x
) - a0
418 if idx
+ 1 < d
.n_parts
:
419 # adjust rotation of next part
420 part1
= d
.parts
[idx
+ 1]
421 if "C_" in part
.type:
422 a0
= part1
.a0
- pi
/ 2
424 a0
= part1
.a0
+ w
.straight(1).angle
- atan2(dp
.y
, dp
.x
)
435 class ArchipackSegment():
437 A single manipulable polyline like segment
438 polyline like segment line or arc based
439 @TODO: share this base class with
440 stair, wall, fence, slab
444 ('S_SEG', 'Straight', '', 0),
445 ('C_SEG', 'Curved', '', 1),
450 length
: FloatProperty(
456 radius
: FloatProperty(
467 subtype
='ANGLE', unit
='ROTATION',
475 subtype
='ANGLE', unit
='ROTATION',
478 offset
: FloatProperty(
480 description
="Add to current segment offset",
482 unit
='LENGTH', subtype
='DISTANCE',
485 linked_idx
: IntProperty(default
=-1)
488 # flag to handle wall's x_offset
489 # when set add wall offset value to segment offset
490 # pay attention at allowing per wall segment offset
492 manipulators
: CollectionProperty(type=archipack_manipulator
)
494 def find_in_selection(self
, context
):
495 raise NotImplementedError
497 def update(self
, context
, manipulable_refresh
=False):
498 props
= self
.find_in_selection(context
)
499 if props
is not None:
500 props
.update(context
, manipulable_refresh
)
502 def draw_insert(self
, context
, layout
, index
):
504 May implement draw for insert / remove segment operators
508 def draw(self
, context
, layout
, index
):
510 box
.prop(self
, "type", text
=str(index
+ 1))
511 self
.draw_insert(context
, box
, index
)
512 if self
.type in ['C_SEG']:
513 box
.prop(self
, "radius")
516 box
.prop(self
, "length")
518 # box.prop(self, "offset")
521 class archipack_slab_part(ArchipackSegment
, PropertyGroup
):
523 def draw_insert(self
, context
, layout
, index
):
524 row
= layout
.row(align
=True)
525 row
.operator("archipack.slab_insert", text
="Split").index
= index
526 row
.operator("archipack.slab_balcony", text
="Balcony").index
= index
527 row
.operator("archipack.slab_remove", text
="Remove").index
= index
529 def find_in_selection(self
, context
):
531 find witch selected object this instance belongs to
532 provide support for "copy to selected"
534 selected
= context
.selected_objects
[:]
536 props
= archipack_slab
.datablock(o
)
538 for part
in props
.parts
:
544 class archipack_slab(ArchipackObject
, Manipulable
, PropertyGroup
):
546 n_parts
: IntProperty(
549 default
=1, update
=update_manipulators
551 parts
: CollectionProperty(type=archipack_slab_part
)
552 closed
: BoolProperty(
555 options
={'SKIP_SAVE'},
556 update
=update_manipulators
559 parts_expand
: BoolProperty(
560 options
={'SKIP_SAVE'},
564 x_offset
: FloatProperty(
567 default
=0.0, precision
=2, step
=1,
568 unit
='LENGTH', subtype
='DISTANCE',
573 default
=0.3, precision
=2, step
=1,
574 unit
='LENGTH', subtype
='DISTANCE',
577 auto_synch
: BoolProperty(
579 description
="Keep wall in synch when editing",
581 update
=update_manipulators
585 # will only affect slab parts sharing a wall
587 childs
: CollectionProperty(type=archipack_slab_child
)
588 # Flag to prevent mesh update while making bulk changes over variables
590 # .auto_update = False
592 # .auto_update = True
593 auto_update
: BoolProperty(
594 options
={'SKIP_SAVE'},
596 update
=update_manipulators
599 def get_generator(self
):
600 g
= SlabGenerator(self
.parts
)
601 for part
in self
.parts
:
602 # type, radius, da, length
608 g
.locate_manipulators()
611 def insert_part(self
, context
, where
):
612 self
.manipulable_disable(context
)
613 self
.auto_update
= False
614 # the part we do split
615 part_0
= self
.parts
[where
]
619 part_1
= self
.parts
[len(self
.parts
) - 1]
620 part_1
.type = part_0
.type
621 part_1
.length
= part_0
.length
622 part_1
.offset
= part_0
.offset
623 part_1
.da
= part_0
.da
625 # move after current one
626 self
.parts
.move(len(self
.parts
) - 1, where
+ 1)
628 for c
in self
.childs
:
631 self
.setup_manipulators()
632 self
.auto_update
= True
634 def insert_balcony(self
, context
, where
):
635 self
.manipulable_disable(context
)
636 self
.auto_update
= False
638 # the part we do split
639 part_0
= self
.parts
[where
]
645 part_1
= self
.parts
[len(self
.parts
) - 1]
646 part_1
.type = "S_SEG"
648 part_1
.da
= part_0
.da
650 # move after current one
651 self
.parts
.move(len(self
.parts
) - 1, where
+ 1)
655 part_1
= self
.parts
[len(self
.parts
) - 1]
656 part_1
.type = part_0
.type
657 part_1
.length
= part_0
.length
658 part_1
.radius
= part_0
.radius
+ 1.5
659 part_1
.da
= part_0
.da
661 # move after current one
662 self
.parts
.move(len(self
.parts
) - 1, where
+ 2)
666 part_1
= self
.parts
[len(self
.parts
) - 1]
667 part_1
.type = "S_SEG"
669 part_1
.da
= part_0
.da
671 # move after current one
672 self
.parts
.move(len(self
.parts
) - 1, where
+ 3)
676 part_1
= self
.parts
[len(self
.parts
) - 1]
677 part_1
.type = part_0
.type
678 part_1
.length
= part_0
.length
679 part_1
.radius
= part_0
.radius
680 part_1
.offset
= part_0
.offset
681 part_1
.da
= part_0
.da
683 # move after current one
684 self
.parts
.move(len(self
.parts
) - 1, where
+ 4)
687 self
.setup_manipulators()
689 for c
in self
.childs
:
693 self
.auto_update
= True
694 g
= self
.get_generator()
696 o
= context
.active_object
697 bpy
.ops
.archipack
.fence(auto_manipulate
=False)
698 c
= context
.active_object
699 c
.select_set(state
=True)
700 c
.data
.archipack_fence
[0].n_parts
= 3
701 c
.select_set(state
=False)
703 c
.location
= Vector((0, 0, 0))
705 c
.location
= g
.segs
[where
+ 1].p0
.to_3d()
706 self
.add_child(c
.name
, where
+ 1)
707 # c.matrix_world.translation = g.segs[where].p1.to_3d()
708 o
.select_set(state
=True)
709 context
.view_layer
.objects
.active
= o
710 self
.relocate_childs(context
, o
, g
)
712 def add_part(self
, context
, length
):
713 self
.manipulable_disable(context
)
714 self
.auto_update
= False
718 self
.setup_manipulators()
719 self
.auto_update
= True
722 def add_child(self
, name
, idx
):
723 c
= self
.childs
.add()
727 def setup_childs(self
, o
, g
):
730 call after a boolean oop
732 # print("setup_childs")
734 itM
= o
.matrix_world
.inverted()
738 if (c
.data
and 'archipack_fence' in c
.data
):
739 pt
= (itM
@ c
.matrix_world
.translation
).to_2d()
740 for idx
, seg
in enumerate(g
.segs
):
741 # may be optimized with a bound check
742 res
, d
, t
= seg
.point_sur_segment(pt
)
746 dist
= abs(t
) * seg
.length
747 if dist
< dmax
and abs(d
) < dmax
:
748 # print("%s %s %s %s" % (idx, dist, d, c.name))
749 self
.add_child(c
.name
, idx
)
752 # store index of segments with p0 match
755 if o
.parent
is not None:
757 for i
, part
in enumerate(self
.parts
):
760 # find first child wall
762 for c
in o
.parent
.children
:
763 if c
.data
and "archipack_wall2" in c
.data
:
764 d
= c
.data
.archipack_wall2
[0]
768 og
= d
.get_generator()
770 for i
, part
in enumerate(self
.parts
):
772 while ji
< d
.n_parts
+ 1:
773 if (g
.segs
[i
].p0
- og
.segs
[ji
].p0
).length
< 0.005:
776 # print("link: %s to %s" % (i, ji))
780 def relocate_childs(self
, context
, o
, g
):
782 Move and resize childs after edition
784 # print("relocate_childs")
787 # must store - idx of shared segs
788 # -> store this in parts provide 1:1 map
789 # share type: full, start only, end only
790 # -> may compute on the fly with idx stored
791 # when full segment does match
792 # -update type, radius, length, a0, and da
793 # when start only does match
794 # -update type, radius, a0
795 # when end only does match
796 # -compute length/radius
798 # handle p0 and p1 changes right in Generator (archipack_2d)
799 # and retrieve params from there
801 if o
.parent
is not None:
804 for child
in o
.parent
.children
:
805 if child
.data
and "archipack_wall2" in child
.data
:
810 d
= wall
.data
.archipack_wall2
[0]
811 d
.auto_update
= False
812 w
= d
.get_generator()
817 for i
, part
in enumerate(self
.parts
):
818 idx
= part
.linked_idx
821 if i
+ 1 < self
.n_parts
:
822 next_idx
= self
.parts
[i
+ 1].linked_idx
824 next_idx
= self
.parts
[0].linked_idx
830 # start and shared: update rotation
831 a
= seg
.angle
- w
.segs
[idx
].angle
837 w
.segs
[last_idx
].p1
= seg
.p0
841 if (idx
+ 1 == next_idx
) or (next_idx
== 0 and i
+ 1 == self
.n_parts
):
842 # shared: should move last point
843 # and apply to next segments
844 # this is overridden for common segs
845 # but translate non common ones
846 dp
= seg
.p1
- w
.segs
[idx
].p1
849 # shared: transfer type too
850 if "C_" in part
.type:
851 d
.parts
[idx
].type = 'C_WALL'
852 w
.segs
[idx
] = CurvedSlab(seg
.c
, seg
.r
, seg
.a0
, seg
.da
)
854 d
.parts
[idx
].type = 'S_WALL'
855 w
.segs
[idx
] = StraightSlab(seg
.p
.copy(), seg
.v
.copy())
859 # only last is shared
860 # note: on next run will be part of start
861 last_idx
= next_idx
- 1
865 for i
, seg
in enumerate(w
.segs
):
867 d
.parts
[i
].a0
= seg
.delta_angle(last_seg
)
871 if "C_" in d
.parts
[i
].type:
872 d
.parts
[i
].radius
= seg
.r
873 d
.parts
[i
].da
= seg
.da
875 d
.parts
[i
].length
= max(0.01, seg
.length
)
877 wall
.select_set(state
=True)
878 context
.view_layer
.objects
.active
= wall
881 wall
.select_set(state
=False)
883 o
.select_set(state
=True)
884 context
.view_layer
.objects
.active
= o
886 wall
.matrix_world
= o
.matrix_world
.copy()
889 for child
in self
.childs
:
890 c
, d
= child
.get_child(context
)
894 a
= g
.segs
[child
.idx
].angle
895 x
, y
= g
.segs
[child
.idx
].p0
900 c
.select_set(state
=True)
902 # auto_update need object to be active to
903 # setup manipulators on the right object
904 context
.view_layer
.objects
.active
= c
906 d
.auto_update
= False
907 for i
, part
in enumerate(d
.parts
):
908 if "C_" in self
.parts
[i
+ child
.idx
].type:
909 part
.type = "C_FENCE"
911 part
.type = "S_FENCE"
912 part
.a0
= self
.parts
[i
+ child
.idx
].a0
913 part
.da
= self
.parts
[i
+ child
.idx
].da
914 part
.length
= self
.parts
[i
+ child
.idx
].length
915 part
.radius
= self
.parts
[i
+ child
.idx
].radius
916 d
.parts
[0].a0
= pi
/ 2
918 c
.select_set(state
=False)
920 context
.view_layer
.objects
.active
= o
922 c
.matrix_world
= tM
@ Matrix([
929 def remove_part(self
, context
, where
):
930 self
.manipulable_disable(context
)
931 self
.auto_update
= False
937 g
= self
.get_generator()
938 w
= g
.segs
[where
- 1]
939 w
.p1
= g
.segs
[where
].p1
941 if where
+ 1 < self
.n_parts
:
942 self
.parts
[where
+ 1].a0
= g
.segs
[where
+ 1].delta_angle(w
)
944 part
= self
.parts
[where
- 1]
946 if "C_" in part
.type:
949 part
.length
= w
.length
952 part
.a0
= w
.delta_angle(g
.segs
[where
- 2])
954 part
.a0
= w
.straight(1, 0).angle
956 for c
in self
.childs
:
959 self
.parts
.remove(where
)
961 # fix snap manipulators index
962 self
.setup_manipulators()
963 self
.auto_update
= True
965 def update_parts(self
, o
, update_childs
=False):
966 # print("update_parts")
970 # as last one is end point of last segment or closing one
972 for i
in range(len(self
.parts
), self
.n_parts
, -1):
974 self
.parts
.remove(i
- 1)
977 for i
in range(len(self
.parts
), self
.n_parts
):
981 self
.setup_manipulators()
983 g
= self
.get_generator()
985 if o
is not None and (row_change
or update_childs
):
986 self
.setup_childs(o
, g
)
990 def setup_manipulators(self
):
992 if len(self
.manipulators
) < 1:
993 s
= self
.manipulators
.add()
996 s
.normal
= Vector((0, 1, 0))
998 for i
in range(self
.n_parts
):
1000 n_manips
= len(p
.manipulators
)
1002 s
= p
.manipulators
.add()
1003 s
.type_key
= "ANGLE"
1005 p
.manipulators
[0].type_key
= 'ANGLE'
1007 s
= p
.manipulators
.add()
1009 s
.prop1_name
= "length"
1011 s
= p
.manipulators
.add()
1012 s
.type_key
= 'WALL_SNAP'
1013 s
.prop1_name
= str(i
)
1016 s
= p
.manipulators
.add()
1017 s
.type_key
= 'DUMB_STRING'
1018 s
.prop1_name
= str(i
+ 1)
1019 p
.manipulators
[2].prop1_name
= str(i
)
1020 p
.manipulators
[3].prop1_name
= str(i
+ 1)
1022 self
.parts
[-1].manipulators
[0].type_key
= 'DUMB_ANGLE'
1024 def is_cw(self
, pts
):
1028 d
+= (p
.x
* p0
.y
- p
.y
* p0
.x
)
1032 def interpolate_bezier(self
, pts
, wM
, p0
, p1
, resolution
):
1033 # straight segment, worth testing here
1034 # since this can lower points count by a resolution factor
1035 # use normalized to handle non linear t
1037 pts
.append(wM
@ p0
.co
.to_3d())
1039 v
= (p1
.co
- p0
.co
).normalized()
1040 d1
= (p0
.handle_right
- p0
.co
).normalized()
1041 d2
= (p1
.co
- p1
.handle_left
).normalized()
1042 if d1
== v
and d2
== v
:
1043 pts
.append(wM
@ p0
.co
.to_3d())
1045 seg
= interpolate_bezier(wM
@ p0
.co
,
1046 wM
@ p0
.handle_right
,
1047 wM
@ p1
.handle_left
,
1050 for i
in range(resolution
):
1051 pts
.append(seg
[i
].to_3d())
1053 def from_spline(self
, wM
, resolution
, spline
):
1055 if spline
.type == 'POLY':
1056 pts
= [wM
@ p
.co
.to_3d() for p
in spline
.points
]
1057 if spline
.use_cyclic_u
:
1059 elif spline
.type == 'BEZIER':
1060 points
= spline
.bezier_points
1061 for i
in range(1, len(points
)):
1064 self
.interpolate_bezier(pts
, wM
, p0
, p1
, resolution
)
1065 if spline
.use_cyclic_u
:
1068 self
.interpolate_bezier(pts
, wM
, p0
, p1
, resolution
)
1071 pts
.append(wM
@ points
[-1].co
)
1073 self
.from_points(pts
, spline
.use_cyclic_u
)
1075 def from_points(self
, pts
, closed
):
1078 pts
= list(reversed(pts
))
1080 self
.auto_update
= False
1082 self
.n_parts
= len(pts
) - 1
1084 self
.update_parts(None)
1088 for i
, p1
in enumerate(pts
):
1090 da
= atan2(dp
.y
, dp
.x
) - a0
1095 if i
>= len(self
.parts
):
1098 p
.length
= dp
.to_2d().length
1104 self
.closed
= closed
1105 self
.auto_update
= True
1107 def make_surface(self
, o
, verts
):
1111 bm
.verts
.ensure_lookup_table()
1112 for i
in range(1, len(verts
)):
1113 bm
.edges
.new((bm
.verts
[i
- 1], bm
.verts
[i
]))
1114 bm
.edges
.new((bm
.verts
[-1], bm
.verts
[0]))
1115 bm
.edges
.ensure_lookup_table()
1116 bmesh
.ops
.contextual_create(bm
, geom
=bm
.edges
)
1120 def unwrap_uv(self
, o
):
1122 bm
.from_mesh(o
.data
)
1123 for face
in bm
.faces
:
1124 face
.select
= face
.material_index
> 0
1126 bpy
.ops
.uv
.cube_project(scale_to_bounds
=False, correct_aspect
=True)
1128 for face
in bm
.faces
:
1129 face
.select
= face
.material_index
< 1
1131 bpy
.ops
.uv
.smart_project(use_aspect
=True, stretch_to_bounds
=False)
1134 def update(self
, context
, manipulable_refresh
=False, update_childs
=False):
1136 o
= self
.find_in_selection(context
, self
.auto_update
)
1141 # clean up manipulators before any data model change
1142 if manipulable_refresh
:
1143 self
.manipulable_disable(context
)
1145 g
= self
.update_parts(o
, update_childs
)
1147 # relocate before cutting segs
1148 self
.relocate_childs(context
, o
, g
)
1150 o
.select_set(state
=True)
1151 context
.view_layer
.objects
.active
= o
1155 g
.slab(context
, o
, self
)
1157 modif
= o
.modifiers
.get('Slab')
1159 modif
= o
.modifiers
.new('Slab', 'SOLIDIFY')
1160 modif
.use_quality_normals
= True
1161 modif
.use_even_offset
= True
1162 modif
.material_offset_rim
= 2
1163 modif
.material_offset
= 1
1165 modif
.thickness
= self
.z
1167 o
.data
.use_auto_smooth
= True
1168 bpy
.ops
.object.shade_smooth()
1171 self
.manipulators
[0].set_pts([
1175 ], normal
=g
.segs
[0].straight(-1, 0).v
.to_3d())
1177 # enable manipulators rebuild
1178 if manipulable_refresh
:
1179 self
.manipulable_refresh
= True
1182 self
.restore_context(context
)
1184 def manipulable_setup(self
, context
):
1187 this one assume context.active_object is the instance this
1188 data belongs to, failing to do so will result in wrong
1189 manipulators set on active object
1191 self
.manipulable_disable(context
)
1193 o
= context
.active_object
1195 self
.setup_manipulators()
1197 for i
, part
in enumerate(self
.parts
):
1198 if i
>= self
.n_parts
:
1203 self
.manip_stack
.append(part
.manipulators
[0].setup(context
, o
, part
))
1205 # length / radius + angle
1206 self
.manip_stack
.append(part
.manipulators
[1].setup(context
, o
, part
))
1209 self
.manip_stack
.append(part
.manipulators
[2].setup(context
, o
, self
))
1211 self
.manip_stack
.append(part
.manipulators
[3].setup(context
, o
, self
))
1213 for m
in self
.manipulators
:
1214 self
.manip_stack
.append(m
.setup(context
, o
, self
))
1216 def manipulable_invoke(self
, context
):
1218 call this in operator invoke()
1220 # print("manipulable_invoke")
1221 if self
.manipulate_mode
:
1222 self
.manipulable_disable(context
)
1225 o
= context
.active_object
1226 g
= self
.get_generator()
1227 # setup childs manipulators
1228 self
.setup_childs(o
, g
)
1229 self
.manipulable_setup(context
)
1230 self
.manipulate_mode
= True
1232 self
._manipulable
_invoke
(context
)
1237 def update_hole(self
, context
):
1238 # update parent's roof only when manipulated
1239 self
.update(context
, update_parent
=True)
1242 def update_operation(self
, context
):
1243 self
.reverse(context
, make_ccw
=(self
.operation
== 'INTERSECTION'))
1246 class archipack_slab_cutter_segment(ArchipackCutterPart
, PropertyGroup
):
1247 manipulators
: CollectionProperty(type=archipack_manipulator
)
1248 type : EnumProperty(
1251 ('DEFAULT', 'Side', 'Side with rake', 0),
1252 ('BOTTOM', 'Bottom', 'Bottom with gutter', 1),
1253 ('LINK', 'Side link', 'Side without decoration', 2),
1254 ('AXIS', 'Top', 'Top part with hip and beam', 3)
1255 # ('LINK_VALLEY', 'Side valley', 'Side with valley', 3),
1256 # ('LINK_HIP', 'Side hip', 'Side with hip', 4)
1262 def find_in_selection(self
, context
):
1263 selected
= context
.selected_objects
[:]
1265 d
= archipack_slab_cutter
.datablock(o
)
1267 for part
in d
.parts
:
1272 def draw(self
, layout
, context
, index
):
1274 box
.label(text
="Part:" + str(index
+ 1))
1275 # box.prop(self, "type", text=str(index + 1))
1276 box
.prop(self
, "length")
1277 box
.prop(self
, "a0")
1280 class archipack_slab_cutter(ArchipackCutter
, ArchipackObject
, Manipulable
, PropertyGroup
):
1281 parts
: CollectionProperty(type=archipack_slab_cutter_segment
)
1283 def update_points(self
, context
, o
, pts
, update_parent
=False):
1284 self
.auto_update
= False
1285 self
.from_points(pts
)
1286 self
.auto_update
= True
1288 self
.update_parent(context
, o
)
1290 def update_parent(self
, context
, o
):
1292 d
= archipack_slab
.datablock(o
.parent
)
1294 o
.parent
.select_set(state
=True)
1295 context
.view_layer
.objects
.active
= o
.parent
1297 o
.parent
.select_set(state
=False)
1298 context
.view_layer
.objects
.active
= o
1301 class ARCHIPACK_PT_slab(Panel
):
1302 """Archipack Slab"""
1303 bl_idname
= "ARCHIPACK_PT_slab"
1305 bl_space_type
= 'VIEW_3D'
1306 bl_region_type
= 'UI'
1307 # bl_context = 'object'
1308 bl_category
= 'Archipack'
1311 def poll(cls
, context
):
1312 return archipack_slab
.filter(context
.active_object
)
1314 def draw(self
, context
):
1315 o
= context
.active_object
1316 prop
= archipack_slab
.datablock(o
)
1319 layout
= self
.layout
1320 layout
.operator('archipack.slab_manipulate', icon
='VIEW_PAN')
1322 box
.operator('archipack.slab_cutter').parent
= o
.name
1326 box
.prop(prop
, 'auto_synch')
1329 if prop
.parts_expand
:
1330 row
.prop(prop
, 'parts_expand', icon
="TRIA_DOWN", text
="Parts", emboss
=False)
1331 box
.prop(prop
, 'n_parts')
1332 # box.prop(prop, 'closed')
1333 for i
, part
in enumerate(prop
.parts
):
1334 part
.draw(context
, layout
, i
)
1336 row
.prop(prop
, 'parts_expand', icon
="TRIA_RIGHT", text
="Parts", emboss
=False)
1339 class ARCHIPACK_PT_slab_cutter(Panel
):
1340 bl_idname
= "ARCHIPACK_PT_slab_cutter"
1341 bl_label
= "Slab Cutter"
1342 bl_space_type
= 'VIEW_3D'
1343 bl_region_type
= 'UI'
1344 bl_category
= 'Archipack'
1347 def poll(cls
, context
):
1348 return archipack_slab_cutter
.filter(context
.active_object
)
1350 def draw(self
, context
):
1351 prop
= archipack_slab_cutter
.datablock(context
.active_object
)
1354 layout
= self
.layout
1355 scene
= context
.scene
1357 box
.operator('archipack.slab_cutter_manipulate', icon
='VIEW_PAN')
1358 box
.prop(prop
, 'operation', text
="")
1360 box
.label(text
="From curve")
1361 box
.prop_search(prop
, "user_defined_path", scene
, "objects", text
="", icon
='OUTLINER_OB_CURVE')
1362 if prop
.user_defined_path
!= "":
1363 box
.prop(prop
, 'user_defined_resolution')
1364 # box.prop(prop, 'x_offset')
1365 # box.prop(prop, 'angle_limit')
1367 box.prop_search(prop, "boundary", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
1369 prop
.draw(layout
, context
)
1372 # ------------------------------------------------------------------
1373 # Define operator class to create object
1374 # ------------------------------------------------------------------
1377 class ARCHIPACK_OT_slab_insert(Operator
):
1378 bl_idname
= "archipack.slab_insert"
1380 bl_description
= "Insert part"
1381 bl_category
= 'Archipack'
1382 bl_options
= {'REGISTER', 'UNDO'}
1383 index
: IntProperty(default
=0)
1385 def execute(self
, context
):
1386 if context
.mode
== "OBJECT":
1387 d
= archipack_slab
.datablock(context
.active_object
)
1389 return {'CANCELLED'}
1390 d
.insert_part(context
, self
.index
)
1393 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1394 return {'CANCELLED'}
1397 class ARCHIPACK_OT_slab_balcony(Operator
):
1398 bl_idname
= "archipack.slab_balcony"
1400 bl_description
= "Insert part"
1401 bl_category
= 'Archipack'
1402 bl_options
= {'REGISTER', 'UNDO'}
1403 index
: IntProperty(default
=0)
1405 def execute(self
, context
):
1406 if context
.mode
== "OBJECT":
1407 d
= archipack_slab
.datablock(context
.active_object
)
1409 return {'CANCELLED'}
1410 d
.insert_balcony(context
, self
.index
)
1413 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1414 return {'CANCELLED'}
1417 class ARCHIPACK_OT_slab_remove(Operator
):
1418 bl_idname
= "archipack.slab_remove"
1420 bl_description
= "Remove part"
1421 bl_category
= 'Archipack'
1422 bl_options
= {'REGISTER', 'UNDO'}
1423 index
: IntProperty(default
=0)
1425 def execute(self
, context
):
1426 if context
.mode
== "OBJECT":
1427 d
= archipack_slab
.datablock(context
.active_object
)
1429 return {'CANCELLED'}
1430 d
.remove_part(context
, self
.index
)
1433 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1434 return {'CANCELLED'}
1437 # ------------------------------------------------------------------
1438 # Define operator class to create object
1439 # ------------------------------------------------------------------
1442 class ARCHIPACK_OT_slab(ArchipackCreateTool
, Operator
):
1443 bl_idname
= "archipack.slab"
1445 bl_description
= "Slab"
1446 bl_category
= 'Archipack'
1447 bl_options
= {'REGISTER', 'UNDO'}
1449 def create(self
, context
):
1450 m
= bpy
.data
.meshes
.new("Slab")
1451 o
= bpy
.data
.objects
.new("Slab", m
)
1452 d
= m
.archipack_slab
.add()
1453 # make manipulators selectable
1454 d
.manipulable_selectable
= True
1455 self
.link_object_to_scene(context
, o
)
1456 o
.select_set(state
=True)
1457 context
.view_layer
.objects
.active
= o
1459 self
.add_material(o
)
1462 # -----------------------------------------------------
1464 # -----------------------------------------------------
1465 def execute(self
, context
):
1466 if context
.mode
== "OBJECT":
1467 bpy
.ops
.object.select_all(action
="DESELECT")
1468 o
= self
.create(context
)
1469 o
.location
= bpy
.context
.scene
.cursor
.location
1470 o
.select_set(state
=True)
1471 context
.view_layer
.objects
.active
= o
1475 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1476 return {'CANCELLED'}
1479 class ARCHIPACK_OT_slab_from_curve(Operator
):
1480 bl_idname
= "archipack.slab_from_curve"
1481 bl_label
= "Slab curve"
1482 bl_description
= "Create a slab from a curve"
1483 bl_category
= 'Archipack'
1484 bl_options
= {'REGISTER', 'UNDO'}
1486 auto_manipulate
: BoolProperty(default
=True)
1489 def poll(self
, context
):
1490 return context
.active_object
is not None and context
.active_object
.type == 'CURVE'
1491 # -----------------------------------------------------
1492 # Draw (create UI interface)
1493 # -----------------------------------------------------
1494 # noinspection PyUnusedLocal
1496 def draw(self
, context
):
1497 layout
= self
.layout
1499 row
.label(text
="Use Properties panel (N) to define parms", icon
='INFO')
1501 def create(self
, context
):
1502 curve
= context
.active_object
1503 bpy
.ops
.archipack
.slab(auto_manipulate
=self
.auto_manipulate
)
1504 o
= context
.view_layer
.objects
.active
1505 d
= archipack_slab
.datablock(o
)
1506 spline
= curve
.data
.splines
[0]
1507 d
.from_spline(curve
.matrix_world
, 12, spline
)
1508 if spline
.type == 'POLY':
1509 pt
= spline
.points
[0].co
1510 elif spline
.type == 'BEZIER':
1511 pt
= spline
.bezier_points
[0].co
1513 pt
= Vector((0, 0, 0))
1515 o
.matrix_world
= curve
.matrix_world
@ Matrix
.Translation(pt
)
1518 # -----------------------------------------------------
1520 # -----------------------------------------------------
1521 def execute(self
, context
):
1522 if context
.mode
== "OBJECT":
1523 bpy
.ops
.object.select_all(action
="DESELECT")
1524 self
.create(context
)
1527 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1528 return {'CANCELLED'}
1531 class ARCHIPACK_OT_slab_from_wall(Operator
):
1532 bl_idname
= "archipack.slab_from_wall"
1534 bl_description
= "Create a slab from a wall"
1535 bl_category
= 'Archipack'
1536 bl_options
= {'REGISTER', 'UNDO'}
1538 auto_manipulate
: BoolProperty(default
=True)
1539 ceiling
: BoolProperty(default
=False)
1542 def poll(self
, context
):
1543 o
= context
.active_object
1544 return o
is not None and o
.data
is not None and 'archipack_wall2' in o
.data
1546 def create(self
, context
):
1547 wall
= context
.active_object
1548 wd
= wall
.data
.archipack_wall2
[0]
1549 bpy
.ops
.archipack
.slab(auto_manipulate
=False)
1550 o
= context
.view_layer
.objects
.active
1551 d
= archipack_slab
.datablock(o
)
1552 d
.auto_update
= False
1555 d
.n_parts
= wd
.n_parts
+ 1
1556 for part
in wd
.parts
:
1558 if "S_" in part
.type:
1562 p
.length
= part
.length
1563 p
.radius
= part
.radius
1566 d
.auto_update
= True
1569 o
.matrix_world
= Matrix([
1572 [0, 0, 1, wd
.z
+ d
.z
],
1574 ]) @ wall
.matrix_world
1576 o
.matrix_world
= wall
.matrix_world
.copy()
1577 bpy
.ops
.object.select_all(action
='DESELECT')
1578 # parenting childs to wall reference point
1579 if wall
.parent
is None:
1580 x
, y
, z
= wall
.bound_box
[0]
1581 context
.scene
.cursor
.location
= wall
.matrix_world
@ Vector((x
, y
, z
))
1583 context
.view_layer
.objects
.active
= wall
1584 bpy
.ops
.archipack
.reference_point()
1586 wall
.parent
.select_set(state
=True)
1587 context
.view_layer
.objects
.active
= wall
.parent
1588 wall
.select_set(state
=True)
1589 o
.select_set(state
=True)
1590 bpy
.ops
.archipack
.parent_to_reference()
1591 wall
.parent
.select_set(state
=False)
1595 # -----------------------------------------------------
1597 # -----------------------------------------------------
1598 def execute(self
, context
):
1599 if context
.mode
== "OBJECT":
1600 bpy
.ops
.object.select_all(action
="DESELECT")
1601 o
= self
.create(context
)
1602 o
.select_set(state
=True)
1603 context
.view_layer
.objects
.active
= o
1604 if self
.auto_manipulate
:
1605 bpy
.ops
.archipack
.slab_manipulate('INVOKE_DEFAULT')
1608 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1609 return {'CANCELLED'}
1612 class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool
, Operator
):
1613 bl_idname
= "archipack.slab_cutter"
1614 bl_label
= "Slab Cutter"
1615 bl_description
= "Slab Cutter"
1616 bl_category
= 'Archipack'
1617 bl_options
= {'REGISTER', 'UNDO'}
1619 parent
: StringProperty("")
1621 def create(self
, context
):
1622 m
= bpy
.data
.meshes
.new("Slab Cutter")
1623 o
= bpy
.data
.objects
.new("Slab Cutter", m
)
1624 d
= m
.archipack_slab_cutter
.add()
1625 parent
= context
.scene
.objects
.get(self
.parent
.strip())
1626 if parent
is not None:
1628 bbox
= parent
.bound_box
1634 o
.matrix_world
= parent
.matrix_world
@ Matrix([
1651 pd
= archipack_slab
.datablock(parent
)
1652 pd
.boundary
= o
.name
1654 o
.location
= context
.scene
.cursor
.location
1655 # make manipulators selectable
1656 d
.manipulable_selectable
= True
1657 self
.link_object_to_scene(context
, o
)
1658 o
.select_set(state
=True)
1659 context
.view_layer
.objects
.active
= o
1660 # self.add_material(o)
1662 update_operation(d
, context
)
1665 # -----------------------------------------------------
1667 # -----------------------------------------------------
1668 def execute(self
, context
):
1669 if context
.mode
== "OBJECT":
1670 bpy
.ops
.object.select_all(action
="DESELECT")
1671 o
= self
.create(context
)
1672 o
.select_set(state
=True)
1673 context
.view_layer
.objects
.active
= o
1677 self
.report({'WARNING'}, "Archipack: Option only valid in Object mode")
1678 return {'CANCELLED'}
1681 # ------------------------------------------------------------------
1682 # Define operator class to manipulate object
1683 # ------------------------------------------------------------------
1686 class ARCHIPACK_OT_slab_manipulate(Operator
):
1687 bl_idname
= "archipack.slab_manipulate"
1688 bl_label
= "Manipulate"
1689 bl_description
= "Manipulate"
1690 bl_options
= {'REGISTER', 'UNDO'}
1693 def poll(self
, context
):
1694 return archipack_slab
.filter(context
.active_object
)
1696 def invoke(self
, context
, event
):
1697 d
= archipack_slab
.datablock(context
.active_object
)
1698 d
.manipulable_invoke(context
)
1702 class ARCHIPACK_OT_slab_cutter_manipulate(Operator
):
1703 bl_idname
= "archipack.slab_cutter_manipulate"
1704 bl_label
= "Manipulate"
1705 bl_description
= "Manipulate"
1706 bl_options
= {'REGISTER', 'UNDO'}
1709 def poll(self
, context
):
1710 return archipack_slab_cutter
.filter(context
.active_object
)
1712 def invoke(self
, context
, event
):
1713 d
= archipack_slab_cutter
.datablock(context
.active_object
)
1714 d
.manipulable_invoke(context
)
1719 bpy
.utils
.register_class(archipack_slab_cutter_segment
)
1720 bpy
.utils
.register_class(archipack_slab_cutter
)
1721 Mesh
.archipack_slab_cutter
= CollectionProperty(type=archipack_slab_cutter
)
1722 bpy
.utils
.register_class(ARCHIPACK_OT_slab_cutter
)
1723 bpy
.utils
.register_class(ARCHIPACK_PT_slab_cutter
)
1724 bpy
.utils
.register_class(ARCHIPACK_OT_slab_cutter_manipulate
)
1726 bpy
.utils
.register_class(archipack_slab_material
)
1727 bpy
.utils
.register_class(archipack_slab_child
)
1728 bpy
.utils
.register_class(archipack_slab_part
)
1729 bpy
.utils
.register_class(archipack_slab
)
1730 Mesh
.archipack_slab
= CollectionProperty(type=archipack_slab
)
1731 bpy
.utils
.register_class(ARCHIPACK_PT_slab
)
1732 bpy
.utils
.register_class(ARCHIPACK_OT_slab
)
1733 bpy
.utils
.register_class(ARCHIPACK_OT_slab_insert
)
1734 bpy
.utils
.register_class(ARCHIPACK_OT_slab_balcony
)
1735 bpy
.utils
.register_class(ARCHIPACK_OT_slab_remove
)
1736 # bpy.utils.register_class(ARCHIPACK_OT_slab_manipulate_ctx)
1737 bpy
.utils
.register_class(ARCHIPACK_OT_slab_manipulate
)
1738 bpy
.utils
.register_class(ARCHIPACK_OT_slab_from_curve
)
1739 bpy
.utils
.register_class(ARCHIPACK_OT_slab_from_wall
)
1743 bpy
.utils
.unregister_class(archipack_slab_material
)
1744 bpy
.utils
.unregister_class(archipack_slab_child
)
1745 bpy
.utils
.unregister_class(archipack_slab_part
)
1746 bpy
.utils
.unregister_class(archipack_slab
)
1747 del Mesh
.archipack_slab
1748 bpy
.utils
.unregister_class(ARCHIPACK_PT_slab
)
1749 bpy
.utils
.unregister_class(ARCHIPACK_OT_slab
)
1750 bpy
.utils
.unregister_class(ARCHIPACK_OT_slab_insert
)
1751 bpy
.utils
.unregister_class(ARCHIPACK_OT_slab_balcony
)
1752 bpy
.utils
.unregister_class(ARCHIPACK_OT_slab_remove
)
1753 # bpy.utils.unregister_class(ARCHIPACK_OT_slab_manipulate_ctx)
1754 bpy
.utils
.unregister_class(ARCHIPACK_OT_slab_manipulate
)
1755 bpy
.utils
.unregister_class(ARCHIPACK_OT_slab_from_curve
)
1756 bpy
.utils
.unregister_class(ARCHIPACK_OT_slab_from_wall
)
1757 del Mesh
.archipack_slab_cutter
1758 bpy
.utils
.unregister_class(archipack_slab_cutter_segment
)
1759 bpy
.utils
.unregister_class(archipack_slab_cutter
)
1760 bpy
.utils
.unregister_class(ARCHIPACK_OT_slab_cutter
)
1761 bpy
.utils
.unregister_class(ARCHIPACK_PT_slab_cutter
)
1762 bpy
.utils
.unregister_class(ARCHIPACK_OT_slab_cutter_manipulate
)