1# -*- coding:utf-8 -*-
2
3# ##### BEGIN GPL LICENSE BLOCK #####
4#
5#  This program is free software; you can redistribute it and/or
6#  modify it under the terms of the GNU General Public License
7#  as published by the Free Software Foundation; either version 2
8#  of the License, or (at your option) any later version.
9#
10#  This program is distributed in the hope that it will be useful,
11#  but WITHOUT ANY WARRANTY; without even the implied warranty of
12#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#  GNU General Public License for more details.
14#
15#  You should have received a copy of the GNU General Public License
16#  along with this program; if not, write to the Free Software Foundation,
17#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
18#
19# ##### END GPL LICENSE BLOCK #####
20
21# <pep8 compliant>
22
23# ----------------------------------------------------------
24# Author: Stephen Leger (s-leger)
25#
26# ----------------------------------------------------------
27# noinspection PyUnresolvedReferences
28import bpy
29# noinspection PyUnresolvedReferences
30from bpy.types import Operator, PropertyGroup, Mesh, Panel
31from bpy.props import (
32    FloatProperty, BoolProperty, IntProperty, CollectionProperty,
33    StringProperty, EnumProperty, FloatVectorProperty
34    )
35from .bmesh_utils import BmeshEdit as bmed
36from .panel import Panel as Lofter
37from mathutils import Vector, Matrix
38from math import sin, cos, pi, floor, acos
39from .archipack_manipulator import Manipulable, archipack_manipulator
40from .archipack_2d import Line, Arc
41from .archipack_preset import ArchipackPreset, PresetMenuOperator
42from .archipack_object import ArchipackCreateTool, ArchipackObject
43
44
45class Stair():
46    def __init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z):
47        self.steps_type = steps_type
48        self.nose_type = nose_type
49        self.l_shape = None
50        self.r_shape = None
51        self.next_type = 'NONE'
52        self.last_type = 'NONE'
53        self.z_mode = z_mode
54        # depth of open step
55        self.nose_z = nose_z
56        # size under the step on bottom
57        self.bottom_z = bottom_z
58        self.left_offset = left_offset
59        self.right_offset = right_offset
60        self.last_height = 0
61
62    def set_matids(self, matids):
63        self.idmat_top, self.idmat_step_front, self.idmat_raise, \
64        self.idmat_side, self.idmat_bottom, self.idmat_step_side = matids
65
66    def set_height(self, step_height, z0):
67        self.step_height = step_height
68        self.z0 = z0
69
70    @property
71    def height(self):
72        return self.n_step * self.step_height
73
74    @property
75    def top_offset(self):
76        return self.t_step / self.step_depth
77
78    @property
79    def top(self):
80        return self.z0 + self.height
81
82    @property
83    def left_length(self):
84        return self.get_length("LEFT")
85
86    @property
87    def right_length(self):
88        return self.get_length("RIGHT")
89
90    def step_size(self, step_depth):
91        t_step, n_step = self.steps(step_depth)
92        self.n_step = n_step
93        self.t_step = t_step
94        self.step_depth = step_depth
95        return n_step
96
97    def p3d_left(self, verts, p2d, i, t, landing=False):
98        x, y = p2d
99        nose_z = min(self.step_height, self.nose_z)
100        zl = self.z0 + t * self.height
101        zs = self.z0 + i * self.step_height
102        if self.z_mode == 'LINEAR':
103            z0 = max(0, zl)
104            z1 = z0 - self.bottom_z
105            verts.extend([(x, y, z0), (x, y, z1)])
106        else:
107            if "FULL" in self.steps_type:
108                z0 = 0
109            else:
110                z0 = max(0, zl - nose_z - self.bottom_z)
111            z3 = zs + max(0, self.step_height - nose_z)
112            z4 = zs + self.step_height
113            if landing:
114                if "FULL" in self.steps_type:
115                    z2 = 0
116                    z1 = 0
117                else:
118                    z2 = max(0, min(z3, z3 - self.bottom_z))
119                    z1 = z2
120            else:
121                z1 = min(z3, max(z0, zl - nose_z))
122                z2 = min(z3, max(z1, zl))
123            verts.extend([(x, y, z0),
124                        (x, y, z1),
125                        (x, y, z2),
126                        (x, y, z3),
127                        (x, y, z4)])
128
129    def p3d_right(self, verts, p2d, i, t, landing=False):
130        x, y = p2d
131        nose_z = min(self.step_height, self.nose_z)
132        zl = self.z0 + t * self.height
133        zs = self.z0 + i * self.step_height
134        if self.z_mode == 'LINEAR':
135            z0 = max(0, zl)
136            z1 = z0 - self.bottom_z
137            verts.extend([(x, y, z1), (x, y, z0)])
138        else:
139            if "FULL" in self.steps_type:
140                z0 = 0
141            else:
142                z0 = max(0, zl - nose_z - self.bottom_z)
143            z3 = zs + max(0, self.step_height - nose_z)
144            z4 = zs + self.step_height
145            if landing:
146                if "FULL" in self.steps_type:
147                    z2 = 0
148                    z1 = 0
149                else:
150                    z2 = max(0, min(z3, z3 - self.bottom_z))
151                    z1 = z2
152            else:
153                z1 = min(z3, max(z0, zl - nose_z))
154                z2 = min(z3, max(z1, zl))
155            verts.extend([(x, y, z4),
156                          (x, y, z3),
157                          (x, y, z2),
158                          (x, y, z1),
159                          (x, y, z0)])
160
161    def p3d_cstep_left(self, verts, p2d, i, t):
162        x, y = p2d
163        nose_z = min(self.step_height, self.nose_z)
164        zs = self.z0 + i * self.step_height
165        z3 = zs + max(0, self.step_height - nose_z)
166        z1 = min(z3, zs - nose_z)
167        verts.append((x, y, z1))
168        verts.append((x, y, z3))
169
170    def p3d_cstep_right(self, verts, p2d, i, t):
171        x, y = p2d
172        nose_z = min(self.step_height, self.nose_z)
173        zs = self.z0 + i * self.step_height
174        z3 = zs + max(0, self.step_height - nose_z)
175        z1 = min(z3, zs - nose_z)
176        verts.append((x, y, z3))
177        verts.append((x, y, z1))
178
179    def straight_stair(self, length):
180        self.next_type = 'STAIR'
181        s = self.straight(length)
182        return StraightStair(s.p, s.v, self.left_offset, self.right_offset, self.steps_type,
183                self.nose_type, self.z_mode, self.nose_z, self.bottom_z)
184
185    def straight_landing(self, length, last_type='STAIR'):
186        self.next_type = 'LANDING'
187        s = self.straight(length)
188        return StraightLanding(s.p, s.v, self.left_offset, self.right_offset, self.steps_type,
189                self.nose_type, self.z_mode, self.nose_z, self.bottom_z, last_type=last_type)
190
191    def curved_stair(self, da, radius, left_shape, right_shape, double_limit=pi):
192        self.next_type = 'STAIR'
193        n = self.normal(1)
194        n.v = radius * n.v.normalized()
195        if da < 0:
196            n.v = -n.v
197        a0 = n.angle
198        c = n.p - n.v
199        return CurvedStair(c, radius, a0, da, self.left_offset, self.right_offset,
200                self.steps_type, self.nose_type, self.z_mode, self.nose_z, self.bottom_z,
201                left_shape, right_shape, double_limit=double_limit)
202
203    def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi, last_type='STAIR'):
204        self.next_type = 'LANDING'
205        n = self.normal(1)
206        n.v = radius * n.v.normalized()
207        if da < 0:
208            n.v = -n.v
209        a0 = n.angle
210        c = n.p - n.v
211        return CurvedLanding(c, radius, a0, da, self.left_offset, self.right_offset,
212                self.steps_type, self.nose_type, self.z_mode, self.nose_z, self.bottom_z,
213                left_shape, right_shape, double_limit=double_limit, last_type=last_type)
214
215    def get_z(self, t, mode):
216        if mode == 'LINEAR':
217            return self.z0 + t * self.height
218        else:
219            step = 1 + floor(t / self.t_step)
220            return self.z0 + step * self.step_height
221
222    def make_profile(self, t, side, profile, verts, faces, matids, next=None, tnext=0):
223        z0 = self.get_z(t, 'LINEAR')
224        dz1 = 0
225        t, part, dz0, shape = self.get_part(t, side)
226        if next is not None:
227            tnext, next, dz1, shape1 = next.get_part(tnext, side)
228        xy, s = part.proj_xy(t, next)
229        v_xy = s * xy.to_3d()
230        z, s = part.proj_z(t, dz0, next, dz1)
231        v_z = s * Vector((-xy.y * z.x, xy.x * z.x, z.y))
232        x, y = part.lerp(t)
233        verts += [Vector((x, y, z0)) + v.x * v_xy + v.y * v_z for v in profile]
234
235    def project_uv(self, rM, uvs, verts, indexes, up_axis='Z'):
236        if up_axis == 'Z':
237            uvs.append([(rM @ Vector(verts[i])).to_2d() for i in indexes])
238        elif up_axis == 'Y':
239            uvs.append([(x, z) for x, y, z in [(rM @ Vector(verts[i])) for i in indexes]])
240        else:
241            uvs.append([(y, z) for x, y, z in [(rM @ Vector(verts[i])) for i in indexes]])
242
243    def get_proj_matrix(self, part, t, nose_y):
244        # a matrix to project verts
245        # into uv space for horizontal parts of this step
246        # so uv = (rM @ vertex).to_2d()
247        tl = t - nose_y / self.get_length("LEFT")
248        tr = t - nose_y / self.get_length("RIGHT")
249        t2, part, dz, shape = self.get_part(tl, "LEFT")
250        p0 = part.lerp(t2)
251        t2, part, dz, shape = self.get_part(tr, "RIGHT")
252        p1 = part.lerp(t2)
253        v = (p1 - p0).normalized()
254        return Matrix([
255            [-v.y, v.x, 0, p0.x],
256            [v.x, v.y, 0, p0.y],
257            [0, 0, 1, 0],
258            [0, 0, 0, 1]
259        ]).inverted()
260
261    def _make_nose(self, i, s, verts, faces, matids, uvs, nose_y):
262
263        t = self.t_step * i
264
265        # a matrix to project verts
266        # into uv space for horizontal parts of this step
267        # so uv = (rM @ vertex).to_2d()
268        rM = self.get_proj_matrix(self, t, nose_y)
269
270        if self.z_mode == 'LINEAR':
271            return rM
272
273        f = len(verts)
274
275        tl = t - nose_y / self.get_length("LEFT")
276        tr = t - nose_y / self.get_length("RIGHT")
277
278        t2, part, dz, shape = self.get_part(tl, "LEFT")
279        p0 = part.lerp(t2)
280        self.p3d_left(verts, p0, s, t2)
281
282        t2, part, dz, shape = self.get_part(tr, "RIGHT")
283        p1 = part.lerp(t2)
284        self.p3d_right(verts, p1, s, t2)
285
286        start = 3
287        end = 6
288        offset = 10
289
290        # left, top, right
291        matids.extend([self.idmat_step_side,
292             self.idmat_top,
293             self.idmat_step_side])
294
295        faces += [(f + j, f + j + 1, f + j + offset + 1, f + j + offset) for j in range(start, end)]
296
297        u = nose_y
298        v = (p1 - p0).length
299        w = verts[f + 2][2] - verts[f + 3][2]
300        s = int((end - start) / 2)
301
302        uvs += [[(u, verts[f + j][2]), (u, verts[f + j + 1][2]),
303            (0, verts[f + j + 1][2]), (0, verts[f + j][2])] for j in range(start, start + s)]
304
305        uvs.append([(0, 0), (0, v), (u, v), (u, 0)])
306
307        uvs += [[(u, verts[f + j][2]), (u, verts[f + j + 1][2]),
308            (0, verts[f + j + 1][2]), (0, verts[f + j][2])] for j in range(start + s + 1, end)]
309
310        if 'STRAIGHT' in self.nose_type or 'OPEN' in self.steps_type:
311            # face bottom
312            matids.append(self.idmat_bottom)
313            faces.append((f + end, f + start, f + offset + start, f + offset + end))
314            uvs.append([(u, v), (u, 0), (0, 0), (0, v)])
315
316        if self.steps_type != 'OPEN':
317            if 'STRAIGHT' in self.nose_type:
318                # front face bottom straight
319                matids.append(self.idmat_raise)
320                faces.append((f + 12, f + 17, f + 16, f + 13))
321                uvs.append([(0, w), (v, w), (v, 0), (0, 0)])
322
323            elif 'OBLIQUE' in self.nose_type:
324                # front face bottom oblique
325                matids.append(self.idmat_raise)
326                faces.append((f + 12, f + 17, f + 6, f + 3))
327
328                uvs.append([(0, w), (v, w), (v, 0), (0, 0)])
329
330                matids.append(self.idmat_side)
331                faces.append((f + 3, f + 13, f + 12))
332                uvs.append([(0, 0), (u, 0), (u, w)])
333
334                matids.append(self.idmat_side)
335                faces.append((f + 6, f + 17, f + 16))
336                uvs.append([(0, 0), (u, w), (u, 0)])
337
338        # front face top
339        w = verts[f + 3][2] - verts[f + 4][2]
340        matids.append(self.idmat_step_front)
341        faces.append((f + 4, f + 3, f + 6, f + 5))
342        uvs.append([(0, 0), (0, w), (v, w), (v, 0)])
343        return rM
344
345    def make_faces(self, f, rM, verts, faces, matids, uvs):
346
347        if self.z_mode == 'LINEAR':
348            start = 0
349            end = 3
350            offset = 4
351            matids.extend([self.idmat_side,
352                 self.idmat_top,
353                 self.idmat_side,
354                 self.idmat_bottom])
355        elif "OPEN" in self.steps_type:
356            # faces dessus-dessous-lateral marches fermees
357            start = 3
358            end = 6
359            offset = 10
360            matids.extend([self.idmat_step_side,
361                 self.idmat_top,
362                 self.idmat_step_side,
363                 self.idmat_bottom])
364        else:
365            # faces dessus-dessous-lateral marches fermees
366            start = 0
367            end = 9
368            offset = 10
369            matids.extend([self.idmat_side,
370                 self.idmat_side,
371                 self.idmat_side,
372                 self.idmat_step_side,
373                 self.idmat_top,
374                 self.idmat_step_side,
375                 self.idmat_side,
376                 self.idmat_side,
377                 self.idmat_side,
378                 self.idmat_bottom])
379
380        u_l0 = 0
381        u_l1 = self.t_step * self.left_length
382        u_r0 = 0
383        u_r1 = self.t_step * self.right_length
384
385        s = int((end - start) / 2)
386        uvs += [[(u_l0, verts[f + j][2]), (u_l0, verts[f + j + 1][2]),
387            (u_l1, verts[f + j + offset + 1][2]), (u_l1, verts[f + j + offset][2])] for j in range(start, start + s)]
388
389        self.project_uv(rM, uvs, verts, [f + start + s, f + start + s + 1,
390            f + start + s + offset + 1, f + start + s + offset])
391
392        uvs += [[(u_r0, verts[f + j][2]), (u_r0, verts[f + j + 1][2]),
393            (u_r1, verts[f + j + offset + 1][2]), (u_r1, verts[f + j + offset][2])] for j in range(start + s + 1, end)]
394
395        self.project_uv(rM, uvs, verts, [f + end, f + start, f + offset + start, f + offset + end])
396
397        faces += [(f + j, f + j + 1, f + j + offset + 1, f + j + offset) for j in range(start, end)]
398        faces.append((f + end, f + start, f + offset + start, f + offset + end))
399
400
401class StraightStair(Stair, Line):
402    def __init__(self, p, v, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z):
403        Stair.__init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z)
404        Line.__init__(self, p, v)
405        self.l_line = self.offset(-left_offset)
406        self.r_line = self.offset(right_offset)
407
408    def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
409
410        rM = self._make_nose(i, i, verts, faces, matids, uvs, nose_y)
411
412        t0 = self.t_step * i
413
414        f = len(verts)
415
416        p = self.l_line.lerp(t0)
417        self.p3d_left(verts, p, i, t0)
418        p = self.r_line.lerp(t0)
419        self.p3d_right(verts, p, i, t0)
420
421        t1 = t0 + self.t_step
422
423        p = self.l_line.lerp(t1)
424        self.p3d_left(verts, p, i, t1)
425        p = self.r_line.lerp(t1)
426        self.p3d_right(verts, p, i, t1)
427
428        self.make_faces(f, rM, verts, faces, matids, uvs)
429
430        if "OPEN" in self.steps_type:
431            faces.append((f + 13, f + 14, f + 15, f + 16))
432            matids.append(self.idmat_step_front)
433            uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])
434
435    def get_length(self, side):
436        return self.length
437
438    def get_lerp_vect(self, posts, side, i, t_step, respect_edges, z_offset=0, t0_abs=None):
439        if t0_abs is not None:
440            t0 = t0_abs
441        else:
442            t0 = i * t_step
443        t, part, dz, shape = self.get_part(t0, side)
444        dz /= part.length
445        n = part.normal(t)
446        z0 = self.get_z(t0, 'STEP')
447        z1 = self.get_z(t0, 'LINEAR')
448        posts.append((n, dz, z0, z1 + t0 * z_offset))
449        return [t0]
450
451    def n_posts(self, post_spacing, side, respect_edges):
452        return self.steps(post_spacing)
453
454    def get_part(self, t, side):
455        if side == 'LEFT':
456            part = self.l_line
457        else:
458            part = self.r_line
459        return t, part, self.height, 'LINE'
460
461
462class CurvedStair(Stair, Arc):
463    def __init__(self, c, radius, a0, da, left_offset, right_offset, steps_type, nose_type,
464        z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=pi):
465
466        Stair.__init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z)
467        Arc.__init__(self, c, radius, a0, da)
468        self.l_shape = left_shape
469        self.r_shape = right_shape
470        self.edges_multiples = round(abs(da), 6) > double_limit
471        # left arc, tangent at start and end
472        self.l_arc, self.l_t0, self.l_t1, self.l_tc = self.set_offset(-left_offset, left_shape)
473        self.r_arc, self.r_t0, self.r_t1, self.r_tc = self.set_offset(right_offset, right_shape)
474
475    def set_offset(self, offset, shape):
476        arc = self.offset(offset)
477        t0 = arc.tangeant(0, 1)
478        t1 = arc.tangeant(1, 1)
479        tc = arc.tangeant(0.5, 1)
480        if self.edges_multiples:
481            i, p, t = t0.intersect(tc)
482            tc.v *= 2 * t
483            tc.p = p
484            i, p, t2 = tc.intersect(t1)
485        else:
486            i, p, t = t0.intersect(t1)
487        t0.v *= t
488        t1.p = p
489        t1.v *= t
490        return arc, t0, t1, tc
491
492    def get_length(self, side):
493        if side == 'RIGHT':
494            arc = self.r_arc
495            shape = self.r_shape
496            t0 = self.r_t0
497        else:
498            arc = self.l_arc
499            shape = self.l_shape
500            t0 = self.l_t0
501        if shape == 'CIRCLE':
502            return arc.length
503        else:
504            if self.edges_multiples:
505                # two edges
506                return t0.length * 4
507            else:
508                return t0.length * 2
509
510    def _make_step(self, t_step, i, s, verts, landing=False):
511
512        tb = t_step * i
513
514        f = len(verts)
515
516        t, part, dz, shape = self.get_part(tb, "LEFT")
517        p = part.lerp(t)
518        self.p3d_left(verts, p, s, tb, landing)
519
520        t, part, dz, shape = self.get_part(tb, "RIGHT")
521        p = part.lerp(t)
522        self.p3d_right(verts, p, s, tb, landing)
523        return f
524
525    def _make_edge(self, t_step, i, j, f, rM, verts, faces, matids, uvs):
526        tb = t_step * i
527        # make edges verts after regular ones
528        if self.l_shape != 'CIRCLE' or self.r_shape != 'CIRCLE':
529            if self.edges_multiples:
530                # edge 1
531                if tb < 0.25 and tb + t_step > 0.25:
532                    f0 = f
533                    f = len(verts)
534                    if self.l_shape == 'CIRCLE':
535                        self.p3d_left(verts, self.l_arc.lerp(0.25), j, 0.25)
536                    else:
537                        self.p3d_left(verts, self.l_tc.p, j, 0.25)
538                    if self.r_shape == 'CIRCLE':
539                        self.p3d_right(verts, self.r_arc.lerp(0.25), j, 0.25)
540                    else:
541                        self.p3d_right(verts, self.r_tc.p, j, 0.25)
542                    self.make_faces(f0, rM, verts, faces, matids, uvs)
543                # edge 2
544                if tb < 0.75 and tb + t_step > 0.75:
545                    f0 = f
546                    f = len(verts)
547                    if self.l_shape == 'CIRCLE':
548                        self.p3d_left(verts, self.l_arc.lerp(0.75), j, 0.75)
549                    else:
550                        self.p3d_left(verts, self.l_t1.p, j, 0.75)
551                    if self.r_shape == 'CIRCLE':
552                        self.p3d_right(verts, self.r_arc.lerp(0.75), j, 0.75)
553                    else:
554                        self.p3d_right(verts, self.r_t1.p, j, 0.75)
555                    self.make_faces(f0, rM, verts, faces, matids, uvs)
556            else:
557                if tb < 0.5 and tb + t_step > 0.5:
558                    f0 = f
559                    f = len(verts)
560                    # the step goes through the edge
561                    if self.l_shape == 'CIRCLE':
562                        self.p3d_left(verts, self.l_arc.lerp(0.5), j, 0.5)
563                    else:
564                        self.p3d_left(verts, self.l_t1.p, j, 0.5)
565                    if self.r_shape == 'CIRCLE':
566                        self.p3d_right(verts, self.r_arc.lerp(0.5), j, 0.5)
567                    else:
568                        self.p3d_right(verts, self.r_t1.p, j, 0.5)
569                    self.make_faces(f0, rM, verts, faces, matids, uvs)
570        return f
571
572    def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
573
574        # open stair with closed face
575
576        # step nose
577        rM = self._make_nose(i, i, verts, faces, matids, uvs, nose_y)
578        f = 0
579        if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
580            # every 6 degree
581            n_subs = max(1, int(abs(self.da) / pi * 30 / self.n_step))
582            t_step = self.t_step / n_subs
583            for j in range(n_subs):
584                f0 = f
585                f = self._make_step(t_step, n_subs * i + j, i, verts)
586                if j > 0:
587                    self.make_faces(f0, rM, verts, faces, matids, uvs)
588                f = self._make_edge(t_step, n_subs * i + j, i, f, rM, verts, faces, matids, uvs)
589        else:
590            f = self._make_step(self.t_step, i, i, verts)
591            f = self._make_edge(self.t_step, i, i, f, rM, verts, faces, matids, uvs)
592
593        self._make_step(self.t_step, i + 1, i, verts)
594        self.make_faces(f, rM, verts, faces, matids, uvs)
595
596        if "OPEN" in self.steps_type and self.z_mode != 'LINEAR':
597            # back face top
598            faces.append((f + 13, f + 14, f + 15, f + 16))
599            matids.append(self.idmat_step_front)
600            uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])
601
602    def get_part(self, t, side):
603        if side == 'RIGHT':
604            arc = self.r_arc
605            shape = self.r_shape
606            t0, t1, tc = self.r_t0, self.r_t1, self.r_tc
607        else:
608            arc = self.l_arc
609            shape = self.l_shape
610            t0, t1, tc = self.l_t0, self.l_t1, self.l_tc
611        if shape == 'CIRCLE':
612            return t, arc, self.height, shape
613        else:
614            if self.edges_multiples:
615                # two edges
616                if t <= 0.25:
617                    return 4 * t, t0, 0.25 * self.height, shape
618                elif t <= 0.75:
619                    return 2 * (t - 0.25), tc, 0.5 * self.height, shape
620                else:
621                    return 4 * (t - 0.75), t1, 0.25 * self.height, shape
622            else:
623                if t <= 0.5:
624                    return 2 * t, t0, 0.5 * self.height, shape
625                else:
626                    return 2 * (t - 0.5), t1, 0.5 * self.height, shape
627
628    def get_lerp_vect(self, posts, side, i, t_step, respect_edges, z_offset=0, t0_abs=None):
629        if t0_abs is not None:
630            t0 = t0_abs
631        else:
632            t0 = i * t_step
633        res = [t0]
634        t1 = t0 + t_step
635        zs = self.get_z(t0, 'STEP')
636        zl = self.get_z(t0, 'LINEAR')
637
638        # vect normal
639        t, part, dz, shape = self.get_part(t0, side)
640        n = part.normal(t)
641        dz /= part.length
642        posts.append((n, dz, zs, zl + t0 * z_offset))
643
644        if shape != 'CIRCLE' and respect_edges:
645            if self.edges_multiples:
646                if t0 < 0.25 and t1 > 0.25:
647                    zs = self.get_z(0.25, 'STEP')
648                    zl = self.get_z(0.25, 'LINEAR')
649                    t, part, dz, shape = self.get_part(0.25, side)
650                    n = part.normal(1)
651                    posts.append((n, dz, zs, zl + 0.25 * z_offset))
652                    res.append(0.25)
653                if t0 < 0.75 and t1 > 0.75:
654                    zs = self.get_z(0.75, 'STEP')
655                    zl = self.get_z(0.75, 'LINEAR')
656                    t, part, dz, shape = self.get_part(0.75, side)
657                    n = part.normal(1)
658                    posts.append((n, dz, zs, zl + 0.75 * z_offset))
659                    res.append(0.75)
660            elif t0 < 0.5 and t1 > 0.5:
661                    zs = self.get_z(0.5, 'STEP')
662                    zl = self.get_z(0.5, 'LINEAR')
663                    t, part, dz, shape = self.get_part(0.5, side)
664                    n = part.normal(1)
665                    posts.append((n, dz, zs, zl + 0.5 * z_offset))
666                    res.append(0.5)
667        return res
668
669    def n_posts(self, post_spacing, side, respect_edges):
670        if side == 'LEFT':
671            arc, t0, shape = self.l_arc, self.l_t0, self.l_shape
672        else:
673            arc, t0, shape = self.r_arc, self.r_t0, self.r_shape
674        step_factor = 1
675        if shape == 'CIRCLE':
676            length = arc.length
677        else:
678            if self.edges_multiples:
679                if respect_edges:
680                    step_factor = 2
681                length = 4 * t0.length
682            else:
683                length = 2 * t0.length
684        steps = step_factor * max(1, round(length / post_spacing, 0))
685        # print("respect_edges:%s t_step:%s n_step:%s" % (respect_edges, 1.0 / steps, int(steps)))
686        return 1.0 / steps, int(steps)
687
688
689class StraightLanding(StraightStair):
690    def __init__(self, p, v, left_offset, right_offset, steps_type,
691            nose_type, z_mode, nose_z, bottom_z, last_type='STAIR'):
692
693        StraightStair.__init__(self, p, v, left_offset, right_offset, steps_type,
694            nose_type, z_mode, nose_z, bottom_z)
695
696        self.last_type = last_type
697
698    @property
699    def height(self):
700        return 0
701
702    @property
703    def top_offset(self):
704        return self.t_step / self.v.length
705
706    @property
707    def top(self):
708        if self.next_type == 'LANDING':
709            return self.z0
710        else:
711            return self.z0 + self.step_height
712
713    def step_size(self, step_depth):
714        self.n_step = 1
715        self.t_step = 1
716        self.step_depth = step_depth
717        if self.last_type == 'LANDING':
718            return 0
719        else:
720            return 1
721
722    def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
723
724        if i == 0 and self.last_type != 'LANDING':
725            rM = self._make_nose(i, 0, verts, faces, matids, uvs, nose_y)
726        else:
727            rM = self.get_proj_matrix(self.l_line, self.t_step * i, nose_y)
728
729        f = len(verts)
730        j = 0
731        t0 = self.t_step * i
732
733        p = self.l_line.lerp(t0)
734        self.p3d_left(verts, p, j, t0)
735
736        p = self.r_line.lerp(t0)
737        self.p3d_right(verts, p, j, t0)
738
739        t1 = t0 + self.t_step
740        p = self.l_line.lerp(t1)
741        self.p3d_left(verts, p, j, t1, self.next_type != 'LANDING')
742
743        p = self.r_line.lerp(t1)
744        self.p3d_right(verts, p, j, t1, self.next_type != 'LANDING')
745
746        self.make_faces(f, rM, verts, faces, matids, uvs)
747
748        if "OPEN" in self.steps_type and self.next_type != 'LANDING':
749            faces.append((f + 13, f + 14, f + 15, f + 16))
750            matids.append(self.idmat_step_front)
751            uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])
752
753    def straight_landing(self, length):
754        return Stair.straight_landing(self, length, last_type='LANDING')
755
756    def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi):
757        return Stair.curved_landing(self, da, radius, left_shape,
758            right_shape, double_limit=double_limit, last_type='LANDING')
759
760    def get_z(self, t, mode):
761        if mode == 'STEP':
762            return self.z0 + self.step_height
763        else:
764            return self.z0
765
766
767class CurvedLanding(CurvedStair):
768    def __init__(self, c, radius, a0, da, left_offset, right_offset, steps_type,
769        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=pi, last_type='STAIR'):
770
771        CurvedStair.__init__(self, c, radius, a0, da, left_offset, right_offset, steps_type,
772            nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=double_limit)
773
774        self.last_type = last_type
775
776    @property
777    def top_offset(self):
778        if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
779            return self.t_step / self.step_depth
780        else:
781            if self.edges_multiples:
782                return 0.5 / self.length
783            else:
784                return 1 / self.length
785
786    @property
787    def height(self):
788        return 0
789
790    @property
791    def top(self):
792        if self.next_type == 'LANDING':
793            return self.z0
794        else:
795            return self.z0 + self.step_height
796
797    def step_size(self, step_depth):
798        if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
799            t_step, n_step = self.steps(step_depth)
800        else:
801            if self.edges_multiples:
802                t_step, n_step = 0.5, 2
803            else:
804                t_step, n_step = 1, 1
805        self.n_step = n_step
806        self.t_step = t_step
807        self.step_depth = step_depth
808        if self.last_type == 'LANDING':
809            return 0
810        else:
811            return 1
812
813    def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
814
815        if i == 0 and 'LANDING' not in self.last_type:
816            rM = self._make_nose(i, 0, verts, faces, matids, uvs, nose_y)
817        else:
818            rM = self.get_proj_matrix(self.l_arc, self.t_step * i, nose_y)
819
820        f = len(verts)
821
822        if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
823            n_subs = max(1, int(abs(self.da / pi * 30 / self.n_step)))
824            t_step = self.t_step / n_subs
825            for j in range(n_subs):
826                f0 = f
827                f = self._make_step(t_step, n_subs * i + j, 0, verts)
828                if j > 0:
829                    self.make_faces(f0, rM, verts, faces, matids, uvs)
830                f = self._make_edge(t_step, n_subs * i + j, 0, f, rM, verts, faces, matids, uvs)
831        else:
832            f = self._make_step(self.t_step, i, 0, verts)
833            f = self._make_edge(self.t_step, i, 0, f, rM, verts, faces, matids, uvs)
834
835        self._make_step(self.t_step, i + 1, 0, verts, i == self.n_step - 1 and 'LANDING' not in self.next_type)
836        self.make_faces(f, rM, verts, faces, matids, uvs)
837
838        if "OPEN" in self.steps_type and 'LANDING' not in self.next_type:
839            faces.append((f + 13, f + 14, f + 15, f + 16))
840            matids.append(self.idmat_step_front)
841            uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])
842
843    def straight_landing(self, length):
844        return Stair.straight_landing(self, length, last_type='LANDING')
845
846    def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi):
847        return Stair.curved_landing(self, da, radius, left_shape,
848            right_shape, double_limit=double_limit, last_type='LANDING')
849
850    def get_z(self, t, mode):
851        if mode == 'STEP':
852            return self.z0 + self.step_height
853        else:
854            return self.z0
855
856
857class StairGenerator():
858    def __init__(self, parts):
859        self.parts = parts
860        self.last_type = 'NONE'
861        self.stairs = []
862        self.steps_type = 'NONE'
863        self.sum_da = 0
864        self.user_defined_post = None
865        self.user_defined_uvs = None
866        self.user_defined_mat = None
867
868    def add_part(self, type, steps_type, nose_type, z_mode, nose_z, bottom_z, center,
869            radius, da, width_left, width_right, length, left_shape, right_shape):
870
871        self.steps_type = steps_type
872        if len(self.stairs) < 1:
873            s = None
874        else:
875            s = self.stairs[-1]
876
877        if "S_" not in type:
878            self.sum_da += da
879
880        # start a new stair
881        if s is None:
882            if type == 'S_STAIR':
883                p = Vector((0, 0))
884                v = Vector((0, length))
885                s = StraightStair(p, v, width_left, width_right, steps_type, nose_type, z_mode, nose_z, bottom_z)
886            elif type == 'C_STAIR':
887                if da < 0:
888                    c = Vector((radius, 0))
889                else:
890                    c = Vector((-radius, 0))
891                s = CurvedStair(c, radius, 0, da, width_left, width_right, steps_type,
892                        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape)
893            elif type == 'D_STAIR':
894                if da < 0:
895                    c = Vector((radius, 0))
896                else:
897                    c = Vector((-radius, 0))
898                s = CurvedStair(c, radius, 0, da, width_left, width_right, steps_type,
899                        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=0)
900            elif type == 'S_LANDING':
901                p = Vector((0, 0))
902                v = Vector((0, length))
903                s = StraightLanding(p, v, width_left, width_right, steps_type, nose_type, z_mode, nose_z, bottom_z)
904            elif type == 'C_LANDING':
905                if da < 0:
906                    c = Vector((radius, 0))
907                else:
908                    c = Vector((-radius, 0))
909                s = CurvedLanding(c, radius, 0, da, width_left, width_right, steps_type,
910                        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape)
911            elif type == 'D_LANDING':
912                if da < 0:
913                    c = Vector((radius, 0))
914                else:
915                    c = Vector((-radius, 0))
916                s = CurvedLanding(c, radius, 0, da, width_left, width_right, steps_type,
917                        nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=0)
918        else:
919            if type == 'S_STAIR':
920                s = s.straight_stair(length)
921            elif type == 'C_STAIR':
922                s = s.curved_stair(da, radius, left_shape, right_shape)
923            elif type == 'D_STAIR':
924                s = s.curved_stair(da, radius, left_shape, right_shape, double_limit=0)
925            elif type == 'S_LANDING':
926                s = s.straight_landing(length)
927            elif type == 'C_LANDING':
928                s = s.curved_landing(da, radius, left_shape, right_shape)
929            elif type == 'D_LANDING':
930                s = s.curved_landing(da, radius, left_shape, right_shape, double_limit=0)
931        self.stairs.append(s)
932        self.last_type = type
933
934    def n_steps(self, step_depth):
935        n_steps = 0
936        for stair in self.stairs:
937            n_steps += stair.step_size(step_depth)
938        return n_steps
939
940    def set_height(self, step_height):
941        z = 0
942        for stair in self.stairs:
943            stair.set_height(step_height, z)
944            z = stair.top
945
946    def make_stair(self, height, step_depth, verts, faces, matids, uvs, nose_y=0):
947        n_steps = self.n_steps(step_depth)
948        self.set_height(height / n_steps)
949
950        for s, stair in enumerate(self.stairs):
951            if s < len(self.parts):
952                manipulator = self.parts[s].manipulators[0]
953                # Store Gl Points for manipulators
954                if 'Curved' in type(stair).__name__:
955                    c = stair.c
956                    p0 = (stair.p0 - c).to_3d()
957                    p1 = (stair.p1 - c).to_3d()
958                    manipulator.set_pts([(c.x, c.y, stair.top), p0, p1])
959                    manipulator.type_key = 'ARC_ANGLE_RADIUS'
960                    manipulator.prop1_name = 'da'
961                    manipulator.prop2_name = 'radius'
962                else:
963                    if self.sum_da > 0:
964                        side = 1
965                    else:
966                        side = -1
967                    v0 = stair.p0
968                    v1 = stair.p1
969                    manipulator.set_pts([(v0.x, v0.y, stair.top), (v1.x, v1.y, stair.top), (side, 0, 0)])
970                    manipulator.type_key = 'SIZE'
971                    manipulator.prop1_name = 'length'
972
973            for i in range(stair.n_step):
974                stair.make_step(i, verts, faces, matids, uvs, nose_y=nose_y)
975                if s < len(self.stairs) - 1 and self.steps_type != 'OPEN' and \
976                        'Landing' in type(stair).__name__ and stair.next_type != "LANDING":
977                    f = len(verts) - 10
978                    faces.append((f, f + 1, f + 8, f + 9))
979                    matids.append(self.stairs[-1].idmat_bottom)
980                    u = verts[f + 1][2] - verts[f][2]
981                    v = (Vector(verts[f]) - Vector(verts[f + 9])).length
982                    uvs.append([(0, 0), (0, u), (v, u), (v, 0)])
983
984        if self.steps_type != 'OPEN' and len(self.stairs) > 0:
985            f = len(verts) - 10
986            faces.append((f, f + 1, f + 2, f + 3, f + 4, f + 5, f + 6, f + 7, f + 8, f + 9))
987            matids.append(self.stairs[-1].idmat_bottom)
988            uvs.append([(0, 0), (.1, 0), (.2, 0), (.3, 0), (.4, 0), (.4, 1), (.3, 1), (.2, 1), (.1, 1), (0, 1)])
989
990    def setup_user_defined_post(self, o, post_x, post_y, post_z):
991        self.user_defined_post = o
992        x = o.bound_box[6][0] - o.bound_box[0][0]
993        y = o.bound_box[6][1] - o.bound_box[0][1]
994        z = o.bound_box[6][2] - o.bound_box[0][2]
995        self.user_defined_post_scale = Vector((post_x / x, post_y / -y, post_z / z))
996        m = o.data
997        # create vertex group lookup dictionary for names
998        vgroup_names = {vgroup.index: vgroup.name for vgroup in o.vertex_groups}
999        # create dictionary of vertex group assignments per vertex
1000        self.vertex_groups = [[vgroup_names[g.group] for g in v.groups] for v in m.vertices]
1001        # uvs
1002        uv_act = m.uv_layers.active
1003        if uv_act is not None:
1004            uv_layer = uv_act.data
1005            self.user_defined_uvs = [[uv_layer[li].uv for li in p.loop_indices] for p in m.polygons]
1006        else:
1007            self.user_defined_uvs = [[(0, 0) for i in p.vertices] for p in m.polygons]
1008        # material ids
1009        self.user_defined_mat = [p.material_index for p in m.polygons]
1010
1011    def get_user_defined_post(self, tM, z0, z1, z2, slope, post_z, verts, faces, matids, uvs):
1012        f = len(verts)
1013        m = self.user_defined_post.data
1014        for i, g in enumerate(self.vertex_groups):
1015            co = m.vertices[i].co.copy()
1016            co.x *= self.user_defined_post_scale.x
1017            co.y *= self.user_defined_post_scale.y
1018            co.z *= self.user_defined_post_scale.z
1019            if 'Top' in g:
1020                co.z += z2
1021            elif 'Bottom' in g:
1022                co.z += 0
1023            else:
1024                co.z += z1
1025            if 'Slope' in g:
1026                co.z += co.y * slope
1027            verts.append(tM @ co)
1028        matids += self.user_defined_mat
1029        faces += [tuple([i + f for i in p.vertices]) for p in m.polygons]
1030        uvs += self.user_defined_uvs
1031
1032    def get_post(self, post, post_x, post_y, post_z, post_alt, sub_offset_x,
1033            id_mat, verts, faces, matids, uvs, bottom="STEP"):
1034
1035        n, dz, zs, zl = post
1036        slope = dz * post_y
1037
1038        if self.user_defined_post is not None:
1039            if bottom == "STEP":
1040                z0 = zs
1041            else:
1042                z0 = zl
1043            z1 = zl - z0
1044            z2 = zl - z0
1045            x, y = -n.v.normalized()
1046            tM = Matrix([
1047                [x, y, 0, n.p.x],
1048                [y, -x, 0, n.p.y],
1049                [0, 0, 1, z0 + post_alt],
1050                [0, 0, 0, 1]
1051            ])
1052            self.get_user_defined_post(tM, z0, z1, z2, dz, post_z, verts, faces, matids, uvs)
1053            return
1054
1055        z3 = zl + post_z + post_alt - slope
1056        z4 = zl + post_z + post_alt + slope
1057        if bottom == "STEP":
1058            z0 = zs + post_alt
1059            z1 = zs + post_alt
1060        else:
1061            z0 = zl + post_alt - slope
1062            z1 = zl + post_alt + slope
1063        vn = n.v.normalized()
1064        dx = post_x * vn
1065        dy = post_y * Vector((vn.y, -vn.x))
1066        oy = sub_offset_x * vn
1067        x0, y0 = n.p - dx + dy + oy
1068        x1, y1 = n.p - dx - dy + oy
1069        x2, y2 = n.p + dx - dy + oy
1070        x3, y3 = n.p + dx + dy + oy
1071        f = len(verts)
1072        verts.extend([(x0, y0, z0), (x0, y0, z3),
1073                    (x1, y1, z1), (x1, y1, z4),
1074                    (x2, y2, z1), (x2, y2, z4),
1075                    (x3, y3, z0), (x3, y3, z3)])
1076        faces.extend([(f, f + 1, f + 3, f + 2),
1077                    (f + 2, f + 3, f + 5, f + 4),
1078                    (f + 4, f + 5, f + 7, f + 6),
1079                    (f + 6, f + 7, f + 1, f),
1080                    (f, f + 2, f + 4, f + 6),
1081                    (f + 7, f + 5, f + 3, f + 1)])
1082        matids.extend([id_mat, id_mat, id_mat, id_mat, id_mat, id_mat])
1083        x = [(0, 0), (0, post_z), (post_x, post_z), (post_x, 0)]
1084        y = [(0, 0), (0, post_z), (post_y, post_z), (post_y, 0)]
1085        z = [(0, 0), (post_x, 0), (post_x, post_y), (0, post_y)]
1086        uvs.extend([x, y, x, y, z, z])
1087
1088    def get_panel(self, subs, altitude, panel_x, panel_z, sub_offset_x, idmat, verts, faces, matids, uvs):
1089        n_subs = len(subs)
1090        if n_subs < 1:
1091            return
1092        f = len(verts)
1093        x0 = sub_offset_x - 0.5 * panel_x
1094        x1 = sub_offset_x + 0.5 * panel_x
1095        z0 = 0
1096        z1 = panel_z
1097        profile = [Vector((x0, z0)), Vector((x1, z0)), Vector((x1, z1)), Vector((x0, z1))]
1098        user_path_uv_v = []
1099        n_sections = n_subs - 1
1100        n, dz, zs, zl = subs[0]
1101        p0 = n.p
1102        v0 = n.v.normalized()
1103        for s, section in enumerate(subs):
1104            n, dz, zs, zl = section
1105            p1 = n.p
1106            if s < n_sections:
1107                v1 = subs[s + 1][0].v.normalized()
1108            dir = (v0 + v1).normalized()
1109            scale = 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1)))))
1110            for p in profile:
1111                x, y = n.p + scale * p.x * dir
1112                z = zl + p.y + altitude
1113                verts.append((x, y, z))
1114            if s > 0:
1115                user_path_uv_v.append((p1 - p0).length)
1116            p0 = p1
1117            v0 = v1
1118
1119        # build faces using Panel
1120        lofter = Lofter(
1121            # closed_shape, index, x, y, idmat
1122            True,
1123            [i for i in range(len(profile))],
1124            [p.x for p in profile],
1125            [p.y for p in profile],
1126            [idmat for i in range(len(profile))],
1127            closed_path=False,
1128            user_path_uv_v=user_path_uv_v,
1129            user_path_verts=n_subs
1130            )
1131        faces += lofter.faces(16, offset=f, path_type='USER_DEFINED')
1132        matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED')
1133        v = Vector((0, 0))
1134        uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED')
1135
1136    def reset_shapes(self):
1137        for s, stair in enumerate(self.stairs):
1138            if 'Curved' in type(stair).__name__:
1139                stair.l_shape = self.parts[s].left_shape
1140                stair.r_shape = self.parts[s].right_shape
1141
1142    def make_subs(self, height, step_depth, x, y, z, post_y, altitude, bottom, side, slice,
1143            post_spacing, sub_spacing, respect_edges, move_x, x_offset, sub_offset_x, mat,
1144            verts, faces, matids, uvs):
1145
1146        n_steps = self.n_steps(step_depth)
1147        self.set_height(height / n_steps)
1148        n_stairs = len(self.stairs) - 1
1149        subs = []
1150
1151        if side == "LEFT":
1152            offset = move_x - x_offset
1153            # offset_sub = offset - sub_offset_x
1154        else:
1155            offset = move_x + x_offset
1156            # offset_sub = offset + sub_offset_x
1157
1158        for s, stair in enumerate(self.stairs):
1159            if 'Curved' in type(stair).__name__:
1160                if side == "LEFT":
1161                    part = stair.l_arc
1162                    shape = stair.l_shape
1163                else:
1164                    part = stair.r_arc
1165                    shape = stair.r_shape
1166                # Note: use left part as reference for post distances
1167                # use right part as reference for panels
1168                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(offset, shape)
1169                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(offset, shape)
1170            else:
1171                stair.l_line = stair.offset(offset)
1172                stair.r_line = stair.offset(offset)
1173                part = stair.l_line
1174
1175            lerp_z = 0
1176            edge_t = 1
1177            edge_size = 0
1178            # interpolate z near end landing
1179            if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR':
1180                if not slice:
1181                    line = stair.normal(1).offset(self.stairs[s + 1].step_depth)
1182                    res, p, t_part = part.intersect(line)
1183                    # does perpendicular line intersects circle ?
1184                    if res:
1185                        edge_size = self.stairs[s + 1].step_depth / stair.get_length(side)
1186                        edge_t = 1 - edge_size
1187                    else:
1188                        # in this case, lerp z over one step
1189                        lerp_z = stair.step_height
1190
1191            t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)
1192
1193            # space between posts
1194            sp = stair.get_length(side)
1195            # post size
1196            t_post = post_y / sp
1197
1198            if s == n_stairs:
1199                n_step += 1
1200            for i in range(n_step):
1201                res_t = stair.get_lerp_vect([], side, i, t_step, respect_edges)
1202                # subs
1203                if s < n_stairs or i < n_step - 1:
1204                    res_t.append((i + 1) * t_step)
1205                for j in range(len(res_t) - 1):
1206                    t0 = res_t[j] + t_post
1207                    t1 = res_t[j + 1] - t_post
1208                    dt = t1 - t0
1209                    n_subs = int(sp * dt / sub_spacing)
1210                    if n_subs > 0:
1211                        t_subs = dt / n_subs
1212                        for k in range(1, n_subs):
1213                            t = t0 + k * t_subs
1214                            stair.get_lerp_vect(subs, side, 1, t0 + k * t_subs, False)
1215                            if t > edge_t:
1216                                n, dz, z0, z1 = subs[-1]
1217                                subs[-1] = n, dz, z0, z1 + (t - edge_t) / edge_size * stair.step_height
1218                            if lerp_z > 0:
1219                                n, dz, z0, z1 = subs[-1]
1220                                subs[-1] = n, dz, z0, z1 + t * stair.step_height
1221
1222        for i, post in enumerate(subs):
1223            self.get_post(post, x, y, z, altitude, sub_offset_x, mat, verts, faces, matids, uvs, bottom=bottom)
1224
1225    def make_post(self, height, step_depth, x, y, z, altitude, side, post_spacing, respect_edges, move_x, x_offset, mat,
1226        verts, faces, matids, uvs):
1227        n_steps = self.n_steps(step_depth)
1228        self.set_height(height / n_steps)
1229        l_posts = []
1230        n_stairs = len(self.stairs) - 1
1231
1232        for s, stair in enumerate(self.stairs):
1233            if type(stair).__name__ in ['CurvedStair', 'CurvedLanding']:
1234                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(move_x - x_offset, stair.l_shape)
1235                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(move_x + x_offset, stair.r_shape)
1236            else:
1237                stair.l_line = stair.offset(move_x - x_offset)
1238                stair.r_line = stair.offset(move_x + x_offset)
1239
1240            t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)
1241
1242            if s == n_stairs:
1243                n_step += 1
1244            for i in range(n_step):
1245                stair.get_lerp_vect(l_posts, side, i, t_step, respect_edges)
1246
1247                if s == n_stairs and i == n_step - 1:
1248                    n, dz, z0, z1 = l_posts[-1]
1249                    l_posts[-1] = (n, dz, z0 - stair.step_height, z1)
1250
1251        for i, post in enumerate(l_posts):
1252            self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs)
1253
1254    def make_panels(self, height, step_depth, x, z, post_y, altitude, side, post_spacing,
1255            panel_dist, respect_edges, move_x, x_offset, sub_offset_x, mat, verts, faces, matids, uvs):
1256
1257        n_steps = self.n_steps(step_depth)
1258        self.set_height(height / n_steps)
1259        subs = []
1260        n_stairs = len(self.stairs) - 1
1261
1262        if side == "LEFT":
1263            offset = move_x - x_offset
1264        else:
1265            offset = move_x + x_offset
1266
1267        for s, stair in enumerate(self.stairs):
1268
1269            is_circle = False
1270            if 'Curved' in type(stair).__name__:
1271                if side == "LEFT":
1272                    is_circle = stair.l_shape == "CIRCLE"
1273                    shape = stair.l_shape
1274                else:
1275                    is_circle = stair.r_shape == "CIRCLE"
1276                    shape = stair.r_shape
1277                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(offset, shape)
1278                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(offset, shape)
1279            else:
1280                stair.l_line = stair.offset(offset)
1281                stair.r_line = stair.offset(offset)
1282
1283            # space between posts
1284            sp = stair.get_length(side)
1285
1286            t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)
1287
1288            if is_circle and 'Curved' in type(stair).__name__:
1289                panel_da = abs(stair.da) / pi * 180 / n_step
1290                panel_step = max(1, int(panel_da / 6))
1291            else:
1292                panel_step = 1
1293
1294            # post size
1295            t_post = (post_y + panel_dist) / sp
1296
1297            if s == n_stairs:
1298                n_step += 1
1299            for i in range(n_step):
1300                res_t = stair.get_lerp_vect([], side, i, t_step, respect_edges)
1301                # subs
1302                if s < n_stairs or i < n_step - 1:
1303                    res_t.append((i + 1) * t_step)
1304                for j in range(len(res_t) - 1):
1305                    t0 = res_t[j] + t_post
1306                    t1 = res_t[j + 1] - t_post
1307                    dt = t1 - t0
1308                    t_curve = dt / panel_step
1309                    if dt > 0:
1310                        panel = []
1311                        for k in range(panel_step):
1312                            stair.get_lerp_vect(panel, side, 1, t_curve, True, t0_abs=t0 + k * t_curve)
1313                        stair.get_lerp_vect(panel, side, 1, t1, False)
1314                        subs.append(panel)
1315        for sub in subs:
1316            self.get_panel(sub, altitude, x, z, sub_offset_x, mat, verts, faces, matids, uvs)
1317
1318    def make_part(self, height, step_depth, part_x, part_z, x_move, x_offset,
1319            z_offset, z_mode, steps_type, verts, faces, matids, uvs):
1320
1321        params = [(stair.z_mode, stair.l_shape, stair.r_shape,
1322            stair.bottom_z, stair.steps_type) for stair in self.stairs]
1323
1324        for stair in self.stairs:
1325            if x_offset > 0:
1326                stair.l_shape = stair.r_shape
1327            else:
1328                stair.r_shape = stair.l_shape
1329            stair.steps_type = steps_type
1330            stair.z_mode = "LINEAR"
1331            stair.bottom_z = part_z
1332            if 'Curved' in type(stair).__name__:
1333                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = \
1334                    stair.set_offset(x_move + x_offset + 0.5 * part_x, stair.l_shape)
1335                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = \
1336                    stair.set_offset(x_move + x_offset - 0.5 * part_x, stair.r_shape)
1337            else:
1338                stair.l_line = stair.offset(x_move + x_offset + 0.5 * part_x)
1339                stair.r_line = stair.offset(x_move + x_offset - 0.5 * part_x)
1340        n_steps = self.n_steps(step_depth)
1341        self.set_height(height / n_steps)
1342        for j, stair in enumerate(self.stairs):
1343            stair.z0 += z_offset + part_z
1344            stair.n_step *= 2
1345            stair.t_step /= 2
1346            stair.step_height /= 2
1347            for i in range(stair.n_step):
1348                stair.make_step(i, verts, faces, matids, uvs, nose_y=0)
1349            stair.n_step /= 2
1350            stair.t_step *= 2
1351            stair.step_height *= 2
1352            stair.z_mode = params[j][0]
1353            stair.l_shape = params[j][1]
1354            stair.r_shape = params[j][2]
1355            stair.bottom_z = params[j][3]
1356            stair.steps_type = params[j][4]
1357            stair.z0 -= z_offset + part_z
1358
1359    def make_profile(self, profile, idmat, side, slice, height, step_depth,
1360            x_offset, z_offset, extend, verts, faces, matids, uvs):
1361
1362        for stair in self.stairs:
1363            if 'Curved' in type(stair).__name__:
1364                stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(-x_offset, stair.l_shape)
1365                stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(x_offset, stair.r_shape)
1366            else:
1367                stair.l_line = stair.offset(-x_offset)
1368                stair.r_line = stair.offset(x_offset)
1369
1370        n_steps = self.n_steps(step_depth)
1371        self.set_height(height / n_steps)
1372
1373        n_stairs = len(self.stairs) - 1
1374
1375        if n_stairs < 0:
1376            return
1377
1378        sections = []
1379        sections.append([])
1380
1381        # first step
1382        if extend != 0:
1383            t = -extend / self.stairs[0].length
1384            self.stairs[0].get_lerp_vect(sections[-1], side, 1, t, True)
1385
1386        for s, stair in enumerate(self.stairs):
1387            n_step = 1
1388            is_circle = False
1389
1390            if 'Curved' in type(stair).__name__:
1391                if side == "LEFT":
1392                    part = stair.l_arc
1393                    is_circle = stair.l_shape == "CIRCLE"
1394                else:
1395                    part = stair.r_arc
1396                    is_circle = stair.r_shape == "CIRCLE"
1397            else:
1398                if side == "LEFT":
1399                    part = stair.l_line
1400                else:
1401                    part = stair.r_line
1402
1403            if is_circle:
1404                n_step = 3 * stair.n_step
1405
1406            t_step = 1 / n_step
1407
1408            last_t = 1.0
1409            do_last = True
1410            lerp_z = 0
1411            # last section 1 step before stair
1412            if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR':
1413                if not slice:
1414                    line = stair.normal(1).offset(self.stairs[s + 1].step_depth)
1415                    res, p, t_part = part.intersect(line)
1416                    # does perpendicular line intersects circle ?
1417                    if res:
1418                        last_t = 1 - self.stairs[s + 1].step_depth / stair.get_length(side)
1419                        if last_t < 0:
1420                            do_last = False
1421                    else:
1422                        # in this case, lerp z over one step
1423                        do_last = False
1424                        lerp_z = stair.step_height
1425
1426            if s == n_stairs:
1427                n_step += 1
1428
1429            for i in range(n_step):
1430                res_t = stair.get_lerp_vect(sections[-1], side, i, t_step, True, z_offset=lerp_z)
1431                # remove corner section
1432                for cur_t in res_t:
1433                    if cur_t > 0 and cur_t > last_t:
1434                        sections[-1] = sections[-1][:-1]
1435
1436            # last section 1 step before next stair start
1437            if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR':
1438                if do_last:
1439                    stair.get_lerp_vect(sections[-1], side, 1, last_t, False)
1440                if slice:
1441                    sections.append([])
1442                    if extend > 0:
1443                        t = -extend / self.stairs[s + 1].length
1444                        self.stairs[s + 1].get_lerp_vect(sections[-1], side, 1, t, True)
1445
1446        t = 1 + extend / self.stairs[-1].length
1447        self.stairs[-1].get_lerp_vect(sections[-1], side, 1, t, True)
1448
1449        for cur_sect in sections:
1450            user_path_verts = len(cur_sect)
1451            f = len(verts)
1452            if user_path_verts > 0:
1453                user_path_uv_v = []
1454                n, dz, z0, z1 = cur_sect[-1]
1455                cur_sect[-1] = (n, dz, z0 - stair.step_height, z1)
1456                n_sections = user_path_verts - 1
1457                n, dz, zs, zl = cur_sect[0]
1458                p0 = n.p
1459                v0 = n.v.normalized()
1460                for s, section in enumerate(cur_sect):
1461                    n, dz, zs, zl = section
1462                    p1 = n.p
1463                    if s < n_sections:
1464                        v1 = cur_sect[s + 1][0].v.normalized()
1465                    dir = (v0 + v1).normalized()
1466                    scale = 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1)))))
1467                    for p in profile:
1468                        x, y = n.p + scale * p.x * dir
1469                        z = zl + p.y + z_offset
1470                        verts.append((x, y, z))
1471                    if s > 0:
1472                        user_path_uv_v.append((p1 - p0).length)
1473                    p0 = p1
1474                    v0 = v1
1475
1476                # build faces using Panel
1477                lofter = Lofter(
1478                    # closed_shape, index, x, y, idmat
1479                    True,
1480                    [i for i in range(len(profile))],
1481                    [p.x for p in profile],
1482                    [p.y for p in profile],
1483                    [idmat for i in range(len(profile))],
1484                    closed_path=False,
1485                    user_path_uv_v=user_path_uv_v,
1486                    user_path_verts=user_path_verts
1487                    )
1488                faces += lofter.faces(16, offset=f, path_type='USER_DEFINED')
1489                matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED')
1490                v = Vector((0, 0))
1491                uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED')
1492
1493    def set_matids(self, id_materials):
1494        for stair in self.stairs:
1495            stair.set_matids(id_materials)
1496
1497
1498def update(self, context):
1499    self.update(context)
1500
1501
1502def update_manipulators(self, context):
1503    self.update(context, manipulable_refresh=True)
1504
1505
1506def update_preset(self, context):
1507    auto_update = self.auto_update
1508    self.auto_update = False
1509    if self.presets == 'STAIR_I':
1510        self.n_parts = 1
1511        self.update_parts()
1512        self.parts[0].type = 'S_STAIR'
1513    elif self.presets == 'STAIR_L':
1514        self.n_parts = 3
1515        self.update_parts()
1516        self.parts[0].type = 'S_STAIR'
1517        self.parts[1].type = 'C_STAIR'
1518        self.parts[2].type = 'S_STAIR'
1519        self.da = pi / 2
1520    elif self.presets == 'STAIR_U':
1521        self.n_parts = 3
1522        self.update_parts()
1523        self.parts[0].type = 'S_STAIR'
1524        self.parts[1].type = 'D_STAIR'
1525        self.parts[2].type = 'S_STAIR'
1526        self.da = pi
1527    elif self.presets == 'STAIR_O':
1528        self.n_parts = 2
1529        self.update_parts()
1530        self.parts[0].type = 'D_STAIR'
1531        self.parts[1].type = 'D_STAIR'
1532        self.da = pi
1533    # keep auto_update state same
1534    # prevent unwanted load_preset update
1535    self.auto_update = auto_update
1536
1537
1538materials_enum = (
1539            ('0', 'Ceiling', '', 0),
1540            ('1', 'White', '', 1),
1541            ('2', 'Concrete', '', 2),
1542            ('3', 'Wood', '', 3),
1543            ('4', 'Metal', '', 4),
1544            ('5', 'Glass', '', 5)
1545            )
1546
1547
1548class archipack_stair_material(PropertyGroup):
1549    index : EnumProperty(
1550        items=materials_enum,
1551        default='4',
1552        update=update
1553        )
1554
1555    def find_datablock_in_selection(self, context):
1556        """
1557            find witch selected object this instance belongs to
1558            provide support for "copy to selected"
1559        """
1560        selected = context.selected_objects[:]
1561        for o in selected:
1562            props = archipack_stair.datablock(o)
1563            if props:
1564                for part in props.rail_mat:
1565                    if part == self:
1566                        return props
1567        return None
1568
1569    def update(self, context):
1570        props = self.find_datablock_in_selection(context)
1571        if props is not None:
1572            props.update(context)
1573
1574
1575class archipack_stair_part(PropertyGroup):
1576    type : EnumProperty(
1577            items=(
1578                ('S_STAIR', 'Straight stair', '', 0),
1579                ('C_STAIR', 'Curved stair', '', 1),
1580                ('D_STAIR', 'Dual Curved stair', '', 2),
1581                ('S_LANDING', 'Straight landing', '', 3),
1582                ('C_LANDING', 'Curved landing', '', 4),
1583                ('D_LANDING', 'Dual Curved landing', '', 5)
1584                ),
1585            default='S_STAIR',
1586            update=update_manipulators
1587            )
1588    length : FloatProperty(
1589            name="Length",
1590            min=0.01,
1591            default=2.0,
1592            unit='LENGTH', subtype='DISTANCE',
1593            update=update
1594            )
1595    radius : FloatProperty(
1596            name="Radius",
1597            min=0.01,
1598            default=0.7,
1599            unit='LENGTH', subtype='DISTANCE',
1600            update=update
1601            )
1602    da : FloatProperty(
1603            name="Angle",
1604            min=-pi,
1605            max=pi,
1606            default=pi / 2,
1607            subtype='ANGLE', unit='ROTATION',
1608            update=update
1609            )
1610    left_shape : EnumProperty(
1611            items=(
1612                ('RECTANGLE', 'Straight', '', 0),
1613                ('CIRCLE', 'Curved ', '', 1)
1614                ),
1615            default='RECTANGLE',
1616            update=update
1617            )
1618    right_shape : EnumProperty(
1619            items=(
1620                ('RECTANGLE', 'Straight', '', 0),
1621                ('CIRCLE', 'Curved ', '', 1)
1622                ),
1623            default='RECTANGLE',
1624            update=update
1625            )
1626    manipulators : CollectionProperty(type=archipack_manipulator)
1627
1628    def find_datablock_in_selection(self, context):
1629        """
1630            find witch selected object this instance belongs to
1631            provide support for "copy to selected"
1632        """
1633        selected = context.selected_objects[:]
1634        for o in selected:
1635            props = archipack_stair.datablock(o)
1636            if props:
1637                for part in props.parts:
1638                    if part == self:
1639                        return props
1640        return None
1641
1642    def update(self, context, manipulable_refresh=False):
1643        props = self.find_datablock_in_selection(context)
1644        if props is not None:
1645            props.update(context, manipulable_refresh)
1646
1647    def draw(self, layout, context, index, user_mode):
1648        if user_mode:
1649            box = layout.box()
1650            row = box.row()
1651            row.prop(self, "type", text=str(index + 1))
1652            if self.type in ['C_STAIR', 'C_LANDING', 'D_STAIR', 'D_LANDING']:
1653                row = box.row()
1654                row.prop(self, "radius")
1655                row = box.row()
1656                row.prop(self, "da")
1657            else:
1658                row = box.row()
1659                row.prop(self, "length")
1660            if self.type in ['C_STAIR', 'C_LANDING', 'D_STAIR', 'D_LANDING']:
1661                row = box.row(align=True)
1662                row.prop(self, "left_shape", text="")
1663                row.prop(self, "right_shape", text="")
1664        else:
1665            if self.type in ['S_STAIR', 'S_LANDING']:
1666                box = layout.box()
1667                row = box.row()
1668                row.prop(self, "length")
1669
1670
1671class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
1672
1673    parts : CollectionProperty(type=archipack_stair_part)
1674    n_parts : IntProperty(
1675            name="Parts",
1676            min=1,
1677            max=32,
1678            default=1, update=update_manipulators
1679            )
1680    step_depth : FloatProperty(
1681            name="Going",
1682            min=0.2,
1683            default=0.25,
1684            unit='LENGTH', subtype='DISTANCE',
1685            update=update
1686            )
1687    width : FloatProperty(
1688            name="Width",
1689            min=0.01,
1690            default=1.2,
1691            unit='LENGTH', subtype='DISTANCE',
1692            update=update
1693            )
1694    height : FloatProperty(
1695            name="Height",
1696            min=0.1,
1697            default=2.4, precision=2, step=1,
1698            unit='LENGTH', subtype='DISTANCE',
1699            update=update
1700            )
1701    nose_y : FloatProperty(
1702            name="Depth",
1703            min=0.0,
1704            default=0.02, precision=2, step=1,
1705            unit='LENGTH', subtype='DISTANCE',
1706            update=update
1707            )
1708    x_offset : FloatProperty(
1709            name="Offset",
1710            default=0.0, precision=2, step=1,
1711            unit='LENGTH', subtype='DISTANCE',
1712            update=update
1713            )
1714    nose_z : FloatProperty(
1715            name="Height",
1716            min=0.001,
1717            default=0.03, precision=2, step=1,
1718            unit='LENGTH', subtype='DISTANCE',
1719            update=update
1720            )
1721    bottom_z : FloatProperty(
1722            name="Thickness",
1723            min=0.001,
1724            default=0.03, precision=2, step=1,
1725            unit='LENGTH', subtype='DISTANCE',
1726            update=update
1727            )
1728    radius : FloatProperty(
1729            name="Radius",
1730            min=0.5,
1731            default=0.7,
1732            unit='LENGTH', subtype='DISTANCE',
1733            update=update
1734            )
1735    da : FloatProperty(
1736            name="Angle",
1737            min=-pi,
1738            max=pi,
1739            default=pi / 2,
1740            subtype='ANGLE', unit='ROTATION',
1741            update=update
1742            )
1743    total_angle : FloatProperty(
1744            name="Angle",
1745            min=-50 * pi,
1746            max=50 * pi,
1747            default=2 * pi,
1748            subtype='ANGLE', unit='ROTATION',
1749            update=update
1750            )
1751    steps_type : EnumProperty(
1752            name="Steps",
1753            items=(
1754                ('CLOSED', 'Closed', '', 0),
1755                ('FULL', 'Full height', '', 1),
1756                ('OPEN', 'Open ', '', 2)
1757                ),
1758            default='CLOSED',
1759            update=update
1760            )
1761    nose_type : EnumProperty(
1762            name="Nosing",
1763            items=(
1764                ('STRAIGHT', 'Straight', '', 0),
1765                ('OBLIQUE', 'Oblique', '', 1),
1766                ),
1767            default='STRAIGHT',
1768            update=update
1769            )
1770    left_shape : EnumProperty(
1771            items=(
1772                ('RECTANGLE', 'Straight', '', 0),
1773                ('CIRCLE', 'Curved ', '', 1)
1774                ),
1775            default='RECTANGLE',
1776            update=update
1777            )
1778    right_shape : EnumProperty(
1779            items=(
1780                ('RECTANGLE', 'Straight', '', 0),
1781                ('CIRCLE', 'Curved ', '', 1)
1782                ),
1783            default='RECTANGLE',
1784            update=update
1785            )
1786    z_mode : EnumProperty(
1787            name="Interp z",
1788            items=(
1789                ('STANDARD', 'Standard', '', 0),
1790                ('LINEAR', 'Bottom Linear', '', 1),
1791                ('LINEAR_TOP', 'All Linear', '', 2)
1792                ),
1793            default='STANDARD',
1794            update=update
1795            )
1796    presets : EnumProperty(
1797            items=(
1798                ('STAIR_I', 'I stair', '', 0),
1799                ('STAIR_L', 'L stair', '', 1),
1800                ('STAIR_U', 'U stair', '', 2),
1801                ('STAIR_O', 'O stair', '', 3),
1802                ('STAIR_USER', 'User defined stair', '', 4),
1803                ),
1804            default='STAIR_I', update=update_preset
1805            )
1806    left_post : BoolProperty(
1807            name='left',
1808            default=True,
1809            update=update
1810            )
1811    right_post : BoolProperty(
1812            name='right',
1813            default=True,
1814            update=update
1815            )
1816    post_spacing : FloatProperty(
1817            name="Spacing",
1818            min=0.1,
1819            default=1.0, precision=2, step=1,
1820            unit='LENGTH', subtype='DISTANCE',
1821            update=update
1822            )
1823    post_x : FloatProperty(
1824            name="Width",
1825            min=0.001,
1826            default=0.04, precision=2, step=1,
1827            unit='LENGTH', subtype='DISTANCE',
1828            update=update
1829            )
1830    post_y : FloatProperty(
1831            name="Length",
1832            min=0.001,
1833            default=0.04, precision=2, step=1,
1834            unit='LENGTH', subtype='DISTANCE',
1835            update=update
1836            )
1837    post_z : FloatProperty(
1838            name="Height",
1839            min=0.001,
1840            default=1, precision=2, step=1,
1841            unit='LENGTH', subtype='DISTANCE',
1842            update=update
1843            )
1844    post_alt : FloatProperty(
1845            name="Altitude",
1846            min=-100,
1847            default=0, precision=2, step=1,
1848            unit='LENGTH', subtype='DISTANCE',
1849            update=update
1850            )
1851    post_offset_x : FloatProperty(
1852            name="Offset",
1853            min=-100.0, max=100,
1854            default=0.02, precision=2, step=1,
1855            unit='LENGTH', subtype='DISTANCE',
1856            update=update
1857            )
1858    post_corners : BoolProperty(
1859            name="Only on edges",
1860            update=update,
1861            default=False
1862            )
1863    user_defined_post_enable : BoolProperty(
1864            name="User",
1865            update=update,
1866            default=True
1867            )
1868    user_defined_post : StringProperty(
1869            name="User defined",
1870            update=update
1871            )
1872    idmat_post : EnumProperty(
1873            name="Post",
1874            items=materials_enum,
1875            default='4',
1876            update=update
1877            )
1878    left_subs : BoolProperty(
1879            name='left',
1880            default=False,
1881            update=update
1882            )
1883    right_subs : BoolProperty(
1884            name='right',
1885            default=False,
1886            update=update
1887            )
1888    subs_spacing : FloatProperty(
1889            name="Spacing",
1890            min=0.05,
1891            default=0.10, precision=2, step=1,
1892            unit='LENGTH', subtype='DISTANCE',
1893            update=update
1894            )
1895    subs_x : FloatProperty(
1896            name="Width",
1897            min=0.001,
1898            default=0.02, precision=2, step=1,
1899            unit='LENGTH', subtype='DISTANCE',
1900            update=update
1901            )
1902    subs_y : FloatProperty(
1903            name="Length",
1904            min=0.001,
1905            default=0.02, precision=2, step=1,
1906            unit='LENGTH', subtype='DISTANCE',
1907            update=update
1908            )
1909    subs_z : FloatProperty(
1910            name="Height",
1911            min=0.001,
1912            default=1, precision=2, step=1,
1913            unit='LENGTH', subtype='DISTANCE',
1914            update=update
1915            )
1916    subs_alt : FloatProperty(
1917            name="Altitude",
1918            min=-100,
1919            default=0, precision=2, step=1,
1920            unit='LENGTH', subtype='DISTANCE',
1921            update=update
1922            )
1923    subs_offset_x : FloatProperty(
1924            name="Offset",
1925            min=-100.0, max=100,
1926            default=0.0, precision=2, step=1,
1927            unit='LENGTH', subtype='DISTANCE',
1928            update=update
1929            )
1930    subs_bottom : EnumProperty(
1931            name="Bottom",
1932            items=(
1933                ('STEP', 'Follow step', '', 0),
1934                ('LINEAR', 'Linear', '', 1),
1935                ),
1936            default='STEP',
1937            update=update
1938            )
1939    user_defined_subs_enable : BoolProperty(
1940            name="User",
1941            update=update,
1942            default=True
1943            )
1944    user_defined_subs : StringProperty(
1945            name="User defined",
1946            update=update
1947            )
1948    idmat_subs : EnumProperty(
1949            name="Subs",
1950            items=materials_enum,
1951            default='4',
1952            update=update
1953            )
1954    left_panel : BoolProperty(
1955            name='left',
1956            default=True,
1957            update=update
1958            )
1959    right_panel : BoolProperty(
1960            name='right',
1961            default=True,
1962            update=update
1963            )
1964    panel_alt : FloatProperty(
1965            name="Altitude",
1966            default=0.25, precision=2, step=1,
1967            unit='LENGTH', subtype='DISTANCE',
1968            update=update
1969            )
1970    panel_x : FloatProperty(
1971            name="Width",
1972            min=0.001,
1973            default=0.01, precision=2, step=1,
1974            unit='LENGTH', subtype='DISTANCE',
1975            update=update
1976            )
1977    panel_z : FloatProperty(
1978            name="Height",
1979            min=0.001,
1980            default=0.6, precision=2, step=1,
1981            unit='LENGTH', subtype='DISTANCE',
1982            update=update
1983            )
1984    panel_dist : FloatProperty(
1985            name="Spacing",
1986            min=0.001,
1987            default=0.05, precision=2, step=1,
1988            unit='LENGTH', subtype='DISTANCE',
1989            update=update
1990            )
1991    panel_offset_x : FloatProperty(
1992            name="Offset",
1993            default=0.0, precision=2, step=1,
1994            unit='LENGTH', subtype='DISTANCE',
1995            update=update
1996            )
1997    idmat_panel : EnumProperty(
1998            name="Panels",
1999            items=materials_enum,
2000            default='5',
2001            update=update
2002            )
2003    left_rail : BoolProperty(
2004            name="left",
2005            update=update,
2006            default=False
2007            )
2008    right_rail : BoolProperty(
2009            name="right",
2010            update=update,
2011            default=False
2012            )
2013    rail_n : IntProperty(
2014            name="#",
2015            default=1,
2016            min=0,
2017            max=31,
2018            update=update
2019            )
2020    rail_x : FloatVectorProperty(
2021            name="Width",
2022            default=[
2023                0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
2024                0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
2025                0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
2026                0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05
2027            ],
2028            size=31,
2029            min=0.001,
2030            precision=2, step=1,
2031            unit='LENGTH',
2032            update=update
2033            )
2034    rail_z : FloatVectorProperty(
2035            name="Height",
2036            default=[
2037                0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
2038                0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
2039                0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
2040                0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05
2041            ],
2042            size=31,
2043            min=0.001,
2044            precision=2, step=1,
2045            unit='LENGTH',
2046            update=update
2047            )
2048    rail_offset : FloatVectorProperty(
2049            name="Offset",
2050            default=[
2051                0, 0, 0, 0, 0, 0, 0, 0,
2052                0, 0, 0, 0, 0, 0, 0, 0,
2053                0, 0, 0, 0, 0, 0, 0, 0,
2054                0, 0, 0, 0, 0, 0, 0
2055            ],
2056            size=31,
2057            precision=2, step=1,
2058            unit='LENGTH',
2059            update=update
2060            )
2061    rail_alt : FloatVectorProperty(
2062            name="Altitude",
2063            default=[
2064                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
2065                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
2066                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
2067                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
2068            ],
2069            size=31,
2070            precision=2, step=1,
2071            unit='LENGTH',
2072            update=update
2073            )
2074    rail_mat : CollectionProperty(type=archipack_stair_material)
2075
2076    left_handrail : BoolProperty(
2077            name="left",
2078            update=update,
2079            default=True
2080            )
2081    right_handrail : BoolProperty(
2082            name="right",
2083            update=update,
2084            default=True
2085            )
2086    handrail_offset : FloatProperty(
2087            name="Offset",
2088            default=0.0, precision=2, step=1,
2089            unit='LENGTH', subtype='DISTANCE',
2090            update=update
2091            )
2092    handrail_alt : FloatProperty(
2093            name="Altitude",
2094            default=1.0, precision=2, step=1,
2095            unit='LENGTH', subtype='DISTANCE',
2096            update=update
2097            )
2098    handrail_extend : FloatProperty(
2099            name="Extend",
2100            default=0.1, precision=2, step=1,
2101            unit='LENGTH', subtype='DISTANCE',
2102            update=update
2103            )
2104    handrail_slice_left : BoolProperty(
2105            name='Slice',
2106            default=True,
2107            update=update
2108            )
2109    handrail_slice_right : BoolProperty(
2110            name='Slice',
2111            default=True,
2112            update=update
2113            )
2114    handrail_profil : EnumProperty(
2115            name="Profil",
2116            items=(
2117                ('SQUARE', 'Square', '', 0),
2118                ('CIRCLE', 'Circle', '', 1),
2119                ('COMPLEX', 'Circle over square', '', 2)
2120                ),
2121            default='SQUARE',
2122            update=update
2123            )
2124    handrail_x : FloatProperty(
2125            name="Width",
2126            min=0.001,
2127            default=0.04, precision=2, step=1,
2128            unit='LENGTH', subtype='DISTANCE',
2129            update=update
2130            )
2131    handrail_y : FloatProperty(
2132            name="Height",
2133            min=0.001,
2134            default=0.04, precision=2, step=1,
2135            unit='LENGTH', subtype='DISTANCE',
2136            update=update
2137            )
2138    handrail_radius : FloatProperty(
2139            name="Radius",
2140            min=0.001,
2141            default=0.02, precision=2, step=1,
2142            unit='LENGTH', subtype='DISTANCE',
2143            update=update
2144            )
2145
2146    left_string : BoolProperty(
2147            name="left",
2148            update=update,
2149            default=False
2150            )
2151    right_string : BoolProperty(
2152            name="right",
2153            update=update,
2154            default=False
2155            )
2156    string_x : FloatProperty(
2157            name="Width",
2158            min=-100.0,
2159            default=0.02, precision=2, step=1,
2160            unit='LENGTH', subtype='DISTANCE',
2161            update=update
2162            )
2163    string_z : FloatProperty(
2164            name="Height",
2165            default=0.3, precision=2, step=1,
2166            unit='LENGTH', subtype='DISTANCE',
2167            update=update
2168            )
2169    string_offset : FloatProperty(
2170            name="Offset",
2171            default=0.0, precision=2, step=1,
2172            unit='LENGTH', subtype='DISTANCE',
2173            update=update
2174            )
2175    string_alt : FloatProperty(
2176            name="Altitude",
2177            default=-0.04, precision=2, step=1,
2178            unit='LENGTH', subtype='DISTANCE',
2179            update=update
2180            )
2181
2182    idmat_bottom : EnumProperty(
2183            name="Bottom",
2184            items=materials_enum,
2185            default='1',
2186            update=update
2187            )
2188    idmat_raise : EnumProperty(
2189            name="Raise",
2190            items=materials_enum,
2191            default='1',
2192            update=update
2193            )
2194    idmat_step_front : EnumProperty(
2195            name="Step front",
2196            items=materials_enum,
2197            default='3',
2198            update=update
2199            )
2200    idmat_top : EnumProperty(
2201            name="Top",
2202            items=materials_enum,
2203            default='3',
2204            update=update
2205            )
2206    idmat_side : EnumProperty(
2207            name="Side",
2208            items=materials_enum,
2209            default='1',
2210            update=update
2211            )
2212    idmat_step_side : EnumProperty(
2213            name="Step Side",
2214            items=materials_enum,
2215            default='3',
2216            update=update
2217            )
2218    idmat_handrail : EnumProperty(
2219            name="Handrail",
2220            items=materials_enum,
2221            default='3',
2222            update=update
2223            )
2224    idmat_string : EnumProperty(
2225            name="String",
2226            items=materials_enum,
2227            default='3',
2228            update=update
2229            )
2230
2231    # UI layout related
2232    parts_expand : BoolProperty(
2233            default=False
2234            )
2235    steps_expand : BoolProperty(
2236            default=False
2237            )
2238    rail_expand : BoolProperty(
2239            default=False
2240            )
2241    idmats_expand : BoolProperty(
2242            default=False
2243            )
2244    handrail_expand : BoolProperty(
2245            default=False
2246            )
2247    string_expand : BoolProperty(
2248            default=False
2249            )
2250    post_expand : BoolProperty(
2251            default=False
2252            )
2253    panel_expand : BoolProperty(
2254            default=False
2255            )
2256    subs_expand : BoolProperty(
2257            default=False
2258            )
2259
2260    auto_update : BoolProperty(
2261            options={'SKIP_SAVE'},
2262            default=True,
2263            update=update_manipulators
2264            )
2265
2266    def setup_manipulators(self):
2267
2268        if len(self.manipulators) == 0:
2269            s = self.manipulators.add()
2270            s.prop1_name = "width"
2271            s = self.manipulators.add()
2272            s.prop1_name = "height"
2273            s.normal = Vector((0, 1, 0))
2274
2275        for i in range(self.n_parts):
2276            p = self.parts[i]
2277            n_manips = len(p.manipulators)
2278            if n_manips < 1:
2279                m = p.manipulators.add()
2280                m.type_key = 'SIZE'
2281                m.prop1_name = 'length'
2282
2283    def update_parts(self):
2284
2285        # remove rails materials
2286        for i in range(len(self.rail_mat), self.rail_n, -1):
2287            self.rail_mat.remove(i - 1)
2288
2289        # add rails
2290        for i in range(len(self.rail_mat), self.rail_n):
2291            self.rail_mat.add()
2292
2293        # remove parts
2294        for i in range(len(self.parts), self.n_parts, -1):
2295            self.parts.remove(i - 1)
2296
2297        # add parts
2298        for i in range(len(self.parts), self.n_parts):
2299            self.parts.add()
2300
2301        self.setup_manipulators()
2302
2303    def update(self, context, manipulable_refresh=False):
2304
2305        o = self.find_in_selection(context, self.auto_update)
2306
2307        if o is None:
2308            return
2309
2310        # clean up manipulators before any data model change
2311        if manipulable_refresh:
2312            self.manipulable_disable(context)
2313
2314        self.update_parts()
2315
2316        center = Vector((0, 0))
2317        verts = []
2318        faces = []
2319        matids = []
2320        uvs = []
2321        id_materials = [int(self.idmat_top), int(self.idmat_step_front), int(self.idmat_raise),
2322                        int(self.idmat_side), int(self.idmat_bottom), int(self.idmat_step_side)]
2323
2324        # depth at bottom
2325        bottom_z = self.bottom_z
2326        if self.steps_type == 'OPEN':
2327            # depth at front
2328            bottom_z = self.nose_z
2329
2330        width_left = 0.5 * self.width - self.x_offset
2331        width_right = 0.5 * self.width + self.x_offset
2332
2333        self.manipulators[0].set_pts([(-width_left, 0, 0), (width_right, 0, 0), (1, 0, 0)])
2334        self.manipulators[1].set_pts([(0, 0, 0), (0, 0, self.height), (1, 0, 0)])
2335
2336        g = StairGenerator(self.parts)
2337        if self.presets == 'STAIR_USER':
2338            for part in self.parts:
2339                g.add_part(part.type, self.steps_type, self.nose_type, self.z_mode, self.nose_z,
2340                        bottom_z, center, max(width_left + 0.01, width_right + 0.01, part.radius), part.da,
2341                        width_left, width_right, part.length, part.left_shape, part.right_shape)
2342
2343        elif self.presets == 'STAIR_O':
2344            n_parts = max(1, int(round(abs(self.total_angle) / pi, 0)))
2345            if self.total_angle > 0:
2346                dir = 1
2347            else:
2348                dir = -1
2349            last_da = self.total_angle - dir * (n_parts - 1) * pi
2350            if dir * last_da > pi:
2351                n_parts += 1
2352                last_da -= dir * pi
2353            abs_last = dir * last_da
2354
2355            for part in range(n_parts - 1):
2356                g.add_part('D_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
2357                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), dir * pi,
2358                            width_left, width_right, 1.0, self.left_shape, self.right_shape)
2359            if round(abs_last, 2) > 0:
2360                if abs_last > pi / 2:
2361                    g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
2362                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius),
2363                            dir * pi / 2,
2364                            width_left, width_right, 1.0, self.left_shape, self.right_shape)
2365                    g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
2366                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius),
2367                            last_da - dir * pi / 2,
2368                            width_left, width_right, 1.0, self.left_shape, self.right_shape)
2369                else:
2370                    g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
2371                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), last_da,
2372                            width_left, width_right, 1.0, self.left_shape, self.right_shape)
2373        else:
2374            # STAIR_L STAIR_I STAIR_U
2375            for part in self.parts:
2376                g.add_part(part.type, self.steps_type, self.nose_type, self.z_mode, self.nose_z,
2377                            bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), self.da,
2378                            width_left, width_right, part.length, self.left_shape, self.right_shape)
2379
2380        # Stair basis
2381        g.set_matids(id_materials)
2382        g.make_stair(self.height, self.step_depth, verts, faces, matids, uvs, nose_y=self.nose_y)
2383
2384        # Ladder
2385        offset_x = 0.5 * self.width - self.post_offset_x
2386        post_spacing = self.post_spacing
2387        if self.post_corners:
2388            post_spacing = 10000
2389
2390        if self.user_defined_post_enable:
2391            # user defined posts
2392            user_def_post = context.scene.objects.get(self.user_defined_post.strip())
2393            if user_def_post is not None and user_def_post.type == 'MESH':
2394                g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z)
2395
2396        if self.left_post:
2397            g.make_post(self.height, self.step_depth, 0.5 * self.post_x, 0.5 * self.post_y,
2398                    self.post_z, self.post_alt, 'LEFT', post_spacing, self.post_corners,
2399                    self.x_offset, offset_x, int(self.idmat_post), verts, faces, matids, uvs)
2400
2401        if self.right_post:
2402            g.make_post(self.height, self.step_depth, 0.5 * self.post_x, 0.5 * self.post_y,
2403                    self.post_z, self.post_alt, 'RIGHT', post_spacing, self.post_corners,
2404                    self.x_offset, offset_x, int(self.idmat_post), verts, faces, matids, uvs)
2405
2406        # reset user def posts
2407        g.user_defined_post = None
2408
2409        # user defined subs
2410        if self.user_defined_subs_enable:
2411            user_def_subs = context.scene.objects.get(self.user_defined_subs.strip())
2412            if user_def_subs is not None and user_def_subs.type == 'MESH':
2413                g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z)
2414
2415        if self.left_subs:
2416            g.make_subs(self.height, self.step_depth, 0.5 * self.subs_x, 0.5 * self.subs_y,
2417                    self.subs_z, 0.5 * self.post_y, self.subs_alt, self.subs_bottom, 'LEFT',
2418                    self.handrail_slice_left, post_spacing, self.subs_spacing, self.post_corners,
2419                    self.x_offset, offset_x, -self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs)
2420
2421        if self.right_subs:
2422            g.make_subs(self.height, self.step_depth, 0.5 * self.subs_x, 0.5 * self.subs_y,
2423                    self.subs_z, 0.5 * self.post_y, self.subs_alt, self.subs_bottom, 'RIGHT',
2424                    self.handrail_slice_right, post_spacing, self.subs_spacing, self.post_corners,
2425                    self.x_offset, offset_x, self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs)
2426
2427        g.user_defined_post = None
2428
2429        if self.left_panel:
2430            g.make_panels(self.height, self.step_depth, 0.5 * self.panel_x, self.panel_z, 0.5 * self.post_y,
2431                    self.panel_alt, 'LEFT', post_spacing, self.panel_dist, self.post_corners,
2432                    self.x_offset, offset_x, -self.panel_offset_x, int(self.idmat_panel), verts, faces, matids, uvs)
2433
2434        if self.right_panel:
2435            g.make_panels(self.height, self.step_depth, 0.5 * self.panel_x, self.panel_z, 0.5 * self.post_y,
2436                    self.panel_alt, 'RIGHT', post_spacing, self.panel_dist, self.post_corners,
2437                    self.x_offset, offset_x, self.panel_offset_x, int(self.idmat_panel), verts, faces, matids, uvs)
2438
2439        if self.right_rail:
2440            for i in range(self.rail_n):
2441                id_materials = [int(self.rail_mat[i].index) for j in range(6)]
2442                g.set_matids(id_materials)
2443                g.make_part(self.height, self.step_depth, self.rail_x[i], self.rail_z[i],
2444                        self.x_offset, offset_x + self.rail_offset[i],
2445                        self.rail_alt[i], 'LINEAR', 'CLOSED', verts, faces, matids, uvs)
2446
2447        if self.left_rail:
2448            for i in range(self.rail_n):
2449                id_materials = [int(self.rail_mat[i].index) for j in range(6)]
2450                g.set_matids(id_materials)
2451                g.make_part(self.height, self.step_depth, self.rail_x[i], self.rail_z[i],
2452                        self.x_offset, -offset_x - self.rail_offset[i],
2453                        self.rail_alt[i], 'LINEAR', 'CLOSED', verts, faces, matids, uvs)
2454
2455        if self.handrail_profil == 'COMPLEX':
2456            sx = self.handrail_x
2457            sy = self.handrail_y
2458            handrail = [Vector((sx * x, sy * y)) for x, y in [
2459            (-0.28, 1.83), (-0.355, 1.77), (-0.415, 1.695), (-0.46, 1.605), (-0.49, 1.51), (-0.5, 1.415),
2460            (-0.49, 1.315), (-0.46, 1.225), (-0.415, 1.135), (-0.355, 1.06), (-0.28, 1.0), (-0.255, 0.925),
2461            (-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),
2462            (0.28, 1.0), (0.355, 1.06), (0.415, 1.135), (0.46, 1.225), (0.49, 1.315), (0.5, 1.415),
2463            (0.49, 1.51), (0.46, 1.605), (0.415, 1.695), (0.355, 1.77), (0.28, 1.83), (0.19, 1.875),
2464            (0.1, 1.905), (0.0, 1.915), (-0.095, 1.905), (-0.19, 1.875)]]
2465
2466        elif self.handrail_profil == 'SQUARE':
2467            x = 0.5 * self.handrail_x
2468            y = self.handrail_y
2469            handrail = [Vector((-x, y)), Vector((-x, 0)), Vector((x, 0)), Vector((x, y))]
2470        elif self.handrail_profil == 'CIRCLE':
2471            r = self.handrail_radius
2472            handrail = [Vector((r * sin(0.1 * -a * pi), r * (0.5 + cos(0.1 * -a * pi)))) for a in range(0, 20)]
2473
2474        if self.right_handrail:
2475            g.make_profile(handrail, int(self.idmat_handrail), "RIGHT", self.handrail_slice_right,
2476                self.height, self.step_depth, self.x_offset + offset_x + self.handrail_offset,
2477                self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs)
2478
2479        if self.left_handrail:
2480            g.make_profile(handrail, int(self.idmat_handrail), "LEFT", self.handrail_slice_left,
2481                self.height, self.step_depth, -self.x_offset + offset_x + self.handrail_offset,
2482                self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs)
2483
2484        w = 0.5 * self.string_x
2485        h = self.string_z
2486        string = [Vector((-w, 0)), Vector((w, 0)), Vector((w, h)), Vector((-w, h))]
2487
2488        if self.right_string:
2489            g.make_profile(string, int(self.idmat_string), "RIGHT", False, self.height, self.step_depth,
2490                self.x_offset + 0.5 * self.width + self.string_offset,
2491                self.string_alt, 0, verts, faces, matids, uvs)
2492
2493        if self.left_string:
2494            g.make_profile(string, int(self.idmat_string), "LEFT", False, self.height, self.step_depth,
2495                -self.x_offset + 0.5 * self.width + self.string_offset,
2496                self.string_alt, 0, verts, faces, matids, uvs)
2497
2498        bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs, weld=True, clean=True)
2499
2500        # enable manipulators rebuild
2501        if manipulable_refresh:
2502            self.manipulable_refresh = True
2503
2504        self.restore_context(context)
2505
2506    def manipulable_setup(self, context):
2507        """
2508            TODO: Implement the setup part as per parent object basis
2509
2510            self.manipulable_disable(context)
2511            o = context.active_object
2512            for m in self.manipulators:
2513                self.manip_stack.append(m.setup(context, o, self))
2514
2515        """
2516        self.manipulable_disable(context)
2517        o = context.active_object
2518
2519        self.setup_manipulators()
2520
2521        if self.presets != 'STAIR_O':
2522            for i, part in enumerate(self.parts):
2523                if i >= self.n_parts:
2524                    break
2525                if "S_" in part.type or self.presets in ['STAIR_USER']:
2526                    for j, m in enumerate(part.manipulators):
2527                        self.manip_stack.append(m.setup(context, o, part))
2528
2529        if self.presets in ['STAIR_U', 'STAIR_L']:
2530            self.manip_stack.append(self.parts[1].manipulators[0].setup(context, o, self))
2531
2532        for m in self.manipulators:
2533            self.manip_stack.append(m.setup(context, o, self))
2534
2535
2536class ARCHIPACK_PT_stair(Panel):
2537    bl_idname = "ARCHIPACK_PT_stair"
2538    bl_label = "Stair"
2539    bl_space_type = 'VIEW_3D'
2540    bl_region_type = 'UI'
2541    # bl_context = 'object'
2542    bl_category = 'Archipack'
2543
2544    @classmethod
2545    def poll(cls, context):
2546        return archipack_stair.filter(context.active_object)
2547
2548    def draw(self, context):
2549        prop = archipack_stair.datablock(context.active_object)
2550        if prop is None:
2551            return
2552        scene = context.scene
2553        layout = self.layout
2554        row = layout.row(align=True)
2555        row.operator('archipack.stair_manipulate', icon='VIEW_PAN')
2556        row = layout.row(align=True)
2557        row.prop(prop, 'presets', text="")
2558        box = layout.box()
2559        # box.label(text="Styles")
2560        row = box.row(align=True)
2561        # row.menu("ARCHIPACK_MT_stair_preset", text=bpy.types.ARCHIPACK_MT_stair_preset.bl_label)
2562        row.operator("archipack.stair_preset_menu", text=bpy.types.ARCHIPACK_OT_stair_preset_menu.bl_label)
2563        row.operator("archipack.stair_preset", text="", icon='ADD')
2564        row.operator("archipack.stair_preset", text="", icon='REMOVE').remove_active = True
2565        box = layout.box()
2566        box.prop(prop, 'width')
2567        box.prop(prop, 'height')
2568        box.prop(prop, 'bottom_z')
2569        box.prop(prop, 'x_offset')
2570        # box.prop(prop, 'z_mode')
2571        box = layout.box()
2572        row = box.row()
2573        if prop.parts_expand:
2574            row.prop(prop, 'parts_expand', icon="TRIA_DOWN", text="Parts", emboss=False)
2575            if prop.presets == 'STAIR_USER':
2576                box.prop(prop, 'n_parts')
2577            if prop.presets != 'STAIR_USER':
2578                row = box.row(align=True)
2579                row.prop(prop, "left_shape", text="")
2580                row.prop(prop, "right_shape", text="")
2581                row = box.row()
2582                row.prop(prop, "radius")
2583                row = box.row()
2584                if prop.presets == 'STAIR_O':
2585                    row.prop(prop, 'total_angle')
2586                else:
2587                    row.prop(prop, 'da')
2588            if prop.presets != 'STAIR_O':
2589                for i, part in enumerate(prop.parts):
2590                    part.draw(layout, context, i, prop.presets == 'STAIR_USER')
2591        else:
2592            row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", text="Parts", emboss=False)
2593
2594        box = layout.box()
2595        row = box.row()
2596        if prop.steps_expand:
2597            row.prop(prop, 'steps_expand', icon="TRIA_DOWN", text="Steps", emboss=False)
2598            box.prop(prop, 'steps_type')
2599            box.prop(prop, 'step_depth')
2600            box.prop(prop, 'nose_type')
2601            box.prop(prop, 'nose_z')
2602            box.prop(prop, 'nose_y')
2603        else:
2604            row.prop(prop, 'steps_expand', icon="TRIA_RIGHT", text="Steps", emboss=False)
2605
2606        box = layout.box()
2607        row = box.row(align=True)
2608        if prop.handrail_expand:
2609            row.prop(prop, 'handrail_expand', icon="TRIA_DOWN", text="Handrail", emboss=False)
2610        else:
2611            row.prop(prop, 'handrail_expand', icon="TRIA_RIGHT", text="Handrail", emboss=False)
2612
2613        row.prop(prop, 'left_handrail')
2614        row.prop(prop, 'right_handrail')
2615
2616        if prop.handrail_expand:
2617            box.prop(prop, 'handrail_alt')
2618            box.prop(prop, 'handrail_offset')
2619            box.prop(prop, 'handrail_extend')
2620            box.prop(prop, 'handrail_profil')
2621            if prop.handrail_profil != 'CIRCLE':
2622                box.prop(prop, 'handrail_x')
2623                box.prop(prop, 'handrail_y')
2624            else:
2625                box.prop(prop, 'handrail_radius')
2626            row = box.row(align=True)
2627            row.prop(prop, 'handrail_slice_left')
2628            row.prop(prop, 'handrail_slice_right')
2629
2630        box = layout.box()
2631        row = box.row(align=True)
2632        if prop.string_expand:
2633            row.prop(prop, 'string_expand', icon="TRIA_DOWN", text="String", emboss=False)
2634        else:
2635            row.prop(prop, 'string_expand', icon="TRIA_RIGHT", text="String", emboss=False)
2636        row.prop(prop, 'left_string')
2637        row.prop(prop, 'right_string')
2638        if prop.string_expand:
2639            box.prop(prop, 'string_x')
2640            box.prop(prop, 'string_z')
2641            box.prop(prop, 'string_alt')
2642            box.prop(prop, 'string_offset')
2643
2644        box = layout.box()
2645        row = box.row(align=True)
2646        if prop.post_expand:
2647            row.prop(prop, 'post_expand', icon="TRIA_DOWN", text="Post", emboss=False)
2648        else:
2649            row.prop(prop, 'post_expand', icon="TRIA_RIGHT", text="Post", emboss=False)
2650        row.prop(prop, 'left_post')
2651        row.prop(prop, 'right_post')
2652        if prop.post_expand:
2653            box.prop(prop, 'post_corners')
2654            if not prop.post_corners:
2655                box.prop(prop, 'post_spacing')
2656            box.prop(prop, 'post_x')
2657            box.prop(prop, 'post_y')
2658            box.prop(prop, 'post_z')
2659            box.prop(prop, 'post_alt')
2660            box.prop(prop, 'post_offset_x')
2661            row = box.row(align=True)
2662            row.prop(prop, 'user_defined_post_enable', text="")
2663            row.prop_search(prop, "user_defined_post", scene, "objects", text="")
2664
2665        box = layout.box()
2666        row = box.row(align=True)
2667        if prop.subs_expand:
2668            row.prop(prop, 'subs_expand', icon="TRIA_DOWN", text="Subs", emboss=False)
2669        else:
2670            row.prop(prop, 'subs_expand', icon="TRIA_RIGHT", text="Subs", emboss=False)
2671
2672        row.prop(prop, 'left_subs')
2673        row.prop(prop, 'right_subs')
2674        if prop.subs_expand:
2675            box.prop(prop, 'subs_spacing')
2676            box.prop(prop, 'subs_x')
2677            box.prop(prop, 'subs_y')
2678            box.prop(prop, 'subs_z')
2679            box.prop(prop, 'subs_alt')
2680            box.prop(prop, 'subs_offset_x')
2681            box.prop(prop, 'subs_bottom')
2682            row = box.row(align=True)
2683            row.prop(prop, 'user_defined_subs_enable', text="")
2684            row.prop_search(prop, "user_defined_subs", scene, "objects", text="")
2685
2686        box = layout.box()
2687        row = box.row(align=True)
2688        if prop.panel_expand:
2689            row.prop(prop, 'panel_expand', icon="TRIA_DOWN", text="Panels", emboss=False)
2690        else:
2691            row.prop(prop, 'panel_expand', icon="TRIA_RIGHT", text="Panels", emboss=False)
2692        row.prop(prop, 'left_panel')
2693        row.prop(prop, 'right_panel')
2694        if prop.panel_expand:
2695            box.prop(prop, 'panel_dist')
2696            box.prop(prop, 'panel_x')
2697            box.prop(prop, 'panel_z')
2698            box.prop(prop, 'panel_alt')
2699            box.prop(prop, 'panel_offset_x')
2700
2701        box = layout.box()
2702        row = box.row(align=True)
2703        if prop.rail_expand:
2704            row.prop(prop, 'rail_expand', icon="TRIA_DOWN", text="Rails", emboss=False)
2705        else:
2706            row.prop(prop, 'rail_expand', icon="TRIA_RIGHT", text="Rails", emboss=False)
2707        row.prop(prop, 'left_rail')
2708        row.prop(prop, 'right_rail')
2709        if prop.rail_expand:
2710            box.prop(prop, 'rail_n')
2711            for i in range(prop.rail_n):
2712                box = layout.box()
2713                box.label(text="Rail " + str(i + 1))
2714                box.prop(prop, 'rail_x', index=i)
2715                box.prop(prop, 'rail_z', index=i)
2716                box.prop(prop, 'rail_alt', index=i)
2717                box.prop(prop, 'rail_offset', index=i)
2718                box.prop(prop.rail_mat[i], 'index', text="")
2719
2720        box = layout.box()
2721        row = box.row()
2722
2723        if prop.idmats_expand:
2724            row.prop(prop, 'idmats_expand', icon="TRIA_DOWN", text="Materials", emboss=False)
2725            box.prop(prop, 'idmat_top')
2726            box.prop(prop, 'idmat_side')
2727            box.prop(prop, 'idmat_bottom')
2728            box.prop(prop, 'idmat_step_side')
2729            box.prop(prop, 'idmat_step_front')
2730            box.prop(prop, 'idmat_raise')
2731            box.prop(prop, 'idmat_handrail')
2732            box.prop(prop, 'idmat_panel')
2733            box.prop(prop, 'idmat_post')
2734            box.prop(prop, 'idmat_subs')
2735            box.prop(prop, 'idmat_string')
2736        else:
2737            row.prop(prop, 'idmats_expand', icon="TRIA_RIGHT", text="Materials", emboss=False)
2738
2739
2740# ------------------------------------------------------------------
2741# Define operator class to create object
2742# ------------------------------------------------------------------
2743
2744
2745class ARCHIPACK_OT_stair(ArchipackCreateTool, Operator):
2746    bl_idname = "archipack.stair"
2747    bl_label = "Stair"
2748    bl_description = "Create a Stair"
2749    bl_category = 'Archipack'
2750    bl_options = {'REGISTER', 'UNDO'}
2751
2752    def create(self, context):
2753        m = bpy.data.meshes.new("Stair")
2754        o = bpy.data.objects.new("Stair", m)
2755        d = m.archipack_stair.add()
2756        self.link_object_to_scene(context, o)
2757        o.select_set(state=True)
2758        context.view_layer.objects.active = o
2759        self.load_preset(d)
2760        self.add_material(o)
2761        m.auto_smooth_angle = 0.20944
2762        return o
2763
2764    # -----------------------------------------------------
2765    # Execute
2766    # -----------------------------------------------------
2767    def execute(self, context):
2768        if context.mode == "OBJECT":
2769            bpy.ops.object.select_all(action="DESELECT")
2770            o = self.create(context)
2771            o.location = context.scene.cursor.location
2772            o.select_set(state=True)
2773            context.view_layer.objects.active = o
2774            self.manipulate()
2775            return {'FINISHED'}
2776        else:
2777            self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
2778            return {'CANCELLED'}
2779
2780# ------------------------------------------------------------------
2781# Define operator class to manipulate object
2782# ------------------------------------------------------------------
2783
2784
2785class ARCHIPACK_OT_stair_manipulate(Operator):
2786    bl_idname = "archipack.stair_manipulate"
2787    bl_label = "Manipulate"
2788    bl_description = "Manipulate"
2789    bl_options = {'REGISTER', 'UNDO'}
2790
2791    @classmethod
2792    def poll(self, context):
2793        return archipack_stair.filter(context.active_object)
2794
2795    def invoke(self, context, event):
2796        d = archipack_stair.datablock(context.active_object)
2797        d.manipulable_invoke(context)
2798        return {'FINISHED'}
2799
2800
2801# ------------------------------------------------------------------
2802# Define operator class to load / save presets
2803# ------------------------------------------------------------------
2804
2805
2806class ARCHIPACK_OT_stair_preset_menu(PresetMenuOperator, Operator):
2807    bl_description = "Show Stair Presets"
2808    bl_idname = "archipack.stair_preset_menu"
2809    bl_label = "Stair style"
2810    preset_subdir = "archipack_stair"
2811
2812
2813class ARCHIPACK_OT_stair_preset(ArchipackPreset, Operator):
2814    """Add a Stair Preset"""
2815    bl_idname = "archipack.stair_preset"
2816    bl_label = "Add Stair Style"
2817    preset_menu = "ARCHIPACK_OT_stair_preset_menu"
2818
2819    @property
2820    def blacklist(self):
2821        return ['manipulators']
2822
2823
2824def register():
2825    bpy.utils.register_class(archipack_stair_material)
2826    bpy.utils.register_class(archipack_stair_part)
2827    bpy.utils.register_class(archipack_stair)
2828    Mesh.archipack_stair = CollectionProperty(type=archipack_stair)
2829    bpy.utils.register_class(ARCHIPACK_PT_stair)
2830    bpy.utils.register_class(ARCHIPACK_OT_stair)
2831    bpy.utils.register_class(ARCHIPACK_OT_stair_preset_menu)
2832    bpy.utils.register_class(ARCHIPACK_OT_stair_preset)
2833    bpy.utils.register_class(ARCHIPACK_OT_stair_manipulate)
2834
2835
2836def unregister():
2837    bpy.utils.unregister_class(archipack_stair_material)
2838    bpy.utils.unregister_class(archipack_stair_part)
2839    bpy.utils.unregister_class(archipack_stair)
2840    del Mesh.archipack_stair
2841    bpy.utils.unregister_class(ARCHIPACK_PT_stair)
2842    bpy.utils.unregister_class(ARCHIPACK_OT_stair)
2843    bpy.utils.unregister_class(ARCHIPACK_OT_stair_preset_menu)
2844    bpy.utils.unregister_class(ARCHIPACK_OT_stair_preset)
2845    bpy.utils.unregister_class(ARCHIPACK_OT_stair_manipulate)
2846