# -*- 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 math import sin, cos, pi, floor, acos
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 Stair():
    def __init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z):
        self.steps_type = steps_type
        self.nose_type = nose_type
        self.l_shape = None
        self.r_shape = None
        self.next_type = 'NONE'
        self.last_type = 'NONE'
        self.z_mode = z_mode
        # depth of open step
        self.nose_z = nose_z
        # size under the step on bottom
        self.bottom_z = bottom_z
        self.left_offset = left_offset
        self.right_offset = right_offset
        self.last_height = 0

    def set_matids(self, matids):
        self.idmat_top, self.idmat_step_front, self.idmat_raise, \
        self.idmat_side, self.idmat_bottom, self.idmat_step_side = matids

    def set_height(self, step_height, z0):
        self.step_height = step_height
        self.z0 = z0

    @property
    def height(self):
        return self.n_step * self.step_height

    @property
    def top_offset(self):
        return self.t_step / self.step_depth

    @property
    def top(self):
        return self.z0 + self.height

    @property
    def left_length(self):
        return self.get_length("LEFT")

    @property
    def right_length(self):
        return self.get_length("RIGHT")

    def step_size(self, step_depth):
        t_step, n_step = self.steps(step_depth)
        self.n_step = n_step
        self.t_step = t_step
        self.step_depth = step_depth
        return n_step

    def p3d_left(self, verts, p2d, i, t, landing=False):
        x, y = p2d
        nose_z = min(self.step_height, self.nose_z)
        zl = self.z0 + t * self.height
        zs = self.z0 + i * self.step_height
        if self.z_mode == 'LINEAR':
            z0 = max(0, zl)
            z1 = z0 - self.bottom_z
            verts.extend([(x, y, z0), (x, y, z1)])
        else:
            if "FULL" in self.steps_type:
                z0 = 0
            else:
                z0 = max(0, zl - nose_z - self.bottom_z)
            z3 = zs + max(0, self.step_height - nose_z)
            z4 = zs + self.step_height
            if landing:
                if "FULL" in self.steps_type:
                    z2 = 0
                    z1 = 0
                else:
                    z2 = max(0, min(z3, z3 - self.bottom_z))
                    z1 = z2
            else:
                z1 = min(z3, max(z0, zl - nose_z))
                z2 = min(z3, max(z1, zl))
            verts.extend([(x, y, z0),
                        (x, y, z1),
                        (x, y, z2),
                        (x, y, z3),
                        (x, y, z4)])

    def p3d_right(self, verts, p2d, i, t, landing=False):
        x, y = p2d
        nose_z = min(self.step_height, self.nose_z)
        zl = self.z0 + t * self.height
        zs = self.z0 + i * self.step_height
        if self.z_mode == 'LINEAR':
            z0 = max(0, zl)
            z1 = z0 - self.bottom_z
            verts.extend([(x, y, z1), (x, y, z0)])
        else:
            if "FULL" in self.steps_type:
                z0 = 0
            else:
                z0 = max(0, zl - nose_z - self.bottom_z)
            z3 = zs + max(0, self.step_height - nose_z)
            z4 = zs + self.step_height
            if landing:
                if "FULL" in self.steps_type:
                    z2 = 0
                    z1 = 0
                else:
                    z2 = max(0, min(z3, z3 - self.bottom_z))
                    z1 = z2
            else:
                z1 = min(z3, max(z0, zl - nose_z))
                z2 = min(z3, max(z1, zl))
            verts.extend([(x, y, z4),
                          (x, y, z3),
                          (x, y, z2),
                          (x, y, z1),
                          (x, y, z0)])

    def p3d_cstep_left(self, verts, p2d, i, t):
        x, y = p2d
        nose_z = min(self.step_height, self.nose_z)
        zs = self.z0 + i * self.step_height
        z3 = zs + max(0, self.step_height - nose_z)
        z1 = min(z3, zs - nose_z)
        verts.append((x, y, z1))
        verts.append((x, y, z3))

    def p3d_cstep_right(self, verts, p2d, i, t):
        x, y = p2d
        nose_z = min(self.step_height, self.nose_z)
        zs = self.z0 + i * self.step_height
        z3 = zs + max(0, self.step_height - nose_z)
        z1 = min(z3, zs - nose_z)
        verts.append((x, y, z3))
        verts.append((x, y, z1))

    def straight_stair(self, length):
        self.next_type = 'STAIR'
        s = self.straight(length)
        return StraightStair(s.p, s.v, self.left_offset, self.right_offset, self.steps_type,
                self.nose_type, self.z_mode, self.nose_z, self.bottom_z)

    def straight_landing(self, length, last_type='STAIR'):
        self.next_type = 'LANDING'
        s = self.straight(length)
        return StraightLanding(s.p, s.v, self.left_offset, self.right_offset, self.steps_type,
                self.nose_type, self.z_mode, self.nose_z, self.bottom_z, last_type=last_type)

    def curved_stair(self, da, radius, left_shape, right_shape, double_limit=pi):
        self.next_type = 'STAIR'
        n = self.normal(1)
        n.v = radius * n.v.normalized()
        if da < 0:
            n.v = -n.v
        a0 = n.angle
        c = n.p - n.v
        return CurvedStair(c, radius, a0, da, self.left_offset, self.right_offset,
                self.steps_type, self.nose_type, self.z_mode, self.nose_z, self.bottom_z,
                left_shape, right_shape, double_limit=double_limit)

    def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi, last_type='STAIR'):
        self.next_type = 'LANDING'
        n = self.normal(1)
        n.v = radius * n.v.normalized()
        if da < 0:
            n.v = -n.v
        a0 = n.angle
        c = n.p - n.v
        return CurvedLanding(c, radius, a0, da, self.left_offset, self.right_offset,
                self.steps_type, self.nose_type, self.z_mode, self.nose_z, self.bottom_z,
                left_shape, right_shape, double_limit=double_limit, last_type=last_type)

    def get_z(self, t, mode):
        if mode == 'LINEAR':
            return self.z0 + t * self.height
        else:
            step = 1 + floor(t / self.t_step)
            return self.z0 + step * self.step_height

    def make_profile(self, t, side, profile, verts, faces, matids, next=None, tnext=0):
        z0 = self.get_z(t, 'LINEAR')
        dz1 = 0
        t, part, dz0, shape = self.get_part(t, side)
        if next is not None:
            tnext, next, dz1, shape1 = next.get_part(tnext, side)
        xy, s = part.proj_xy(t, next)
        v_xy = s * xy.to_3d()
        z, s = part.proj_z(t, dz0, next, dz1)
        v_z = s * Vector((-xy.y * z.x, xy.x * z.x, z.y))
        x, y = part.lerp(t)
        verts += [Vector((x, y, z0)) + v.x * v_xy + v.y * v_z for v in profile]

    def project_uv(self, rM, uvs, verts, indexes, up_axis='Z'):
        if up_axis == 'Z':
            uvs.append([(rM @ Vector(verts[i])).to_2d() for i in indexes])
        elif up_axis == 'Y':
            uvs.append([(x, z) for x, y, z in [(rM @ Vector(verts[i])) for i in indexes]])
        else:
            uvs.append([(y, z) for x, y, z in [(rM @ Vector(verts[i])) for i in indexes]])

    def get_proj_matrix(self, part, t, nose_y):
        # a matrix to project verts
        # into uv space for horizontal parts of this step
        # so uv = (rM @ vertex).to_2d()
        tl = t - nose_y / self.get_length("LEFT")
        tr = t - nose_y / self.get_length("RIGHT")
        t2, part, dz, shape = self.get_part(tl, "LEFT")
        p0 = part.lerp(t2)
        t2, part, dz, shape = self.get_part(tr, "RIGHT")
        p1 = part.lerp(t2)
        v = (p1 - p0).normalized()
        return Matrix([
            [-v.y, v.x, 0, p0.x],
            [v.x, v.y, 0, p0.y],
            [0, 0, 1, 0],
            [0, 0, 0, 1]
        ]).inverted()

    def _make_nose(self, i, s, verts, faces, matids, uvs, nose_y):

        t = self.t_step * i

        # a matrix to project verts
        # into uv space for horizontal parts of this step
        # so uv = (rM @ vertex).to_2d()
        rM = self.get_proj_matrix(self, t, nose_y)

        if self.z_mode == 'LINEAR':
            return rM

        f = len(verts)

        tl = t - nose_y / self.get_length("LEFT")
        tr = t - nose_y / self.get_length("RIGHT")

        t2, part, dz, shape = self.get_part(tl, "LEFT")
        p0 = part.lerp(t2)
        self.p3d_left(verts, p0, s, t2)

        t2, part, dz, shape = self.get_part(tr, "RIGHT")
        p1 = part.lerp(t2)
        self.p3d_right(verts, p1, s, t2)

        start = 3
        end = 6
        offset = 10

        # left, top, right
        matids.extend([self.idmat_step_side,
             self.idmat_top,
             self.idmat_step_side])

        faces += [(f + j, f + j + 1, f + j + offset + 1, f + j + offset) for j in range(start, end)]

        u = nose_y
        v = (p1 - p0).length
        w = verts[f + 2][2] - verts[f + 3][2]
        s = int((end - start) / 2)

        uvs += [[(u, verts[f + j][2]), (u, verts[f + j + 1][2]),
            (0, verts[f + j + 1][2]), (0, verts[f + j][2])] for j in range(start, start + s)]

        uvs.append([(0, 0), (0, v), (u, v), (u, 0)])

        uvs += [[(u, verts[f + j][2]), (u, verts[f + j + 1][2]),
            (0, verts[f + j + 1][2]), (0, verts[f + j][2])] for j in range(start + s + 1, end)]

        if 'STRAIGHT' in self.nose_type or 'OPEN' in self.steps_type:
            # face bottom
            matids.append(self.idmat_bottom)
            faces.append((f + end, f + start, f + offset + start, f + offset + end))
            uvs.append([(u, v), (u, 0), (0, 0), (0, v)])

        if self.steps_type != 'OPEN':
            if 'STRAIGHT' in self.nose_type:
                # front face bottom straight
                matids.append(self.idmat_raise)
                faces.append((f + 12, f + 17, f + 16, f + 13))
                uvs.append([(0, w), (v, w), (v, 0), (0, 0)])

            elif 'OBLIQUE' in self.nose_type:
                # front face bottom oblique
                matids.append(self.idmat_raise)
                faces.append((f + 12, f + 17, f + 6, f + 3))

                uvs.append([(0, w), (v, w), (v, 0), (0, 0)])

                matids.append(self.idmat_side)
                faces.append((f + 3, f + 13, f + 12))
                uvs.append([(0, 0), (u, 0), (u, w)])

                matids.append(self.idmat_side)
                faces.append((f + 6, f + 17, f + 16))
                uvs.append([(0, 0), (u, w), (u, 0)])

        # front face top
        w = verts[f + 3][2] - verts[f + 4][2]
        matids.append(self.idmat_step_front)
        faces.append((f + 4, f + 3, f + 6, f + 5))
        uvs.append([(0, 0), (0, w), (v, w), (v, 0)])
        return rM

    def make_faces(self, f, rM, verts, faces, matids, uvs):

        if self.z_mode == 'LINEAR':
            start = 0
            end = 3
            offset = 4
            matids.extend([self.idmat_side,
                 self.idmat_top,
                 self.idmat_side,
                 self.idmat_bottom])
        elif "OPEN" in self.steps_type:
            # faces dessus-dessous-lateral marches fermees
            start = 3
            end = 6
            offset = 10
            matids.extend([self.idmat_step_side,
                 self.idmat_top,
                 self.idmat_step_side,
                 self.idmat_bottom])
        else:
            # faces dessus-dessous-lateral marches fermees
            start = 0
            end = 9
            offset = 10
            matids.extend([self.idmat_side,
                 self.idmat_side,
                 self.idmat_side,
                 self.idmat_step_side,
                 self.idmat_top,
                 self.idmat_step_side,
                 self.idmat_side,
                 self.idmat_side,
                 self.idmat_side,
                 self.idmat_bottom])

        u_l0 = 0
        u_l1 = self.t_step * self.left_length
        u_r0 = 0
        u_r1 = self.t_step * self.right_length

        s = int((end - start) / 2)
        uvs += [[(u_l0, verts[f + j][2]), (u_l0, verts[f + j + 1][2]),
            (u_l1, verts[f + j + offset + 1][2]), (u_l1, verts[f + j + offset][2])] for j in range(start, start + s)]

        self.project_uv(rM, uvs, verts, [f + start + s, f + start + s + 1,
            f + start + s + offset + 1, f + start + s + offset])

        uvs += [[(u_r0, verts[f + j][2]), (u_r0, verts[f + j + 1][2]),
            (u_r1, verts[f + j + offset + 1][2]), (u_r1, verts[f + j + offset][2])] for j in range(start + s + 1, end)]

        self.project_uv(rM, uvs, verts, [f + end, f + start, f + offset + start, f + offset + end])

        faces += [(f + j, f + j + 1, f + j + offset + 1, f + j + offset) for j in range(start, end)]
        faces.append((f + end, f + start, f + offset + start, f + offset + end))


class StraightStair(Stair, Line):
    def __init__(self, p, v, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z):
        Stair.__init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z)
        Line.__init__(self, p, v)
        self.l_line = self.offset(-left_offset)
        self.r_line = self.offset(right_offset)

    def make_step(self, i, verts, faces, matids, uvs, nose_y=0):

        rM = self._make_nose(i, i, verts, faces, matids, uvs, nose_y)

        t0 = self.t_step * i

        f = len(verts)

        p = self.l_line.lerp(t0)
        self.p3d_left(verts, p, i, t0)
        p = self.r_line.lerp(t0)
        self.p3d_right(verts, p, i, t0)

        t1 = t0 + self.t_step

        p = self.l_line.lerp(t1)
        self.p3d_left(verts, p, i, t1)
        p = self.r_line.lerp(t1)
        self.p3d_right(verts, p, i, t1)

        self.make_faces(f, rM, verts, faces, matids, uvs)

        if "OPEN" in self.steps_type:
            faces.append((f + 13, f + 14, f + 15, f + 16))
            matids.append(self.idmat_step_front)
            uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])

    def get_length(self, side):
        return self.length

    def get_lerp_vect(self, posts, side, i, t_step, respect_edges, z_offset=0, t0_abs=None):
        if t0_abs is not None:
            t0 = t0_abs
        else:
            t0 = i * t_step
        t, part, dz, shape = self.get_part(t0, side)
        dz /= part.length
        n = part.normal(t)
        z0 = self.get_z(t0, 'STEP')
        z1 = self.get_z(t0, 'LINEAR')
        posts.append((n, dz, z0, z1 + t0 * z_offset))
        return [t0]

    def n_posts(self, post_spacing, side, respect_edges):
        return self.steps(post_spacing)

    def get_part(self, t, side):
        if side == 'LEFT':
            part = self.l_line
        else:
            part = self.r_line
        return t, part, self.height, 'LINE'


class CurvedStair(Stair, Arc):
    def __init__(self, c, radius, a0, da, left_offset, right_offset, steps_type, nose_type,
        z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=pi):

        Stair.__init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z)
        Arc.__init__(self, c, radius, a0, da)
        self.l_shape = left_shape
        self.r_shape = right_shape
        self.edges_multiples = round(abs(da), 6) > double_limit
        # left arc, tangent at start and end
        self.l_arc, self.l_t0, self.l_t1, self.l_tc = self.set_offset(-left_offset, left_shape)
        self.r_arc, self.r_t0, self.r_t1, self.r_tc = self.set_offset(right_offset, right_shape)

    def set_offset(self, offset, shape):
        arc = self.offset(offset)
        t0 = arc.tangeant(0, 1)
        t1 = arc.tangeant(1, 1)
        tc = arc.tangeant(0.5, 1)
        if self.edges_multiples:
            i, p, t = t0.intersect(tc)
            tc.v *= 2 * t
            tc.p = p
            i, p, t2 = tc.intersect(t1)
        else:
            i, p, t = t0.intersect(t1)
        t0.v *= t
        t1.p = p
        t1.v *= t
        return arc, t0, t1, tc

    def get_length(self, side):
        if side == 'RIGHT':
            arc = self.r_arc
            shape = self.r_shape
            t0 = self.r_t0
        else:
            arc = self.l_arc
            shape = self.l_shape
            t0 = self.l_t0
        if shape == 'CIRCLE':
            return arc.length
        else:
            if self.edges_multiples:
                # two edges
                return t0.length * 4
            else:
                return t0.length * 2

    def _make_step(self, t_step, i, s, verts, landing=False):

        tb = t_step * i

        f = len(verts)

        t, part, dz, shape = self.get_part(tb, "LEFT")
        p = part.lerp(t)
        self.p3d_left(verts, p, s, tb, landing)

        t, part, dz, shape = self.get_part(tb, "RIGHT")
        p = part.lerp(t)
        self.p3d_right(verts, p, s, tb, landing)
        return f

    def _make_edge(self, t_step, i, j, f, rM, verts, faces, matids, uvs):
        tb = t_step * i
        # make edges verts after regular ones
        if self.l_shape != 'CIRCLE' or self.r_shape != 'CIRCLE':
            if self.edges_multiples:
                # edge 1
                if tb < 0.25 and tb + t_step > 0.25:
                    f0 = f
                    f = len(verts)
                    if self.l_shape == 'CIRCLE':
                        self.p3d_left(verts, self.l_arc.lerp(0.25), j, 0.25)
                    else:
                        self.p3d_left(verts, self.l_tc.p, j, 0.25)
                    if self.r_shape == 'CIRCLE':
                        self.p3d_right(verts, self.r_arc.lerp(0.25), j, 0.25)
                    else:
                        self.p3d_right(verts, self.r_tc.p, j, 0.25)
                    self.make_faces(f0, rM, verts, faces, matids, uvs)
                # edge 2
                if tb < 0.75 and tb + t_step > 0.75:
                    f0 = f
                    f = len(verts)
                    if self.l_shape == 'CIRCLE':
                        self.p3d_left(verts, self.l_arc.lerp(0.75), j, 0.75)
                    else:
                        self.p3d_left(verts, self.l_t1.p, j, 0.75)
                    if self.r_shape == 'CIRCLE':
                        self.p3d_right(verts, self.r_arc.lerp(0.75), j, 0.75)
                    else:
                        self.p3d_right(verts, self.r_t1.p, j, 0.75)
                    self.make_faces(f0, rM, verts, faces, matids, uvs)
            else:
                if tb < 0.5 and tb + t_step > 0.5:
                    f0 = f
                    f = len(verts)
                    # the step goes through the edge
                    if self.l_shape == 'CIRCLE':
                        self.p3d_left(verts, self.l_arc.lerp(0.5), j, 0.5)
                    else:
                        self.p3d_left(verts, self.l_t1.p, j, 0.5)
                    if self.r_shape == 'CIRCLE':
                        self.p3d_right(verts, self.r_arc.lerp(0.5), j, 0.5)
                    else:
                        self.p3d_right(verts, self.r_t1.p, j, 0.5)
                    self.make_faces(f0, rM, verts, faces, matids, uvs)
        return f

    def make_step(self, i, verts, faces, matids, uvs, nose_y=0):

        # open stair with closed face

        # step nose
        rM = self._make_nose(i, i, verts, faces, matids, uvs, nose_y)
        f = 0
        if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
            # every 6 degree
            n_subs = max(1, int(abs(self.da) / pi * 30 / self.n_step))
            t_step = self.t_step / n_subs
            for j in range(n_subs):
                f0 = f
                f = self._make_step(t_step, n_subs * i + j, i, verts)
                if j > 0:
                    self.make_faces(f0, rM, verts, faces, matids, uvs)
                f = self._make_edge(t_step, n_subs * i + j, i, f, rM, verts, faces, matids, uvs)
        else:
            f = self._make_step(self.t_step, i, i, verts)
            f = self._make_edge(self.t_step, i, i, f, rM, verts, faces, matids, uvs)

        self._make_step(self.t_step, i + 1, i, verts)
        self.make_faces(f, rM, verts, faces, matids, uvs)

        if "OPEN" in self.steps_type and self.z_mode != 'LINEAR':
            # back face top
            faces.append((f + 13, f + 14, f + 15, f + 16))
            matids.append(self.idmat_step_front)
            uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])

    def get_part(self, t, side):
        if side == 'RIGHT':
            arc = self.r_arc
            shape = self.r_shape
            t0, t1, tc = self.r_t0, self.r_t1, self.r_tc
        else:
            arc = self.l_arc
            shape = self.l_shape
            t0, t1, tc = self.l_t0, self.l_t1, self.l_tc
        if shape == 'CIRCLE':
            return t, arc, self.height, shape
        else:
            if self.edges_multiples:
                # two edges
                if t <= 0.25:
                    return 4 * t, t0, 0.25 * self.height, shape
                elif t <= 0.75:
                    return 2 * (t - 0.25), tc, 0.5 * self.height, shape
                else:
                    return 4 * (t - 0.75), t1, 0.25 * self.height, shape
            else:
                if t <= 0.5:
                    return 2 * t, t0, 0.5 * self.height, shape
                else:
                    return 2 * (t - 0.5), t1, 0.5 * self.height, shape

    def get_lerp_vect(self, posts, side, i, t_step, respect_edges, z_offset=0, t0_abs=None):
        if t0_abs is not None:
            t0 = t0_abs
        else:
            t0 = i * t_step
        res = [t0]
        t1 = t0 + t_step
        zs = self.get_z(t0, 'STEP')
        zl = self.get_z(t0, 'LINEAR')

        # vect normal
        t, part, dz, shape = self.get_part(t0, side)
        n = part.normal(t)
        dz /= part.length
        posts.append((n, dz, zs, zl + t0 * z_offset))

        if shape != 'CIRCLE' and respect_edges:
            if self.edges_multiples:
                if t0 < 0.25 and t1 > 0.25:
                    zs = self.get_z(0.25, 'STEP')
                    zl = self.get_z(0.25, 'LINEAR')
                    t, part, dz, shape = self.get_part(0.25, side)
                    n = part.normal(1)
                    posts.append((n, dz, zs, zl + 0.25 * z_offset))
                    res.append(0.25)
                if t0 < 0.75 and t1 > 0.75:
                    zs = self.get_z(0.75, 'STEP')
                    zl = self.get_z(0.75, 'LINEAR')
                    t, part, dz, shape = self.get_part(0.75, side)
                    n = part.normal(1)
                    posts.append((n, dz, zs, zl + 0.75 * z_offset))
                    res.append(0.75)
            elif t0 < 0.5 and t1 > 0.5:
                    zs = self.get_z(0.5, 'STEP')
                    zl = self.get_z(0.5, 'LINEAR')
                    t, part, dz, shape = self.get_part(0.5, side)
                    n = part.normal(1)
                    posts.append((n, dz, zs, zl + 0.5 * z_offset))
                    res.append(0.5)
        return res

    def n_posts(self, post_spacing, side, respect_edges):
        if side == 'LEFT':
            arc, t0, shape = self.l_arc, self.l_t0, self.l_shape
        else:
            arc, t0, shape = self.r_arc, self.r_t0, self.r_shape
        step_factor = 1
        if shape == 'CIRCLE':
            length = arc.length
        else:
            if self.edges_multiples:
                if respect_edges:
                    step_factor = 2
                length = 4 * t0.length
            else:
                length = 2 * t0.length
        steps = step_factor * max(1, round(length / post_spacing, 0))
        # print("respect_edges:%s t_step:%s n_step:%s" % (respect_edges, 1.0 / steps, int(steps)))
        return 1.0 / steps, int(steps)


class StraightLanding(StraightStair):
    def __init__(self, p, v, left_offset, right_offset, steps_type,
            nose_type, z_mode, nose_z, bottom_z, last_type='STAIR'):

        StraightStair.__init__(self, p, v, left_offset, right_offset, steps_type,
            nose_type, z_mode, nose_z, bottom_z)

        self.last_type = last_type

    @property
    def height(self):
        return 0

    @property
    def top_offset(self):
        return self.t_step / self.v.length

    @property
    def top(self):
        if self.next_type == 'LANDING':
            return self.z0
        else:
            return self.z0 + self.step_height

    def step_size(self, step_depth):
        self.n_step = 1
        self.t_step = 1
        self.step_depth = step_depth
        if self.last_type == 'LANDING':
            return 0
        else:
            return 1

    def make_step(self, i, verts, faces, matids, uvs, nose_y=0):

        if i == 0 and self.last_type != 'LANDING':
            rM = self._make_nose(i, 0, verts, faces, matids, uvs, nose_y)
        else:
            rM = self.get_proj_matrix(self.l_line, self.t_step * i, nose_y)

        f = len(verts)
        j = 0
        t0 = self.t_step * i

        p = self.l_line.lerp(t0)
        self.p3d_left(verts, p, j, t0)

        p = self.r_line.lerp(t0)
        self.p3d_right(verts, p, j, t0)

        t1 = t0 + self.t_step
        p = self.l_line.lerp(t1)
        self.p3d_left(verts, p, j, t1, self.next_type != 'LANDING')

        p = self.r_line.lerp(t1)
        self.p3d_right(verts, p, j, t1, self.next_type != 'LANDING')

        self.make_faces(f, rM, verts, faces, matids, uvs)

        if "OPEN" in self.steps_type and self.next_type != 'LANDING':
            faces.append((f + 13, f + 14, f + 15, f + 16))
            matids.append(self.idmat_step_front)
            uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])

    def straight_landing(self, length):
        return Stair.straight_landing(self, length, last_type='LANDING')

    def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi):
        return Stair.curved_landing(self, da, radius, left_shape,
            right_shape, double_limit=double_limit, last_type='LANDING')

    def get_z(self, t, mode):
        if mode == 'STEP':
            return self.z0 + self.step_height
        else:
            return self.z0


class CurvedLanding(CurvedStair):
    def __init__(self, c, radius, a0, da, left_offset, right_offset, steps_type,
        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=pi, last_type='STAIR'):

        CurvedStair.__init__(self, c, radius, a0, da, left_offset, right_offset, steps_type,
            nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=double_limit)

        self.last_type = last_type

    @property
    def top_offset(self):
        if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
            return self.t_step / self.step_depth
        else:
            if self.edges_multiples:
                return 0.5 / self.length
            else:
                return 1 / self.length

    @property
    def height(self):
        return 0

    @property
    def top(self):
        if self.next_type == 'LANDING':
            return self.z0
        else:
            return self.z0 + self.step_height

    def step_size(self, step_depth):
        if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
            t_step, n_step = self.steps(step_depth)
        else:
            if self.edges_multiples:
                t_step, n_step = 0.5, 2
            else:
                t_step, n_step = 1, 1
        self.n_step = n_step
        self.t_step = t_step
        self.step_depth = step_depth
        if self.last_type == 'LANDING':
            return 0
        else:
            return 1

    def make_step(self, i, verts, faces, matids, uvs, nose_y=0):

        if i == 0 and 'LANDING' not in self.last_type:
            rM = self._make_nose(i, 0, verts, faces, matids, uvs, nose_y)
        else:
            rM = self.get_proj_matrix(self.l_arc, self.t_step * i, nose_y)

        f = len(verts)

        if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
            n_subs = max(1, int(abs(self.da / pi * 30 / self.n_step)))
            t_step = self.t_step / n_subs
            for j in range(n_subs):
                f0 = f
                f = self._make_step(t_step, n_subs * i + j, 0, verts)
                if j > 0:
                    self.make_faces(f0, rM, verts, faces, matids, uvs)
                f = self._make_edge(t_step, n_subs * i + j, 0, f, rM, verts, faces, matids, uvs)
        else:
            f = self._make_step(self.t_step, i, 0, verts)
            f = self._make_edge(self.t_step, i, 0, f, rM, verts, faces, matids, uvs)

        self._make_step(self.t_step, i + 1, 0, verts, i == self.n_step - 1 and 'LANDING' not in self.next_type)
        self.make_faces(f, rM, verts, faces, matids, uvs)

        if "OPEN" in self.steps_type and 'LANDING' not in self.next_type:
            faces.append((f + 13, f + 14, f + 15, f + 16))
            matids.append(self.idmat_step_front)
            uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])

    def straight_landing(self, length):
        return Stair.straight_landing(self, length, last_type='LANDING')

    def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi):
        return Stair.curved_landing(self, da, radius, left_shape,
            right_shape, double_limit=double_limit, last_type='LANDING')

    def get_z(self, t, mode):
        if mode == 'STEP':
            return self.z0 + self.step_height
        else:
            return self.z0


class StairGenerator():
    def __init__(self, parts):
        self.parts = parts
        self.last_type = 'NONE'
        self.stairs = []
        self.steps_type = 'NONE'
        self.sum_da = 0
        self.user_defined_post = None
        self.user_defined_uvs = None
        self.user_defined_mat = None

    def add_part(self, type, steps_type, nose_type, z_mode, nose_z, bottom_z, center,
            radius, da, width_left, width_right, length, left_shape, right_shape):

        self.steps_type = steps_type
        if len(self.stairs) < 1:
            s = None
        else:
            s = self.stairs[-1]

        if "S_" not in type:
            self.sum_da += da

        # start a new stair
        if s is None:
            if type == 'S_STAIR':
                p = Vector((0, 0))
                v = Vector((0, length))
                s = StraightStair(p, v, width_left, width_right, steps_type, nose_type, z_mode, nose_z, bottom_z)
            elif type == 'C_STAIR':
                if da < 0:
                    c = Vector((radius, 0))
                else:
                    c = Vector((-radius, 0))
                s = CurvedStair(c, radius, 0, da, width_left, width_right, steps_type,
                        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape)
            elif type == 'D_STAIR':
                if da < 0:
                    c = Vector((radius, 0))
                else:
                    c = Vector((-radius, 0))
                s = CurvedStair(c, radius, 0, da, width_left, width_right, steps_type,
                        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=0)
            elif type == 'S_LANDING':
                p = Vector((0, 0))
                v = Vector((0, length))
                s = StraightLanding(p, v, width_left, width_right, steps_type, nose_type, z_mode, nose_z, bottom_z)
            elif type == 'C_LANDING':
                if da < 0:
                    c = Vector((radius, 0))
                else:
                    c = Vector((-radius, 0))
                s = CurvedLanding(c, radius, 0, da, width_left, width_right, steps_type,
                        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape)
            elif type == 'D_LANDING':
                if da < 0:
                    c = Vector((radius, 0))
                else:
                    c = Vector((-radius, 0))
                s = CurvedLanding(c, radius, 0, da, width_left, width_right, steps_type,
                        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=0)
        else:
            if type == 'S_STAIR':
                s = s.straight_stair(length)
            elif type == 'C_STAIR':
                s = s.curved_stair(da, radius, left_shape, right_shape)
            elif type == 'D_STAIR':
                s = s.curved_stair(da, radius, left_shape, right_shape, double_limit=0)
            elif type == 'S_LANDING':
                s = s.straight_landing(length)
            elif type == 'C_LANDING':
                s = s.curved_landing(da, radius, left_shape, right_shape)
            elif type == 'D_LANDING':
                s = s.curved_landing(da, radius, left_shape, right_shape, double_limit=0)
        self.stairs.append(s)
        self.last_type = type

    def n_steps(self, step_depth):
        n_steps = 0
        for stair in self.stairs:
            n_steps += stair.step_size(step_depth)
        return n_steps

    def set_height(self, step_height):
        z = 0
        for stair in self.stairs:
            stair.set_height(step_height, z)
            z = stair.top

    def make_stair(self, height, step_depth, verts, faces, matids, uvs, nose_y=0):
        n_steps = self.n_steps(step_depth)
        self.set_height(height / n_steps)

        for s, stair in enumerate(self.stairs):
            if s < len(self.parts):
                manipulator = self.parts[s].manipulators[0]
                # Store Gl Points for manipulators
                if 'Curved' in type(stair).__name__:
                    c = stair.c
                    p0 = (stair.p0 - c).to_3d()
                    p1 = (stair.p1 - c).to_3d()
                    manipulator.set_pts([(c.x, c.y, stair.top), p0, p1])
                    manipulator.type_key = 'ARC_ANGLE_RADIUS'
                    manipulator.prop1_name = 'da'
                    manipulator.prop2_name = 'radius'
                else:
                    if self.sum_da > 0:
                        side = 1
                    else:
                        side = -1
                    v0 = stair.p0
                    v1 = stair.p1
                    manipulator.set_pts([(v0.x, v0.y, stair.top), (v1.x, v1.y, stair.top), (side, 0, 0)])
                    manipulator.type_key = 'SIZE'
                    manipulator.prop1_name = 'length'

            for i in range(stair.n_step):
                stair.make_step(i, verts, faces, matids, uvs, nose_y=nose_y)
                if s < len(self.stairs) - 1 and self.steps_type != 'OPEN' and \
                        'Landing' in type(stair).__name__ and stair.next_type != "LANDING":
                    f = len(verts) - 10
                    faces.append((f, f + 1, f + 8, f + 9))
                    matids.append(self.stairs[-1].idmat_bottom)
                    u = verts[f + 1][2] - verts[f][2]
                    v = (Vector(verts[f]) - Vector(verts[f + 9])).length
                    uvs.append([(0, 0), (0, u), (v, u), (v, 0)])

        if self.steps_type != 'OPEN' and len(self.stairs) > 0:
            f = len(verts) - 10
            faces.append((f, f + 1, f + 2, f + 3, f + 4, f + 5, f + 6, f + 7, f + 8, f + 9))
            matids.append(self.stairs[-1].idmat_bottom)
            uvs.append([(0, 0), (.1, 0), (.2, 0), (.3, 0), (.4, 0), (.4, 1), (.3, 1), (.2, 1), (.1, 1), (0, 1)])

    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 'Top' in g:
                co.z += z2
            elif 'Bottom' in g:
                co.z += 0
            else:
                co.z += z1
            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, bottom="STEP"):

        n, dz, zs, zl = post
        slope = dz * post_y

        if self.user_defined_post is not None:
            if bottom == "STEP":
                z0 = zs
            else:
                z0 = zl
            z1 = zl - z0
            z2 = zl - z0
            x, y = -n.v.normalized()
            tM = Matrix([
                [x, y, 0, n.p.x],
                [y, -x, 0, n.p.y],
                [0, 0, 1, z0 + post_alt],
                [0, 0, 0, 1]
            ])
            self.get_user_defined_post(tM, z0, z1, z2, dz, post_z, verts, faces, matids, uvs)
            return

        z3 = zl + post_z + post_alt - slope
        z4 = zl + post_z + post_alt + slope
        if bottom == "STEP":
            z0 = zs + post_alt
            z1 = zs + post_alt
        else:
            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, zs, zl = subs[0]
        p0 = n.p
        v0 = n.v.normalized()
        for s, section in enumerate(subs):
            n, dz, zs, 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.dot(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 reset_shapes(self):
        for s, stair in enumerate(self.stairs):
            if 'Curved' in type(stair).__name__:
                stair.l_shape = self.parts[s].left_shape
                stair.r_shape = self.parts[s].right_shape

    def make_subs(self, height, step_depth, x, y, z, post_y, altitude, bottom, side, slice,
            post_spacing, sub_spacing, respect_edges, move_x, x_offset, sub_offset_x, mat,
            verts, faces, matids, uvs):

        n_steps = self.n_steps(step_depth)
        self.set_height(height / n_steps)
        n_stairs = len(self.stairs) - 1
        subs = []

        if side == "LEFT":
            offset = move_x - x_offset
            # offset_sub = offset - sub_offset_x
        else:
            offset = move_x + x_offset
            # offset_sub = offset + sub_offset_x

        for s, stair in enumerate(self.stairs):
            if 'Curved' in type(stair).__name__:
                if side == "LEFT":
                    part = stair.l_arc
                    shape = stair.l_shape
                else:
                    part = stair.r_arc
                    shape = stair.r_shape
                # Note: use left part as reference for post distances
                # use right part as reference for panels
                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(offset, shape)
                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(offset, shape)
            else:
                stair.l_line = stair.offset(offset)
                stair.r_line = stair.offset(offset)
                part = stair.l_line

            lerp_z = 0
            edge_t = 1
            edge_size = 0
            # interpolate z near end landing
            if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR':
                if not slice:
                    line = stair.normal(1).offset(self.stairs[s + 1].step_depth)
                    res, p, t_part = part.intersect(line)
                    # does perpendicular line intersects circle ?
                    if res:
                        edge_size = self.stairs[s + 1].step_depth / stair.get_length(side)
                        edge_t = 1 - edge_size
                    else:
                        # in this case, lerp z over one step
                        lerp_z = stair.step_height

            t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)

            # space between posts
            sp = stair.get_length(side)
            # post size
            t_post = post_y / sp

            if s == n_stairs:
                n_step += 1
            for i in range(n_step):
                res_t = stair.get_lerp_vect([], side, i, t_step, respect_edges)
                # subs
                if s < n_stairs or i < n_step - 1:
                    res_t.append((i + 1) * t_step)
                for j in range(len(res_t) - 1):
                    t0 = res_t[j] + t_post
                    t1 = res_t[j + 1] - t_post
                    dt = t1 - t0
                    n_subs = int(sp * dt / sub_spacing)
                    if n_subs > 0:
                        t_subs = dt / n_subs
                        for k in range(1, n_subs):
                            t = t0 + k * t_subs
                            stair.get_lerp_vect(subs, side, 1, t0 + k * t_subs, False)
                            if t > edge_t:
                                n, dz, z0, z1 = subs[-1]
                                subs[-1] = n, dz, z0, z1 + (t - edge_t) / edge_size * stair.step_height
                            if lerp_z > 0:
                                n, dz, z0, z1 = subs[-1]
                                subs[-1] = n, dz, z0, z1 + t * stair.step_height

        for i, post in enumerate(subs):
            self.get_post(post, x, y, z, altitude, sub_offset_x, mat, verts, faces, matids, uvs, bottom=bottom)

    def make_post(self, height, step_depth, x, y, z, altitude, side, post_spacing, respect_edges, move_x, x_offset, mat,
        verts, faces, matids, uvs):
        n_steps = self.n_steps(step_depth)
        self.set_height(height / n_steps)
        l_posts = []
        n_stairs = len(self.stairs) - 1

        for s, stair in enumerate(self.stairs):
            if type(stair).__name__ in ['CurvedStair', 'CurvedLanding']:
                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(move_x - x_offset, stair.l_shape)
                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(move_x + x_offset, stair.r_shape)
            else:
                stair.l_line = stair.offset(move_x - x_offset)
                stair.r_line = stair.offset(move_x + x_offset)

            t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)

            if s == n_stairs:
                n_step += 1
            for i in range(n_step):
                stair.get_lerp_vect(l_posts, side, i, t_step, respect_edges)

                if s == n_stairs and i == n_step - 1:
                    n, dz, z0, z1 = l_posts[-1]
                    l_posts[-1] = (n, dz, z0 - stair.step_height, z1)

        for i, post in enumerate(l_posts):
            self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs)

    def make_panels(self, height, step_depth, x, z, post_y, altitude, side, post_spacing,
            panel_dist, respect_edges, move_x, x_offset, sub_offset_x, mat, verts, faces, matids, uvs):

        n_steps = self.n_steps(step_depth)
        self.set_height(height / n_steps)
        subs = []
        n_stairs = len(self.stairs) - 1

        if side == "LEFT":
            offset = move_x - x_offset
        else:
            offset = move_x + x_offset

        for s, stair in enumerate(self.stairs):

            is_circle = False
            if 'Curved' in type(stair).__name__:
                if side == "LEFT":
                    is_circle = stair.l_shape == "CIRCLE"
                    shape = stair.l_shape
                else:
                    is_circle = stair.r_shape == "CIRCLE"
                    shape = stair.r_shape
                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(offset, shape)
                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(offset, shape)
            else:
                stair.l_line = stair.offset(offset)
                stair.r_line = stair.offset(offset)

            # space between posts
            sp = stair.get_length(side)

            t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)

            if is_circle and 'Curved' in type(stair).__name__:
                panel_da = abs(stair.da) / pi * 180 / n_step
                panel_step = max(1, int(panel_da / 6))
            else:
                panel_step = 1

            # post size
            t_post = (post_y + panel_dist) / sp

            if s == n_stairs:
                n_step += 1
            for i in range(n_step):
                res_t = stair.get_lerp_vect([], side, i, t_step, respect_edges)
                # subs
                if s < n_stairs or i < n_step - 1:
                    res_t.append((i + 1) * t_step)
                for j in range(len(res_t) - 1):
                    t0 = res_t[j] + t_post
                    t1 = res_t[j + 1] - t_post
                    dt = t1 - t0
                    t_curve = dt / panel_step
                    if dt > 0:
                        panel = []
                        for k in range(panel_step):
                            stair.get_lerp_vect(panel, side, 1, t_curve, True, t0_abs=t0 + k * t_curve)
                        stair.get_lerp_vect(panel, side, 1, t1, False)
                        subs.append(panel)
        for sub in subs:
            self.get_panel(sub, altitude, x, z, sub_offset_x, mat, verts, faces, matids, uvs)

    def make_part(self, height, step_depth, part_x, part_z, x_move, x_offset,
            z_offset, z_mode, steps_type, verts, faces, matids, uvs):

        params = [(stair.z_mode, stair.l_shape, stair.r_shape,
            stair.bottom_z, stair.steps_type) for stair in self.stairs]

        for stair in self.stairs:
            if x_offset > 0:
                stair.l_shape = stair.r_shape
            else:
                stair.r_shape = stair.l_shape
            stair.steps_type = steps_type
            stair.z_mode = "LINEAR"
            stair.bottom_z = part_z
            if 'Curved' in type(stair).__name__:
                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = \
                    stair.set_offset(x_move + x_offset + 0.5 * part_x, stair.l_shape)
                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = \
                    stair.set_offset(x_move + x_offset - 0.5 * part_x, stair.r_shape)
            else:
                stair.l_line = stair.offset(x_move + x_offset + 0.5 * part_x)
                stair.r_line = stair.offset(x_move + x_offset - 0.5 * part_x)
        n_steps = self.n_steps(step_depth)
        self.set_height(height / n_steps)
        for j, stair in enumerate(self.stairs):
            stair.z0 += z_offset + part_z
            stair.n_step *= 2
            stair.t_step /= 2
            stair.step_height /= 2
            for i in range(stair.n_step):
                stair.make_step(i, verts, faces, matids, uvs, nose_y=0)
            stair.n_step /= 2
            stair.t_step *= 2
            stair.step_height *= 2
            stair.z_mode = params[j][0]
            stair.l_shape = params[j][1]
            stair.r_shape = params[j][2]
            stair.bottom_z = params[j][3]
            stair.steps_type = params[j][4]
            stair.z0 -= z_offset + part_z

    def make_profile(self, profile, idmat, side, slice, height, step_depth,
            x_offset, z_offset, extend, verts, faces, matids, uvs):

        for stair in self.stairs:
            if 'Curved' in type(stair).__name__:
                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(-x_offset, stair.l_shape)
                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(x_offset, stair.r_shape)
            else:
                stair.l_line = stair.offset(-x_offset)
                stair.r_line = stair.offset(x_offset)

        n_steps = self.n_steps(step_depth)
        self.set_height(height / n_steps)

        n_stairs = len(self.stairs) - 1

        if n_stairs < 0:
            return

        sections = []
        sections.append([])

        # first step
        if extend != 0:
            t = -extend / self.stairs[0].length
            self.stairs[0].get_lerp_vect(sections[-1], side, 1, t, True)

        for s, stair in enumerate(self.stairs):
            n_step = 1
            is_circle = False

            if 'Curved' in type(stair).__name__:
                if side == "LEFT":
                    part = stair.l_arc
                    is_circle = stair.l_shape == "CIRCLE"
                else:
                    part = stair.r_arc
                    is_circle = stair.r_shape == "CIRCLE"
            else:
                if side == "LEFT":
                    part = stair.l_line
                else:
                    part = stair.r_line

            if is_circle:
                n_step = 3 * stair.n_step

            t_step = 1 / n_step

            last_t = 1.0
            do_last = True
            lerp_z = 0
            # last section 1 step before stair
            if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR':
                if not slice:
                    line = stair.normal(1).offset(self.stairs[s + 1].step_depth)
                    res, p, t_part = part.intersect(line)
                    # does perpendicular line intersects circle ?
                    if res:
                        last_t = 1 - self.stairs[s + 1].step_depth / stair.get_length(side)
                        if last_t < 0:
                            do_last = False
                    else:
                        # in this case, lerp z over one step
                        do_last = False
                        lerp_z = stair.step_height

            if s == n_stairs:
                n_step += 1

            for i in range(n_step):
                res_t = stair.get_lerp_vect(sections[-1], side, i, t_step, True, z_offset=lerp_z)
                # remove corner section
                for cur_t in res_t:
                    if cur_t > 0 and cur_t > last_t:
                        sections[-1] = sections[-1][:-1]

            # last section 1 step before next stair start
            if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR':
                if do_last:
                    stair.get_lerp_vect(sections[-1], side, 1, last_t, False)
                if slice:
                    sections.append([])
                    if extend > 0:
                        t = -extend / self.stairs[s + 1].length
                        self.stairs[s + 1].get_lerp_vect(sections[-1], side, 1, t, True)

        t = 1 + extend / self.stairs[-1].length
        self.stairs[-1].get_lerp_vect(sections[-1], side, 1, t, True)

        for cur_sect in sections:
            user_path_verts = len(cur_sect)
            f = len(verts)
            if user_path_verts > 0:
                user_path_uv_v = []
                n, dz, z0, z1 = cur_sect[-1]
                cur_sect[-1] = (n, dz, z0 - stair.step_height, z1)
                n_sections = user_path_verts - 1
                n, dz, zs, zl = cur_sect[0]
                p0 = n.p
                v0 = n.v.normalized()
                for s, section in enumerate(cur_sect):
                    n, dz, zs, zl = section
                    p1 = n.p
                    if s < n_sections:
                        v1 = cur_sect[s + 1][0].v.normalized()
                    dir = (v0 + v1).normalized()
                    scale = 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1)))))
                    for p in profile:
                        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=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 set_matids(self, id_materials):
        for stair in self.stairs:
            stair.set_matids(id_materials)


def update(self, context):
    self.update(context)


def update_manipulators(self, context):
    self.update(context, manipulable_refresh=True)


def update_preset(self, context):
    auto_update = self.auto_update
    self.auto_update = False
    if self.presets == 'STAIR_I':
        self.n_parts = 1
        self.update_parts()
        self.parts[0].type = 'S_STAIR'
    elif self.presets == 'STAIR_L':
        self.n_parts = 3
        self.update_parts()
        self.parts[0].type = 'S_STAIR'
        self.parts[1].type = 'C_STAIR'
        self.parts[2].type = 'S_STAIR'
        self.da = pi / 2
    elif self.presets == 'STAIR_U':
        self.n_parts = 3
        self.update_parts()
        self.parts[0].type = 'S_STAIR'
        self.parts[1].type = 'D_STAIR'
        self.parts[2].type = 'S_STAIR'
        self.da = pi
    elif self.presets == 'STAIR_O':
        self.n_parts = 2
        self.update_parts()
        self.parts[0].type = 'D_STAIR'
        self.parts[1].type = 'D_STAIR'
        self.da = pi
    # keep auto_update state same
    # prevent unwanted load_preset update
    self.auto_update = auto_update


materials_enum = (
            ('0', 'Ceiling', '', 0),
            ('1', 'White', '', 1),
            ('2', 'Concrete', '', 2),
            ('3', 'Wood', '', 3),
            ('4', 'Metal', '', 4),
            ('5', 'Glass', '', 5)
            )


class archipack_stair_material(PropertyGroup):
    index : EnumProperty(
        items=materials_enum,
        default='4',
        update=update
        )

    def find_datablock_in_selection(self, context):
        """
            find witch selected object this instance belongs to
            provide support for "copy to selected"
        """
        selected = context.selected_objects[:]
        for o in selected:
            props = archipack_stair.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_stair_part(PropertyGroup):
    type : EnumProperty(
            items=(
                ('S_STAIR', 'Straight stair', '', 0),
                ('C_STAIR', 'Curved stair', '', 1),
                ('D_STAIR', 'Dual Curved stair', '', 2),
                ('S_LANDING', 'Straight landing', '', 3),
                ('C_LANDING', 'Curved landing', '', 4),
                ('D_LANDING', 'Dual Curved landing', '', 5)
                ),
            default='S_STAIR',
            update=update_manipulators
            )
    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="Angle",
            min=-pi,
            max=pi,
            default=pi / 2,
            subtype='ANGLE', unit='ROTATION',
            update=update
            )
    left_shape : EnumProperty(
            items=(
                ('RECTANGLE', 'Straight', '', 0),
                ('CIRCLE', 'Curved ', '', 1)
                ),
            default='RECTANGLE',
            update=update
            )
    right_shape : EnumProperty(
            items=(
                ('RECTANGLE', 'Straight', '', 0),
                ('CIRCLE', 'Curved ', '', 1)
                ),
            default='RECTANGLE',
            update=update
            )
    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 = context.selected_objects[:]
        for o in selected:
            props = archipack_stair.datablock(o)
            if props:
                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, user_mode):
        if user_mode:
            box = layout.box()
            row = box.row()
            row.prop(self, "type", text=str(index + 1))
            if self.type in ['C_STAIR', 'C_LANDING', 'D_STAIR', 'D_LANDING']:
                row = box.row()
                row.prop(self, "radius")
                row = box.row()
                row.prop(self, "da")
            else:
                row = box.row()
                row.prop(self, "length")
            if self.type in ['C_STAIR', 'C_LANDING', 'D_STAIR', 'D_LANDING']:
                row = box.row(align=True)
                row.prop(self, "left_shape", text="")
                row.prop(self, "right_shape", text="")
        else:
            if self.type in ['S_STAIR', 'S_LANDING']:
                box = layout.box()
                row = box.row()
                row.prop(self, "length")


class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):

    parts : CollectionProperty(type=archipack_stair_part)
    n_parts : IntProperty(
            name="Parts",
            min=1,
            max=32,
            default=1, update=update_manipulators
            )
    step_depth : FloatProperty(
            name="Going",
            min=0.2,
            default=0.25,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    width : FloatProperty(
            name="Width",
            min=0.01,
            default=1.2,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    height : FloatProperty(
            name="Height",
            min=0.1,
            default=2.4, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    nose_y : FloatProperty(
            name="Depth",
            min=0.0,
            default=0.02, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    x_offset : FloatProperty(
            name="Offset",
            default=0.0, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    nose_z : FloatProperty(
            name="Height",
            min=0.001,
            default=0.03, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    bottom_z : FloatProperty(
            name="Thickness",
            min=0.001,
            default=0.03, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    radius : FloatProperty(
            name="Radius",
            min=0.5,
            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
            )
    total_angle : FloatProperty(
            name="Angle",
            min=-50 * pi,
            max=50 * pi,
            default=2 * pi,
            subtype='ANGLE', unit='ROTATION',
            update=update
            )
    steps_type : EnumProperty(
            name="Steps",
            items=(
                ('CLOSED', 'Closed', '', 0),
                ('FULL', 'Full height', '', 1),
                ('OPEN', 'Open ', '', 2)
                ),
            default='CLOSED',
            update=update
            )
    nose_type : EnumProperty(
            name="Nosing",
            items=(
                ('STRAIGHT', 'Straight', '', 0),
                ('OBLIQUE', 'Oblique', '', 1),
                ),
            default='STRAIGHT',
            update=update
            )
    left_shape : EnumProperty(
            items=(
                ('RECTANGLE', 'Straight', '', 0),
                ('CIRCLE', 'Curved ', '', 1)
                ),
            default='RECTANGLE',
            update=update
            )
    right_shape : EnumProperty(
            items=(
                ('RECTANGLE', 'Straight', '', 0),
                ('CIRCLE', 'Curved ', '', 1)
                ),
            default='RECTANGLE',
            update=update
            )
    z_mode : EnumProperty(
            name="Interp z",
            items=(
                ('STANDARD', 'Standard', '', 0),
                ('LINEAR', 'Bottom Linear', '', 1),
                ('LINEAR_TOP', 'All Linear', '', 2)
                ),
            default='STANDARD',
            update=update
            )
    presets : EnumProperty(
            items=(
                ('STAIR_I', 'I stair', '', 0),
                ('STAIR_L', 'L stair', '', 1),
                ('STAIR_U', 'U stair', '', 2),
                ('STAIR_O', 'O stair', '', 3),
                ('STAIR_USER', 'User defined stair', '', 4),
                ),
            default='STAIR_I', update=update_preset
            )
    left_post : BoolProperty(
            name='left',
            default=True,
            update=update
            )
    right_post : BoolProperty(
            name='right',
            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,
            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",
            min=-100,
            default=0, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    post_offset_x : FloatProperty(
            name="Offset",
            min=-100.0, max=100,
            default=0.02, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    post_corners : BoolProperty(
            name="Only on edges",
            update=update,
            default=False
            )
    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='4',
            update=update
            )
    left_subs : BoolProperty(
            name='left',
            default=False,
            update=update
            )
    right_subs : BoolProperty(
            name='right',
            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",
            min=-100,
            default=0, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    subs_offset_x : FloatProperty(
            name="Offset",
            min=-100.0, max=100,
            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='4',
            update=update
            )
    left_panel : BoolProperty(
            name='left',
            default=True,
            update=update
            )
    right_panel : BoolProperty(
            name='right',
            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="Spacing",
            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='5',
            update=update
            )
    left_rail : BoolProperty(
            name="left",
            update=update,
            default=False
            )
    right_rail : BoolProperty(
            name="right",
            update=update,
            default=False
            )
    rail_n : IntProperty(
            name="#",
            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_stair_material)

    left_handrail : BoolProperty(
            name="left",
            update=update,
            default=True
            )
    right_handrail : BoolProperty(
            name="right",
            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",
            default=0.1, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    handrail_slice_left : 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
            )

    left_string : BoolProperty(
            name="left",
            update=update,
            default=False
            )
    right_string : BoolProperty(
            name="right",
            update=update,
            default=False
            )
    string_x : FloatProperty(
            name="Width",
            min=-100.0,
            default=0.02, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    string_z : FloatProperty(
            name="Height",
            default=0.3, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    string_offset : FloatProperty(
            name="Offset",
            default=0.0, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )
    string_alt : FloatProperty(
            name="Altitude",
            default=-0.04, precision=2, step=1,
            unit='LENGTH', subtype='DISTANCE',
            update=update
            )

    idmat_bottom : EnumProperty(
            name="Bottom",
            items=materials_enum,
            default='1',
            update=update
            )
    idmat_raise : EnumProperty(
            name="Raise",
            items=materials_enum,
            default='1',
            update=update
            )
    idmat_step_front : EnumProperty(
            name="Step front",
            items=materials_enum,
            default='3',
            update=update
            )
    idmat_top : EnumProperty(
            name="Top",
            items=materials_enum,
            default='3',
            update=update
            )
    idmat_side : EnumProperty(
            name="Side",
            items=materials_enum,
            default='1',
            update=update
            )
    idmat_step_side : EnumProperty(
            name="Step Side",
            items=materials_enum,
            default='3',
            update=update
            )
    idmat_handrail : EnumProperty(
            name="Handrail",
            items=materials_enum,
            default='3',
            update=update
            )
    idmat_string : EnumProperty(
            name="String",
            items=materials_enum,
            default='3',
            update=update
            )

    # UI layout related
    parts_expand : BoolProperty(
            default=False
            )
    steps_expand : BoolProperty(
            default=False
            )
    rail_expand : BoolProperty(
            default=False
            )
    idmats_expand : BoolProperty(
            default=False
            )
    handrail_expand : BoolProperty(
            default=False
            )
    string_expand : BoolProperty(
            default=False
            )
    post_expand : BoolProperty(
            default=False
            )
    panel_expand : BoolProperty(
            default=False
            )
    subs_expand : BoolProperty(
            default=False
            )

    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 < 1:
                m = p.manipulators.add()
                m.type_key = 'SIZE'
                m.prop1_name = 'length'

    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 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()

        center = Vector((0, 0))
        verts = []
        faces = []
        matids = []
        uvs = []
        id_materials = [int(self.idmat_top), int(self.idmat_step_front), int(self.idmat_raise),
                        int(self.idmat_side), int(self.idmat_bottom), int(self.idmat_step_side)]

        # depth at bottom
        bottom_z = self.bottom_z
        if self.steps_type == 'OPEN':
            # depth at front
            bottom_z = self.nose_z

        width_left = 0.5 * self.width - self.x_offset
        width_right = 0.5 * self.width + self.x_offset

        self.manipulators[0].set_pts([(-width_left, 0, 0), (width_right, 0, 0), (1, 0, 0)])
        self.manipulators[1].set_pts([(0, 0, 0), (0, 0, self.height), (1, 0, 0)])

        g = StairGenerator(self.parts)
        if self.presets == 'STAIR_USER':
            for part in self.parts:
                g.add_part(part.type, self.steps_type, self.nose_type, self.z_mode, self.nose_z,
                        bottom_z, center, max(width_left + 0.01, width_right + 0.01, part.radius), part.da,
                        width_left, width_right, part.length, part.left_shape, part.right_shape)

        elif self.presets == 'STAIR_O':
            n_parts = max(1, int(round(abs(self.total_angle) / pi, 0)))
            if self.total_angle > 0:
                dir = 1
            else:
                dir = -1
            last_da = self.total_angle - dir * (n_parts - 1) * pi
            if dir * last_da > pi:
                n_parts += 1
                last_da -= dir * pi
            abs_last = dir * last_da

            for part in range(n_parts - 1):
                g.add_part('D_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), dir * pi,
                            width_left, width_right, 1.0, self.left_shape, self.right_shape)
            if round(abs_last, 2) > 0:
                if abs_last > pi / 2:
                    g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius),
                            dir * pi / 2,
                            width_left, width_right, 1.0, self.left_shape, self.right_shape)
                    g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius),
                            last_da - dir * pi / 2,
                            width_left, width_right, 1.0, self.left_shape, self.right_shape)
                else:
                    g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), last_da,
                            width_left, width_right, 1.0, self.left_shape, self.right_shape)
        else:
            # STAIR_L STAIR_I STAIR_U
            for part in self.parts:
                g.add_part(part.type, self.steps_type, self.nose_type, self.z_mode, self.nose_z,
                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), self.da,
                            width_left, width_right, part.length, self.left_shape, self.right_shape)

        # Stair basis
        g.set_matids(id_materials)
        g.make_stair(self.height, self.step_depth, verts, faces, matids, uvs, nose_y=self.nose_y)

        # Ladder
        offset_x = 0.5 * self.width - self.post_offset_x
        post_spacing = self.post_spacing
        if self.post_corners:
            post_spacing = 10000

        if self.user_defined_post_enable:
            # user defined posts
            user_def_post = context.scene.objects.get(self.user_defined_post.strip())
            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.left_post:
            g.make_post(self.height, self.step_depth, 0.5 * self.post_x, 0.5 * self.post_y,
                    self.post_z, self.post_alt, 'LEFT', post_spacing, self.post_corners,
                    self.x_offset, offset_x, int(self.idmat_post), verts, faces, matids, uvs)

        if self.right_post:
            g.make_post(self.height, self.step_depth, 0.5 * self.post_x, 0.5 * self.post_y,
                    self.post_z, self.post_alt, 'RIGHT', post_spacing, self.post_corners,
                    self.x_offset, offset_x, 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.strip())
            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.left_subs:
            g.make_subs(self.height, self.step_depth, 0.5 * self.subs_x, 0.5 * self.subs_y,
                    self.subs_z, 0.5 * self.post_y, self.subs_alt, self.subs_bottom, 'LEFT',
                    self.handrail_slice_left, post_spacing, self.subs_spacing, self.post_corners,
                    self.x_offset, offset_x, -self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs)

        if self.right_subs:
            g.make_subs(self.height, self.step_depth, 0.5 * self.subs_x, 0.5 * self.subs_y,
                    self.subs_z, 0.5 * self.post_y, self.subs_alt, self.subs_bottom, 'RIGHT',
                    self.handrail_slice_right, post_spacing, self.subs_spacing, self.post_corners,
                    self.x_offset, offset_x, self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs)

        g.user_defined_post = None

        if self.left_panel:
            g.make_panels(self.height, self.step_depth, 0.5 * self.panel_x, self.panel_z, 0.5 * self.post_y,
                    self.panel_alt, 'LEFT', post_spacing, self.panel_dist, self.post_corners,
                    self.x_offset, offset_x, -self.panel_offset_x, int(self.idmat_panel), verts, faces, matids, uvs)

        if self.right_panel:
            g.make_panels(self.height, self.step_depth, 0.5 * self.panel_x, self.panel_z, 0.5 * self.post_y,
                    self.panel_alt, 'RIGHT', post_spacing, self.panel_dist, self.post_corners,
                    self.x_offset, offset_x, self.panel_offset_x, int(self.idmat_panel), verts, faces, matids, uvs)

        if self.right_rail:
            for i in range(self.rail_n):
                id_materials = [int(self.rail_mat[i].index) for j in range(6)]
                g.set_matids(id_materials)
                g.make_part(self.height, self.step_depth, self.rail_x[i], self.rail_z[i],
                        self.x_offset, offset_x + self.rail_offset[i],
                        self.rail_alt[i], 'LINEAR', 'CLOSED', verts, faces, matids, uvs)

        if self.left_rail:
            for i in range(self.rail_n):
                id_materials = [int(self.rail_mat[i].index) for j in range(6)]
                g.set_matids(id_materials)
                g.make_part(self.height, self.step_depth, self.rail_x[i], self.rail_z[i],
                        self.x_offset, -offset_x - self.rail_offset[i],
                        self.rail_alt[i], 'LINEAR', 'CLOSED', 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.right_handrail:
            g.make_profile(handrail, int(self.idmat_handrail), "RIGHT", self.handrail_slice_right,
                self.height, self.step_depth, self.x_offset + offset_x + self.handrail_offset,
                self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs)

        if self.left_handrail:
            g.make_profile(handrail, int(self.idmat_handrail), "LEFT", self.handrail_slice_left,
                self.height, self.step_depth, -self.x_offset + offset_x + self.handrail_offset,
                self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs)

        w = 0.5 * self.string_x
        h = self.string_z
        string = [Vector((-w, 0)), Vector((w, 0)), Vector((w, h)), Vector((-w, h))]

        if self.right_string:
            g.make_profile(string, int(self.idmat_string), "RIGHT", False, self.height, self.step_depth,
                self.x_offset + 0.5 * self.width + self.string_offset,
                self.string_alt, 0, verts, faces, matids, uvs)

        if self.left_string:
            g.make_profile(string, int(self.idmat_string), "LEFT", False, self.height, self.step_depth,
                -self.x_offset + 0.5 * self.width + self.string_offset,
                self.string_alt, 0, 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

        self.restore_context(context)

    def manipulable_setup(self, context):
        """
            TODO: Implement the setup part as per parent object basis

            self.manipulable_disable(context)
            o = context.active_object
            for m in self.manipulators:
                self.manip_stack.append(m.setup(context, o, self))

        """
        self.manipulable_disable(context)
        o = context.active_object

        self.setup_manipulators()

        if self.presets != 'STAIR_O':
            for i, part in enumerate(self.parts):
                if i >= self.n_parts:
                    break
                if "S_" in part.type or self.presets in ['STAIR_USER']:
                    for j, m in enumerate(part.manipulators):
                        self.manip_stack.append(m.setup(context, o, part))

        if self.presets in ['STAIR_U', 'STAIR_L']:
            self.manip_stack.append(self.parts[1].manipulators[0].setup(context, o, self))

        for m in self.manipulators:
            self.manip_stack.append(m.setup(context, o, self))


class ARCHIPACK_PT_stair(Panel):
    bl_idname = "ARCHIPACK_PT_stair"
    bl_label = "Stair"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    # bl_context = 'object'
    bl_category = 'Archipack'

    @classmethod
    def poll(cls, context):
        return archipack_stair.filter(context.active_object)

    def draw(self, context):
        prop = archipack_stair.datablock(context.active_object)
        if prop is None:
            return
        scene = context.scene
        layout = self.layout
        row = layout.row(align=True)
        row.operator('archipack.stair_manipulate', icon='VIEW_PAN')
        row = layout.row(align=True)
        row.prop(prop, 'presets', text="")
        box = layout.box()
        # box.label(text="Styles")
        row = box.row(align=True)
        # row.menu("ARCHIPACK_MT_stair_preset", text=bpy.types.ARCHIPACK_MT_stair_preset.bl_label)
        row.operator("archipack.stair_preset_menu", text=bpy.types.ARCHIPACK_OT_stair_preset_menu.bl_label)
        row.operator("archipack.stair_preset", text="", icon='ADD')
        row.operator("archipack.stair_preset", text="", icon='REMOVE').remove_active = True
        box = layout.box()
        box.prop(prop, 'width')
        box.prop(prop, 'height')
        box.prop(prop, 'bottom_z')
        box.prop(prop, 'x_offset')
        # box.prop(prop, 'z_mode')
        box = layout.box()
        row = box.row()
        if prop.parts_expand:
            row.prop(prop, 'parts_expand', icon="TRIA_DOWN", text="Parts", emboss=False)
            if prop.presets == 'STAIR_USER':
                box.prop(prop, 'n_parts')
            if prop.presets != 'STAIR_USER':
                row = box.row(align=True)
                row.prop(prop, "left_shape", text="")
                row.prop(prop, "right_shape", text="")
                row = box.row()
                row.prop(prop, "radius")
                row = box.row()
                if prop.presets == 'STAIR_O':
                    row.prop(prop, 'total_angle')
                else:
                    row.prop(prop, 'da')
            if prop.presets != 'STAIR_O':
                for i, part in enumerate(prop.parts):
                    part.draw(layout, context, i, prop.presets == 'STAIR_USER')
        else:
            row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", text="Parts", emboss=False)

        box = layout.box()
        row = box.row()
        if prop.steps_expand:
            row.prop(prop, 'steps_expand', icon="TRIA_DOWN", text="Steps", emboss=False)
            box.prop(prop, 'steps_type')
            box.prop(prop, 'step_depth')
            box.prop(prop, 'nose_type')
            box.prop(prop, 'nose_z')
            box.prop(prop, 'nose_y')
        else:
            row.prop(prop, 'steps_expand', icon="TRIA_RIGHT", text="Steps", emboss=False)

        box = layout.box()
        row = box.row(align=True)
        if prop.handrail_expand:
            row.prop(prop, 'handrail_expand', icon="TRIA_DOWN", text="Handrail", emboss=False)
        else:
            row.prop(prop, 'handrail_expand', icon="TRIA_RIGHT", text="Handrail", emboss=False)

        row.prop(prop, 'left_handrail')
        row.prop(prop, 'right_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_left')
            row.prop(prop, 'handrail_slice_right')

        box = layout.box()
        row = box.row(align=True)
        if prop.string_expand:
            row.prop(prop, 'string_expand', icon="TRIA_DOWN", text="String", emboss=False)
        else:
            row.prop(prop, 'string_expand', icon="TRIA_RIGHT", text="String", emboss=False)
        row.prop(prop, 'left_string')
        row.prop(prop, 'right_string')
        if prop.string_expand:
            box.prop(prop, 'string_x')
            box.prop(prop, 'string_z')
            box.prop(prop, 'string_alt')
            box.prop(prop, 'string_offset')

        box = layout.box()
        row = box.row(align=True)
        if prop.post_expand:
            row.prop(prop, 'post_expand', icon="TRIA_DOWN", text="Post", emboss=False)
        else:
            row.prop(prop, 'post_expand', icon="TRIA_RIGHT", text="Post", emboss=False)
        row.prop(prop, 'left_post')
        row.prop(prop, 'right_post')
        if prop.post_expand:
            box.prop(prop, 'post_corners')
            if not prop.post_corners:
                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')
            box.prop(prop, 'post_offset_x')
            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", text="Subs", emboss=False)
        else:
            row.prop(prop, 'subs_expand', icon="TRIA_RIGHT", text="Subs", emboss=False)

        row.prop(prop, 'left_subs')
        row.prop(prop, 'right_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')
            box.prop(prop, 'subs_bottom')
            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", text="Panels", emboss=False)
        else:
            row.prop(prop, 'panel_expand', icon="TRIA_RIGHT", text="Panels", emboss=False)
        row.prop(prop, 'left_panel')
        row.prop(prop, 'right_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", text="Rails", emboss=False)
        else:
            row.prop(prop, 'rail_expand', icon="TRIA_RIGHT", text="Rails", emboss=False)
        row.prop(prop, 'left_rail')
        row.prop(prop, 'right_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", text="Materials", emboss=False)
            box.prop(prop, 'idmat_top')
            box.prop(prop, 'idmat_side')
            box.prop(prop, 'idmat_bottom')
            box.prop(prop, 'idmat_step_side')
            box.prop(prop, 'idmat_step_front')
            box.prop(prop, 'idmat_raise')
            box.prop(prop, 'idmat_handrail')
            box.prop(prop, 'idmat_panel')
            box.prop(prop, 'idmat_post')
            box.prop(prop, 'idmat_subs')
            box.prop(prop, 'idmat_string')
        else:
            row.prop(prop, 'idmats_expand', icon="TRIA_RIGHT", text="Materials", emboss=False)


# ------------------------------------------------------------------
# Define operator class to create object
# ------------------------------------------------------------------


class ARCHIPACK_OT_stair(ArchipackCreateTool, Operator):
    bl_idname = "archipack.stair"
    bl_label = "Stair"
    bl_description = "Create a Stair"
    bl_category = 'Archipack'
    bl_options = {'REGISTER', 'UNDO'}

    def create(self, context):
        m = bpy.data.meshes.new("Stair")
        o = bpy.data.objects.new("Stair", m)
        d = m.archipack_stair.add()
        self.link_object_to_scene(context, o)
        o.select_set(state=True)
        context.view_layer.objects.active = o
        self.load_preset(d)
        self.add_material(o)
        m.auto_smooth_angle = 0.20944
        return o

    # -----------------------------------------------------
    # Execute
    # -----------------------------------------------------
    def execute(self, context):
        if context.mode == "OBJECT":
            bpy.ops.object.select_all(action="DESELECT")
            o = self.create(context)
            o.location = context.scene.cursor.location
            o.select_set(state=True)
            context.view_layer.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_stair_manipulate(Operator):
    bl_idname = "archipack.stair_manipulate"
    bl_label = "Manipulate"
    bl_description = "Manipulate"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(self, context):
        return archipack_stair.filter(context.active_object)

    def invoke(self, context, event):
        d = archipack_stair.datablock(context.active_object)
        d.manipulable_invoke(context)
        return {'FINISHED'}


# ------------------------------------------------------------------
# Define operator class to load / save presets
# ------------------------------------------------------------------


class ARCHIPACK_OT_stair_preset_menu(PresetMenuOperator, Operator):
    bl_description = "Show Stair Presets"
    bl_idname = "archipack.stair_preset_menu"
    bl_label = "Stair style"
    preset_subdir = "archipack_stair"


class ARCHIPACK_OT_stair_preset(ArchipackPreset, Operator):
    """Add a Stair Preset"""
    bl_idname = "archipack.stair_preset"
    bl_label = "Add Stair Style"
    preset_menu = "ARCHIPACK_OT_stair_preset_menu"

    @property
    def blacklist(self):
        return ['manipulators']


def register():
    bpy.utils.register_class(archipack_stair_material)
    bpy.utils.register_class(archipack_stair_part)
    bpy.utils.register_class(archipack_stair)
    Mesh.archipack_stair = CollectionProperty(type=archipack_stair)
    bpy.utils.register_class(ARCHIPACK_PT_stair)
    bpy.utils.register_class(ARCHIPACK_OT_stair)
    bpy.utils.register_class(ARCHIPACK_OT_stair_preset_menu)
    bpy.utils.register_class(ARCHIPACK_OT_stair_preset)
    bpy.utils.register_class(ARCHIPACK_OT_stair_manipulate)


def unregister():
    bpy.utils.unregister_class(archipack_stair_material)
    bpy.utils.unregister_class(archipack_stair_part)
    bpy.utils.unregister_class(archipack_stair)
    del Mesh.archipack_stair
    bpy.utils.unregister_class(ARCHIPACK_PT_stair)
    bpy.utils.unregister_class(ARCHIPACK_OT_stair)
    bpy.utils.unregister_class(ARCHIPACK_OT_stair_preset_menu)
    bpy.utils.unregister_class(ARCHIPACK_OT_stair_preset)
    bpy.utils.unregister_class(ARCHIPACK_OT_stair_manipulate)
