text: # -*- 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, CollectionProperty,
      StringProperty, EnumProperty, FloatVectorProperty
      )
  from .bmesh_utils import BmeshEdit as bmed
  from .panel import Panel as Lofter
  from mathutils import Vector, Matrix
  from mathutils.geometry import interpolate_bezier
  from math import sin, cos, pi, acos, atan2
  from .archipack_manipulator import Manipulable, archipack_manipulator
  from .archipack_2d import Line, Arc
  from .archipack_preset import ArchipackPreset, PresetMenuOperator
  from .archipack_object import ArchipackCreateTool, ArchipackObject
  
  
  class Fence():
  
      def __init__(self):
          # total distance from start
          self.dist = 0
          self.t_start = 0
          self.t_end = 0
          self.dz = 0
          self.z0 = 0
          self.a0 = 0
  
      def set_offset(self, offset, last=None):
          """
              Offset line and compute intersection point
              between segments
          """
          self.line = self.make_offset(offset, last)
  
      @property
      def t_diff(self):
          return self.t_end - self.t_start
  
      def straight_fence(self, a0, length):
          s = self.straight(length).rotate(a0)
          return StraightFence(s.p, s.v)
  
      def curved_fence(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 CurvedFence(c, radius, a0, da)
  
  
  class StraightFence(Fence, Line):
      def __str__(self):
          return "t_start:{} t_end:{} dist:{}".format(self.t_start, self.t_end, self.dist)
  
      def __init__(self, p, v):
          Fence.__init__(self)
          Line.__init__(self, p, v)
  
  
  class CurvedFence(Fence, Arc):
      def __str__(self):
          return "t_start:{} t_end:{} dist:{}".format(self.t_start, self.t_end, self.dist)
  
      def __init__(self, c, radius, a0, da):
          Fence.__init__(self)
          Arc.__init__(self, c, radius, a0, da)
  
  
  class FenceSegment():
      def __str__(self):
          return "t_start:{} t_end:{} n_step:{}  t_step:{} i_start:{} i_end:{}".format(
              self.t_start, self.t_end, self.n_step, self.t_step, self.i_start, self.i_end)
  
      def __init__(self, t_start, t_end, n_step, t_step, i_start, i_end):
          self.t_start = t_start
          self.t_end = t_end
          self.n_step = n_step
          self.t_step = t_step
          self.i_start = i_start
          self.i_end = i_end
  
  
  class FenceGenerator():
  
      def __init__(self, parts):
          self.parts = parts
          self.segs = []
          self.length = 0
          self.user_defined_post = None
          self.user_defined_uvs = None
          self.user_defined_mat = None
  
      def add_part(self, part):
  
          if len(self.segs) < 1:
              s = None
          else:
              s = self.segs[-1]
  
          # start a new fence
          if s is None:
              if part.type == 'S_FENCE':
                  p = Vector((0, 0))
                  v = part.length * Vector((cos(part.a0), sin(part.a0)))
                  s = StraightFence(p, v)
              elif part.type == 'C_FENCE':
                  c = -part.radius * Vector((cos(part.a0), sin(part.a0)))
                  s = CurvedFence(c, part.radius, part.a0, part.da)
          else:
              if part.type == 'S_FENCE':
                  s = s.straight_fence(part.a0, part.length)
              elif part.type == 'C_FENCE':
                  s = s.curved_fence(part.a0, part.da, part.radius)
  
          # s.dist = self.length
          # self.length += s.length
          self.segs.append(s)
          self.last_type = type
  
      def set_offset(self, offset):
          # @TODO:
          # re-evaluate length of offset line here
          last = None
          for seg in self.segs:
              seg.set_offset(offset, last)
              last = seg.line
  
      def param_t(self, angle_limit, post_spacing):
          """
              setup corners and fences dz
              compute index of fences wich belong to each group of fences between corners
              compute t of each fence
          """
          # segments are group of parts separated by limit angle
          self.segments = []
          i_start = 0
          t_start = 0
          dist_0 = 0
          z = 0
          self.length = 0
          n_parts = len(self.parts) - 1
          for i, f in enumerate(self.segs):
              f.dist = self.length
              self.length += f.line.length
  
          vz0 = Vector((1, 0))
          angle_z = 0
          for i, f in enumerate(self.segs):
              dz = self.parts[i].dz
              if f.dist > 0:
                  f.t_start = f.dist / self.length
              else:
                  f.t_start = 0
  
              f.t_end = (f.dist + f.line.length) / self.length
              f.z0 = z
              f.dz = dz
              z += dz
  
              if i < n_parts:
  
                  vz1 = Vector((self.segs[i + 1].length, self.parts[i + 1].dz))
                  angle_z = abs(vz0.angle_signed(vz1))
                  vz0 = vz1
  
                  if (abs(self.parts[i + 1].a0) >= angle_limit or angle_z >= angle_limit):
                      l_seg = f.dist + f.line.length - dist_0
                      t_seg = f.t_end - t_start
                      n_fences = max(1, int(l_seg / post_spacing))
                      t_fence = t_seg / n_fences
                      segment = FenceSegment(t_start, f.t_end, n_fences, t_fence, i_start, i)
                      dist_0 = f.dist + f.line.length
                      t_start = f.t_end
                      i_start = i
                      self.segments.append(segment)
  
              manipulators = self.parts[i].manipulators
              p0 = f.line.p0.to_3d()
              p1 = f.line.p1.to_3d()
              # angle from last to current segment
              if i > 0:
                  v0 = self.segs[i - 1].line.straight(-1, 1).v.to_3d()
                  v1 = f.line.straight(1, 0).v.to_3d()
                  manipulators[0].set_pts([p0, v0, v1])
  
              if type(f).__name__ == "StraightFence":
                  # 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.line.p0 - f.c).to_3d()
                  v1 = (f.line.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)])
  
          f = self.segs[-1]
          l_seg = f.dist + f.line.length - dist_0
          t_seg = f.t_end - t_start
          n_fences = max(1, int(l_seg / post_spacing))
          t_fence = t_seg / n_fences
          segment = FenceSegment(t_start, f.t_end, n_fences, t_fence, i_start, len(self.segs) - 1)
          self.segments.append(segment)
  
      def setup_user_defined_post(self, o, post_x, post_y, post_z):
          self.user_defined_post = o
          x = o.bound_box[6][0] - o.bound_box[0][0]
          y = o.bound_box[6][1] - o.bound_box[0][1]
          z = o.bound_box[6][2] - o.bound_box[0][2]
          self.user_defined_post_scale = Vector((post_x / x, post_y / -y, post_z / z))
          m = o.data
          # create vertex group lookup dictionary for names
          vgroup_names = {vgroup.index: vgroup.name for vgroup in o.vertex_groups}
          # create dictionary of vertex group assignments per vertex
          self.vertex_groups = [[vgroup_names[g.group] for g in v.groups] for v in m.vertices]
          # uvs
          uv_act = m.uv_layers.active
          if uv_act is not None:
              uv_layer = uv_act.data
              self.user_defined_uvs = [[uv_layer[li].uv for li in p.loop_indices] for p in m.polygons]
          else:
              self.user_defined_uvs = [[(0, 0) for i in p.vertices] for p in m.polygons]
          # material ids
          self.user_defined_mat = [p.material_index for p in m.polygons]
  
      def get_user_defined_post(self, tM, z0, z1, z2, slope, post_z, verts, faces, matids, uvs):
          f = len(verts)
          m = self.user_defined_post.data
          for i, g in enumerate(self.vertex_groups):
              co = m.vertices[i].co.copy()
              co.x *= self.user_defined_post_scale.x
              co.y *= self.user_defined_post_scale.y
              co.z *= self.user_defined_post_scale.z
              if 'Slope' in g:
                  co.z += co.y * slope
              verts.append(tM * co)
          matids += self.user_defined_mat
          faces += [tuple([i + f for i in p.vertices]) for p in m.polygons]
          uvs += self.user_defined_uvs
  
      def get_post(self, post, post_x, post_y, post_z, post_alt, sub_offset_x,
              id_mat, verts, faces, matids, uvs):
  
          n, dz, zl = post
          slope = dz * post_y
  
          if self.user_defined_post is not None:
              x, y = -n.v.normalized()
              p = n.p + sub_offset_x * n.v.normalized()
              tM = Matrix([
                  [x, y, 0, p.x],
                  [y, -x, 0, p.y],
                  [0, 0, 1, zl + post_alt],
                  [0, 0, 0, 1]
              ])
              self.get_user_defined_post(tM, zl, 0, 0, dz, post_z, verts, faces, matids, uvs)
              return
  
          z3 = zl + post_z + post_alt - slope
          z4 = zl + post_z + post_alt + slope
          z0 = zl + post_alt - slope
          z1 = zl + post_alt + slope
          vn = n.v.normalized()
          dx = post_x * vn
          dy = post_y * Vector((vn.y, -vn.x))
          oy = sub_offset_x * vn
          x0, y0 = n.p - dx + dy + oy
          x1, y1 = n.p - dx - dy + oy
          x2, y2 = n.p + dx - dy + oy
          x3, y3 = n.p + dx + dy + oy
          f = len(verts)
          verts.extend([(x0, y0, z0), (x0, y0, z3),
                      (x1, y1, z1), (x1, y1, z4),
                      (x2, y2, z1), (x2, y2, z4),
                      (x3, y3, z0), (x3, y3, z3)])
          faces.extend([(f, f + 1, f + 3, f + 2),
                      (f + 2, f + 3, f + 5, f + 4),
                      (f + 4, f + 5, f + 7, f + 6),
                      (f + 6, f + 7, f + 1, f),
                      (f, f + 2, f + 4, f + 6),
                      (f + 7, f + 5, f + 3, f + 1)])
          matids.extend([id_mat, id_mat, id_mat, id_mat, id_mat, id_mat])
          x = [(0, 0), (0, post_z), (post_x, post_z), (post_x, 0)]
          y = [(0, 0), (0, post_z), (post_y, post_z), (post_y, 0)]
          z = [(0, 0), (post_x, 0), (post_x, post_y), (0, post_y)]
          uvs.extend([x, y, x, y, z, z])
  
      def get_panel(self, subs, altitude, panel_x, panel_z, sub_offset_x, idmat, verts, faces, matids, uvs):
          n_subs = len(subs)
          if n_subs < 1:
              return
          f = len(verts)
          x0 = sub_offset_x - 0.5 * panel_x
          x1 = sub_offset_x + 0.5 * panel_x
          z0 = 0
          z1 = panel_z
          profile = [Vector((x0, z0)), Vector((x1, z0)), Vector((x1, z1)), Vector((x0, z1))]
          user_path_uv_v = []
          n_sections = n_subs - 1
          n, dz, zl = subs[0]
          p0 = n.p
          v0 = n.v.normalized()
          for s, section in enumerate(subs):
              n, dz, zl = section
              p1 = n.p
              if s < n_sections:
                  v1 = subs[s + 1][0].v.normalized()
              dir = (v0 + v1).normalized()
              scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1))))
              for p in profile:
                  x, y = n.p + scale * p.x * dir
                  z = zl + p.y + altitude
                  verts.append((x, y, z))
              if s > 0:
                  user_path_uv_v.append((p1 - p0).length)
              p0 = p1
              v0 = v1
  
          # build faces using Panel
          lofter = Lofter(
              # closed_shape, index, x, y, idmat
              True,
              [i for i in range(len(profile))],
              [p.x for p in profile],
              [p.y for p in profile],
              [idmat for i in range(len(profile))],
              closed_path=False,
              user_path_uv_v=user_path_uv_v,
              user_path_verts=n_subs
              )
          faces += lofter.faces(16, offset=f, path_type='USER_DEFINED')
          matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED')
          v = Vector((0, 0))
          uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED')
  
      def make_subs(self, x, y, z, post_y, altitude,
              sub_spacing, offset_x, sub_offset_x, mat, verts, faces, matids, uvs):
  
          t_post = (0.5 * post_y - y) / self.length
          t_spacing = (sub_spacing + y) / self.length
  
          for segment in self.segments:
              t_step = segment.t_step
              t_start = segment.t_start + t_post
              s = 0
              s_sub = t_step - 2 * t_post
              n_sub = int(s_sub / t_spacing)
              if n_sub > 0:
                  t_sub = s_sub / n_sub
              else:
                  t_sub = 1
              i = segment.i_start
              while s < segment.n_step:
                  t_cur = t_start + s * t_step
                  for j in range(1, n_sub):
                      t_s = t_cur + t_sub * j
                      while self.segs[i].t_end < t_s:
                          i += 1
                      f = self.segs[i]
                      t = (t_s - f.t_start) / f.t_diff
                      n = f.line.normal(t)
                      post = (n, f.dz / f.length, f.z0 + f.dz * t)
                      self.get_post(post, x, y, z, altitude, sub_offset_x, mat, verts, faces, matids, uvs)
                  s += 1
  
      def make_post(self, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs):
  
          for segment in self.segments:
              t_step = segment.t_step
              t_start = segment.t_start
              s = 0
              i = segment.i_start
              while s < segment.n_step:
                  t_cur = t_start + s * t_step
                  while self.segs[i].t_end < t_cur:
                      i += 1
                  f = self.segs[i]
                  t = (t_cur - f.t_start) / f.t_diff
                  n = f.line.normal(t)
                  post = (n, f.dz / f.line.length, f.z0 + f.dz * t)
                  # self.get_post(post, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs)
                  self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs)
                  s += 1
  
              if segment.i_end + 1 == len(self.segs):
                  f = self.segs[segment.i_end]
                  n = f.line.normal(1)
                  post = (n, f.dz / f.line.length, f.z0 + f.dz)
                  # self.get_post(post, x, y, z, altitude, x_offset, mat, verts, faces, matids, uvs)
                  self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs)
  
      def make_panels(self, x, z, post_y, altitude, panel_dist,
              offset_x, sub_offset_x, idmat, verts, faces, matids, uvs):
  
          t_post = (0.5 * post_y + panel_dist) / self.length
          for segment in self.segments:
              t_step = segment.t_step
              t_start = segment.t_start
              s = 0
              i = segment.i_start
              while s < segment.n_step:
                  subs = []
                  t_cur = t_start + s * t_step + t_post
                  t_end = t_start + (s + 1) * t_step - t_post
                  # find first section
                  while self.segs[i].t_end < t_cur and i < segment.i_end:
                      i += 1
                  f = self.segs[i]
                  # 1st section
                  t = (t_cur - f.t_start) / f.t_diff
                  n = f.line.normal(t)
                  subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t))
                  # crossing sections -> new segment
                  while i < segment.i_end:
                      f = self.segs[i]
                      if f.t_end < t_end:
                          if type(f).__name__ == 'CurvedFence':
                              # cant end after segment
                              t0 = max(0, (t_cur - f.t_start) / f.t_diff)
                              t1 = min(1, (t_end - f.t_start) / f.t_diff)
                              n_s = int(max(1, abs(f.da) * (5) / pi - 1))
                              dt = (t1 - t0) / n_s
                              for j in range(1, n_s + 1):
                                  t = t0 + dt * j
                                  n = f.line.sized_normal(t, 1)
                                  # n.p = f.lerp(x_offset)
                                  subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t))
                          else:
                              n = f.line.normal(1)
                              subs.append((n, f.dz / f.line.length, f.z0 + f.dz))
                      if f.t_end >= t_end:
                          break
                      elif f.t_start < t_end:
                          i += 1
  
                  f = self.segs[i]
                  # last section
                  if type(f).__name__ == 'CurvedFence':
                      # cant start before segment
                      t0 = max(0, (t_cur - f.t_start) / f.t_diff)
                      t1 = min(1, (t_end - f.t_start) / f.t_diff)
                      n_s = int(max(1, abs(f.da) * (5) / pi - 1))
                      dt = (t1 - t0) / n_s
                      for j in range(1, n_s + 1):
                          t = t0 + dt * j
                          n = f.line.sized_normal(t, 1)
                          # n.p = f.lerp(x_offset)
                          subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t))
                  else:
                      t = (t_end - f.t_start) / f.t_diff
                      n = f.line.normal(t)
                      subs.append((n, f.dz / f.line.length, f.z0 + f.dz * t))
  
                  # self.get_panel(subs, altitude, x, z, 0, idmat, verts, faces, matids, uvs)
                  self.get_panel(subs, altitude, x, z, sub_offset_x, idmat, verts, faces, matids, uvs)
                  s += 1
  
      def make_profile(self, profile, idmat,
              x_offset, z_offset, extend, verts, faces, matids, uvs):
  
          last = None
          for seg in self.segs:
              seg.p_line = seg.make_offset(x_offset, last)
              last = seg.p_line
  
          n_fences = len(self.segs) - 1
  
          if n_fences < 0:
              return
  
          sections = []
  
          f = self.segs[0]
  
          # first step
          if extend != 0 and f.p_line.length != 0:
              t = -extend / self.segs[0].p_line.length
              n = f.p_line.sized_normal(t, 1)
              # n.p = f.lerp(x_offset)
              sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz * t))
  
          # add first section
          n = f.p_line.sized_normal(0, 1)
          # n.p = f.lerp(x_offset)
          sections.append((n, f.dz / f.p_line.length, f.z0))
  
          for s, f in enumerate(self.segs):
              if f.p_line.length == 0:
                  continue
              if type(f).__name__ == 'CurvedFence':
                  n_s = int(max(1, abs(f.da) * 30 / pi - 1))
                  for i in range(1, n_s + 1):
                      t = i / n_s
                      n = f.p_line.sized_normal(t, 1)
                      # n.p = f.lerp(x_offset)
                      sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz * t))
              else:
                  n = f.p_line.sized_normal(1, 1)
                  # n.p = f.lerp(x_offset)
                  sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz))
  
          if extend != 0 and f.p_line.length != 0:
              t = 1 + extend / self.segs[-1].p_line.length
              n = f.p_line.sized_normal(t, 1)
              # n.p = f.lerp(x_offset)
              sections.append((n, f.dz / f.p_line.length, f.z0 + f.dz * t))
  
          user_path_verts = len(sections)
          offset = len(verts)
          if user_path_verts > 0:
              user_path_uv_v = []
              n, dz, z0 = sections[-1]
              sections[-1] = (n, dz, z0)
              n_sections = user_path_verts - 1
              n, dz, zl = sections[0]
              p0 = n.p
              v0 = n.v.normalized()
              for s, section in enumerate(sections):
                  n, dz, zl = section
                  p1 = n.p
                  if s < n_sections:
                      v1 = sections[s + 1][0].v.normalized()
                  dir = (v0 + v1).normalized()
                  scale = min(10, 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1)))))
                  for p in profile:
                      # x, y = n.p + scale * (x_offset + p.x) * dir
                      x, y = n.p + scale * p.x * dir
                      z = zl + p.y + z_offset
                      verts.append((x, y, z))
                  if s > 0:
                      user_path_uv_v.append((p1 - p0).length)
                  p0 = p1
                  v0 = v1
  
              # build faces using Panel
              lofter = Lofter(
                  # closed_shape, index, x, y, idmat
                  True,
                  [i for i in range(len(profile))],
                  [p.x for p in profile],
                  [p.y for p in profile],
                  [idmat for i in range(len(profile))],
                  closed_path=False,
                  user_path_uv_v=user_path_uv_v,
                  user_path_verts=user_path_verts
                  )
              faces += lofter.faces(16, offset=offset, path_type='USER_DEFINED')
              matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED')
              v = Vector((0, 0))
              uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED')
  
  
  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)
  
  
  def update_type(self, context):
  
      d = self.find_datablock_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_fence(part.a0, part.length)
              else:
                  w = w0.curved_fence(part.a0, part.da, part.radius)
          else:
              g = FenceGenerator(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
  
  
  materials_enum = (
              ('0', 'Wood', '', 0),
              ('1', 'Metal', '', 1),
              ('2', 'Glass', '', 2)
              )
  
  
  class archipack_fence_material(PropertyGroup):
      index = EnumProperty(
          items=materials_enum,
          default='0',
          update=update
          )
  
      def find_datablock_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_fence.datablock(o)
              if props:
                  for part in props.rail_mat:
                      if part == self:
                          return props
          return None
  
      def update(self, context):
          props = self.find_datablock_in_selection(context)
          if props is not None:
              props.update(context)
  
  
  class archipack_fence_part(PropertyGroup):
      type = EnumProperty(
              items=(
                  ('S_FENCE', 'Straight fence', '', 0),
                  ('C_FENCE', 'Curved fence', '', 1),
                  ),
              default='S_FENCE',
              update=update_type
              )
      length = FloatProperty(
              name="length",
              min=0.01,
              default=2.0,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      radius = FloatProperty(
              name="radius",
              min=0.01,
              default=0.7,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      da = FloatProperty(
              name="total angle",
              min=-pi,
              max=pi,
              default=pi / 2,
              subtype='ANGLE', unit='ROTATION',
              update=update
              )
      a0 = FloatProperty(
              name="angle",
              min=-2 * pi,
              max=2 * pi,
              default=0,
              subtype='ANGLE', unit='ROTATION',
              update=update
              )
      dz = FloatProperty(
              name="delta z",
              default=0,
              unit='LENGTH', subtype='DISTANCE'
              )
  
      manipulators = CollectionProperty(type=archipack_manipulator)
  
      def find_datablock_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_fence.datablock(o)
              if props is not None:
                  for part in props.parts:
                      if part == self:
                          return props
          return None
  
      def update(self, context, manipulable_refresh=False):
          props = self.find_datablock_in_selection(context)
          if props is not None:
              props.update(context, manipulable_refresh)
  
      def draw(self, layout, context, index):
          box = layout.box()
          row = box.row()
          row.prop(self, "type", text=str(index + 1))
          if self.type in ['C_FENCE']:
              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")
  
  
  class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
  
      parts = CollectionProperty(type=archipack_fence_part)
      user_defined_path = StringProperty(
              name="user defined",
              update=update_path
              )
      user_defined_spline = IntProperty(
              name="Spline index",
              min=0,
              default=0,
              update=update_path
              )
      user_defined_resolution = IntProperty(
              name="resolution",
              min=1,
              max=128,
              default=12, update=update_path
              )
      n_parts = IntProperty(
              name="parts",
              min=1,
              default=1, update=update_manipulators
              )
      x_offset = FloatProperty(
              name="x offset",
              default=0.0, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
  
      radius = FloatProperty(
              name="radius",
              min=0.01,
              default=0.7,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      da = FloatProperty(
              name="angle",
              min=-pi,
              max=pi,
              default=pi / 2,
              subtype='ANGLE', unit='ROTATION',
              update=update
              )
      angle_limit = FloatProperty(
              name="angle",
              min=0,
              max=2 * pi,
              default=pi / 8,
              subtype='ANGLE', unit='ROTATION',
              update=update_manipulators
              )
      shape = EnumProperty(
              items=(
                  ('RECTANGLE', 'Straight', '', 0),
                  ('CIRCLE', 'Curved ', '', 1)
                  ),
              default='RECTANGLE',
              update=update
              )
      post = BoolProperty(
              name='enable',
              default=True,
              update=update
              )
      post_spacing = FloatProperty(
              name="spacing",
              min=0.1,
              default=1.0, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      post_x = FloatProperty(
              name="width",
              min=0.001,
              default=0.04, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      post_y = FloatProperty(
              name="length",
              min=0.001, max=1000,
              default=0.04, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      post_z = FloatProperty(
              name="height",
              min=0.001,
              default=1, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      post_alt = FloatProperty(
              name="altitude",
              default=0, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      user_defined_post_enable = BoolProperty(
              name="User",
              update=update,
              default=True
              )
      user_defined_post = StringProperty(
              name="user defined",
              update=update
              )
      idmat_post = EnumProperty(
              name="Post",
              items=materials_enum,
              default='1',
              update=update
              )
      subs = BoolProperty(
              name='enable',
              default=False,
              update=update
              )
      subs_spacing = FloatProperty(
              name="spacing",
              min=0.05,
              default=0.10, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      subs_x = FloatProperty(
              name="width",
              min=0.001,
              default=0.02, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      subs_y = FloatProperty(
              name="length",
              min=0.001,
              default=0.02, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      subs_z = FloatProperty(
              name="height",
              min=0.001,
              default=1, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      subs_alt = FloatProperty(
              name="altitude",
              default=0, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      subs_offset_x = FloatProperty(
              name="offset",
              default=0.0, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      subs_bottom = EnumProperty(
              name="Bottom",
              items=(
                  ('STEP', 'Follow step', '', 0),
                  ('LINEAR', 'Linear', '', 1),
                  ),
              default='STEP',
              update=update
              )
      user_defined_subs_enable = BoolProperty(
              name="User",
              update=update,
              default=True
              )
      user_defined_subs = StringProperty(
              name="user defined",
              update=update
              )
      idmat_subs = EnumProperty(
              name="Subs",
              items=materials_enum,
              default='1',
              update=update
              )
      panel = BoolProperty(
              name='enable',
              default=True,
              update=update
              )
      panel_alt = FloatProperty(
              name="altitude",
              default=0.25, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      panel_x = FloatProperty(
              name="width",
              min=0.001,
              default=0.01, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      panel_z = FloatProperty(
              name="height",
              min=0.001,
              default=0.6, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      panel_dist = FloatProperty(
              name="space",
              min=0.001,
              default=0.05, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      panel_offset_x = FloatProperty(
              name="offset",
              default=0.0, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      idmat_panel = EnumProperty(
              name="Panels",
              items=materials_enum,
              default='2',
              update=update
              )
      rail = BoolProperty(
              name="enable",
              update=update,
              default=False
              )
      rail_n = IntProperty(
              name="number",
              default=1,
              min=0,
              max=31,
              update=update
              )
      rail_x = FloatVectorProperty(
              name="width",
              default=[
                  0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
                  0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
                  0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
                  0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05
              ],
              size=31,
              min=0.001,
              precision=2, step=1,
              unit='LENGTH',
              update=update
              )
      rail_z = FloatVectorProperty(
              name="height",
              default=[
                  0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
                  0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
                  0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
                  0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05
              ],
              size=31,
              min=0.001,
              precision=2, step=1,
              unit='LENGTH',
              update=update
              )
      rail_offset = FloatVectorProperty(
              name="offset",
              default=[
                  0, 0, 0, 0, 0, 0, 0, 0,
                  0, 0, 0, 0, 0, 0, 0, 0,
                  0, 0, 0, 0, 0, 0, 0, 0,
                  0, 0, 0, 0, 0, 0, 0
              ],
              size=31,
              precision=2, step=1,
              unit='LENGTH',
              update=update
              )
      rail_alt = FloatVectorProperty(
              name="altitude",
              default=[
                  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
                  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
                  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
                  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
              ],
              size=31,
              precision=2, step=1,
              unit='LENGTH',
              update=update
              )
      rail_mat = CollectionProperty(type=archipack_fence_material)
  
      handrail = BoolProperty(
              name="enable",
              update=update,
              default=True
              )
      handrail_offset = FloatProperty(
              name="offset",
              default=0.0, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      handrail_alt = FloatProperty(
              name="altitude",
              default=1.0, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      handrail_extend = FloatProperty(
              name="extend",
              min=0,
              default=0.1, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      handrail_slice = BoolProperty(
              name='slice',
              default=True,
              update=update
              )
      handrail_slice_right = BoolProperty(
              name='slice',
              default=True,
              update=update
              )
      handrail_profil = EnumProperty(
              name="Profil",
              items=(
                  ('SQUARE', 'Square', '', 0),
                  ('CIRCLE', 'Circle', '', 1),
                  ('COMPLEX', 'Circle over square', '', 2)
                  ),
              default='SQUARE',
              update=update
              )
      handrail_x = FloatProperty(
              name="width",
              min=0.001,
              default=0.04, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      handrail_y = FloatProperty(
              name="height",
              min=0.001,
              default=0.04, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      handrail_radius = FloatProperty(
              name="radius",
              min=0.001,
              default=0.02, precision=2, step=1,
              unit='LENGTH', subtype='DISTANCE',
              update=update
              )
      idmat_handrail = EnumProperty(
              name="Handrail",
              items=materials_enum,
              default='0',
              update=update
              )
  
      # UI layout related
      parts_expand = BoolProperty(
              default=False
              )
      rail_expand = BoolProperty(
              default=False
              )
      idmats_expand = BoolProperty(
              default=False
              )
      handrail_expand = BoolProperty(
              default=False
              )
      post_expand = BoolProperty(
              default=False
              )
      panel_expand = BoolProperty(
              default=False
              )
      subs_expand = BoolProperty(
              default=False
              )
  
      # 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 setup_manipulators(self):
  
          if len(self.manipulators) == 0:
              s = self.manipulators.add()
              s.prop1_name = "width"
              s = self.manipulators.add()
              s.prop1_name = "height"
              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 == 0:
                  s = p.manipulators.add()
                  s.type_key = "ANGLE"
                  s.prop1_name = "a0"
                  s = p.manipulators.add()
                  s.type_key = "SIZE"
                  s.prop1_name = "length"
                  s = p.manipulators.add()
                  # s.type_key = 'SNAP_POINT'
                  s.type_key = 'WALL_SNAP'
                  s.prop1_name = str(i)
                  s.prop2_name = 'post_z'
  
      def update_parts(self):
  
          # remove rails materials
          for i in range(len(self.rail_mat), self.rail_n, -1):
              self.rail_mat.remove(i - 1)
  
          # add rails
          for i in range(len(self.rail_mat), self.rail_n):
              self.rail_mat.add()
  
          # remove parts
          for i in range(len(self.parts), self.n_parts, -1):
              self.parts.remove(i - 1)
  
          # add parts
          for i in range(len(self.parts), self.n_parts):
              self.parts.add()
  
          self.setup_manipulators()
  
      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, context, wM, resolution, spline):
  
          o = self.find_in_selection(context)
  
          if o is None:
              return
  
          tM = wM.copy()
          tM.row[0].normalize()
          tM.row[1].normalize()
          tM.row[2].normalize()
          pts = []
          if spline.type == 'POLY':
              pt = spline.points[0].co
              pts = [wM * p.co.to_3d() for p in spline.points]
              if spline.use_cyclic_u:
                  pts.append(pts[0])
          elif spline.type == 'BEZIER':
              pt = spline.bezier_points[0].co
              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)
          auto_update = self.auto_update
          self.auto_update = False
  
          self.n_parts = len(pts) - 1
          self.update_parts()
  
          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
              p = self.parts[i]
              p.length = dp.to_2d().length
              p.dz = dp.z
              p.a0 = da
              a0 += da
              p0 = p1
  
          self.auto_update = auto_update
  
          o.matrix_world = tM * Matrix([
              [1, 0, 0, pt.x],
              [0, 1, 0, pt.y],
              [0, 0, 1, pt.z],
              [0, 0, 0, 1]
              ])
  
      def update_path(self, context):
          path = context.scene.objects.get(self.user_defined_path)
          if path is not None and path.type == 'CURVE':
              splines = path.data.splines
              if len(splines) > self.user_defined_spline:
                  self.from_spline(
                      context,
                      path.matrix_world,
                      self.user_defined_resolution,
                      splines[self.user_defined_spline])
  
      def get_generator(self):
          g = FenceGenerator(self.parts)
          for part in self.parts:
              # type, radius, da, length
              g.add_part(part)
  
          g.set_offset(self.x_offset)
          # param_t(da, part_length)
          g.param_t(self.angle_limit, self.post_spacing)
          return g
  
      def update(self, context, manipulable_refresh=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)
  
          self.update_parts()
  
          verts = []
          faces = []
          matids = []
          uvs = []
  
          g = self.get_generator()
  
          # depth at bottom
          # self.manipulators[1].set_pts([(0, 0, 0), (0, 0, self.height), (1, 0, 0)])
  
          if self.user_defined_post_enable:
              # user defined posts
              user_def_post = context.scene.objects.get(self.user_defined_post)
              if user_def_post is not None and user_def_post.type == 'MESH':
                  g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z)
  
          if self.post:
              g.make_post(0.5 * self.post_x, 0.5 * self.post_y, self.post_z,
                      self.post_alt, self.x_offset,
                      int(self.idmat_post), verts, faces, matids, uvs)
  
          # reset user def posts
          g.user_defined_post = None
  
          # user defined subs
          if self.user_defined_subs_enable:
              user_def_subs = context.scene.objects.get(self.user_defined_subs)
              if user_def_subs is not None and user_def_subs.type == 'MESH':
                  g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z)
  
          if self.subs:
              g.make_subs(0.5 * self.subs_x, 0.5 * self.subs_y, self.subs_z,
                      self.post_y, self.subs_alt, self.subs_spacing,
                      self.x_offset, self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs)
  
          g.user_defined_post = None
  
          if self.panel:
              g.make_panels(0.5 * self.panel_x, self.panel_z, self.post_y,
                      self.panel_alt, self.panel_dist, self.x_offset, self.panel_offset_x,
                      int(self.idmat_panel), verts, faces, matids, uvs)
  
          if self.rail:
              for i in range(self.rail_n):
                  x = 0.5 * self.rail_x[i]
                  y = self.rail_z[i]
                  rail = [Vector((-x, y)), Vector((-x, 0)), Vector((x, 0)), Vector((x, y))]
                  g.make_profile(rail, int(self.rail_mat[i].index), self.x_offset - self.rail_offset[i],
                          self.rail_alt[i], 0, verts, faces, matids, uvs)
  
          if self.handrail_profil == 'COMPLEX':
              sx = self.handrail_x
              sy = self.handrail_y
              handrail = [Vector((sx * x, sy * y)) for x, y in [
              (-0.28, 1.83), (-0.355, 1.77), (-0.415, 1.695), (-0.46, 1.605), (-0.49, 1.51), (-0.5, 1.415),
              (-0.49, 1.315), (-0.46, 1.225), (-0.415, 1.135), (-0.355, 1.06), (-0.28, 1.0), (-0.255, 0.925),
              (-0.33, 0.855), (-0.5, 0.855), (-0.5, 0.0), (0.5, 0.0), (0.5, 0.855), (0.33, 0.855), (0.255, 0.925),
              (0.28, 1.0), (0.355, 1.06), (0.415, 1.135), (0.46, 1.225), (0.49, 1.315), (0.5, 1.415),
              (0.49, 1.51), (0.46, 1.605), (0.415, 1.695), (0.355, 1.77), (0.28, 1.83), (0.19, 1.875),
              (0.1, 1.905), (0.0, 1.915), (-0.095, 1.905), (-0.19, 1.875)]]
  
          elif self.handrail_profil == 'SQUARE':
              x = 0.5 * self.handrail_x
              y = self.handrail_y
              handrail = [Vector((-x, y)), Vector((-x, 0)), Vector((x, 0)), Vector((x, y))]
          elif self.handrail_profil == 'CIRCLE':
              r = self.handrail_radius
              handrail = [Vector((r * sin(0.1 * -a * pi), r * (0.5 + cos(0.1 * -a * pi)))) for a in range(0, 20)]
  
          if self.handrail:
              g.make_profile(handrail, int(self.idmat_handrail), self.x_offset - self.handrail_offset,
                  self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs)
  
          bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs, weld=True, clean=True)
  
          # 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))
  
          for m in self.manipulators:
              self.manip_stack.append(m.setup(context, o, self))
  
  
  class ARCHIPACK_PT_fence(Panel):
      bl_idname = "ARCHIPACK_PT_fence"
      bl_label = "Fence"
      bl_space_type = 'VIEW_3D'
      bl_region_type = 'UI'
      bl_category = 'ArchiPack'
  
      @classmethod
      def poll(cls, context):
          return archipack_fence.filter(context.active_object)
  
      def draw(self, context):
          prop = archipack_fence.datablock(context.active_object)
          if prop is None:
              return
          scene = context.scene
          layout = self.layout
          row = layout.row(align=True)
          row.operator('archipack.fence_manipulate', icon='HAND')
          box = layout.box()
          # box.label(text="Styles")
          row = box.row(align=True)
          row.operator("archipack.fence_preset_menu", text=bpy.types.ARCHIPACK_OT_fence_preset_menu.bl_label)
          row.operator("archipack.fence_preset", text="", icon='ZOOMIN')
          row.operator("archipack.fence_preset", text="", icon='ZOOMOUT').remove_active = True
          box = layout.box()
          row = box.row(align=True)
          row.operator("archipack.fence_curve_update", text="", icon='FILE_REFRESH')
          row.prop_search(prop, "user_defined_path", scene, "objects", text="", icon='OUTLINER_OB_CURVE')
          if prop.user_defined_path is not "":
              box.prop(prop, 'user_defined_spline')
              box.prop(prop, 'user_defined_resolution')
          box.prop(prop, 'angle_limit')
          box.prop(prop, 'x_offset')
          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')
              for i, part in enumerate(prop.parts):
                  part.draw(layout, context, i)
          else:
              row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False)
  
          box = layout.box()
          row = box.row(align=True)
          if prop.handrail_expand:
              row.prop(prop, 'handrail_expand', icon="TRIA_DOWN", icon_only=True, text="Handrail", emboss=False)
          else:
              row.prop(prop, 'handrail_expand', icon="TRIA_RIGHT", icon_only=True, text="Handrail", emboss=False)
  
          row.prop(prop, 'handrail')
  
          if prop.handrail_expand:
              box.prop(prop, 'handrail_alt')
              box.prop(prop, 'handrail_offset')
              box.prop(prop, 'handrail_extend')
              box.prop(prop, 'handrail_profil')
              if prop.handrail_profil != 'CIRCLE':
                  box.prop(prop, 'handrail_x')
                  box.prop(prop, 'handrail_y')
              else:
                  box.prop(prop, 'handrail_radius')
              row = box.row(align=True)
              row.prop(prop, 'handrail_slice')
  
          box = layout.box()
          row = box.row(align=True)
          if prop.post_expand:
              row.prop(prop, 'post_expand', icon="TRIA_DOWN", icon_only=True, text="Post", emboss=False)
          else:
              row.prop(prop, 'post_expand', icon="TRIA_RIGHT", icon_only=True, text="Post", emboss=False)
          row.prop(prop, 'post')
          if prop.post_expand:
              box.prop(prop, 'post_spacing')
              box.prop(prop, 'post_x')
              box.prop(prop, 'post_y')
              box.prop(prop, 'post_z')
              box.prop(prop, 'post_alt')
              row = box.row(align=True)
              row.prop(prop, 'user_defined_post_enable', text="")
              row.prop_search(prop, "user_defined_post", scene, "objects", text="")
  
          box = layout.box()
          row = box.row(align=True)
          if prop.subs_expand:
              row.prop(prop, 'subs_expand', icon="TRIA_DOWN", icon_only=True, text="Subs", emboss=False)
          else:
              row.prop(prop, 'subs_expand', icon="TRIA_RIGHT", icon_only=True, text="Subs", emboss=False)
  
          row.prop(prop, 'subs')
          if prop.subs_expand:
              box.prop(prop, 'subs_spacing')
              box.prop(prop, 'subs_x')
              box.prop(prop, 'subs_y')
              box.prop(prop, 'subs_z')
              box.prop(prop, 'subs_alt')
              box.prop(prop, 'subs_offset_x')
              row = box.row(align=True)
              row.prop(prop, 'user_defined_subs_enable', text="")
              row.prop_search(prop, "user_defined_subs", scene, "objects", text="")
  
          box = layout.box()
          row = box.row(align=True)
          if prop.panel_expand:
              row.prop(prop, 'panel_expand', icon="TRIA_DOWN", icon_only=True, text="Panels", emboss=False)
          else:
              row.prop(prop, 'panel_expand', icon="TRIA_RIGHT", icon_only=True, text="Panels", emboss=False)
          row.prop(prop, 'panel')
          if prop.panel_expand:
              box.prop(prop, 'panel_dist')
              box.prop(prop, 'panel_x')
              box.prop(prop, 'panel_z')
              box.prop(prop, 'panel_alt')
              box.prop(prop, 'panel_offset_x')
  
          box = layout.box()
          row = box.row(align=True)
          if prop.rail_expand:
              row.prop(prop, 'rail_expand', icon="TRIA_DOWN", icon_only=True, text="Rails", emboss=False)
          else:
              row.prop(prop, 'rail_expand', icon="TRIA_RIGHT", icon_only=True, text="Rails", emboss=False)
          row.prop(prop, 'rail')
          if prop.rail_expand:
              box.prop(prop, 'rail_n')
              for i in range(prop.rail_n):
                  box = layout.box()
                  box.label(text="Rail " + str(i + 1))
                  box.prop(prop, 'rail_x', index=i)
                  box.prop(prop, 'rail_z', index=i)
                  box.prop(prop, 'rail_alt', index=i)
                  box.prop(prop, 'rail_offset', index=i)
                  box.prop(prop.rail_mat[i], 'index', text="")
  
          box = layout.box()
          row = box.row()
  
          if prop.idmats_expand:
              row.prop(prop, 'idmats_expand', icon="TRIA_DOWN", icon_only=True, text="Materials", emboss=False)
              box.prop(prop, 'idmat_handrail')
              box.prop(prop, 'idmat_panel')
              box.prop(prop, 'idmat_post')
              box.prop(prop, 'idmat_subs')
          else:
              row.prop(prop, 'idmats_expand', icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False)
  
  # ------------------------------------------------------------------
  # Define operator class to create object
  # ------------------------------------------------------------------
  
  
  class ARCHIPACK_OT_fence(ArchipackCreateTool, Operator):
      bl_idname = "archipack.fence"
      bl_label = "Fence"
      bl_description = "Fence"
      bl_category = 'Archipack'
      bl_options = {'REGISTER', 'UNDO'}
  
      def create(self, context):
          m = bpy.data.meshes.new("Fence")
          o = bpy.data.objects.new("Fence", m)
          d = m.archipack_fence.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
  
      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'}
  
  
  # ------------------------------------------------------------------
  # Define operator class to create object
  # ------------------------------------------------------------------
  
  class ARCHIPACK_OT_fence_curve_update(Operator):
      bl_idname = "archipack.fence_curve_update"
      bl_label = "Fence curve update"
      bl_description = "Update fence data from curve"
      bl_category = 'Archipack'
      bl_options = {'REGISTER', 'UNDO'}
  
      @classmethod
      def poll(self, context):
          return archipack_fence.filter(context.active_object)
  
      def draw(self, context):
          layout = self.layout
          row = layout.row()
          row.label("Use Properties panel (N) to define parms", icon='INFO')
  
      def execute(self, context):
          if context.mode == "OBJECT":
              d = archipack_fence.datablock(context.active_object)
              d.update_path(context)
              return {'FINISHED'}
          else:
              self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
              return {'CANCELLED'}
  
  
  class ARCHIPACK_OT_fence_from_curve(ArchipackCreateTool, Operator):
      bl_idname = "archipack.fence_from_curve"
      bl_label = "Fence curve"
      bl_description = "Create a fence from a curve"
      bl_category = 'Archipack'
      bl_options = {'REGISTER', 'UNDO'}
  
      @classmethod
      def poll(self, context):
          return context.active_object is not None and context.active_object.type == 'CURVE'
  
      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):
          o = None
          curve = context.active_object
          for i, spline in enumerate(curve.data.splines):
              bpy.ops.archipack.fence('INVOKE_DEFAULT', auto_manipulate=False)
              o = context.active_object
              d = archipack_fence.datablock(o)
              d.auto_update = False
              d.user_defined_spline = i
              d.user_defined_path = curve.name
              d.auto_update = True
          return o
  
      def execute(self, context):
          if context.mode == "OBJECT":
              bpy.ops.object.select_all(action="DESELECT")
              o = self.create(context)
              if o is not None:
                  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'}
  
  # ------------------------------------------------------------------
  # Define operator class to manipulate object
  # ------------------------------------------------------------------
  
  
  class ARCHIPACK_OT_fence_manipulate(Operator):
      bl_idname = "archipack.fence_manipulate"
      bl_label = "Manipulate"
      bl_description = "Manipulate"
      bl_options = {'REGISTER', 'UNDO'}
  
      @classmethod
      def poll(self, context):
          return archipack_fence.filter(context.active_object)
  
      def invoke(self, context, event):
          d = archipack_fence.datablock(context.active_object)
          d.manipulable_invoke(context)
          return {'FINISHED'}
  
  
  # ------------------------------------------------------------------
  # Define operator class to load / save presets
  # ------------------------------------------------------------------
  
  
  class ARCHIPACK_OT_fence_preset_menu(PresetMenuOperator, Operator):
      bl_idname = "archipack.fence_preset_menu"
      bl_label = "Fence Styles"
      preset_subdir = "archipack_fence"
  
  
  class ARCHIPACK_OT_fence_preset(ArchipackPreset, Operator):
      """Add a Fence Preset"""
      bl_idname = "archipack.fence_preset"
      bl_label = "Add Fence Style"
      preset_menu = "ARCHIPACK_OT_fence_preset_menu"
  
      @property
      def blacklist(self):
          return ['manipulators', 'n_parts', 'parts', 'user_defined_path', 'user_defined_spline']
  
  
  def register():
      bpy.utils.register_class(archipack_fence_material)
      bpy.utils.register_class(archipack_fence_part)
      bpy.utils.register_class(archipack_fence)
      Mesh.archipack_fence = CollectionProperty(type=archipack_fence)
      bpy.utils.register_class(ARCHIPACK_OT_fence_preset_menu)
      bpy.utils.register_class(ARCHIPACK_PT_fence)
      bpy.utils.register_class(ARCHIPACK_OT_fence)
      bpy.utils.register_class(ARCHIPACK_OT_fence_preset)
      bpy.utils.register_class(ARCHIPACK_OT_fence_manipulate)
      bpy.utils.register_class(ARCHIPACK_OT_fence_from_curve)
      bpy.utils.register_class(ARCHIPACK_OT_fence_curve_update)
  
  
  def unregister():
      bpy.utils.unregister_class(archipack_fence_material)
      bpy.utils.unregister_class(archipack_fence_part)
      bpy.utils.unregister_class(archipack_fence)
      del Mesh.archipack_fence
      bpy.utils.unregister_class(ARCHIPACK_OT_fence_preset_menu)
      bpy.utils.unregister_class(ARCHIPACK_PT_fence)
      bpy.utils.unregister_class(ARCHIPACK_OT_fence)
      bpy.utils.unregister_class(ARCHIPACK_OT_fence_preset)
      bpy.utils.unregister_class(ARCHIPACK_OT_fence_manipulate)
      bpy.utils.unregister_class(ARCHIPACK_OT_fence_from_curve)
      bpy.utils.unregister_class(ARCHIPACK_OT_fence_curve_update)
  