Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Leger <stephen@3dservices.ch>2017-07-22 14:25:28 +0300
committerStephen Leger <stephen@3dservices.ch>2017-07-22 14:26:04 +0300
commitc1ab9b4b9c6c0226f8d7789b92efda9b0f33cfd1 (patch)
tree37d5a97c758fa9af48d1dfb5428edd72072d882a /archipack/archipack_slab.py
parent5638a8783502138500912061dde0e8ee476d7fca (diff)
archipack: T52120 release to official
Diffstat (limited to 'archipack/archipack_slab.py')
-rw-r--r--archipack/archipack_slab.py1505
1 files changed, 1505 insertions, 0 deletions
diff --git a/archipack/archipack_slab.py b/archipack/archipack_slab.py
new file mode 100644
index 00000000..d29c1678
--- /dev/null
+++ b/archipack/archipack_slab.py
@@ -0,0 +1,1505 @@
+# -*- coding:utf-8 -*-
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+# ----------------------------------------------------------
+# Author: Stephen Leger (s-leger)
+#
+# ----------------------------------------------------------
+# noinspection PyUnresolvedReferences
+import bpy
+# noinspection PyUnresolvedReferences
+from bpy.types import Operator, PropertyGroup, Mesh, Panel
+from bpy.props import (
+ FloatProperty, BoolProperty, IntProperty,
+ StringProperty, EnumProperty,
+ CollectionProperty
+ )
+import bmesh
+from mathutils import Vector, Matrix
+from mathutils.geometry import interpolate_bezier
+from math import sin, cos, pi, atan2
+from .archipack_manipulator import Manipulable, archipack_manipulator
+from .archipack_object import ArchipackCreateTool, ArchipackObject
+from .archipack_2d import Line, Arc
+
+
+class Slab():
+
+ def __init__(self):
+ # self.colour_inactive = (1, 1, 1, 1)
+ pass
+
+ def set_offset(self, offset, last=None):
+ """
+ Offset line and compute intersection point
+ between segments
+ """
+ self.line = self.make_offset(offset, last)
+
+ def straight_slab(self, a0, length):
+ s = self.straight(length).rotate(a0)
+ return StraightSlab(s.p, s.v)
+
+ def curved_slab(self, a0, da, radius):
+ n = self.normal(1).rotate(a0).scale(radius)
+ if da < 0:
+ n.v = -n.v
+ a0 = n.angle
+ c = n.p - n.v
+ return CurvedSlab(c, radius, a0, da)
+
+
+class StraightSlab(Slab, Line):
+
+ def __init__(self, p, v):
+ Line.__init__(self, p, v)
+ Slab.__init__(self)
+
+
+class CurvedSlab(Slab, Arc):
+
+ def __init__(self, c, radius, a0, da):
+ Arc.__init__(self, c, radius, a0, da)
+ Slab.__init__(self)
+
+
+class SlabGenerator():
+
+ def __init__(self, parts):
+ self.parts = parts
+ self.segs = []
+
+ def add_part(self, part):
+
+ if len(self.segs) < 1:
+ s = None
+ else:
+ s = self.segs[-1]
+ # start a new slab
+ if s is None:
+ if part.type == 'S_SEG':
+ p = Vector((0, 0))
+ v = part.length * Vector((cos(part.a0), sin(part.a0)))
+ s = StraightSlab(p, v)
+ elif part.type == 'C_SEG':
+ c = -part.radius * Vector((cos(part.a0), sin(part.a0)))
+ s = CurvedSlab(c, part.radius, part.a0, part.da)
+ else:
+ if part.type == 'S_SEG':
+ s = s.straight_slab(part.a0, part.length)
+ elif part.type == 'C_SEG':
+ s = s.curved_slab(part.a0, part.da, part.radius)
+
+ self.segs.append(s)
+ self.last_type = part.type
+
+ def set_offset(self):
+ last = None
+ for i, seg in enumerate(self.segs):
+ seg.set_offset(self.parts[i].offset, last)
+ last = seg.line
+
+ """
+ def close(self, closed):
+ # Make last segment implicit closing one
+ if closed:
+ return
+ """
+
+ def close(self, closed):
+ # Make last segment implicit closing one
+ if closed:
+ part = self.parts[-1]
+ w = self.segs[-1]
+ dp = self.segs[0].p0 - self.segs[-1].p0
+ if "C_" in part.type:
+ dw = (w.p1 - w.p0)
+ w.r = part.radius / dw.length * dp.length
+ # angle pt - p0 - angle p0 p1
+ da = atan2(dp.y, dp.x) - atan2(dw.y, dw.x)
+ a0 = w.a0 + da
+ if a0 > pi:
+ a0 -= 2 * pi
+ if a0 < -pi:
+ a0 += 2 * pi
+ w.a0 = a0
+ else:
+ w.v = dp
+
+ if len(self.segs) > 1:
+ w.line = w.make_offset(self.parts[-1].offset, self.segs[-2])
+
+ w = self.segs[-1]
+ p1 = self.segs[0].line.p1
+ self.segs[0].line = self.segs[0].make_offset(self.parts[0].offset, w.line)
+ self.segs[0].line.p1 = p1
+
+ def locate_manipulators(self):
+ """
+ setup manipulators
+ """
+ for i, f in enumerate(self.segs):
+
+ manipulators = self.parts[i].manipulators
+ p0 = f.p0.to_3d()
+ p1 = f.p1.to_3d()
+ # angle from last to current segment
+ if i > 0:
+ v0 = self.segs[i - 1].straight(-1, 1).v.to_3d()
+ v1 = f.straight(1, 0).v.to_3d()
+ manipulators[0].set_pts([p0, v0, v1])
+
+ if type(f).__name__ == "StraightSlab":
+ # segment length
+ manipulators[1].type_key = 'SIZE'
+ manipulators[1].prop1_name = "length"
+ manipulators[1].set_pts([p0, p1, (1, 0, 0)])
+ else:
+ # segment radius + angle
+ v0 = (f.p0 - f.c).to_3d()
+ v1 = (f.p1 - f.c).to_3d()
+ manipulators[1].type_key = 'ARC_ANGLE_RADIUS'
+ manipulators[1].prop1_name = "da"
+ manipulators[1].prop2_name = "radius"
+ manipulators[1].set_pts([f.c.to_3d(), v0, v1])
+
+ # snap manipulator, dont change index !
+ manipulators[2].set_pts([p0, p1, (1, 0, 0)])
+ # dumb segment id
+ manipulators[3].set_pts([p0, p1, (1, 0, 0)])
+
+ def get_verts(self, verts):
+ for slab in self.segs:
+ if "Curved" in type(slab).__name__:
+ for i in range(16):
+ x, y = slab.line.lerp(i / 16)
+ verts.append((x, y, 0))
+ else:
+ x, y = slab.line.p0
+ verts.append((x, y, 0))
+ """
+ for i in range(33):
+ x, y = slab.line.lerp(i / 32)
+ verts.append((x, y, 0))
+ """
+
+ def rotate(self, idx_from, a):
+ """
+ apply rotation to all following segs
+ """
+ self.segs[idx_from].rotate(a)
+ ca = cos(a)
+ sa = sin(a)
+ rM = Matrix([
+ [ca, -sa],
+ [sa, ca]
+ ])
+ # rotation center
+ p0 = self.segs[idx_from].p0
+ for i in range(idx_from + 1, len(self.segs)):
+ seg = self.segs[i]
+ # rotate seg
+ seg.rotate(a)
+ # rotate delta from rotation center to segment start
+ dp = rM * (seg.p0 - p0)
+ seg.translate(dp)
+
+ def translate(self, idx_from, dp):
+ """
+ apply translation to all following segs
+ """
+ self.segs[idx_from].p1 += dp
+ for i in range(idx_from + 1, len(self.segs)):
+ self.segs[i].translate(dp)
+
+ def draw(self, context):
+ """
+ draw generator using gl
+ """
+ for seg in self.segs:
+ seg.draw(context, render=False)
+
+
+def update(self, context):
+ self.update(context)
+
+
+def update_manipulators(self, context):
+ self.update(context, manipulable_refresh=True)
+
+
+def update_path(self, context):
+ self.update_path(context)
+
+
+materials_enum = (
+ ('0', 'Ceiling', '', 0),
+ ('1', 'White', '', 1),
+ ('2', 'Concrete', '', 2),
+ ('3', 'Wood', '', 3),
+ ('4', 'Metal', '', 4),
+ ('5', 'Glass', '', 5)
+ )
+
+
+class archipack_slab_material(PropertyGroup):
+ index = EnumProperty(
+ items=materials_enum,
+ default='4',
+ update=update
+ )
+
+ def find_in_selection(self, context):
+ """
+ find witch selected object this instance belongs to
+ provide support for "copy to selected"
+ """
+ selected = [o for o in context.selected_objects]
+ for o in selected:
+ props = archipack_slab.datablock(o)
+ if props:
+ for part in props.rail_mat:
+ if part == self:
+ return props
+ return None
+
+ def update(self, context):
+ props = self.find_in_selection(context)
+ if props is not None:
+ props.update(context)
+
+
+class archipack_slab_child(PropertyGroup):
+ """
+ Store child fences to be able to sync
+ """
+ child_name = StringProperty()
+ idx = IntProperty()
+
+ def get_child(self, context):
+ d = None
+ child = context.scene.objects.get(self.child_name)
+ if child is not None and child.data is not None:
+ if 'archipack_fence' in child.data:
+ d = child.data.archipack_fence[0]
+ return child, d
+
+
+def update_type(self, context):
+
+ d = self.find_in_selection(context)
+
+ if d is not None and d.auto_update:
+
+ d.auto_update = False
+ # find part index
+ idx = 0
+ for i, part in enumerate(d.parts):
+ if part == self:
+ idx = i
+ break
+
+ part = d.parts[idx]
+ a0 = 0
+ if idx > 0:
+ g = d.get_generator()
+ w0 = g.segs[idx - 1]
+ a0 = w0.straight(1).angle
+ if "C_" in self.type:
+ w = w0.straight_slab(part.a0, part.length)
+ else:
+ w = w0.curved_slab(part.a0, part.da, part.radius)
+ else:
+ g = SlabGenerator(None)
+ g.add_part(self)
+ w = g.segs[0]
+
+ # w0 - w - w1
+ dp = w.p1 - w.p0
+ if "C_" in self.type:
+ part.radius = 0.5 * dp.length
+ part.da = pi
+ a0 = atan2(dp.y, dp.x) - pi / 2 - a0
+ else:
+ part.length = dp.length
+ a0 = atan2(dp.y, dp.x) - a0
+
+ if a0 > pi:
+ a0 -= 2 * pi
+ if a0 < -pi:
+ a0 += 2 * pi
+ part.a0 = a0
+
+ if idx + 1 < d.n_parts:
+ # adjust rotation of next part
+ part1 = d.parts[idx + 1]
+ if "C_" in part.type:
+ a0 = part1.a0 - pi / 2
+ else:
+ a0 = part1.a0 + w.straight(1).angle - atan2(dp.y, dp.x)
+
+ if a0 > pi:
+ a0 -= 2 * pi
+ if a0 < -pi:
+ a0 += 2 * pi
+ part1.a0 = a0
+
+ d.auto_update = True
+
+
+class ArchipackSegment():
+ """
+ A single manipulable polyline like segment
+ polyline like segment line or arc based
+ @TODO: share this base class with
+ stair, wall, fence, slab
+ """
+ type = EnumProperty(
+ items=(
+ ('S_SEG', 'Straight', '', 0),
+ ('C_SEG', 'Curved', '', 1),
+ ),
+ default='S_SEG',
+ update=update_type
+ )
+ length = FloatProperty(
+ name="length",
+ min=0.01,
+ default=2.0,
+ update=update
+ )
+ radius = FloatProperty(
+ name="radius",
+ min=0.5,
+ default=0.7,
+ update=update
+ )
+ da = FloatProperty(
+ name="angle",
+ min=-pi,
+ max=pi,
+ default=pi / 2,
+ subtype='ANGLE', unit='ROTATION',
+ update=update
+ )
+ a0 = FloatProperty(
+ name="start angle",
+ min=-2 * pi,
+ max=2 * pi,
+ default=0,
+ subtype='ANGLE', unit='ROTATION',
+ update=update
+ )
+ offset = FloatProperty(
+ name="Offset",
+ description="Add to current segment offset",
+ default=0,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ linked_idx = IntProperty(default=-1)
+
+ # @TODO:
+ # flag to handle wall's x_offset
+ # when set add wall offset value to segment offset
+ # pay attention at allowing per wall segment offset
+
+ manipulators = CollectionProperty(type=archipack_manipulator)
+
+ def find_in_selection(self, context):
+ raise NotImplementedError
+
+ def update(self, context, manipulable_refresh=False):
+ props = self.find_in_selection(context)
+ if props is not None:
+ props.update(context, manipulable_refresh)
+
+ def draw_insert(self, context, layout, index):
+ """
+ May implement draw for insert / remove segment operators
+ """
+ pass
+
+ def draw(self, context, layout, index):
+ box = layout.box()
+ row = box.row()
+ row.prop(self, "type", text=str(index + 1))
+ self.draw_insert(context, box, index)
+ if self.type in ['C_SEG']:
+ row = box.row()
+ row.prop(self, "radius")
+ row = box.row()
+ row.prop(self, "da")
+ else:
+ row = box.row()
+ row.prop(self, "length")
+ row = box.row()
+ row.prop(self, "a0")
+ row = box.row()
+ row.prop(self, "offset")
+ # row.prop(self, "linked_idx")
+
+
+class archipack_slab_part(ArchipackSegment, PropertyGroup):
+
+ def draw_insert(self, context, layout, index):
+ row = layout.row(align=True)
+ row.operator("archipack.slab_insert", text="Split").index = index
+ row.operator("archipack.slab_balcony", text="Balcony").index = index
+ row.operator("archipack.slab_remove", text="Remove").index = index
+
+ def find_in_selection(self, context):
+ """
+ find witch selected object this instance belongs to
+ provide support for "copy to selected"
+ """
+ selected = [o for o in context.selected_objects]
+ for o in selected:
+ props = archipack_slab.datablock(o)
+ if props:
+ for part in props.parts:
+ if part == self:
+ return props
+ return None
+
+
+class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
+ # boundary
+ n_parts = IntProperty(
+ name="parts",
+ min=1,
+ default=1, update=update_manipulators
+ )
+ parts = CollectionProperty(type=archipack_slab_part)
+ closed = BoolProperty(
+ default=False,
+ name="Close",
+ update=update_manipulators
+ )
+ # UI layout related
+ parts_expand = BoolProperty(
+ options={'SKIP_SAVE'},
+ default=False
+ )
+
+ x_offset = FloatProperty(
+ name="x offset",
+ min=-1000, max=1000,
+ default=0.0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ z = FloatProperty(
+ name="z",
+ default=0.3, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ auto_synch = BoolProperty(
+ name="AutoSynch",
+ description="Keep wall in synch when editing",
+ default=True,
+ update=update_manipulators
+ )
+ # @TODO:
+ # Global slab offset
+ # will only affect slab parts sharing a wall
+
+ childs = CollectionProperty(type=archipack_slab_child)
+ # Flag to prevent mesh update while making bulk changes over variables
+ # use :
+ # .auto_update = False
+ # bulk changes
+ # .auto_update = True
+ auto_update = BoolProperty(
+ options={'SKIP_SAVE'},
+ default=True,
+ update=update_manipulators
+ )
+
+ def get_generator(self):
+ g = SlabGenerator(self.parts)
+ for part in self.parts:
+ # type, radius, da, length
+ g.add_part(part)
+
+ g.set_offset()
+
+ g.close(self.closed)
+ g.locate_manipulators()
+ return g
+
+ def insert_part(self, context, where):
+ self.manipulable_disable(context)
+ self.auto_update = False
+ # the part we do split
+ part_0 = self.parts[where]
+ part_0.length /= 2
+ part_0.da /= 2
+ self.parts.add()
+ part_1 = self.parts[len(self.parts) - 1]
+ part_1.type = part_0.type
+ part_1.length = part_0.length
+ part_1.offset = part_0.offset
+ part_1.da = part_0.da
+ part_1.a0 = 0
+ # move after current one
+ self.parts.move(len(self.parts) - 1, where + 1)
+ self.n_parts += 1
+ for c in self.childs:
+ if c.idx > where:
+ c.idx += 1
+ self.setup_manipulators()
+ self.auto_update = True
+
+ def insert_balcony(self, context, where):
+ self.manipulable_disable(context)
+ self.auto_update = False
+
+ # the part we do split
+ part_0 = self.parts[where]
+ part_0.length /= 3
+ part_0.da /= 3
+
+ # 1st part 90deg
+ self.parts.add()
+ part_1 = self.parts[len(self.parts) - 1]
+ part_1.type = "S_SEG"
+ part_1.length = 1.5
+ part_1.da = part_0.da
+ part_1.a0 = -pi / 2
+ # move after current one
+ self.parts.move(len(self.parts) - 1, where + 1)
+
+ # 2nd part -90deg
+ self.parts.add()
+ part_1 = self.parts[len(self.parts) - 1]
+ part_1.type = part_0.type
+ part_1.length = part_0.length
+ part_1.radius = part_0.radius + 1.5
+ part_1.da = part_0.da
+ part_1.a0 = pi / 2
+ # move after current one
+ self.parts.move(len(self.parts) - 1, where + 2)
+
+ # 3nd part -90deg
+ self.parts.add()
+ part_1 = self.parts[len(self.parts) - 1]
+ part_1.type = "S_SEG"
+ part_1.length = 1.5
+ part_1.da = part_0.da
+ part_1.a0 = pi / 2
+ # move after current one
+ self.parts.move(len(self.parts) - 1, where + 3)
+
+ # 4nd part -90deg
+ self.parts.add()
+ part_1 = self.parts[len(self.parts) - 1]
+ part_1.type = part_0.type
+ part_1.length = part_0.length
+ part_1.radius = part_0.radius
+ part_1.offset = part_0.offset
+ part_1.da = part_0.da
+ part_1.a0 = -pi / 2
+ # move after current one
+ self.parts.move(len(self.parts) - 1, where + 4)
+
+ self.n_parts += 4
+ self.setup_manipulators()
+
+ for c in self.childs:
+ if c.idx > where:
+ c.idx += 4
+
+ self.auto_update = True
+ g = self.get_generator()
+
+ o = context.active_object
+ bpy.ops.archipack.fence(auto_manipulate=False)
+ c = context.active_object
+ c.select = True
+ c.data.archipack_fence[0].n_parts = 3
+ c.select = False
+ # link to o
+ c.location = Vector((0, 0, 0))
+ c.parent = o
+ c.location = g.segs[where + 1].p0.to_3d()
+ self.add_child(c.name, where + 1)
+ # c.matrix_world.translation = g.segs[where].p1.to_3d()
+ o.select = True
+ context.scene.objects.active = o
+ self.relocate_childs(context, o, g)
+
+ def add_part(self, context, length):
+ self.manipulable_disable(context)
+ self.auto_update = False
+ p = self.parts.add()
+ p.length = length
+ self.n_parts += 1
+ self.setup_manipulators()
+ self.auto_update = True
+ return p
+
+ def add_child(self, name, idx):
+ c = self.childs.add()
+ c.child_name = name
+ c.idx = idx
+
+ def setup_childs(self, o, g):
+ """
+ Store childs
+ call after a boolean oop
+ """
+ # print("setup_childs")
+ self.childs.clear()
+ itM = o.matrix_world.inverted()
+
+ dmax = 0.2
+ for c in o.children:
+ if (c.data and 'archipack_fence' in c.data):
+ pt = (itM * c.matrix_world.translation).to_2d()
+ for idx, seg in enumerate(g.segs):
+ # may be optimized with a bound check
+ res, d, t = seg.point_sur_segment(pt)
+ # p1
+ # |-- x
+ # p0
+ dist = abs(t) * seg.length
+ if dist < dmax and abs(d) < dmax:
+ # print("%s %s %s %s" % (idx, dist, d, c.name))
+ self.add_child(c.name, idx)
+
+ # synch wall
+ # store index of segments with p0 match
+ if self.auto_synch:
+
+ if o.parent is not None:
+
+ for i, part in enumerate(self.parts):
+ part.linked_idx = -1
+
+ # find first child wall
+ d = None
+ for c in o.parent.children:
+ if c.data and "archipack_wall2" in c.data:
+ d = c.data.archipack_wall2[0]
+ break
+
+ if d is not None:
+ og = d.get_generator()
+ j = 0
+ for i, part in enumerate(self.parts):
+ ji = j
+ while ji < d.n_parts + 1:
+ if (g.segs[i].p0 - og.segs[ji].p0).length < 0.005:
+ j = ji + 1
+ part.linked_idx = ji
+ # print("link: %s to %s" % (i, ji))
+ break
+ ji += 1
+
+ def relocate_childs(self, context, o, g):
+ """
+ Move and resize childs after edition
+ """
+ # print("relocate_childs")
+
+ # Wall child syncro
+ # must store - idx of shared segs
+ # -> store this in parts provide 1:1 map
+ # share type: full, start only, end only
+ # -> may compute on the fly with idx stored
+ # when full segment does match
+ # -update type, radius, length, a0, and da
+ # when start only does match
+ # -update type, radius, a0
+ # when end only does match
+ # -compute length/radius
+ # @TODO:
+ # handle p0 and p1 changes right in Generator (archipack_2d)
+ # and retrieve params from there
+ if self.auto_synch:
+ if o.parent is not None:
+ wall = None
+
+ for child in o.parent.children:
+ if child.data and "archipack_wall2" in child.data:
+ wall = child
+ break
+
+ if wall is not None:
+ d = wall.data.archipack_wall2[0]
+ d.auto_update = False
+ w = d.get_generator()
+
+ last_idx = -1
+
+ # update og from g
+ for i, part in enumerate(self.parts):
+ idx = part.linked_idx
+ seg = g.segs[i]
+
+ if i + 1 < self.n_parts:
+ next_idx = self.parts[i + 1].linked_idx
+ elif d.closed:
+ next_idx = self.parts[0].linked_idx
+ else:
+ next_idx = -1
+
+ if idx > -1:
+
+ # start and shared: update rotation
+ a = seg.angle - w.segs[idx].angle
+ if abs(a) > 0.00001:
+ w.rotate(idx, a)
+
+ if last_idx > -1:
+ w.segs[last_idx].p1 = seg.p0
+
+ if next_idx > -1:
+
+ if idx + 1 == next_idx:
+ # shared: should move last point
+ # and apply to next segments
+ # this is overriden for common segs
+ # but translate non common ones
+ dp = seg.p1 - w.segs[idx].p1
+ w.translate(idx, dp)
+
+ # shared: transfert type too
+ if "C_" in part.type:
+ d.parts[idx].type = 'C_WALL'
+ w.segs[idx] = CurvedSlab(seg.c, seg.r, seg.a0, seg.da)
+ else:
+ d.parts[idx].type = 'S_WALL'
+ w.segs[idx] = StraightSlab(seg.p.copy(), seg.v.copy())
+ last_idx = -1
+
+ elif next_idx > -1:
+ # only last is shared
+ # note: on next run will be part of start
+ last_idx = next_idx - 1
+
+ # update d from og
+ for i, seg in enumerate(w.segs):
+ if i > 0:
+ d.parts[i].a0 = seg.delta_angle(w.segs[i - 1])
+ else:
+ d.parts[i].a0 = seg.angle
+ if "C_" in d.parts[i].type:
+ d.parts[i].radius = seg.r
+ d.parts[i].da = seg.da
+ else:
+ d.parts[i].length = max(0.01, seg.length)
+
+ wall.select = True
+ context.scene.objects.active = wall
+
+ d.auto_update = True
+ wall.select = False
+
+ o.select = True
+ context.scene.objects.active = o
+
+ wall.matrix_world = o.matrix_world.copy()
+
+ tM = o.matrix_world
+ for child in self.childs:
+ c, d = child.get_child(context)
+ if c is None:
+ continue
+
+ a = g.segs[child.idx].angle
+ x, y = g.segs[child.idx].p0
+ sa = sin(a)
+ ca = cos(a)
+
+ if d is not None:
+ c.select = True
+
+ # auto_update need object to be active to
+ # setup manipulators on the right object
+ context.scene.objects.active = c
+
+ d.auto_update = False
+ for i, part in enumerate(d.parts):
+ if "C_" in self.parts[i + child.idx].type:
+ part.type = "C_FENCE"
+ else:
+ part.type = "S_FENCE"
+ part.a0 = self.parts[i + child.idx].a0
+ part.da = self.parts[i + child.idx].da
+ part.length = self.parts[i + child.idx].length
+ part.radius = self.parts[i + child.idx].radius
+ d.parts[0].a0 = pi / 2
+ d.auto_update = True
+ c.select = False
+
+ context.scene.objects.active = o
+ # preTranslate
+ c.matrix_world = tM * Matrix([
+ [sa, ca, 0, x],
+ [-ca, sa, 0, y],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]
+ ])
+
+ def remove_part(self, context, where):
+ self.manipulable_disable(context)
+ self.auto_update = False
+
+ # preserve shape
+ # using generator
+ if where > 0:
+
+ g = self.get_generator()
+ w = g.segs[where - 1]
+ w.p1 = g.segs[where].p1
+
+ if where + 1 < self.n_parts:
+ self.parts[where + 1].a0 = g.segs[where + 1].delta_angle(w)
+
+ part = self.parts[where - 1]
+
+ if "C_" in part.type:
+ part.radius = w.r
+ else:
+ part.length = w.length
+
+ if where > 1:
+ part.a0 = w.delta_angle(g.segs[where - 2])
+ else:
+ part.a0 = w.straight(1, 0).angle
+
+ for c in self.childs:
+ if c.idx >= where:
+ c.idx -= 1
+ self.parts.remove(where)
+ self.n_parts -= 1
+ # fix snap manipulators index
+ self.setup_manipulators()
+ self.auto_update = True
+
+ def update_parts(self, o, update_childs=False):
+ # print("update_parts")
+ # remove rows
+ # NOTE:
+ # n_parts+1
+ # as last one is end point of last segment or closing one
+ row_change = False
+ for i in range(len(self.parts), self.n_parts, -1):
+ row_change = True
+ self.parts.remove(i - 1)
+
+ # add rows
+ for i in range(len(self.parts), self.n_parts):
+ row_change = True
+ self.parts.add()
+
+ self.setup_manipulators()
+
+ g = self.get_generator()
+
+ if o is not None and (row_change or update_childs):
+ self.setup_childs(o, g)
+
+ return g
+
+ def setup_manipulators(self):
+
+ if len(self.manipulators) < 1:
+ s = self.manipulators.add()
+ s.type_key = "SIZE"
+ s.prop1_name = "z"
+ s.normal = Vector((0, 1, 0))
+
+ for i in range(self.n_parts):
+ p = self.parts[i]
+ n_manips = len(p.manipulators)
+ if n_manips < 1:
+ s = p.manipulators.add()
+ s.type_key = "ANGLE"
+ s.prop1_name = "a0"
+ if n_manips < 2:
+ s = p.manipulators.add()
+ s.type_key = "SIZE"
+ s.prop1_name = "length"
+ if n_manips < 3:
+ s = p.manipulators.add()
+ s.type_key = 'WALL_SNAP'
+ s.prop1_name = str(i)
+ s.prop2_name = 'z'
+ if n_manips < 4:
+ s = p.manipulators.add()
+ s.type_key = 'DUMB_STRING'
+ s.prop1_name = str(i + 1)
+ p.manipulators[2].prop1_name = str(i)
+ p.manipulators[3].prop1_name = str(i + 1)
+
+ self.parts[-1].manipulators[0].type_key = 'DUMB_ANGLE'
+
+ def is_cw(self, pts):
+ p0 = pts[0]
+ d = 0
+ for p in pts[1:]:
+ d += (p.x * p0.y - p.y * p0.x)
+ p0 = p
+ return d > 0
+
+ def interpolate_bezier(self, pts, wM, p0, p1, resolution):
+ # straight segment, worth testing here
+ # since this can lower points count by a resolution factor
+ # use normalized to handle non linear t
+ if resolution == 0:
+ pts.append(wM * p0.co.to_3d())
+ else:
+ v = (p1.co - p0.co).normalized()
+ d1 = (p0.handle_right - p0.co).normalized()
+ d2 = (p1.co - p1.handle_left).normalized()
+ if d1 == v and d2 == v:
+ pts.append(wM * p0.co.to_3d())
+ else:
+ seg = interpolate_bezier(wM * p0.co,
+ wM * p0.handle_right,
+ wM * p1.handle_left,
+ wM * p1.co,
+ resolution + 1)
+ for i in range(resolution):
+ pts.append(seg[i].to_3d())
+
+ def from_spline(self, wM, resolution, spline):
+ pts = []
+ if spline.type == 'POLY':
+ pts = [wM * p.co.to_3d() for p in spline.points]
+ if spline.use_cyclic_u:
+ pts.append(pts[0])
+ elif spline.type == 'BEZIER':
+ points = spline.bezier_points
+ for i in range(1, len(points)):
+ p0 = points[i - 1]
+ p1 = points[i]
+ self.interpolate_bezier(pts, wM, p0, p1, resolution)
+ if spline.use_cyclic_u:
+ p0 = points[-1]
+ p1 = points[0]
+ self.interpolate_bezier(pts, wM, p0, p1, resolution)
+ pts.append(pts[0])
+ else:
+ pts.append(wM * points[-1].co)
+
+ self.from_points(pts, spline.use_cyclic_u)
+
+ def from_points(self, pts, closed):
+
+ if self.is_cw(pts):
+ pts = list(reversed(pts))
+
+ self.auto_update = False
+
+ self.n_parts = len(pts) - 1
+
+ self.update_parts(None)
+
+ p0 = pts.pop(0)
+ a0 = 0
+ for i, p1 in enumerate(pts):
+ dp = p1 - p0
+ da = atan2(dp.y, dp.x) - a0
+ if da > pi:
+ da -= 2 * pi
+ if da < -pi:
+ da += 2 * pi
+ if i >= len(self.parts):
+ break
+ p = self.parts[i]
+ p.length = dp.to_2d().length
+ p.dz = dp.z
+ p.a0 = da
+ a0 += da
+ p0 = p1
+
+ self.closed = closed
+ self.auto_update = True
+
+ def make_surface(self, o, verts):
+ bm = bmesh.new()
+ for v in verts:
+ bm.verts.new(v)
+ bm.verts.ensure_lookup_table()
+ for i in range(1, len(verts)):
+ bm.edges.new((bm.verts[i - 1], bm.verts[i]))
+ bm.edges.new((bm.verts[-1], bm.verts[0]))
+ bm.edges.ensure_lookup_table()
+ bmesh.ops.contextual_create(bm, geom=bm.edges)
+ bm.to_mesh(o.data)
+ bm.free()
+
+ def unwrap_uv(self, o):
+ bm = bmesh.new()
+ bm.from_mesh(o.data)
+ for face in bm.faces:
+ face.select = face.material_index > 0
+ bm.to_mesh(o.data)
+ bpy.ops.uv.cube_project(scale_to_bounds=False, correct_aspect=True)
+
+ for face in bm.faces:
+ face.select = face.material_index < 1
+ bm.to_mesh(o.data)
+ bpy.ops.uv.smart_project(use_aspect=True, stretch_to_bounds=False)
+ bm.free()
+
+ def update(self, context, manipulable_refresh=False, update_childs=False):
+
+ o = self.find_in_selection(context, self.auto_update)
+
+ if o is None:
+ return
+
+ # clean up manipulators before any data model change
+ if manipulable_refresh:
+ self.manipulable_disable(context)
+
+ g = self.update_parts(o, update_childs)
+
+ verts = []
+
+ g.get_verts(verts)
+ if len(verts) > 2:
+ self.make_surface(o, verts)
+
+ modif = o.modifiers.get('Slab')
+ if modif is None:
+ modif = o.modifiers.new('Slab', 'SOLIDIFY')
+ modif.use_quality_normals = True
+ modif.use_even_offset = True
+ modif.material_offset_rim = 2
+ modif.material_offset = 1
+
+ modif.thickness = self.z
+ modif.offset = 1.0
+ o.data.use_auto_smooth = True
+ bpy.ops.object.shade_smooth()
+
+ # Height
+ self.manipulators[0].set_pts([
+ (0, 0, 0),
+ (0, 0, -self.z),
+ (-1, 0, 0)
+ ], normal=g.segs[0].straight(-1, 0).v.to_3d())
+
+ self.relocate_childs(context, o, g)
+
+ # enable manipulators rebuild
+ if manipulable_refresh:
+ self.manipulable_refresh = True
+
+ # restore context
+ self.restore_context(context)
+
+ def manipulable_setup(self, context):
+ """
+ NOTE:
+ this one assume context.active_object is the instance this
+ data belongs to, failing to do so will result in wrong
+ manipulators set on active object
+ """
+ self.manipulable_disable(context)
+
+ o = context.active_object
+
+ self.setup_manipulators()
+
+ for i, part in enumerate(self.parts):
+ if i >= self.n_parts:
+ break
+
+ if i > 0:
+ # start angle
+ self.manip_stack.append(part.manipulators[0].setup(context, o, part))
+
+ # length / radius + angle
+ self.manip_stack.append(part.manipulators[1].setup(context, o, part))
+
+ # snap point
+ self.manip_stack.append(part.manipulators[2].setup(context, o, self))
+ # index
+ self.manip_stack.append(part.manipulators[3].setup(context, o, self))
+
+ for m in self.manipulators:
+ self.manip_stack.append(m.setup(context, o, self))
+
+ def manipulable_invoke(self, context):
+ """
+ call this in operator invoke()
+ """
+ # print("manipulable_invoke")
+ if self.manipulate_mode:
+ self.manipulable_disable(context)
+ return False
+
+ o = context.active_object
+ g = self.get_generator()
+ # setup childs manipulators
+ self.setup_childs(o, g)
+ self.manipulable_setup(context)
+ self.manipulate_mode = True
+
+ self._manipulable_invoke(context)
+
+ return True
+
+
+class ARCHIPACK_PT_slab(Panel):
+ """Archipack Slab"""
+ bl_idname = "ARCHIPACK_PT_slab"
+ bl_label = "Slab"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ # bl_context = 'object'
+ bl_category = 'ArchiPack'
+
+ @classmethod
+ def poll(cls, context):
+ return archipack_slab.filter(context.active_object)
+
+ def draw(self, context):
+ prop = archipack_slab.datablock(context.active_object)
+ if prop is None:
+ return
+ layout = self.layout
+ row = layout.row(align=True)
+ # self.set_context_3dview(context, row)
+ row.operator('archipack.slab_manipulate', icon='HAND')
+ box = layout.box()
+ box.prop(prop, 'z')
+ box = layout.box()
+ box.prop(prop, 'auto_synch')
+ box = layout.box()
+ row = box.row()
+ if prop.parts_expand:
+ row.prop(prop, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False)
+ box.prop(prop, 'n_parts')
+ # box.prop(prop, 'closed')
+ for i, part in enumerate(prop.parts):
+ part.draw(context, layout, i)
+ else:
+ row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False)
+
+
+# ------------------------------------------------------------------
+# Define operator class to create object
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_slab_insert(Operator):
+ bl_idname = "archipack.slab_insert"
+ bl_label = "Insert"
+ bl_description = "Insert part"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+ index = IntProperty(default=0)
+
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ d = archipack_slab.datablock(context.active_object)
+ if d is None:
+ return {'CANCELLED'}
+ d.insert_part(context, self.index)
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+class ARCHIPACK_OT_slab_balcony(Operator):
+ bl_idname = "archipack.slab_balcony"
+ bl_label = "Insert"
+ bl_description = "Insert part"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+ index = IntProperty(default=0)
+
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ d = archipack_slab.datablock(context.active_object)
+ if d is None:
+ return {'CANCELLED'}
+ d.insert_balcony(context, self.index)
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+class ARCHIPACK_OT_slab_remove(Operator):
+ bl_idname = "archipack.slab_remove"
+ bl_label = "Remove"
+ bl_description = "Remove part"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+ index = IntProperty(default=0)
+
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ d = archipack_slab.datablock(context.active_object)
+ if d is None:
+ return {'CANCELLED'}
+ d.remove_part(context, self.index)
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+# ------------------------------------------------------------------
+# Define operator class to create object
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_slab(ArchipackCreateTool, Operator):
+ bl_idname = "archipack.slab"
+ bl_label = "Slab"
+ bl_description = "Slab"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def create(self, context):
+ m = bpy.data.meshes.new("Slab")
+ o = bpy.data.objects.new("Slab", m)
+ d = m.archipack_slab.add()
+ # make manipulators selectable
+ d.manipulable_selectable = True
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.objects.active = o
+ self.load_preset(d)
+ self.add_material(o)
+ return o
+
+ # -----------------------------------------------------
+ # Execute
+ # -----------------------------------------------------
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ bpy.ops.object.select_all(action="DESELECT")
+ o = self.create(context)
+ o.location = bpy.context.scene.cursor_location
+ o.select = True
+ context.scene.objects.active = o
+ self.manipulate()
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+class ARCHIPACK_OT_slab_from_curve(Operator):
+ bl_idname = "archipack.slab_from_curve"
+ bl_label = "Slab curve"
+ bl_description = "Create a slab from a curve"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ auto_manipulate = BoolProperty(default=True)
+
+ @classmethod
+ def poll(self, context):
+ return context.active_object is not None and context.active_object.type == 'CURVE'
+ # -----------------------------------------------------
+ # Draw (create UI interface)
+ # -----------------------------------------------------
+ # noinspection PyUnusedLocal
+
+ def draw(self, context):
+ layout = self.layout
+ row = layout.row()
+ row.label("Use Properties panel (N) to define parms", icon='INFO')
+
+ def create(self, context):
+ curve = context.active_object
+ bpy.ops.archipack.slab(auto_manipulate=self.auto_manipulate)
+ o = context.scene.objects.active
+ d = archipack_slab.datablock(o)
+ spline = curve.data.splines[0]
+ d.from_spline(curve.matrix_world, 12, spline)
+ if spline.type == 'POLY':
+ pt = spline.points[0].co
+ elif spline.type == 'BEZIER':
+ pt = spline.bezier_points[0].co
+ else:
+ pt = Vector((0, 0, 0))
+ # pretranslate
+ o.matrix_world = curve.matrix_world * Matrix([
+ [1, 0, 0, pt.x],
+ [0, 1, 0, pt.y],
+ [0, 0, 1, pt.z],
+ [0, 0, 0, 1]
+ ])
+ return o
+
+ # -----------------------------------------------------
+ # Execute
+ # -----------------------------------------------------
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ bpy.ops.object.select_all(action="DESELECT")
+ self.create(context)
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+class ARCHIPACK_OT_slab_from_wall(Operator):
+ bl_idname = "archipack.slab_from_wall"
+ bl_label = "->Slab"
+ bl_description = "Create a slab from a wall"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ auto_manipulate = BoolProperty(default=True)
+ ceiling = BoolProperty(default=False)
+
+ @classmethod
+ def poll(self, context):
+ o = context.active_object
+ return o is not None and o.data is not None and 'archipack_wall2' in o.data
+
+ def create(self, context):
+ wall = context.active_object
+ wd = wall.data.archipack_wall2[0]
+ bpy.ops.archipack.slab(auto_manipulate=False)
+ o = context.scene.objects.active
+ d = archipack_slab.datablock(o)
+ d.auto_update = False
+ d.closed = True
+ d.parts.clear()
+ d.n_parts = wd.n_parts + 1
+ for part in wd.parts:
+ p = d.parts.add()
+ if "S_" in part.type:
+ p.type = "S_SEG"
+ else:
+ p.type = "C_SEG"
+ p.length = part.length
+ p.radius = part.radius
+ p.da = part.da
+ p.a0 = part.a0
+ d.auto_update = True
+ # pretranslate
+ if self.ceiling:
+ o.matrix_world = Matrix([
+ [1, 0, 0, 0],
+ [0, 1, 0, 0],
+ [0, 0, 1, wd.z + d.z],
+ [0, 0, 0, 1],
+ ]) * wall.matrix_world
+ else:
+ o.matrix_world = wall.matrix_world.copy()
+ bpy.ops.object.select_all(action='DESELECT')
+ # parenting childs to wall reference point
+ if wall.parent is None:
+ x, y, z = wall.bound_box[0]
+ context.scene.cursor_location = wall.matrix_world * Vector((x, y, z))
+ # fix issue #9
+ context.scene.objects.active = wall
+ bpy.ops.archipack.reference_point()
+ else:
+ wall.parent.select = True
+ context.scene.objects.active = wall.parent
+ wall.select = True
+ o.select = True
+ bpy.ops.archipack.parent_to_reference()
+ wall.parent.select = False
+
+ return o
+
+ # -----------------------------------------------------
+ # Execute
+ # -----------------------------------------------------
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ bpy.ops.object.select_all(action="DESELECT")
+ o = self.create(context)
+ o.select = True
+ context.scene.objects.active = o
+ if self.auto_manipulate:
+ bpy.ops.archipack.slab_manipulate('INVOKE_DEFAULT')
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+
+# ------------------------------------------------------------------
+# Define operator class to manipulate object
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_slab_manipulate(Operator):
+ bl_idname = "archipack.slab_manipulate"
+ bl_label = "Manipulate"
+ bl_description = "Manipulate"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ return archipack_slab.filter(context.active_object)
+
+ def invoke(self, context, event):
+ d = archipack_slab.datablock(context.active_object)
+ d.manipulable_invoke(context)
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_class(archipack_slab_material)
+ bpy.utils.register_class(archipack_slab_child)
+ bpy.utils.register_class(archipack_slab_part)
+ bpy.utils.register_class(archipack_slab)
+ Mesh.archipack_slab = CollectionProperty(type=archipack_slab)
+ bpy.utils.register_class(ARCHIPACK_PT_slab)
+ bpy.utils.register_class(ARCHIPACK_OT_slab)
+ bpy.utils.register_class(ARCHIPACK_OT_slab_insert)
+ bpy.utils.register_class(ARCHIPACK_OT_slab_balcony)
+ bpy.utils.register_class(ARCHIPACK_OT_slab_remove)
+ # bpy.utils.register_class(ARCHIPACK_OT_slab_manipulate_ctx)
+ bpy.utils.register_class(ARCHIPACK_OT_slab_manipulate)
+ bpy.utils.register_class(ARCHIPACK_OT_slab_from_curve)
+ bpy.utils.register_class(ARCHIPACK_OT_slab_from_wall)
+
+
+def unregister():
+ bpy.utils.unregister_class(archipack_slab_material)
+ bpy.utils.unregister_class(archipack_slab_child)
+ bpy.utils.unregister_class(archipack_slab_part)
+ bpy.utils.unregister_class(archipack_slab)
+ del Mesh.archipack_slab
+ bpy.utils.unregister_class(ARCHIPACK_PT_slab)
+ bpy.utils.unregister_class(ARCHIPACK_OT_slab)
+ bpy.utils.unregister_class(ARCHIPACK_OT_slab_insert)
+ bpy.utils.unregister_class(ARCHIPACK_OT_slab_balcony)
+ bpy.utils.unregister_class(ARCHIPACK_OT_slab_remove)
+ # bpy.utils.unregister_class(ARCHIPACK_OT_slab_manipulate_ctx)
+ bpy.utils.unregister_class(ARCHIPACK_OT_slab_manipulate)
+ bpy.utils.unregister_class(ARCHIPACK_OT_slab_from_curve)
+ bpy.utils.unregister_class(ARCHIPACK_OT_slab_from_wall)