Sun position: remove unused prop in HDRI mode
[blender-addons.git] / archipack / archipack_slab.py
blobae1d7be4e94d8c44a6e31649ac5c5a2f842dc938
1 # -*- coding:utf-8 -*-
3 # ##### BEGIN GPL LICENSE BLOCK #####
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
19 # ##### END GPL LICENSE BLOCK #####
21 # <pep8 compliant>
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
26 # ----------------------------------------------------------
27 # noinspection PyUnresolvedReferences
28 import bpy
29 # noinspection PyUnresolvedReferences
30 from bpy.types import Operator, PropertyGroup, Mesh, Panel
31 from bpy.props import (
32 FloatProperty, BoolProperty, IntProperty,
33 StringProperty, EnumProperty,
34 CollectionProperty
36 import bmesh
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,
45 ArchipackCutter,
46 ArchipackCutterPart
50 class Slab():
52 def __init__(self):
53 # self.colour_inactive = (1, 1, 1, 1)
54 pass
56 def set_offset(self, offset, last=None):
57 """
58 Offset line and compute intersection point
59 between segments
60 """
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)
69 if da < 0:
70 n.v = -n.v
71 a0 = n.angle
72 c = n.p - n.v
73 return CurvedSlab(c, radius, a0, da)
76 class StraightSlab(Slab, Line):
78 def __init__(self, p, v):
79 Line.__init__(self, p, v)
80 Slab.__init__(self)
83 class CurvedSlab(Slab, Arc):
85 def __init__(self, c, radius, a0, da):
86 Arc.__init__(self, c, radius, a0, da)
87 Slab.__init__(self)
90 class SlabGenerator(CutAblePolygon, CutAbleGenerator):
92 def __init__(self, parts):
93 self.parts = parts
94 self.segs = []
95 self.holes = []
96 self.convex = True
97 self.xsize = 0
99 def add_part(self, part):
101 if len(self.segs) < 1:
102 s = None
103 else:
104 s = self.segs[-1]
105 # start a new slab
106 if s is None:
107 if part.type == 'S_SEG':
108 p = Vector((0, 0))
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)
114 else:
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)
120 self.segs.append(s)
121 self.last_type = part.type
123 def set_offset(self):
124 last = None
125 for i, seg in enumerate(self.segs):
126 seg.set_offset(self.parts[i].offset, last)
127 last = seg.line
129 def close(self, closed):
130 # Make last segment implicit closing one
131 if closed:
132 part = self.parts[-1]
133 w = self.segs[-1]
134 dp = self.segs[0].p0 - self.segs[-1].p0
135 if "C_" in part.type:
136 dw = (w.p1 - w.p0)
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)
140 a0 = w.a0 + da
141 if a0 > pi:
142 a0 -= 2 * pi
143 if a0 < -pi:
144 a0 += 2 * pi
145 w.a0 = a0
146 else:
147 w.v = dp
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):
158 setup manipulators
160 for i, f in enumerate(self.segs):
162 manipulators = self.parts[i].manipulators
163 p0 = f.p0.to_3d()
164 p1 = f.p1.to_3d()
165 # angle from last to current segment
166 if i > 0:
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":
172 # segment length
173 manipulators[1].type_key = 'SIZE'
174 manipulators[1].prop1_name = "length"
175 manipulators[1].set_pts([p0, p1, (1, 0, 0)])
176 else:
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)])
187 # dumb segment id
188 manipulators[3].set_pts([p0, p1, (1, 0, 0)])
190 def get_verts(self, verts):
191 for s in self.segs:
192 if "Curved" in type(s).__name__:
193 for i in range(16):
194 # x, y = slab.line.lerp(i / 16)
195 verts.append(s.lerp(i / 16).to_3d())
196 else:
197 # x, y = s.line.p0
198 verts.append(s.p0.to_3d())
200 for i in range(33):
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)
210 ca = cos(a)
211 sa = sin(a)
212 rM = Matrix([
213 [ca, -sa],
214 [sa, ca]
216 # rotation center
217 p0 = self.segs[idx_from].p0
218 for i in range(idx_from + 1, len(self.segs)):
219 seg = self.segs[i]
220 # rotate seg
221 seg.rotate(a)
222 # rotate delta from rotation center to segment start
223 dp = rM @ (seg.p0 - p0)
224 seg.translate(dp)
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)
241 def limits(self):
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
249 self.limits()
251 self.as_lines(step_angle=0.0502)
252 # self.segs = [s.line for s in self.segs]
254 for b in o.children:
255 d = archipack_slab_cutter.datablock(b)
256 if d is not None:
257 g = d.ensure_direction()
258 g.change_coordsys(b.matrix_world, o.matrix_world)
259 self.slice(g)
261 def slab(self, context, o, d):
263 verts = []
264 self.get_verts(verts)
265 if len(verts) > 2:
267 bm = bmesh.new()
269 for v in verts:
270 bm.verts.new(v)
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,
281 angle_limit=0.01,
282 use_dissolve_boundaries=False,
283 verts=bm.verts,
284 edges=bm.edges,
285 delimit={'MATERIAL'})
287 bm.to_mesh(o.data)
288 bm.free()
289 # geom = bm.faces[:]
290 # verts = bm.verts[:]
291 # bmesh.ops.solidify(bm, geom=geom, thickness=d.z)
293 # merge with object
294 # bmed.bmesh_join(context, o, [bm], normal_update=True)
296 bpy.ops.object.mode_set(mode='OBJECT')
299 def update(self, context):
300 self.update(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)
311 materials_enum = (
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,
324 default='4',
325 update=update
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[:]
334 for o in selected:
335 props = archipack_slab.datablock(o)
336 if props:
337 for part in props.rail_mat:
338 if part == self:
339 return props
340 return None
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()
353 idx : IntProperty()
355 def get_child(self, context):
356 d = None
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]
361 return child, d
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
371 # find part index
372 idx = 0
373 for i, part in enumerate(d.parts):
374 if part == self:
375 idx = i
376 break
378 part = d.parts[idx]
379 a0 = 0
380 if idx > 0:
381 g = d.get_generator()
382 w0 = g.segs[idx - 1]
383 a0 = w0.straight(1).angle
384 if "C_" in self.type:
385 w = w0.straight_slab(part.a0, part.length)
386 else:
387 w = w0.curved_slab(part.a0, part.da, part.radius)
388 else:
389 if "C_" in self.type:
390 p = Vector((0, 0))
391 v = self.length * Vector((cos(self.a0), sin(self.a0)))
392 w = StraightSlab(p, v)
393 a0 = pi / 2
394 else:
395 c = -self.radius * Vector((cos(self.a0), sin(self.a0)))
396 w = CurvedSlab(c, self.radius, self.a0, pi)
398 # w0 - w - w1
399 if idx + 1 == d.n_parts:
400 dp = - w.p0
401 else:
402 dp = w.p1 - w.p0
404 if "C_" in self.type:
405 part.radius = 0.5 * dp.length
406 part.da = pi
407 a0 = atan2(dp.y, dp.x) - pi / 2 - a0
408 else:
409 part.length = dp.length
410 a0 = atan2(dp.y, dp.x) - a0
412 if a0 > pi:
413 a0 -= 2 * pi
414 if a0 < -pi:
415 a0 += 2 * pi
416 part.a0 = 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
423 else:
424 a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x)
426 if a0 > pi:
427 a0 -= 2 * pi
428 if a0 < -pi:
429 a0 += 2 * pi
430 part1.a0 = a0
432 d.auto_update = True
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
442 type : EnumProperty(
443 items=(
444 ('S_SEG', 'Straight', '', 0),
445 ('C_SEG', 'Curved', '', 1),
447 default='S_SEG',
448 update=update_type
450 length : FloatProperty(
451 name="Length",
452 min=0.01,
453 default=2.0,
454 update=update
456 radius : FloatProperty(
457 name="Radius",
458 min=0.5,
459 default=0.7,
460 update=update
462 da : FloatProperty(
463 name="Angle",
464 min=-pi,
465 max=pi,
466 default=pi / 2,
467 subtype='ANGLE', unit='ROTATION',
468 update=update
470 a0 : FloatProperty(
471 name="Start angle",
472 min=-2 * pi,
473 max=2 * pi,
474 default=0,
475 subtype='ANGLE', unit='ROTATION',
476 update=update
478 offset : FloatProperty(
479 name="Offset",
480 description="Add to current segment offset",
481 default=0,
482 unit='LENGTH', subtype='DISTANCE',
483 update=update
485 linked_idx : IntProperty(default=-1)
487 # @TODO:
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
506 pass
508 def draw(self, context, layout, index):
509 box = layout.box()
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")
514 box.prop(self, "da")
515 else:
516 box.prop(self, "length")
517 box.prop(self, "a0")
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[:]
535 for o in selected:
536 props = archipack_slab.datablock(o)
537 if props:
538 for part in props.parts:
539 if part == self:
540 return props
541 return None
544 class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
545 # boundary
546 n_parts : IntProperty(
547 name="Parts",
548 min=1,
549 default=1, update=update_manipulators
551 parts : CollectionProperty(type=archipack_slab_part)
552 closed : BoolProperty(
553 default=True,
554 name="Close",
555 options={'SKIP_SAVE'},
556 update=update_manipulators
558 # UI layout related
559 parts_expand : BoolProperty(
560 options={'SKIP_SAVE'},
561 default=False
564 x_offset : FloatProperty(
565 name="Offset",
566 min=-1000, max=1000,
567 default=0.0, precision=2, step=1,
568 unit='LENGTH', subtype='DISTANCE',
569 update=update
571 z : FloatProperty(
572 name="Thickness",
573 default=0.3, precision=2, step=1,
574 unit='LENGTH', subtype='DISTANCE',
575 update=update
577 auto_synch : BoolProperty(
578 name="Auto-Synch",
579 description="Keep wall in synch when editing",
580 default=True,
581 update=update_manipulators
583 # @TODO:
584 # Global slab offset
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
589 # use :
590 # .auto_update = False
591 # bulk changes
592 # .auto_update = True
593 auto_update : BoolProperty(
594 options={'SKIP_SAVE'},
595 default=True,
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
603 g.add_part(part)
605 g.set_offset()
607 g.close(self.closed)
608 g.locate_manipulators()
609 return g
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]
616 part_0.length /= 2
617 part_0.da /= 2
618 self.parts.add()
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
624 part_1.a0 = 0
625 # move after current one
626 self.parts.move(len(self.parts) - 1, where + 1)
627 self.n_parts += 1
628 for c in self.childs:
629 if c.idx > where:
630 c.idx += 1
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]
640 part_0.length /= 3
641 part_0.da /= 3
643 # 1st part 90deg
644 self.parts.add()
645 part_1 = self.parts[len(self.parts) - 1]
646 part_1.type = "S_SEG"
647 part_1.length = 1.5
648 part_1.da = part_0.da
649 part_1.a0 = -pi / 2
650 # move after current one
651 self.parts.move(len(self.parts) - 1, where + 1)
653 # 2nd part -90deg
654 self.parts.add()
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
660 part_1.a0 = pi / 2
661 # move after current one
662 self.parts.move(len(self.parts) - 1, where + 2)
664 # 3nd part -90deg
665 self.parts.add()
666 part_1 = self.parts[len(self.parts) - 1]
667 part_1.type = "S_SEG"
668 part_1.length = 1.5
669 part_1.da = part_0.da
670 part_1.a0 = pi / 2
671 # move after current one
672 self.parts.move(len(self.parts) - 1, where + 3)
674 # 4nd part -90deg
675 self.parts.add()
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
682 part_1.a0 = -pi / 2
683 # move after current one
684 self.parts.move(len(self.parts) - 1, where + 4)
686 self.n_parts += 4
687 self.setup_manipulators()
689 for c in self.childs:
690 if c.idx > where:
691 c.idx += 4
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)
702 # link to o
703 c.location = Vector((0, 0, 0))
704 c.parent = o
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
715 p = self.parts.add()
716 p.length = length
717 self.n_parts += 1
718 self.setup_manipulators()
719 self.auto_update = True
720 return p
722 def add_child(self, name, idx):
723 c = self.childs.add()
724 c.child_name = name
725 c.idx = idx
727 def setup_childs(self, o, g):
729 Store childs
730 call after a boolean oop
732 # print("setup_childs")
733 self.childs.clear()
734 itM = o.matrix_world.inverted()
736 dmax = 0.2
737 for c in o.children:
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)
743 # p1
744 # |-- x
745 # p0
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)
751 # synch wall
752 # store index of segments with p0 match
753 if self.auto_synch:
755 if o.parent is not None:
757 for i, part in enumerate(self.parts):
758 part.linked_idx = -1
760 # find first child wall
761 d = None
762 for c in o.parent.children:
763 if c.data and "archipack_wall2" in c.data:
764 d = c.data.archipack_wall2[0]
765 break
767 if d is not None:
768 og = d.get_generator()
769 j = 0
770 for i, part in enumerate(self.parts):
771 ji = j
772 while ji < d.n_parts + 1:
773 if (g.segs[i].p0 - og.segs[ji].p0).length < 0.005:
774 j = ji + 1
775 part.linked_idx = ji
776 # print("link: %s to %s" % (i, ji))
777 break
778 ji += 1
780 def relocate_childs(self, context, o, g):
782 Move and resize childs after edition
784 # print("relocate_childs")
786 # Wall child syncro
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
797 # @TODO:
798 # handle p0 and p1 changes right in Generator (archipack_2d)
799 # and retrieve params from there
800 if self.auto_synch:
801 if o.parent is not None:
802 wall = None
804 for child in o.parent.children:
805 if child.data and "archipack_wall2" in child.data:
806 wall = child
807 break
809 if wall is not None:
810 d = wall.data.archipack_wall2[0]
811 d.auto_update = False
812 w = d.get_generator()
814 last_idx = -1
816 # update og from g
817 for i, part in enumerate(self.parts):
818 idx = part.linked_idx
819 seg = g.segs[i]
821 if i + 1 < self.n_parts:
822 next_idx = self.parts[i + 1].linked_idx
823 elif d.closed:
824 next_idx = self.parts[0].linked_idx
825 else:
826 next_idx = -1
828 if idx > -1:
830 # start and shared: update rotation
831 a = seg.angle - w.segs[idx].angle
833 if abs(a) > 0.00001:
834 w.rotate(idx, a)
836 if last_idx > -1:
837 w.segs[last_idx].p1 = seg.p0
839 if next_idx > -1:
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
847 w.translate(idx, dp)
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)
853 else:
854 d.parts[idx].type = 'S_WALL'
855 w.segs[idx] = StraightSlab(seg.p.copy(), seg.v.copy())
856 last_idx = -1
858 elif next_idx > -1:
859 # only last is shared
860 # note: on next run will be part of start
861 last_idx = next_idx - 1
863 # update d from og
864 last_seg = None
865 for i, seg in enumerate(w.segs):
867 d.parts[i].a0 = seg.delta_angle(last_seg)
869 last_seg = seg
871 if "C_" in d.parts[i].type:
872 d.parts[i].radius = seg.r
873 d.parts[i].da = seg.da
874 else:
875 d.parts[i].length = max(0.01, seg.length)
877 wall.select_set(state=True)
878 context.view_layer.objects.active = wall
880 d.auto_update = True
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()
888 tM = o.matrix_world
889 for child in self.childs:
890 c, d = child.get_child(context)
891 if c is None:
892 continue
894 a = g.segs[child.idx].angle
895 x, y = g.segs[child.idx].p0
896 sa = sin(a)
897 ca = cos(a)
899 if d is not None:
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"
910 else:
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
917 d.auto_update = True
918 c.select_set(state=False)
920 context.view_layer.objects.active = o
921 # preTranslate
922 c.matrix_world = tM @ Matrix([
923 [sa, ca, 0, x],
924 [-ca, sa, 0, y],
925 [0, 0, 1, 0],
926 [0, 0, 0, 1]
929 def remove_part(self, context, where):
930 self.manipulable_disable(context)
931 self.auto_update = False
933 # preserve shape
934 # using generator
935 if where > 0:
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:
947 part.radius = w.r
948 else:
949 part.length = w.length
951 if where > 1:
952 part.a0 = w.delta_angle(g.segs[where - 2])
953 else:
954 part.a0 = w.straight(1, 0).angle
956 for c in self.childs:
957 if c.idx >= where:
958 c.idx -= 1
959 self.parts.remove(where)
960 self.n_parts -= 1
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")
967 # remove rows
968 # NOTE:
969 # n_parts+1
970 # as last one is end point of last segment or closing one
971 row_change = False
972 for i in range(len(self.parts), self.n_parts, -1):
973 row_change = True
974 self.parts.remove(i - 1)
976 # add rows
977 for i in range(len(self.parts), self.n_parts):
978 row_change = True
979 self.parts.add()
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)
988 return g
990 def setup_manipulators(self):
992 if len(self.manipulators) < 1:
993 s = self.manipulators.add()
994 s.type_key = "SIZE"
995 s.prop1_name = "z"
996 s.normal = Vector((0, 1, 0))
998 for i in range(self.n_parts):
999 p = self.parts[i]
1000 n_manips = len(p.manipulators)
1001 if n_manips < 1:
1002 s = p.manipulators.add()
1003 s.type_key = "ANGLE"
1004 s.prop1_name = "a0"
1005 p.manipulators[0].type_key = 'ANGLE'
1006 if n_manips < 2:
1007 s = p.manipulators.add()
1008 s.type_key = "SIZE"
1009 s.prop1_name = "length"
1010 if n_manips < 3:
1011 s = p.manipulators.add()
1012 s.type_key = 'WALL_SNAP'
1013 s.prop1_name = str(i)
1014 s.prop2_name = 'z'
1015 if n_manips < 4:
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):
1025 p0 = pts[0]
1026 d = 0
1027 for p in pts[1:]:
1028 d += (p.x * p0.y - p.y * p0.x)
1029 p0 = p
1030 return d > 0
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
1036 if resolution == 0:
1037 pts.append(wM @ p0.co.to_3d())
1038 else:
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())
1044 else:
1045 seg = interpolate_bezier(wM @ p0.co,
1046 wM @ p0.handle_right,
1047 wM @ p1.handle_left,
1048 wM @ p1.co,
1049 resolution + 1)
1050 for i in range(resolution):
1051 pts.append(seg[i].to_3d())
1053 def from_spline(self, wM, resolution, spline):
1054 pts = []
1055 if spline.type == 'POLY':
1056 pts = [wM @ p.co.to_3d() for p in spline.points]
1057 if spline.use_cyclic_u:
1058 pts.append(pts[0])
1059 elif spline.type == 'BEZIER':
1060 points = spline.bezier_points
1061 for i in range(1, len(points)):
1062 p0 = points[i - 1]
1063 p1 = points[i]
1064 self.interpolate_bezier(pts, wM, p0, p1, resolution)
1065 if spline.use_cyclic_u:
1066 p0 = points[-1]
1067 p1 = points[0]
1068 self.interpolate_bezier(pts, wM, p0, p1, resolution)
1069 pts.append(pts[0])
1070 else:
1071 pts.append(wM @ points[-1].co)
1073 self.from_points(pts, spline.use_cyclic_u)
1075 def from_points(self, pts, closed):
1077 if self.is_cw(pts):
1078 pts = list(reversed(pts))
1080 self.auto_update = False
1082 self.n_parts = len(pts) - 1
1084 self.update_parts(None)
1086 p0 = pts.pop(0)
1087 a0 = 0
1088 for i, p1 in enumerate(pts):
1089 dp = p1 - p0
1090 da = atan2(dp.y, dp.x) - a0
1091 if da > pi:
1092 da -= 2 * pi
1093 if da < -pi:
1094 da += 2 * pi
1095 if i >= len(self.parts):
1096 break
1097 p = self.parts[i]
1098 p.length = dp.to_2d().length
1099 p.dz = dp.z
1100 p.a0 = da
1101 a0 += da
1102 p0 = p1
1104 self.closed = closed
1105 self.auto_update = True
1107 def make_surface(self, o, verts):
1108 bm = bmesh.new()
1109 for v in verts:
1110 bm.verts.new(v)
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)
1117 bm.to_mesh(o.data)
1118 bm.free()
1120 def unwrap_uv(self, o):
1121 bm = bmesh.new()
1122 bm.from_mesh(o.data)
1123 for face in bm.faces:
1124 face.select = face.material_index > 0
1125 bm.to_mesh(o.data)
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
1130 bm.to_mesh(o.data)
1131 bpy.ops.uv.smart_project(use_aspect=True, stretch_to_bounds=False)
1132 bm.free()
1134 def update(self, context, manipulable_refresh=False, update_childs=False):
1136 o = self.find_in_selection(context, self.auto_update)
1138 if o is None:
1139 return
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
1153 g.cut(context, o)
1155 g.slab(context, o, self)
1157 modif = o.modifiers.get('Slab')
1158 if modif is None:
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
1166 modif.offset = 1.0
1167 o.data.use_auto_smooth = True
1168 bpy.ops.object.shade_smooth()
1170 # Height
1171 self.manipulators[0].set_pts([
1172 (0, 0, 0),
1173 (0, 0, -self.z),
1174 (-1, 0, 0)
1175 ], normal=g.segs[0].straight(-1, 0).v.to_3d())
1177 # enable manipulators rebuild
1178 if manipulable_refresh:
1179 self.manipulable_refresh = True
1181 # restore context
1182 self.restore_context(context)
1184 def manipulable_setup(self, context):
1186 NOTE:
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:
1199 break
1201 if i > 0:
1202 # start angle
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))
1208 # snap point
1209 self.manip_stack.append(part.manipulators[2].setup(context, o, self))
1210 # index
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)
1223 return False
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)
1234 return True
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(
1249 name="Type",
1250 items=(
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)
1258 default='DEFAULT',
1259 update=update_hole
1262 def find_in_selection(self, context):
1263 selected = context.selected_objects[:]
1264 for o in selected:
1265 d = archipack_slab_cutter.datablock(o)
1266 if d:
1267 for part in d.parts:
1268 if part == self:
1269 return d
1270 return None
1272 def draw(self, layout, context, index):
1273 box = layout.box()
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
1287 if update_parent:
1288 self.update_parent(context, o)
1290 def update_parent(self, context, o):
1292 d = archipack_slab.datablock(o.parent)
1293 if d is not None:
1294 o.parent.select_set(state=True)
1295 context.view_layer.objects.active = o.parent
1296 d.update(context)
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"
1304 bl_label = "Slab"
1305 bl_space_type = 'VIEW_3D'
1306 bl_region_type = 'UI'
1307 # bl_context = 'object'
1308 bl_category = 'Archipack'
1310 @classmethod
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)
1317 if prop is None:
1318 return
1319 layout = self.layout
1320 layout.operator('archipack.slab_manipulate', icon='VIEW_PAN')
1321 box = layout.box()
1322 box.operator('archipack.slab_cutter').parent = o.name
1323 box = layout.box()
1324 box.prop(prop, 'z')
1325 box = layout.box()
1326 box.prop(prop, 'auto_synch')
1327 box = layout.box()
1328 row = box.row()
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)
1335 else:
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'
1346 @classmethod
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)
1352 if prop is None:
1353 return
1354 layout = self.layout
1355 scene = context.scene
1356 box = layout.box()
1357 box.operator('archipack.slab_cutter_manipulate', icon='VIEW_PAN')
1358 box.prop(prop, 'operation', text="")
1359 box = layout.box()
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"
1379 bl_label = "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)
1388 if d is None:
1389 return {'CANCELLED'}
1390 d.insert_part(context, self.index)
1391 return {'FINISHED'}
1392 else:
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"
1399 bl_label = "Insert"
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)
1408 if d is None:
1409 return {'CANCELLED'}
1410 d.insert_balcony(context, self.index)
1411 return {'FINISHED'}
1412 else:
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"
1419 bl_label = "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)
1428 if d is None:
1429 return {'CANCELLED'}
1430 d.remove_part(context, self.index)
1431 return {'FINISHED'}
1432 else:
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"
1444 bl_label = "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
1458 self.load_preset(d)
1459 self.add_material(o)
1460 return o
1462 # -----------------------------------------------------
1463 # Execute
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
1472 self.manipulate()
1473 return {'FINISHED'}
1474 else:
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)
1488 @classmethod
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
1498 row = layout.row()
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
1512 else:
1513 pt = Vector((0, 0, 0))
1514 # pretranslate
1515 o.matrix_world = curve.matrix_world @ Matrix.Translation(pt)
1516 return o
1518 # -----------------------------------------------------
1519 # Execute
1520 # -----------------------------------------------------
1521 def execute(self, context):
1522 if context.mode == "OBJECT":
1523 bpy.ops.object.select_all(action="DESELECT")
1524 self.create(context)
1525 return {'FINISHED'}
1526 else:
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"
1533 bl_label = "->Slab"
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)
1541 @classmethod
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
1553 d.closed = True
1554 d.parts.clear()
1555 d.n_parts = wd.n_parts + 1
1556 for part in wd.parts:
1557 p = d.parts.add()
1558 if "S_" in part.type:
1559 p.type = "S_SEG"
1560 else:
1561 p.type = "C_SEG"
1562 p.length = part.length
1563 p.radius = part.radius
1564 p.da = part.da
1565 p.a0 = part.a0
1566 d.auto_update = True
1567 # pretranslate
1568 if self.ceiling:
1569 o.matrix_world = Matrix([
1570 [1, 0, 0, 0],
1571 [0, 1, 0, 0],
1572 [0, 0, 1, wd.z + d.z],
1573 [0, 0, 0, 1],
1574 ]) @ wall.matrix_world
1575 else:
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))
1582 # fix issue #9
1583 context.view_layer.objects.active = wall
1584 bpy.ops.archipack.reference_point()
1585 else:
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)
1593 return o
1595 # -----------------------------------------------------
1596 # Execute
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')
1606 return {'FINISHED'}
1607 else:
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:
1627 o.parent = parent
1628 bbox = parent.bound_box
1629 angle_90 = pi / 2
1630 x0, y0, z = bbox[0]
1631 x1, y1, z = bbox[6]
1632 x = 0.2 * (x1 - x0)
1633 y = 0.2 * (y1 - y0)
1634 o.matrix_world = parent.matrix_world @ Matrix([
1635 [1, 0, 0, -3 * x],
1636 [0, 1, 0, 0],
1637 [0, 0, 1, 0],
1638 [0, 0, 0, 1]
1640 p = d.parts.add()
1641 p.a0 = - angle_90
1642 p.length = y
1643 p = d.parts.add()
1644 p.a0 = angle_90
1645 p.length = x
1646 p = d.parts.add()
1647 p.a0 = angle_90
1648 p.length = y
1649 d.n_parts = 3
1650 # d.close = True
1651 pd = archipack_slab.datablock(parent)
1652 pd.boundary = o.name
1653 else:
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)
1661 self.load_preset(d)
1662 update_operation(d, context)
1663 return o
1665 # -----------------------------------------------------
1666 # Execute
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
1674 self.manipulate()
1675 return {'FINISHED'}
1676 else:
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'}
1692 @classmethod
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)
1699 return {'FINISHED'}
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'}
1708 @classmethod
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)
1715 return {'FINISHED'}
1718 def register():
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)
1742 def unregister():
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)