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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Leger <stephen@3dservices.ch>2017-07-22 14:25:28 +0300
committerStephen Leger <stephen@3dservices.ch>2017-07-22 14:26:04 +0300
commitc1ab9b4b9c6c0226f8d7789b92efda9b0f33cfd1 (patch)
tree37d5a97c758fa9af48d1dfb5428edd72072d882a /archipack/archipack_stair.py
parent5638a8783502138500912061dde0e8ee476d7fca (diff)
archipack: T52120 release to official
Diffstat (limited to 'archipack/archipack_stair.py')
-rw-r--r--archipack/archipack_stair.py2849
1 files changed, 2849 insertions, 0 deletions
diff --git a/archipack/archipack_stair.py b/archipack/archipack_stair.py
new file mode 100644
index 00000000..c7e7f02c
--- /dev/null
+++ b/archipack/archipack_stair.py
@@ -0,0 +1,2849 @@
+# -*- coding:utf-8 -*-
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+# ----------------------------------------------------------
+# Author: Stephen Leger (s-leger)
+#
+# ----------------------------------------------------------
+# noinspection PyUnresolvedReferences
+import bpy
+# noinspection PyUnresolvedReferences
+from bpy.types import Operator, PropertyGroup, Mesh, Panel
+from bpy.props import (
+ FloatProperty, BoolProperty, IntProperty, CollectionProperty,
+ StringProperty, EnumProperty, FloatVectorProperty
+ )
+from .bmesh_utils import BmeshEdit as bmed
+from .panel import Panel as Lofter
+from mathutils import Vector, Matrix
+from math import sin, cos, pi, floor, acos
+from .archipack_manipulator import Manipulable, archipack_manipulator
+from .archipack_2d import Line, Arc
+from .archipack_preset import ArchipackPreset, PresetMenuOperator
+from .archipack_object import ArchipackCreateTool, ArchipackObject
+
+
+class Stair():
+ def __init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z):
+ self.steps_type = steps_type
+ self.nose_type = nose_type
+ self.l_shape = None
+ self.r_shape = None
+ self.next_type = 'NONE'
+ self.last_type = 'NONE'
+ self.z_mode = z_mode
+ # depth of open step
+ self.nose_z = nose_z
+ # size under the step on bottom
+ self.bottom_z = bottom_z
+ self.left_offset = left_offset
+ self.right_offset = right_offset
+ self.last_height = 0
+
+ def set_matids(self, matids):
+ self.idmat_top, self.idmat_step_front, self.idmat_raise, \
+ self.idmat_side, self.idmat_bottom, self.idmat_step_side = matids
+
+ def set_height(self, step_height, z0):
+ self.step_height = step_height
+ self.z0 = z0
+
+ @property
+ def height(self):
+ return self.n_step * self.step_height
+
+ @property
+ def top_offset(self):
+ return self.t_step / self.step_depth
+
+ @property
+ def top(self):
+ return self.z0 + self.height
+
+ @property
+ def left_length(self):
+ return self.get_length("LEFT")
+
+ @property
+ def right_length(self):
+ return self.get_length("RIGHT")
+
+ def step_size(self, step_depth):
+ t_step, n_step = self.steps(step_depth)
+ self.n_step = n_step
+ self.t_step = t_step
+ self.step_depth = step_depth
+ return n_step
+
+ def p3d_left(self, verts, p2d, i, t, landing=False):
+ x, y = p2d
+ nose_z = min(self.step_height, self.nose_z)
+ zl = self.z0 + t * self.height
+ zs = self.z0 + i * self.step_height
+ if self.z_mode == 'LINEAR':
+ z0 = max(0, zl)
+ z1 = z0 - self.bottom_z
+ verts.extend([(x, y, z0), (x, y, z1)])
+ else:
+ if "FULL" in self.steps_type:
+ z0 = 0
+ else:
+ z0 = max(0, zl - nose_z - self.bottom_z)
+ z3 = zs + max(0, self.step_height - nose_z)
+ z4 = zs + self.step_height
+ if landing:
+ if "FULL" in self.steps_type:
+ z2 = 0
+ z1 = 0
+ else:
+ z2 = max(0, min(z3, z3 - self.bottom_z))
+ z1 = z2
+ else:
+ z1 = min(z3, max(z0, zl - nose_z))
+ z2 = min(z3, max(z1, zl))
+ verts.extend([(x, y, z0),
+ (x, y, z1),
+ (x, y, z2),
+ (x, y, z3),
+ (x, y, z4)])
+
+ def p3d_right(self, verts, p2d, i, t, landing=False):
+ x, y = p2d
+ nose_z = min(self.step_height, self.nose_z)
+ zl = self.z0 + t * self.height
+ zs = self.z0 + i * self.step_height
+ if self.z_mode == 'LINEAR':
+ z0 = max(0, zl)
+ z1 = z0 - self.bottom_z
+ verts.extend([(x, y, z1), (x, y, z0)])
+ else:
+ if "FULL" in self.steps_type:
+ z0 = 0
+ else:
+ z0 = max(0, zl - nose_z - self.bottom_z)
+ z3 = zs + max(0, self.step_height - nose_z)
+ z4 = zs + self.step_height
+ if landing:
+ if "FULL" in self.steps_type:
+ z2 = 0
+ z1 = 0
+ else:
+ z2 = max(0, min(z3, z3 - self.bottom_z))
+ z1 = z2
+ else:
+ z1 = min(z3, max(z0, zl - nose_z))
+ z2 = min(z3, max(z1, zl))
+ verts.extend([(x, y, z4),
+ (x, y, z3),
+ (x, y, z2),
+ (x, y, z1),
+ (x, y, z0)])
+
+ def p3d_cstep_left(self, verts, p2d, i, t):
+ x, y = p2d
+ nose_z = min(self.step_height, self.nose_z)
+ zs = self.z0 + i * self.step_height
+ z3 = zs + max(0, self.step_height - nose_z)
+ z1 = min(z3, zs - nose_z)
+ verts.append((x, y, z1))
+ verts.append((x, y, z3))
+
+ def p3d_cstep_right(self, verts, p2d, i, t):
+ x, y = p2d
+ nose_z = min(self.step_height, self.nose_z)
+ zs = self.z0 + i * self.step_height
+ z3 = zs + max(0, self.step_height - nose_z)
+ z1 = min(z3, zs - nose_z)
+ verts.append((x, y, z3))
+ verts.append((x, y, z1))
+
+ def straight_stair(self, length):
+ self.next_type = 'STAIR'
+ s = self.straight(length)
+ return StraightStair(s.p, s.v, self.left_offset, self.right_offset, self.steps_type,
+ self.nose_type, self.z_mode, self.nose_z, self.bottom_z)
+
+ def straight_landing(self, length, last_type='STAIR'):
+ self.next_type = 'LANDING'
+ s = self.straight(length)
+ return StraightLanding(s.p, s.v, self.left_offset, self.right_offset, self.steps_type,
+ self.nose_type, self.z_mode, self.nose_z, self.bottom_z, last_type=last_type)
+
+ def curved_stair(self, da, radius, left_shape, right_shape, double_limit=pi):
+ self.next_type = 'STAIR'
+ n = self.normal(1)
+ n.v = radius * n.v.normalized()
+ if da < 0:
+ n.v = -n.v
+ a0 = n.angle
+ c = n.p - n.v
+ return CurvedStair(c, radius, a0, da, self.left_offset, self.right_offset,
+ self.steps_type, self.nose_type, self.z_mode, self.nose_z, self.bottom_z,
+ left_shape, right_shape, double_limit=double_limit)
+
+ def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi, last_type='STAIR'):
+ self.next_type = 'LANDING'
+ n = self.normal(1)
+ n.v = radius * n.v.normalized()
+ if da < 0:
+ n.v = -n.v
+ a0 = n.angle
+ c = n.p - n.v
+ return CurvedLanding(c, radius, a0, da, self.left_offset, self.right_offset,
+ self.steps_type, self.nose_type, self.z_mode, self.nose_z, self.bottom_z,
+ left_shape, right_shape, double_limit=double_limit, last_type=last_type)
+
+ def get_z(self, t, mode):
+ if mode == 'LINEAR':
+ return self.z0 + t * self.height
+ else:
+ step = 1 + floor(t / self.t_step)
+ return self.z0 + step * self.step_height
+
+ def make_profile(self, t, side, profile, verts, faces, matids, next=None, tnext=0):
+ z0 = self.get_z(t, 'LINEAR')
+ dz1 = 0
+ t, part, dz0, shape = self.get_part(t, side)
+ if next is not None:
+ tnext, next, dz1, shape1 = next.get_part(tnext, side)
+ xy, s = part.proj_xy(t, next)
+ v_xy = s * xy.to_3d()
+ z, s = part.proj_z(t, dz0, next, dz1)
+ v_z = s * Vector((-xy.y * z.x, xy.x * z.x, z.y))
+ x, y = part.lerp(t)
+ verts += [Vector((x, y, z0)) + v.x * v_xy + v.y * v_z for v in profile]
+
+ def project_uv(self, rM, uvs, verts, indexes, up_axis='Z'):
+ if up_axis == 'Z':
+ uvs.append([(rM * Vector(verts[i])).to_2d() for i in indexes])
+ elif up_axis == 'Y':
+ uvs.append([(x, z) for x, y, z in [(rM * Vector(verts[i])) for i in indexes]])
+ else:
+ uvs.append([(y, z) for x, y, z in [(rM * Vector(verts[i])) for i in indexes]])
+
+ def get_proj_matrix(self, part, t, nose_y):
+ # a matrix to project verts
+ # into uv space for horizontal parts of this step
+ # so uv = (rM * vertex).to_2d()
+ tl = t - nose_y / self.get_length("LEFT")
+ tr = t - nose_y / self.get_length("RIGHT")
+ t2, part, dz, shape = self.get_part(tl, "LEFT")
+ p0 = part.lerp(t2)
+ t2, part, dz, shape = self.get_part(tr, "RIGHT")
+ p1 = part.lerp(t2)
+ v = (p1 - p0).normalized()
+ return Matrix([
+ [-v.y, v.x, 0, p0.x],
+ [v.x, v.y, 0, p0.y],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]
+ ]).inverted()
+
+ def _make_nose(self, i, s, verts, faces, matids, uvs, nose_y):
+
+ t = self.t_step * i
+
+ # a matrix to project verts
+ # into uv space for horizontal parts of this step
+ # so uv = (rM * vertex).to_2d()
+ rM = self.get_proj_matrix(self, t, nose_y)
+
+ if self.z_mode == 'LINEAR':
+ return rM
+
+ f = len(verts)
+
+ tl = t - nose_y / self.get_length("LEFT")
+ tr = t - nose_y / self.get_length("RIGHT")
+
+ t2, part, dz, shape = self.get_part(tl, "LEFT")
+ p0 = part.lerp(t2)
+ self.p3d_left(verts, p0, s, t2)
+
+ t2, part, dz, shape = self.get_part(tr, "RIGHT")
+ p1 = part.lerp(t2)
+ self.p3d_right(verts, p1, s, t2)
+
+ start = 3
+ end = 6
+ offset = 10
+
+ # left, top, right
+ matids.extend([self.idmat_step_side,
+ self.idmat_top,
+ self.idmat_step_side])
+
+ faces += [(f + j, f + j + 1, f + j + offset + 1, f + j + offset) for j in range(start, end)]
+
+ u = nose_y
+ v = (p1 - p0).length
+ w = verts[f + 2][2] - verts[f + 3][2]
+ s = int((end - start) / 2)
+
+ uvs += [[(u, verts[f + j][2]), (u, verts[f + j + 1][2]),
+ (0, verts[f + j + 1][2]), (0, verts[f + j][2])] for j in range(start, start + s)]
+
+ uvs.append([(0, 0), (0, v), (u, v), (u, 0)])
+
+ uvs += [[(u, verts[f + j][2]), (u, verts[f + j + 1][2]),
+ (0, verts[f + j + 1][2]), (0, verts[f + j][2])] for j in range(start + s + 1, end)]
+
+ if 'STRAIGHT' in self.nose_type or 'OPEN' in self.steps_type:
+ # face bottom
+ matids.append(self.idmat_bottom)
+ faces.append((f + end, f + start, f + offset + start, f + offset + end))
+ uvs.append([(u, v), (u, 0), (0, 0), (0, v)])
+
+ if self.steps_type != 'OPEN':
+ if 'STRAIGHT' in self.nose_type:
+ # front face bottom straight
+ matids.append(self.idmat_raise)
+ faces.append((f + 12, f + 17, f + 16, f + 13))
+ uvs.append([(0, w), (v, w), (v, 0), (0, 0)])
+
+ elif 'OBLIQUE' in self.nose_type:
+ # front face bottom oblique
+ matids.append(self.idmat_raise)
+ faces.append((f + 12, f + 17, f + 6, f + 3))
+
+ uvs.append([(0, w), (v, w), (v, 0), (0, 0)])
+
+ matids.append(self.idmat_side)
+ faces.append((f + 3, f + 13, f + 12))
+ uvs.append([(0, 0), (u, 0), (u, w)])
+
+ matids.append(self.idmat_side)
+ faces.append((f + 6, f + 17, f + 16))
+ uvs.append([(0, 0), (u, w), (u, 0)])
+
+ # front face top
+ w = verts[f + 3][2] - verts[f + 4][2]
+ matids.append(self.idmat_step_front)
+ faces.append((f + 4, f + 3, f + 6, f + 5))
+ uvs.append([(0, 0), (0, w), (v, w), (v, 0)])
+ return rM
+
+ def make_faces(self, f, rM, verts, faces, matids, uvs):
+
+ if self.z_mode == 'LINEAR':
+ start = 0
+ end = 3
+ offset = 4
+ matids.extend([self.idmat_side,
+ self.idmat_top,
+ self.idmat_side,
+ self.idmat_bottom])
+ elif "OPEN" in self.steps_type:
+ # faces dessus-dessous-lateral marches fermees
+ start = 3
+ end = 6
+ offset = 10
+ matids.extend([self.idmat_step_side,
+ self.idmat_top,
+ self.idmat_step_side,
+ self.idmat_bottom])
+ else:
+ # faces dessus-dessous-lateral marches fermees
+ start = 0
+ end = 9
+ offset = 10
+ matids.extend([self.idmat_side,
+ self.idmat_side,
+ self.idmat_side,
+ self.idmat_step_side,
+ self.idmat_top,
+ self.idmat_step_side,
+ self.idmat_side,
+ self.idmat_side,
+ self.idmat_side,
+ self.idmat_bottom])
+
+ u_l0 = 0
+ u_l1 = self.t_step * self.left_length
+ u_r0 = 0
+ u_r1 = self.t_step * self.right_length
+
+ s = int((end - start) / 2)
+ uvs += [[(u_l0, verts[f + j][2]), (u_l0, verts[f + j + 1][2]),
+ (u_l1, verts[f + j + offset + 1][2]), (u_l1, verts[f + j + offset][2])] for j in range(start, start + s)]
+
+ self.project_uv(rM, uvs, verts, [f + start + s, f + start + s + 1,
+ f + start + s + offset + 1, f + start + s + offset])
+
+ uvs += [[(u_r0, verts[f + j][2]), (u_r0, verts[f + j + 1][2]),
+ (u_r1, verts[f + j + offset + 1][2]), (u_r1, verts[f + j + offset][2])] for j in range(start + s + 1, end)]
+
+ self.project_uv(rM, uvs, verts, [f + end, f + start, f + offset + start, f + offset + end])
+
+ faces += [(f + j, f + j + 1, f + j + offset + 1, f + j + offset) for j in range(start, end)]
+ faces.append((f + end, f + start, f + offset + start, f + offset + end))
+
+
+class StraightStair(Stair, Line):
+ def __init__(self, p, v, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z):
+ Stair.__init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z)
+ Line.__init__(self, p, v)
+ self.l_line = self.offset(-left_offset)
+ self.r_line = self.offset(right_offset)
+
+ def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
+
+ rM = self._make_nose(i, i, verts, faces, matids, uvs, nose_y)
+
+ t0 = self.t_step * i
+
+ f = len(verts)
+
+ p = self.l_line.lerp(t0)
+ self.p3d_left(verts, p, i, t0)
+ p = self.r_line.lerp(t0)
+ self.p3d_right(verts, p, i, t0)
+
+ t1 = t0 + self.t_step
+
+ p = self.l_line.lerp(t1)
+ self.p3d_left(verts, p, i, t1)
+ p = self.r_line.lerp(t1)
+ self.p3d_right(verts, p, i, t1)
+
+ self.make_faces(f, rM, verts, faces, matids, uvs)
+
+ if "OPEN" in self.steps_type:
+ faces.append((f + 13, f + 14, f + 15, f + 16))
+ matids.append(self.idmat_step_front)
+ uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])
+
+ def get_length(self, side):
+ return self.length
+
+ def get_lerp_vect(self, posts, side, i, t_step, respect_edges, z_offset=0, t0_abs=None):
+ if t0_abs is not None:
+ t0 = t0_abs
+ else:
+ t0 = i * t_step
+ t, part, dz, shape = self.get_part(t0, side)
+ dz /= part.length
+ n = part.normal(t)
+ z0 = self.get_z(t0, 'STEP')
+ z1 = self.get_z(t0, 'LINEAR')
+ posts.append((n, dz, z0, z1 + t0 * z_offset))
+ return [t0]
+
+ def n_posts(self, post_spacing, side, respect_edges):
+ return self.steps(post_spacing)
+
+ def get_part(self, t, side):
+ if side == 'LEFT':
+ part = self.l_line
+ else:
+ part = self.r_line
+ return t, part, self.height, 'LINE'
+
+
+class CurvedStair(Stair, Arc):
+ def __init__(self, c, radius, a0, da, left_offset, right_offset, steps_type, nose_type,
+ z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=pi):
+
+ Stair.__init__(self, left_offset, right_offset, steps_type, nose_type, z_mode, nose_z, bottom_z)
+ Arc.__init__(self, c, radius, a0, da)
+ self.l_shape = left_shape
+ self.r_shape = right_shape
+ self.edges_multiples = round(abs(da), 6) > double_limit
+ # left arc, tangeant at start and end
+ self.l_arc, self.l_t0, self.l_t1, self.l_tc = self.set_offset(-left_offset, left_shape)
+ self.r_arc, self.r_t0, self.r_t1, self.r_tc = self.set_offset(right_offset, right_shape)
+
+ def set_offset(self, offset, shape):
+ arc = self.offset(offset)
+ t0 = arc.tangeant(0, 1)
+ t1 = arc.tangeant(1, 1)
+ tc = arc.tangeant(0.5, 1)
+ if self.edges_multiples:
+ i, p, t = t0.intersect(tc)
+ tc.v *= 2 * t
+ tc.p = p
+ i, p, t2 = tc.intersect(t1)
+ else:
+ i, p, t = t0.intersect(t1)
+ t0.v *= t
+ t1.p = p
+ t1.v *= t
+ return arc, t0, t1, tc
+
+ def get_length(self, side):
+ if side == 'RIGHT':
+ arc = self.r_arc
+ shape = self.r_shape
+ t0 = self.r_t0
+ else:
+ arc = self.l_arc
+ shape = self.l_shape
+ t0 = self.l_t0
+ if shape == 'CIRCLE':
+ return arc.length
+ else:
+ if self.edges_multiples:
+ # two edges
+ return t0.length * 4
+ else:
+ return t0.length * 2
+
+ def _make_step(self, t_step, i, s, verts, landing=False):
+
+ tb = t_step * i
+
+ f = len(verts)
+
+ t, part, dz, shape = self.get_part(tb, "LEFT")
+ p = part.lerp(t)
+ self.p3d_left(verts, p, s, tb, landing)
+
+ t, part, dz, shape = self.get_part(tb, "RIGHT")
+ p = part.lerp(t)
+ self.p3d_right(verts, p, s, tb, landing)
+ return f
+
+ def _make_edge(self, t_step, i, j, f, rM, verts, faces, matids, uvs):
+ tb = t_step * i
+ # make edges verts after regular ones
+ if self.l_shape != 'CIRCLE' or self.r_shape != 'CIRCLE':
+ if self.edges_multiples:
+ # edge 1
+ if tb < 0.25 and tb + t_step > 0.25:
+ f0 = f
+ f = len(verts)
+ if self.l_shape == 'CIRCLE':
+ self.p3d_left(verts, self.l_arc.lerp(0.25), j, 0.25)
+ else:
+ self.p3d_left(verts, self.l_tc.p, j, 0.25)
+ if self.r_shape == 'CIRCLE':
+ self.p3d_right(verts, self.r_arc.lerp(0.25), j, 0.25)
+ else:
+ self.p3d_right(verts, self.r_tc.p, j, 0.25)
+ self.make_faces(f0, rM, verts, faces, matids, uvs)
+ # edge 2
+ if tb < 0.75 and tb + t_step > 0.75:
+ f0 = f
+ f = len(verts)
+ if self.l_shape == 'CIRCLE':
+ self.p3d_left(verts, self.l_arc.lerp(0.75), j, 0.75)
+ else:
+ self.p3d_left(verts, self.l_t1.p, j, 0.75)
+ if self.r_shape == 'CIRCLE':
+ self.p3d_right(verts, self.r_arc.lerp(0.75), j, 0.75)
+ else:
+ self.p3d_right(verts, self.r_t1.p, j, 0.75)
+ self.make_faces(f0, rM, verts, faces, matids, uvs)
+ else:
+ if tb < 0.5 and tb + t_step > 0.5:
+ f0 = f
+ f = len(verts)
+ # the step goes through the edge
+ if self.l_shape == 'CIRCLE':
+ self.p3d_left(verts, self.l_arc.lerp(0.5), j, 0.5)
+ else:
+ self.p3d_left(verts, self.l_t1.p, j, 0.5)
+ if self.r_shape == 'CIRCLE':
+ self.p3d_right(verts, self.r_arc.lerp(0.5), j, 0.5)
+ else:
+ self.p3d_right(verts, self.r_t1.p, j, 0.5)
+ self.make_faces(f0, rM, verts, faces, matids, uvs)
+ return f
+
+ def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
+
+ # open stair with closed face
+
+ # step nose
+ rM = self._make_nose(i, i, verts, faces, matids, uvs, nose_y)
+ f = 0
+ if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
+ # every 6 degree
+ n_subs = max(1, int(abs(self.da) / pi * 30 / self.n_step))
+ t_step = self.t_step / n_subs
+ for j in range(n_subs):
+ f0 = f
+ f = self._make_step(t_step, n_subs * i + j, i, verts)
+ if j > 0:
+ self.make_faces(f0, rM, verts, faces, matids, uvs)
+ f = self._make_edge(t_step, n_subs * i + j, i, f, rM, verts, faces, matids, uvs)
+ else:
+ f = self._make_step(self.t_step, i, i, verts)
+ f = self._make_edge(self.t_step, i, i, f, rM, verts, faces, matids, uvs)
+
+ self._make_step(self.t_step, i + 1, i, verts)
+ self.make_faces(f, rM, verts, faces, matids, uvs)
+
+ if "OPEN" in self.steps_type and self.z_mode != 'LINEAR':
+ # back face top
+ faces.append((f + 13, f + 14, f + 15, f + 16))
+ matids.append(self.idmat_step_front)
+ uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])
+
+ def get_part(self, t, side):
+ if side == 'RIGHT':
+ arc = self.r_arc
+ shape = self.r_shape
+ t0, t1, tc = self.r_t0, self.r_t1, self.r_tc
+ else:
+ arc = self.l_arc
+ shape = self.l_shape
+ t0, t1, tc = self.l_t0, self.l_t1, self.l_tc
+ if shape == 'CIRCLE':
+ return t, arc, self.height, shape
+ else:
+ if self.edges_multiples:
+ # two edges
+ if t <= 0.25:
+ return 4 * t, t0, 0.25 * self.height, shape
+ elif t <= 0.75:
+ return 2 * (t - 0.25), tc, 0.5 * self.height, shape
+ else:
+ return 4 * (t - 0.75), t1, 0.25 * self.height, shape
+ else:
+ if t <= 0.5:
+ return 2 * t, t0, 0.5 * self.height, shape
+ else:
+ return 2 * (t - 0.5), t1, 0.5 * self.height, shape
+
+ def get_lerp_vect(self, posts, side, i, t_step, respect_edges, z_offset=0, t0_abs=None):
+ if t0_abs is not None:
+ t0 = t0_abs
+ else:
+ t0 = i * t_step
+ res = [t0]
+ t1 = t0 + t_step
+ zs = self.get_z(t0, 'STEP')
+ zl = self.get_z(t0, 'LINEAR')
+
+ # vect normal
+ t, part, dz, shape = self.get_part(t0, side)
+ n = part.normal(t)
+ dz /= part.length
+ posts.append((n, dz, zs, zl + t0 * z_offset))
+
+ if shape != 'CIRCLE' and respect_edges:
+ if self.edges_multiples:
+ if t0 < 0.25 and t1 > 0.25:
+ zs = self.get_z(0.25, 'STEP')
+ zl = self.get_z(0.25, 'LINEAR')
+ t, part, dz, shape = self.get_part(0.25, side)
+ n = part.normal(1)
+ posts.append((n, dz, zs, zl + 0.25 * z_offset))
+ res.append(0.25)
+ if t0 < 0.75 and t1 > 0.75:
+ zs = self.get_z(0.75, 'STEP')
+ zl = self.get_z(0.75, 'LINEAR')
+ t, part, dz, shape = self.get_part(0.75, side)
+ n = part.normal(1)
+ posts.append((n, dz, zs, zl + 0.75 * z_offset))
+ res.append(0.75)
+ elif t0 < 0.5 and t1 > 0.5:
+ zs = self.get_z(0.5, 'STEP')
+ zl = self.get_z(0.5, 'LINEAR')
+ t, part, dz, shape = self.get_part(0.5, side)
+ n = part.normal(1)
+ posts.append((n, dz, zs, zl + 0.5 * z_offset))
+ res.append(0.5)
+ return res
+
+ def n_posts(self, post_spacing, side, respect_edges):
+ if side == 'LEFT':
+ arc, t0, shape = self.l_arc, self.l_t0, self.l_shape
+ else:
+ arc, t0, shape = self.r_arc, self.r_t0, self.r_shape
+ step_factor = 1
+ if shape == 'CIRCLE':
+ length = arc.length
+ else:
+ if self.edges_multiples:
+ if respect_edges:
+ step_factor = 2
+ length = 4 * t0.length
+ else:
+ length = 2 * t0.length
+ steps = step_factor * max(1, round(length / post_spacing, 0))
+ # print("respect_edges:%s t_step:%s n_step:%s" % (respect_edges, 1.0 / steps, int(steps)))
+ return 1.0 / steps, int(steps)
+
+
+class StraightLanding(StraightStair):
+ def __init__(self, p, v, left_offset, right_offset, steps_type,
+ nose_type, z_mode, nose_z, bottom_z, last_type='STAIR'):
+
+ StraightStair.__init__(self, p, v, left_offset, right_offset, steps_type,
+ nose_type, z_mode, nose_z, bottom_z)
+
+ self.last_type = last_type
+
+ @property
+ def height(self):
+ return 0
+
+ @property
+ def top_offset(self):
+ return self.t_step / self.v.length
+
+ @property
+ def top(self):
+ if self.next_type == 'LANDING':
+ return self.z0
+ else:
+ return self.z0 + self.step_height
+
+ def step_size(self, step_depth):
+ self.n_step = 1
+ self.t_step = 1
+ self.step_depth = step_depth
+ if self.last_type == 'LANDING':
+ return 0
+ else:
+ return 1
+
+ def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
+
+ if i == 0 and self.last_type != 'LANDING':
+ rM = self._make_nose(i, 0, verts, faces, matids, uvs, nose_y)
+ else:
+ rM = self.get_proj_matrix(self.l_line, self.t_step * i, nose_y)
+
+ f = len(verts)
+ j = 0
+ t0 = self.t_step * i
+
+ p = self.l_line.lerp(t0)
+ self.p3d_left(verts, p, j, t0)
+
+ p = self.r_line.lerp(t0)
+ self.p3d_right(verts, p, j, t0)
+
+ t1 = t0 + self.t_step
+ p = self.l_line.lerp(t1)
+ self.p3d_left(verts, p, j, t1, self.next_type != 'LANDING')
+
+ p = self.r_line.lerp(t1)
+ self.p3d_right(verts, p, j, t1, self.next_type != 'LANDING')
+
+ self.make_faces(f, rM, verts, faces, matids, uvs)
+
+ if "OPEN" in self.steps_type and self.next_type != 'LANDING':
+ faces.append((f + 13, f + 14, f + 15, f + 16))
+ matids.append(self.idmat_step_front)
+ uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])
+
+ def straight_landing(self, length):
+ return Stair.straight_landing(self, length, last_type='LANDING')
+
+ def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi):
+ return Stair.curved_landing(self, da, radius, left_shape,
+ right_shape, double_limit=double_limit, last_type='LANDING')
+
+ def get_z(self, t, mode):
+ if mode == 'STEP':
+ return self.z0 + self.step_height
+ else:
+ return self.z0
+
+
+class CurvedLanding(CurvedStair):
+ def __init__(self, c, radius, a0, da, left_offset, right_offset, steps_type,
+ nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=pi, last_type='STAIR'):
+
+ CurvedStair.__init__(self, c, radius, a0, da, left_offset, right_offset, steps_type,
+ nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=double_limit)
+
+ self.last_type = last_type
+
+ @property
+ def top_offset(self):
+ if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
+ return self.t_step / self.step_depth
+ else:
+ if self.edges_multiples:
+ return 0.5 / self.length
+ else:
+ return 1 / self.length
+
+ @property
+ def height(self):
+ return 0
+
+ @property
+ def top(self):
+ if self.next_type == 'LANDING':
+ return self.z0
+ else:
+ return self.z0 + self.step_height
+
+ def step_size(self, step_depth):
+ if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
+ t_step, n_step = self.steps(step_depth)
+ else:
+ if self.edges_multiples:
+ t_step, n_step = 0.5, 2
+ else:
+ t_step, n_step = 1, 1
+ self.n_step = n_step
+ self.t_step = t_step
+ self.step_depth = step_depth
+ if self.last_type == 'LANDING':
+ return 0
+ else:
+ return 1
+
+ def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
+
+ if i == 0 and 'LANDING' not in self.last_type:
+ rM = self._make_nose(i, 0, verts, faces, matids, uvs, nose_y)
+ else:
+ rM = self.get_proj_matrix(self.l_arc, self.t_step * i, nose_y)
+
+ f = len(verts)
+
+ if self.l_shape == 'CIRCLE' or self.r_shape == 'CIRCLE':
+ n_subs = max(1, int(abs(self.da / pi * 30 / self.n_step)))
+ t_step = self.t_step / n_subs
+ for j in range(n_subs):
+ f0 = f
+ f = self._make_step(t_step, n_subs * i + j, 0, verts)
+ if j > 0:
+ self.make_faces(f0, rM, verts, faces, matids, uvs)
+ f = self._make_edge(t_step, n_subs * i + j, 0, f, rM, verts, faces, matids, uvs)
+ else:
+ f = self._make_step(self.t_step, i, 0, verts)
+ f = self._make_edge(self.t_step, i, 0, f, rM, verts, faces, matids, uvs)
+
+ self._make_step(self.t_step, i + 1, 0, verts, i == self.n_step - 1 and 'LANDING' not in self.next_type)
+ self.make_faces(f, rM, verts, faces, matids, uvs)
+
+ if "OPEN" in self.steps_type and 'LANDING' not in self.next_type:
+ faces.append((f + 13, f + 14, f + 15, f + 16))
+ matids.append(self.idmat_step_front)
+ uvs.append([(0, 0), (0, 1), (1, 1), (1, 0)])
+
+ def straight_landing(self, length):
+ return Stair.straight_landing(self, length, last_type='LANDING')
+
+ def curved_landing(self, da, radius, left_shape, right_shape, double_limit=pi):
+ return Stair.curved_landing(self, da, radius, left_shape,
+ right_shape, double_limit=double_limit, last_type='LANDING')
+
+ def get_z(self, t, mode):
+ if mode == 'STEP':
+ return self.z0 + self.step_height
+ else:
+ return self.z0
+
+
+class StairGenerator():
+ def __init__(self, parts):
+ self.parts = parts
+ self.last_type = 'NONE'
+ self.stairs = []
+ self.steps_type = 'NONE'
+ self.sum_da = 0
+ self.user_defined_post = None
+ self.user_defined_uvs = None
+ self.user_defined_mat = None
+
+ def add_part(self, type, steps_type, nose_type, z_mode, nose_z, bottom_z, center,
+ radius, da, width_left, width_right, length, left_shape, right_shape):
+
+ self.steps_type = steps_type
+ if len(self.stairs) < 1:
+ s = None
+ else:
+ s = self.stairs[-1]
+
+ if "S_" not in type:
+ self.sum_da += da
+
+ # start a new stair
+ if s is None:
+ if type == 'S_STAIR':
+ p = Vector((0, 0))
+ v = Vector((0, length))
+ s = StraightStair(p, v, width_left, width_right, steps_type, nose_type, z_mode, nose_z, bottom_z)
+ elif type == 'C_STAIR':
+ if da < 0:
+ c = Vector((radius, 0))
+ else:
+ c = Vector((-radius, 0))
+ s = CurvedStair(c, radius, 0, da, width_left, width_right, steps_type,
+ nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape)
+ elif type == 'D_STAIR':
+ if da < 0:
+ c = Vector((radius, 0))
+ else:
+ c = Vector((-radius, 0))
+ s = CurvedStair(c, radius, 0, da, width_left, width_right, steps_type,
+ nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=0)
+ elif type == 'S_LANDING':
+ p = Vector((0, 0))
+ v = Vector((0, length))
+ s = StraightLanding(p, v, width_left, width_right, steps_type, nose_type, z_mode, nose_z, bottom_z)
+ elif type == 'C_LANDING':
+ if da < 0:
+ c = Vector((radius, 0))
+ else:
+ c = Vector((-radius, 0))
+ s = CurvedLanding(c, radius, 0, da, width_left, width_right, steps_type,
+ nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape)
+ elif type == 'D_LANDING':
+ if da < 0:
+ c = Vector((radius, 0))
+ else:
+ c = Vector((-radius, 0))
+ s = CurvedLanding(c, radius, 0, da, width_left, width_right, steps_type,
+ nose_type, z_mode, nose_z, bottom_z, left_shape, right_shape, double_limit=0)
+ else:
+ if type == 'S_STAIR':
+ s = s.straight_stair(length)
+ elif type == 'C_STAIR':
+ s = s.curved_stair(da, radius, left_shape, right_shape)
+ elif type == 'D_STAIR':
+ s = s.curved_stair(da, radius, left_shape, right_shape, double_limit=0)
+ elif type == 'S_LANDING':
+ s = s.straight_landing(length)
+ elif type == 'C_LANDING':
+ s = s.curved_landing(da, radius, left_shape, right_shape)
+ elif type == 'D_LANDING':
+ s = s.curved_landing(da, radius, left_shape, right_shape, double_limit=0)
+ self.stairs.append(s)
+ self.last_type = type
+
+ def n_steps(self, step_depth):
+ n_steps = 0
+ for stair in self.stairs:
+ n_steps += stair.step_size(step_depth)
+ return n_steps
+
+ def set_height(self, step_height):
+ z = 0
+ for stair in self.stairs:
+ stair.set_height(step_height, z)
+ z = stair.top
+
+ def make_stair(self, height, step_depth, verts, faces, matids, uvs, nose_y=0):
+ n_steps = self.n_steps(step_depth)
+ self.set_height(height / n_steps)
+
+ for s, stair in enumerate(self.stairs):
+ if s < len(self.parts):
+ manipulator = self.parts[s].manipulators[0]
+ # Store Gl Points for manipulators
+ if 'Curved' in type(stair).__name__:
+ c = stair.c
+ p0 = (stair.p0 - c).to_3d()
+ p1 = (stair.p1 - c).to_3d()
+ manipulator.set_pts([(c.x, c.y, stair.top), p0, p1])
+ manipulator.type_key = 'ARC_ANGLE_RADIUS'
+ manipulator.prop1_name = 'da'
+ manipulator.prop2_name = 'radius'
+ else:
+ if self.sum_da > 0:
+ side = 1
+ else:
+ side = -1
+ v0 = stair.p0
+ v1 = stair.p1
+ manipulator.set_pts([(v0.x, v0.y, stair.top), (v1.x, v1.y, stair.top), (side, 0, 0)])
+ manipulator.type_key = 'SIZE'
+ manipulator.prop1_name = 'length'
+
+ for i in range(stair.n_step):
+ stair.make_step(i, verts, faces, matids, uvs, nose_y=nose_y)
+ if s < len(self.stairs) - 1 and self.steps_type != 'OPEN' and \
+ 'Landing' in type(stair).__name__ and stair.next_type != "LANDING":
+ f = len(verts) - 10
+ faces.append((f, f + 1, f + 8, f + 9))
+ matids.append(self.stairs[-1].idmat_bottom)
+ u = verts[f + 1][2] - verts[f][2]
+ v = (Vector(verts[f]) - Vector(verts[f + 9])).length
+ uvs.append([(0, 0), (0, u), (v, u), (v, 0)])
+
+ if self.steps_type != 'OPEN' and len(self.stairs) > 0:
+ f = len(verts) - 10
+ faces.append((f, f + 1, f + 2, f + 3, f + 4, f + 5, f + 6, f + 7, f + 8, f + 9))
+ matids.append(self.stairs[-1].idmat_bottom)
+ uvs.append([(0, 0), (.1, 0), (.2, 0), (.3, 0), (.4, 0), (.4, 1), (.3, 1), (.2, 1), (.1, 1), (0, 1)])
+
+ def setup_user_defined_post(self, o, post_x, post_y, post_z):
+ self.user_defined_post = o
+ x = o.bound_box[6][0] - o.bound_box[0][0]
+ y = o.bound_box[6][1] - o.bound_box[0][1]
+ z = o.bound_box[6][2] - o.bound_box[0][2]
+ self.user_defined_post_scale = Vector((post_x / x, post_y / -y, post_z / z))
+ m = o.data
+ # create vertex group lookup dictionary for names
+ vgroup_names = {vgroup.index: vgroup.name for vgroup in o.vertex_groups}
+ # create dictionary of vertex group assignments per vertex
+ self.vertex_groups = [[vgroup_names[g.group] for g in v.groups] for v in m.vertices]
+ # uvs
+ uv_act = m.uv_layers.active
+ if uv_act is not None:
+ uv_layer = uv_act.data
+ self.user_defined_uvs = [[uv_layer[li].uv for li in p.loop_indices] for p in m.polygons]
+ else:
+ self.user_defined_uvs = [[(0, 0) for i in p.vertices] for p in m.polygons]
+ # material ids
+ self.user_defined_mat = [p.material_index for p in m.polygons]
+
+ def get_user_defined_post(self, tM, z0, z1, z2, slope, post_z, verts, faces, matids, uvs):
+ f = len(verts)
+ m = self.user_defined_post.data
+ for i, g in enumerate(self.vertex_groups):
+ co = m.vertices[i].co.copy()
+ co.x *= self.user_defined_post_scale.x
+ co.y *= self.user_defined_post_scale.y
+ co.z *= self.user_defined_post_scale.z
+ if 'Top' in g:
+ co.z += z2
+ elif 'Bottom' in g:
+ co.z += 0
+ else:
+ co.z += z1
+ if 'Slope' in g:
+ co.z += co.y * slope
+ verts.append(tM * co)
+ matids += self.user_defined_mat
+ faces += [tuple([i + f for i in p.vertices]) for p in m.polygons]
+ uvs += self.user_defined_uvs
+
+ def get_post(self, post, post_x, post_y, post_z, post_alt, sub_offset_x,
+ id_mat, verts, faces, matids, uvs, bottom="STEP"):
+
+ n, dz, zs, zl = post
+ slope = dz * post_y
+
+ if self.user_defined_post is not None:
+ if bottom == "STEP":
+ z0 = zs
+ else:
+ z0 = zl
+ z1 = zl - z0
+ z2 = zl - z0
+ x, y = -n.v.normalized()
+ tM = Matrix([
+ [x, y, 0, n.p.x],
+ [y, -x, 0, n.p.y],
+ [0, 0, 1, z0 + post_alt],
+ [0, 0, 0, 1]
+ ])
+ self.get_user_defined_post(tM, z0, z1, z2, dz, post_z, verts, faces, matids, uvs)
+ return
+
+ z3 = zl + post_z + post_alt - slope
+ z4 = zl + post_z + post_alt + slope
+ if bottom == "STEP":
+ z0 = zs + post_alt
+ z1 = zs + post_alt
+ else:
+ z0 = zl + post_alt - slope
+ z1 = zl + post_alt + slope
+ vn = n.v.normalized()
+ dx = post_x * vn
+ dy = post_y * Vector((vn.y, -vn.x))
+ oy = sub_offset_x * vn
+ x0, y0 = n.p - dx + dy + oy
+ x1, y1 = n.p - dx - dy + oy
+ x2, y2 = n.p + dx - dy + oy
+ x3, y3 = n.p + dx + dy + oy
+ f = len(verts)
+ verts.extend([(x0, y0, z0), (x0, y0, z3),
+ (x1, y1, z1), (x1, y1, z4),
+ (x2, y2, z1), (x2, y2, z4),
+ (x3, y3, z0), (x3, y3, z3)])
+ faces.extend([(f, f + 1, f + 3, f + 2),
+ (f + 2, f + 3, f + 5, f + 4),
+ (f + 4, f + 5, f + 7, f + 6),
+ (f + 6, f + 7, f + 1, f),
+ (f, f + 2, f + 4, f + 6),
+ (f + 7, f + 5, f + 3, f + 1)])
+ matids.extend([id_mat, id_mat, id_mat, id_mat, id_mat, id_mat])
+ x = [(0, 0), (0, post_z), (post_x, post_z), (post_x, 0)]
+ y = [(0, 0), (0, post_z), (post_y, post_z), (post_y, 0)]
+ z = [(0, 0), (post_x, 0), (post_x, post_y), (0, post_y)]
+ uvs.extend([x, y, x, y, z, z])
+
+ def get_panel(self, subs, altitude, panel_x, panel_z, sub_offset_x, idmat, verts, faces, matids, uvs):
+ n_subs = len(subs)
+ if n_subs < 1:
+ return
+ f = len(verts)
+ x0 = sub_offset_x - 0.5 * panel_x
+ x1 = sub_offset_x + 0.5 * panel_x
+ z0 = 0
+ z1 = panel_z
+ profile = [Vector((x0, z0)), Vector((x1, z0)), Vector((x1, z1)), Vector((x0, z1))]
+ user_path_uv_v = []
+ n_sections = n_subs - 1
+ n, dz, zs, zl = subs[0]
+ p0 = n.p
+ v0 = n.v.normalized()
+ for s, section in enumerate(subs):
+ n, dz, zs, zl = section
+ p1 = n.p
+ if s < n_sections:
+ v1 = subs[s + 1][0].v.normalized()
+ dir = (v0 + v1).normalized()
+ scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1))))
+ for p in profile:
+ x, y = n.p + scale * p.x * dir
+ z = zl + p.y + altitude
+ verts.append((x, y, z))
+ if s > 0:
+ user_path_uv_v.append((p1 - p0).length)
+ p0 = p1
+ v0 = v1
+
+ # build faces using Panel
+ lofter = Lofter(
+ # closed_shape, index, x, y, idmat
+ True,
+ [i for i in range(len(profile))],
+ [p.x for p in profile],
+ [p.y for p in profile],
+ [idmat for i in range(len(profile))],
+ closed_path=False,
+ user_path_uv_v=user_path_uv_v,
+ user_path_verts=n_subs
+ )
+ faces += lofter.faces(16, offset=f, path_type='USER_DEFINED')
+ matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED')
+ v = Vector((0, 0))
+ uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED')
+
+ def reset_shapes(self):
+ for s, stair in enumerate(self.stairs):
+ if 'Curved' in type(stair).__name__:
+ stair.l_shape = self.parts[s].left_shape
+ stair.r_shape = self.parts[s].right_shape
+
+ def make_subs(self, height, step_depth, x, y, z, post_y, altitude, bottom, side, slice,
+ post_spacing, sub_spacing, respect_edges, move_x, x_offset, sub_offset_x, mat,
+ verts, faces, matids, uvs):
+
+ n_steps = self.n_steps(step_depth)
+ self.set_height(height / n_steps)
+ n_stairs = len(self.stairs) - 1
+ subs = []
+
+ if side == "LEFT":
+ offset = move_x - x_offset
+ # offset_sub = offset - sub_offset_x
+ else:
+ offset = move_x + x_offset
+ # offset_sub = offset + sub_offset_x
+
+ for s, stair in enumerate(self.stairs):
+ if 'Curved' in type(stair).__name__:
+ if side == "LEFT":
+ part = stair.l_arc
+ shape = stair.l_shape
+ else:
+ part = stair.r_arc
+ shape = stair.r_shape
+ # Note: use left part as reference for post distances
+ # use right part as reference for panels
+ stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(offset, shape)
+ stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(offset, shape)
+ else:
+ stair.l_line = stair.offset(offset)
+ stair.r_line = stair.offset(offset)
+ part = stair.l_line
+
+ lerp_z = 0
+ edge_t = 1
+ edge_size = 0
+ # interpolate z near end landing
+ if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR':
+ if not slice:
+ line = stair.normal(1).offset(self.stairs[s + 1].step_depth)
+ res, p, t_part = part.intersect(line)
+ # does perpendicular line intersects circle ?
+ if res:
+ edge_size = self.stairs[s + 1].step_depth / stair.get_length(side)
+ edge_t = 1 - edge_size
+ else:
+ # in this case, lerp z over one step
+ lerp_z = stair.step_height
+
+ t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)
+
+ # space between posts
+ sp = stair.get_length(side)
+ # post size
+ t_post = post_y / sp
+
+ if s == n_stairs:
+ n_step += 1
+ for i in range(n_step):
+ res_t = stair.get_lerp_vect([], side, i, t_step, respect_edges)
+ # subs
+ if s < n_stairs or i < n_step - 1:
+ res_t.append((i + 1) * t_step)
+ for j in range(len(res_t) - 1):
+ t0 = res_t[j] + t_post
+ t1 = res_t[j + 1] - t_post
+ dt = t1 - t0
+ n_subs = int(sp * dt / sub_spacing)
+ if n_subs > 0:
+ t_subs = dt / n_subs
+ for k in range(1, n_subs):
+ t = t0 + k * t_subs
+ stair.get_lerp_vect(subs, side, 1, t0 + k * t_subs, False)
+ if t > edge_t:
+ n, dz, z0, z1 = subs[-1]
+ subs[-1] = n, dz, z0, z1 + (t - edge_t) / edge_size * stair.step_height
+ if lerp_z > 0:
+ n, dz, z0, z1 = subs[-1]
+ subs[-1] = n, dz, z0, z1 + t * stair.step_height
+
+ for i, post in enumerate(subs):
+ self.get_post(post, x, y, z, altitude, sub_offset_x, mat, verts, faces, matids, uvs, bottom=bottom)
+
+ def make_post(self, height, step_depth, x, y, z, altitude, side, post_spacing, respect_edges, move_x, x_offset, mat,
+ verts, faces, matids, uvs):
+ n_steps = self.n_steps(step_depth)
+ self.set_height(height / n_steps)
+ l_posts = []
+ n_stairs = len(self.stairs) - 1
+
+ for s, stair in enumerate(self.stairs):
+ if type(stair).__name__ in ['CurvedStair', 'CurvedLanding']:
+ stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(move_x - x_offset, stair.l_shape)
+ stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(move_x + x_offset, stair.r_shape)
+ else:
+ stair.l_line = stair.offset(move_x - x_offset)
+ stair.r_line = stair.offset(move_x + x_offset)
+
+ t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)
+
+ if s == n_stairs:
+ n_step += 1
+ for i in range(n_step):
+ stair.get_lerp_vect(l_posts, side, i, t_step, respect_edges)
+
+ if s == n_stairs and i == n_step - 1:
+ n, dz, z0, z1 = l_posts[-1]
+ l_posts[-1] = (n, dz, z0 - stair.step_height, z1)
+
+ for i, post in enumerate(l_posts):
+ self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs)
+
+ def make_panels(self, height, step_depth, x, z, post_y, altitude, side, post_spacing,
+ panel_dist, respect_edges, move_x, x_offset, sub_offset_x, mat, verts, faces, matids, uvs):
+
+ n_steps = self.n_steps(step_depth)
+ self.set_height(height / n_steps)
+ subs = []
+ n_stairs = len(self.stairs) - 1
+
+ if side == "LEFT":
+ offset = move_x - x_offset
+ else:
+ offset = move_x + x_offset
+
+ for s, stair in enumerate(self.stairs):
+
+ is_circle = False
+ if 'Curved' in type(stair).__name__:
+ if side == "LEFT":
+ is_circle = stair.l_shape == "CIRCLE"
+ shape = stair.l_shape
+ else:
+ is_circle = stair.r_shape == "CIRCLE"
+ shape = stair.r_shape
+ stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(offset, shape)
+ stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(offset, shape)
+ else:
+ stair.l_line = stair.offset(offset)
+ stair.r_line = stair.offset(offset)
+
+ # space between posts
+ sp = stair.get_length(side)
+
+ t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)
+
+ if is_circle and 'Curved' in type(stair).__name__:
+ panel_da = abs(stair.da) / pi * 180 / n_step
+ panel_step = max(1, int(panel_da / 6))
+ else:
+ panel_step = 1
+
+ # post size
+ t_post = (post_y + panel_dist) / sp
+
+ if s == n_stairs:
+ n_step += 1
+ for i in range(n_step):
+ res_t = stair.get_lerp_vect([], side, i, t_step, respect_edges)
+ # subs
+ if s < n_stairs or i < n_step - 1:
+ res_t.append((i + 1) * t_step)
+ for j in range(len(res_t) - 1):
+ t0 = res_t[j] + t_post
+ t1 = res_t[j + 1] - t_post
+ dt = t1 - t0
+ t_curve = dt / panel_step
+ if dt > 0:
+ panel = []
+ for k in range(panel_step):
+ stair.get_lerp_vect(panel, side, 1, t_curve, True, t0_abs=t0 + k * t_curve)
+ stair.get_lerp_vect(panel, side, 1, t1, False)
+ subs.append(panel)
+ for sub in subs:
+ self.get_panel(sub, altitude, x, z, sub_offset_x, mat, verts, faces, matids, uvs)
+
+ def make_part(self, height, step_depth, part_x, part_z, x_move, x_offset,
+ z_offset, z_mode, steps_type, verts, faces, matids, uvs):
+
+ params = [(stair.z_mode, stair.l_shape, stair.r_shape,
+ stair.bottom_z, stair.steps_type) for stair in self.stairs]
+
+ for stair in self.stairs:
+ if x_offset > 0:
+ stair.l_shape = stair.r_shape
+ else:
+ stair.r_shape = stair.l_shape
+ stair.steps_type = steps_type
+ stair.z_mode = "LINEAR"
+ stair.bottom_z = part_z
+ if 'Curved' in type(stair).__name__:
+ stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = \
+ stair.set_offset(x_move + x_offset + 0.5 * part_x, stair.l_shape)
+ stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = \
+ stair.set_offset(x_move + x_offset - 0.5 * part_x, stair.r_shape)
+ else:
+ stair.l_line = stair.offset(x_move + x_offset + 0.5 * part_x)
+ stair.r_line = stair.offset(x_move + x_offset - 0.5 * part_x)
+ n_steps = self.n_steps(step_depth)
+ self.set_height(height / n_steps)
+ for j, stair in enumerate(self.stairs):
+ stair.z0 += z_offset + part_z
+ stair.n_step *= 2
+ stair.t_step /= 2
+ stair.step_height /= 2
+ for i in range(stair.n_step):
+ stair.make_step(i, verts, faces, matids, uvs, nose_y=0)
+ stair.n_step /= 2
+ stair.t_step *= 2
+ stair.step_height *= 2
+ stair.z_mode = params[j][0]
+ stair.l_shape = params[j][1]
+ stair.r_shape = params[j][2]
+ stair.bottom_z = params[j][3]
+ stair.steps_type = params[j][4]
+ stair.z0 -= z_offset + part_z
+
+ def make_profile(self, profile, idmat, side, slice, height, step_depth,
+ x_offset, z_offset, extend, verts, faces, matids, uvs):
+
+ for stair in self.stairs:
+ if 'Curved' in type(stair).__name__:
+ stair.l_arc, stair.l_t0, stair.l_t1, stair.l_tc = stair.set_offset(-x_offset, stair.l_shape)
+ stair.r_arc, stair.r_t0, stair.r_t1, stair.r_tc = stair.set_offset(x_offset, stair.r_shape)
+ else:
+ stair.l_line = stair.offset(-x_offset)
+ stair.r_line = stair.offset(x_offset)
+
+ n_steps = self.n_steps(step_depth)
+ self.set_height(height / n_steps)
+
+ n_stairs = len(self.stairs) - 1
+
+ if n_stairs < 0:
+ return
+
+ sections = []
+ sections.append([])
+
+ # first step
+ if extend != 0:
+ t = -extend / self.stairs[0].length
+ self.stairs[0].get_lerp_vect(sections[-1], side, 1, t, True)
+
+ for s, stair in enumerate(self.stairs):
+ n_step = 1
+ is_circle = False
+
+ if 'Curved' in type(stair).__name__:
+ if side == "LEFT":
+ part = stair.l_arc
+ is_circle = stair.l_shape == "CIRCLE"
+ else:
+ part = stair.r_arc
+ is_circle = stair.r_shape == "CIRCLE"
+ else:
+ if side == "LEFT":
+ part = stair.l_line
+ else:
+ part = stair.r_line
+
+ if is_circle:
+ n_step = 3 * stair.n_step
+
+ t_step = 1 / n_step
+
+ last_t = 1.0
+ do_last = True
+ lerp_z = 0
+ # last section 1 step before stair
+ if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR':
+ if not slice:
+ line = stair.normal(1).offset(self.stairs[s + 1].step_depth)
+ res, p, t_part = part.intersect(line)
+ # does perpendicular line intersects circle ?
+ if res:
+ last_t = 1 - self.stairs[s + 1].step_depth / stair.get_length(side)
+ if last_t < 0:
+ do_last = False
+ else:
+ # in this case, lerp z over one step
+ do_last = False
+ lerp_z = stair.step_height
+
+ if s == n_stairs:
+ n_step += 1
+
+ for i in range(n_step):
+ res_t = stair.get_lerp_vect(sections[-1], side, i, t_step, True, z_offset=lerp_z)
+ # remove corner section
+ for cur_t in res_t:
+ if cur_t > 0 and cur_t > last_t:
+ sections[-1] = sections[-1][:-1]
+
+ # last section 1 step before next stair start
+ if 'Landing' in type(stair).__name__ and stair.next_type == 'STAIR':
+ if do_last:
+ stair.get_lerp_vect(sections[-1], side, 1, last_t, False)
+ if slice:
+ sections.append([])
+ if extend > 0:
+ t = -extend / self.stairs[s + 1].length
+ self.stairs[s + 1].get_lerp_vect(sections[-1], side, 1, t, True)
+
+ t = 1 + extend / self.stairs[-1].length
+ self.stairs[-1].get_lerp_vect(sections[-1], side, 1, t, True)
+
+ for cur_sect in sections:
+ user_path_verts = len(cur_sect)
+ f = len(verts)
+ if user_path_verts > 0:
+ user_path_uv_v = []
+ n, dz, z0, z1 = cur_sect[-1]
+ cur_sect[-1] = (n, dz, z0 - stair.step_height, z1)
+ n_sections = user_path_verts - 1
+ n, dz, zs, zl = cur_sect[0]
+ p0 = n.p
+ v0 = n.v.normalized()
+ for s, section in enumerate(cur_sect):
+ n, dz, zs, zl = section
+ p1 = n.p
+ if s < n_sections:
+ v1 = cur_sect[s + 1][0].v.normalized()
+ dir = (v0 + v1).normalized()
+ scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1))))
+ for p in profile:
+ x, y = n.p + scale * p.x * dir
+ z = zl + p.y + z_offset
+ verts.append((x, y, z))
+ if s > 0:
+ user_path_uv_v.append((p1 - p0).length)
+ p0 = p1
+ v0 = v1
+
+ # build faces using Panel
+ lofter = Lofter(
+ # closed_shape, index, x, y, idmat
+ True,
+ [i for i in range(len(profile))],
+ [p.x for p in profile],
+ [p.y for p in profile],
+ [idmat for i in range(len(profile))],
+ closed_path=False,
+ user_path_uv_v=user_path_uv_v,
+ user_path_verts=user_path_verts
+ )
+ faces += lofter.faces(16, offset=f, path_type='USER_DEFINED')
+ matids += lofter.mat(16, idmat, idmat, path_type='USER_DEFINED')
+ v = Vector((0, 0))
+ uvs += lofter.uv(16, v, v, v, v, 0, v, 0, 0, path_type='USER_DEFINED')
+
+ def set_matids(self, id_materials):
+ for stair in self.stairs:
+ stair.set_matids(id_materials)
+
+
+def update(self, context):
+ self.update(context)
+
+
+def update_manipulators(self, context):
+ self.update(context, manipulable_refresh=True)
+
+
+def update_preset(self, context):
+ auto_update = self.auto_update
+ self.auto_update = False
+ if self.presets == 'STAIR_I':
+ self.n_parts = 1
+ self.update_parts()
+ self.parts[0].type = 'S_STAIR'
+ elif self.presets == 'STAIR_L':
+ self.n_parts = 3
+ self.update_parts()
+ self.parts[0].type = 'S_STAIR'
+ self.parts[1].type = 'C_STAIR'
+ self.parts[2].type = 'S_STAIR'
+ self.da = pi / 2
+ elif self.presets == 'STAIR_U':
+ self.n_parts = 3
+ self.update_parts()
+ self.parts[0].type = 'S_STAIR'
+ self.parts[1].type = 'D_STAIR'
+ self.parts[2].type = 'S_STAIR'
+ self.da = pi
+ elif self.presets == 'STAIR_O':
+ self.n_parts = 2
+ self.update_parts()
+ self.parts[0].type = 'D_STAIR'
+ self.parts[1].type = 'D_STAIR'
+ self.da = pi
+ # keep auto_update state same
+ # prevent unwanted load_preset update
+ self.auto_update = auto_update
+
+
+materials_enum = (
+ ('0', 'Ceiling', '', 0),
+ ('1', 'White', '', 1),
+ ('2', 'Concrete', '', 2),
+ ('3', 'Wood', '', 3),
+ ('4', 'Metal', '', 4),
+ ('5', 'Glass', '', 5)
+ )
+
+
+class archipack_stair_material(PropertyGroup):
+ index = EnumProperty(
+ items=materials_enum,
+ default='4',
+ update=update
+ )
+
+ def find_datablock_in_selection(self, context):
+ """
+ find witch selected object this instance belongs to
+ provide support for "copy to selected"
+ """
+ selected = [o for o in context.selected_objects]
+ for o in selected:
+ props = archipack_stair.datablock(o)
+ if props:
+ for part in props.rail_mat:
+ if part == self:
+ return props
+ return None
+
+ def update(self, context):
+ props = self.find_datablock_in_selection(context)
+ if props is not None:
+ props.update(context)
+
+
+class archipack_stair_part(PropertyGroup):
+ type = EnumProperty(
+ items=(
+ ('S_STAIR', 'Straight stair', '', 0),
+ ('C_STAIR', 'Curved stair', '', 1),
+ ('D_STAIR', 'Dual Curved stair', '', 2),
+ ('S_LANDING', 'Straight landing', '', 3),
+ ('C_LANDING', 'Curved landing', '', 4),
+ ('D_LANDING', 'Dual Curved landing', '', 5)
+ ),
+ default='S_STAIR',
+ update=update_manipulators
+ )
+ length = FloatProperty(
+ name="length",
+ min=0.5,
+ default=2.0,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ radius = FloatProperty(
+ name="radius",
+ min=0.5,
+ default=0.7,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ da = FloatProperty(
+ name="angle",
+ min=-pi,
+ max=pi,
+ default=pi / 2,
+ subtype='ANGLE', unit='ROTATION',
+ update=update
+ )
+ left_shape = EnumProperty(
+ items=(
+ ('RECTANGLE', 'Straight', '', 0),
+ ('CIRCLE', 'Curved ', '', 1)
+ ),
+ default='RECTANGLE',
+ update=update
+ )
+ right_shape = EnumProperty(
+ items=(
+ ('RECTANGLE', 'Straight', '', 0),
+ ('CIRCLE', 'Curved ', '', 1)
+ ),
+ default='RECTANGLE',
+ update=update
+ )
+ manipulators = CollectionProperty(type=archipack_manipulator)
+
+ def find_datablock_in_selection(self, context):
+ """
+ find witch selected object this instance belongs to
+ provide support for "copy to selected"
+ """
+ selected = [o for o in context.selected_objects]
+ for o in selected:
+ props = archipack_stair.datablock(o)
+ if props:
+ for part in props.parts:
+ if part == self:
+ return props
+ return None
+
+ def update(self, context, manipulable_refresh=False):
+ props = self.find_datablock_in_selection(context)
+ if props is not None:
+ props.update(context, manipulable_refresh)
+
+ def draw(self, layout, context, index, user_mode):
+ if user_mode:
+ box = layout.box()
+ row = box.row()
+ row.prop(self, "type", text=str(index + 1))
+ if self.type in ['C_STAIR', 'C_LANDING', 'D_STAIR', 'D_LANDING']:
+ row = box.row()
+ row.prop(self, "radius")
+ row = box.row()
+ row.prop(self, "da")
+ else:
+ row = box.row()
+ row.prop(self, "length")
+ if self.type in ['C_STAIR', 'C_LANDING', 'D_STAIR', 'D_LANDING']:
+ row = box.row(align=True)
+ row.prop(self, "left_shape", text="")
+ row.prop(self, "right_shape", text="")
+ else:
+ if self.type in ['S_STAIR', 'S_LANDING']:
+ box = layout.box()
+ row = box.row()
+ row.prop(self, "length")
+
+
+class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
+
+ parts = CollectionProperty(type=archipack_stair_part)
+ n_parts = IntProperty(
+ name="parts",
+ min=1,
+ max=32,
+ default=1, update=update_manipulators
+ )
+ step_depth = FloatProperty(
+ name="Going",
+ min=0.2,
+ default=0.25,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ width = FloatProperty(
+ name="width",
+ min=0.01,
+ default=1.2,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ height = FloatProperty(
+ name="Height",
+ min=0.1,
+ default=2.4, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ nose_y = FloatProperty(
+ name="Depth",
+ min=0.0,
+ default=0.02, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ x_offset = FloatProperty(
+ name="x offset",
+ default=0.0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ nose_z = FloatProperty(
+ name="Height",
+ min=0.001,
+ default=0.03, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ bottom_z = FloatProperty(
+ name="Stair bottom",
+ min=0.001,
+ default=0.03, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ radius = FloatProperty(
+ name="radius",
+ min=0.5,
+ default=0.7,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ da = FloatProperty(
+ name="angle",
+ min=-pi,
+ max=pi,
+ default=pi / 2,
+ subtype='ANGLE', unit='ROTATION',
+ update=update
+ )
+ total_angle = FloatProperty(
+ name="angle",
+ min=-50 * pi,
+ max=50 * pi,
+ default=2 * pi,
+ subtype='ANGLE', unit='ROTATION',
+ update=update
+ )
+ steps_type = EnumProperty(
+ name="Steps",
+ items=(
+ ('CLOSED', 'Closed', '', 0),
+ ('FULL', 'Full height', '', 1),
+ ('OPEN', 'Open ', '', 2)
+ ),
+ default='CLOSED',
+ update=update
+ )
+ nose_type = EnumProperty(
+ name="Nosing",
+ items=(
+ ('STRAIGHT', 'Straight', '', 0),
+ ('OBLIQUE', 'Oblique', '', 1),
+ ),
+ default='STRAIGHT',
+ update=update
+ )
+ left_shape = EnumProperty(
+ items=(
+ ('RECTANGLE', 'Straight', '', 0),
+ ('CIRCLE', 'Curved ', '', 1)
+ ),
+ default='RECTANGLE',
+ update=update
+ )
+ right_shape = EnumProperty(
+ items=(
+ ('RECTANGLE', 'Straight', '', 0),
+ ('CIRCLE', 'Curved ', '', 1)
+ ),
+ default='RECTANGLE',
+ update=update
+ )
+ z_mode = EnumProperty(
+ name="Interp z",
+ items=(
+ ('STANDARD', 'Standard', '', 0),
+ ('LINEAR', 'Bottom Linear', '', 1),
+ ('LINEAR_TOP', 'All Linear', '', 2)
+ ),
+ default='STANDARD',
+ update=update
+ )
+ presets = EnumProperty(
+ items=(
+ ('STAIR_I', 'I stair', '', 0),
+ ('STAIR_L', 'L stair', '', 1),
+ ('STAIR_U', 'U stair', '', 2),
+ ('STAIR_O', 'O stair', '', 3),
+ ('STAIR_USER', 'User defined stair', '', 4),
+ ),
+ default='STAIR_I', update=update_preset
+ )
+ left_post = BoolProperty(
+ name='left',
+ default=True,
+ update=update
+ )
+ right_post = BoolProperty(
+ name='right',
+ default=True,
+ update=update
+ )
+ post_spacing = FloatProperty(
+ name="spacing",
+ min=0.1,
+ default=1.0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ post_x = FloatProperty(
+ name="width",
+ min=0.001,
+ default=0.04, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ post_y = FloatProperty(
+ name="length",
+ min=0.001,
+ default=0.04, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ post_z = FloatProperty(
+ name="height",
+ min=0.001,
+ default=1, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ post_alt = FloatProperty(
+ name="altitude",
+ min=-100,
+ default=0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ post_offset_x = FloatProperty(
+ name="offset",
+ min=-100.0, max=100,
+ default=0.02, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ post_corners = BoolProperty(
+ name="only on edges",
+ update=update,
+ default=False
+ )
+ user_defined_post_enable = BoolProperty(
+ name="User",
+ update=update,
+ default=True
+ )
+ user_defined_post = StringProperty(
+ name="user defined",
+ update=update
+ )
+ idmat_post = EnumProperty(
+ name="Post",
+ items=materials_enum,
+ default='4',
+ update=update
+ )
+ left_subs = BoolProperty(
+ name='left',
+ default=False,
+ update=update
+ )
+ right_subs = BoolProperty(
+ name='right',
+ default=False,
+ update=update
+ )
+ subs_spacing = FloatProperty(
+ name="spacing",
+ min=0.05,
+ default=0.10, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ subs_x = FloatProperty(
+ name="width",
+ min=0.001,
+ default=0.02, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ subs_y = FloatProperty(
+ name="length",
+ min=0.001,
+ default=0.02, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ subs_z = FloatProperty(
+ name="height",
+ min=0.001,
+ default=1, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ subs_alt = FloatProperty(
+ name="altitude",
+ min=-100,
+ default=0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ subs_offset_x = FloatProperty(
+ name="offset",
+ min=-100.0, max=100,
+ default=0.0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ subs_bottom = EnumProperty(
+ name="Bottom",
+ items=(
+ ('STEP', 'Follow step', '', 0),
+ ('LINEAR', 'Linear', '', 1),
+ ),
+ default='STEP',
+ update=update
+ )
+ user_defined_subs_enable = BoolProperty(
+ name="User",
+ update=update,
+ default=True
+ )
+ user_defined_subs = StringProperty(
+ name="user defined",
+ update=update
+ )
+ idmat_subs = EnumProperty(
+ name="Subs",
+ items=materials_enum,
+ default='4',
+ update=update
+ )
+ left_panel = BoolProperty(
+ name='left',
+ default=True,
+ update=update
+ )
+ right_panel = BoolProperty(
+ name='right',
+ default=True,
+ update=update
+ )
+ panel_alt = FloatProperty(
+ name="altitude",
+ default=0.25, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ panel_x = FloatProperty(
+ name="width",
+ min=0.001,
+ default=0.01, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ panel_z = FloatProperty(
+ name="height",
+ min=0.001,
+ default=0.6, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ panel_dist = FloatProperty(
+ name="space",
+ min=0.001,
+ default=0.05, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ panel_offset_x = FloatProperty(
+ name="offset",
+ default=0.0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ idmat_panel = EnumProperty(
+ name="Panels",
+ items=materials_enum,
+ default='5',
+ update=update
+ )
+ left_rail = BoolProperty(
+ name="left",
+ update=update,
+ default=False
+ )
+ right_rail = BoolProperty(
+ name="right",
+ update=update,
+ default=False
+ )
+ rail_n = IntProperty(
+ name="number",
+ default=1,
+ min=0,
+ max=31,
+ update=update
+ )
+ rail_x = FloatVectorProperty(
+ name="width",
+ default=[
+ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
+ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
+ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
+ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05
+ ],
+ size=31,
+ min=0.001,
+ precision=2, step=1,
+ unit='LENGTH',
+ update=update
+ )
+ rail_z = FloatVectorProperty(
+ name="height",
+ default=[
+ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
+ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
+ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
+ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05
+ ],
+ size=31,
+ min=0.001,
+ precision=2, step=1,
+ unit='LENGTH',
+ update=update
+ )
+ rail_offset = FloatVectorProperty(
+ name="offset",
+ default=[
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0
+ ],
+ size=31,
+ precision=2, step=1,
+ unit='LENGTH',
+ update=update
+ )
+ rail_alt = FloatVectorProperty(
+ name="altitude",
+ default=[
+ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
+ ],
+ size=31,
+ precision=2, step=1,
+ unit='LENGTH',
+ update=update
+ )
+ rail_mat = CollectionProperty(type=archipack_stair_material)
+
+ left_handrail = BoolProperty(
+ name="left",
+ update=update,
+ default=True
+ )
+ right_handrail = BoolProperty(
+ name="right",
+ update=update,
+ default=True
+ )
+ handrail_offset = FloatProperty(
+ name="offset",
+ default=0.0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ handrail_alt = FloatProperty(
+ name="altitude",
+ default=1.0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ handrail_extend = FloatProperty(
+ name="extend",
+ default=0.1, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ handrail_slice_left = BoolProperty(
+ name='slice',
+ default=True,
+ update=update
+ )
+ handrail_slice_right = BoolProperty(
+ name='slice',
+ default=True,
+ update=update
+ )
+ handrail_profil = EnumProperty(
+ name="Profil",
+ items=(
+ ('SQUARE', 'Square', '', 0),
+ ('CIRCLE', 'Circle', '', 1),
+ ('COMPLEX', 'Circle over square', '', 2)
+ ),
+ default='SQUARE',
+ update=update
+ )
+ handrail_x = FloatProperty(
+ name="width",
+ min=0.001,
+ default=0.04, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ handrail_y = FloatProperty(
+ name="height",
+ min=0.001,
+ default=0.04, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ handrail_radius = FloatProperty(
+ name="radius",
+ min=0.001,
+ default=0.02, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+
+ left_string = BoolProperty(
+ name="left",
+ update=update,
+ default=False
+ )
+ right_string = BoolProperty(
+ name="right",
+ update=update,
+ default=False
+ )
+ string_x = FloatProperty(
+ name="width",
+ min=-100.0,
+ default=0.02, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ string_z = FloatProperty(
+ name="height",
+ default=0.3, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ string_offset = FloatProperty(
+ name="offset",
+ default=0.0, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+ string_alt = FloatProperty(
+ name="altitude",
+ default=-0.04, precision=2, step=1,
+ unit='LENGTH', subtype='DISTANCE',
+ update=update
+ )
+
+ idmat_bottom = EnumProperty(
+ name="Bottom",
+ items=materials_enum,
+ default='1',
+ update=update
+ )
+ idmat_raise = EnumProperty(
+ name="Raise",
+ items=materials_enum,
+ default='1',
+ update=update
+ )
+ idmat_step_front = EnumProperty(
+ name="Step front",
+ items=materials_enum,
+ default='3',
+ update=update
+ )
+ idmat_top = EnumProperty(
+ name="Top",
+ items=materials_enum,
+ default='3',
+ update=update
+ )
+ idmat_side = EnumProperty(
+ name="Side",
+ items=materials_enum,
+ default='1',
+ update=update
+ )
+ idmat_step_side = EnumProperty(
+ name="Step Side",
+ items=materials_enum,
+ default='3',
+ update=update
+ )
+ idmat_handrail = EnumProperty(
+ name="Handrail",
+ items=materials_enum,
+ default='3',
+ update=update
+ )
+ idmat_string = EnumProperty(
+ name="String",
+ items=materials_enum,
+ default='3',
+ update=update
+ )
+
+ # UI layout related
+ parts_expand = BoolProperty(
+ default=False
+ )
+ steps_expand = BoolProperty(
+ default=False
+ )
+ rail_expand = BoolProperty(
+ default=False
+ )
+ idmats_expand = BoolProperty(
+ default=False
+ )
+ handrail_expand = BoolProperty(
+ default=False
+ )
+ string_expand = BoolProperty(
+ default=False
+ )
+ post_expand = BoolProperty(
+ default=False
+ )
+ panel_expand = BoolProperty(
+ default=False
+ )
+ subs_expand = BoolProperty(
+ default=False
+ )
+
+ auto_update = BoolProperty(
+ options={'SKIP_SAVE'},
+ default=True,
+ update=update_manipulators
+ )
+
+ def setup_manipulators(self):
+
+ if len(self.manipulators) == 0:
+ s = self.manipulators.add()
+ s.prop1_name = "width"
+ s = self.manipulators.add()
+ s.prop1_name = "height"
+ s.normal = Vector((0, 1, 0))
+
+ for i in range(self.n_parts):
+ p = self.parts[i]
+ n_manips = len(p.manipulators)
+ if n_manips < 1:
+ m = p.manipulators.add()
+ m.type_key = 'SIZE'
+ m.prop1_name = 'length'
+
+ def update_parts(self):
+
+ # remove rails materials
+ for i in range(len(self.rail_mat), self.rail_n, -1):
+ self.rail_mat.remove(i - 1)
+
+ # add rails
+ for i in range(len(self.rail_mat), self.rail_n):
+ self.rail_mat.add()
+
+ # remove parts
+ for i in range(len(self.parts), self.n_parts, -1):
+ self.parts.remove(i - 1)
+
+ # add parts
+ for i in range(len(self.parts), self.n_parts):
+ self.parts.add()
+
+ self.setup_manipulators()
+
+ def update(self, context, manipulable_refresh=False):
+
+ o = self.find_in_selection(context, self.auto_update)
+
+ if o is None:
+ return
+
+ # clean up manipulators before any data model change
+ if manipulable_refresh:
+ self.manipulable_disable(context)
+
+ self.update_parts()
+
+ center = Vector((0, 0))
+ verts = []
+ faces = []
+ matids = []
+ uvs = []
+ id_materials = [int(self.idmat_top), int(self.idmat_step_front), int(self.idmat_raise),
+ int(self.idmat_side), int(self.idmat_bottom), int(self.idmat_step_side)]
+
+ # depth at bottom
+ bottom_z = self.bottom_z
+ if self.steps_type == 'OPEN':
+ # depth at front
+ bottom_z = self.nose_z
+
+ width_left = 0.5 * self.width - self.x_offset
+ width_right = 0.5 * self.width + self.x_offset
+
+ self.manipulators[0].set_pts([(-width_left, 0, 0), (width_right, 0, 0), (1, 0, 0)])
+ self.manipulators[1].set_pts([(0, 0, 0), (0, 0, self.height), (1, 0, 0)])
+
+ g = StairGenerator(self.parts)
+ if self.presets == 'STAIR_USER':
+ for part in self.parts:
+ g.add_part(part.type, self.steps_type, self.nose_type, self.z_mode, self.nose_z,
+ bottom_z, center, max(width_left + 0.01, width_right + 0.01, part.radius), part.da,
+ width_left, width_right, part.length, part.left_shape, part.right_shape)
+
+ elif self.presets == 'STAIR_O':
+ n_parts = max(1, int(round(abs(self.total_angle) / pi, 0)))
+ if self.total_angle > 0:
+ dir = 1
+ else:
+ dir = -1
+ last_da = self.total_angle - dir * (n_parts - 1) * pi
+ if dir * last_da > pi:
+ n_parts += 1
+ last_da -= dir * pi
+ abs_last = dir * last_da
+
+ for part in range(n_parts - 1):
+ g.add_part('D_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
+ bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), dir * pi,
+ width_left, width_right, 1.0, self.left_shape, self.right_shape)
+ if round(abs_last, 2) > 0:
+ if abs_last > pi / 2:
+ g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
+ bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius),
+ dir * pi / 2,
+ width_left, width_right, 1.0, self.left_shape, self.right_shape)
+ g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
+ bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius),
+ last_da - dir * pi / 2,
+ width_left, width_right, 1.0, self.left_shape, self.right_shape)
+ else:
+ g.add_part('C_STAIR', self.steps_type, self.nose_type, self.z_mode, self.nose_z,
+ bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), last_da,
+ width_left, width_right, 1.0, self.left_shape, self.right_shape)
+ else:
+ # STAIR_L STAIR_I STAIR_U
+ for part in self.parts:
+ g.add_part(part.type, self.steps_type, self.nose_type, self.z_mode, self.nose_z,
+ bottom_z, center, max(width_left + 0.01, width_right + 0.01, self.radius), self.da,
+ width_left, width_right, part.length, self.left_shape, self.right_shape)
+
+ # Stair basis
+ g.set_matids(id_materials)
+ g.make_stair(self.height, self.step_depth, verts, faces, matids, uvs, nose_y=self.nose_y)
+
+ # Ladder
+ offset_x = 0.5 * self.width - self.post_offset_x
+ post_spacing = self.post_spacing
+ if self.post_corners:
+ post_spacing = 10000
+
+ if self.user_defined_post_enable:
+ # user defined posts
+ user_def_post = context.scene.objects.get(self.user_defined_post)
+ if user_def_post is not None and user_def_post.type == 'MESH':
+ g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z)
+
+ if self.left_post:
+ g.make_post(self.height, self.step_depth, 0.5 * self.post_x, 0.5 * self.post_y,
+ self.post_z, self.post_alt, 'LEFT', post_spacing, self.post_corners,
+ self.x_offset, offset_x, int(self.idmat_post), verts, faces, matids, uvs)
+
+ if self.right_post:
+ g.make_post(self.height, self.step_depth, 0.5 * self.post_x, 0.5 * self.post_y,
+ self.post_z, self.post_alt, 'RIGHT', post_spacing, self.post_corners,
+ self.x_offset, offset_x, int(self.idmat_post), verts, faces, matids, uvs)
+
+ # reset user def posts
+ g.user_defined_post = None
+
+ # user defined subs
+ if self.user_defined_subs_enable:
+ user_def_subs = context.scene.objects.get(self.user_defined_subs)
+ if user_def_subs is not None and user_def_subs.type == 'MESH':
+ g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z)
+
+ if self.left_subs:
+ g.make_subs(self.height, self.step_depth, 0.5 * self.subs_x, 0.5 * self.subs_y,
+ self.subs_z, 0.5 * self.post_y, self.subs_alt, self.subs_bottom, 'LEFT',
+ self.handrail_slice_left, post_spacing, self.subs_spacing, self.post_corners,
+ self.x_offset, offset_x, -self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs)
+
+ if self.right_subs:
+ g.make_subs(self.height, self.step_depth, 0.5 * self.subs_x, 0.5 * self.subs_y,
+ self.subs_z, 0.5 * self.post_y, self.subs_alt, self.subs_bottom, 'RIGHT',
+ self.handrail_slice_right, post_spacing, self.subs_spacing, self.post_corners,
+ self.x_offset, offset_x, self.subs_offset_x, int(self.idmat_subs), verts, faces, matids, uvs)
+
+ g.user_defined_post = None
+
+ if self.left_panel:
+ g.make_panels(self.height, self.step_depth, 0.5 * self.panel_x, self.panel_z, 0.5 * self.post_y,
+ self.panel_alt, 'LEFT', post_spacing, self.panel_dist, self.post_corners,
+ self.x_offset, offset_x, -self.panel_offset_x, int(self.idmat_panel), verts, faces, matids, uvs)
+
+ if self.right_panel:
+ g.make_panels(self.height, self.step_depth, 0.5 * self.panel_x, self.panel_z, 0.5 * self.post_y,
+ self.panel_alt, 'RIGHT', post_spacing, self.panel_dist, self.post_corners,
+ self.x_offset, offset_x, self.panel_offset_x, int(self.idmat_panel), verts, faces, matids, uvs)
+
+ if self.right_rail:
+ for i in range(self.rail_n):
+ id_materials = [int(self.rail_mat[i].index) for j in range(6)]
+ g.set_matids(id_materials)
+ g.make_part(self.height, self.step_depth, self.rail_x[i], self.rail_z[i],
+ self.x_offset, offset_x + self.rail_offset[i],
+ self.rail_alt[i], 'LINEAR', 'CLOSED', verts, faces, matids, uvs)
+
+ if self.left_rail:
+ for i in range(self.rail_n):
+ id_materials = [int(self.rail_mat[i].index) for j in range(6)]
+ g.set_matids(id_materials)
+ g.make_part(self.height, self.step_depth, self.rail_x[i], self.rail_z[i],
+ self.x_offset, -offset_x - self.rail_offset[i],
+ self.rail_alt[i], 'LINEAR', 'CLOSED', verts, faces, matids, uvs)
+
+ if self.handrail_profil == 'COMPLEX':
+ sx = self.handrail_x
+ sy = self.handrail_y
+ handrail = [Vector((sx * x, sy * y)) for x, y in [
+ (-0.28, 1.83), (-0.355, 1.77), (-0.415, 1.695), (-0.46, 1.605), (-0.49, 1.51), (-0.5, 1.415),
+ (-0.49, 1.315), (-0.46, 1.225), (-0.415, 1.135), (-0.355, 1.06), (-0.28, 1.0), (-0.255, 0.925),
+ (-0.33, 0.855), (-0.5, 0.855), (-0.5, 0.0), (0.5, 0.0), (0.5, 0.855), (0.33, 0.855), (0.255, 0.925),
+ (0.28, 1.0), (0.355, 1.06), (0.415, 1.135), (0.46, 1.225), (0.49, 1.315), (0.5, 1.415),
+ (0.49, 1.51), (0.46, 1.605), (0.415, 1.695), (0.355, 1.77), (0.28, 1.83), (0.19, 1.875),
+ (0.1, 1.905), (0.0, 1.915), (-0.095, 1.905), (-0.19, 1.875)]]
+
+ elif self.handrail_profil == 'SQUARE':
+ x = 0.5 * self.handrail_x
+ y = self.handrail_y
+ handrail = [Vector((-x, y)), Vector((-x, 0)), Vector((x, 0)), Vector((x, y))]
+ elif self.handrail_profil == 'CIRCLE':
+ r = self.handrail_radius
+ handrail = [Vector((r * sin(0.1 * -a * pi), r * (0.5 + cos(0.1 * -a * pi)))) for a in range(0, 20)]
+
+ if self.right_handrail:
+ g.make_profile(handrail, int(self.idmat_handrail), "RIGHT", self.handrail_slice_right,
+ self.height, self.step_depth, self.x_offset + offset_x + self.handrail_offset,
+ self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs)
+
+ if self.left_handrail:
+ g.make_profile(handrail, int(self.idmat_handrail), "LEFT", self.handrail_slice_left,
+ self.height, self.step_depth, -self.x_offset + offset_x + self.handrail_offset,
+ self.handrail_alt, self.handrail_extend, verts, faces, matids, uvs)
+
+ w = 0.5 * self.string_x
+ h = self.string_z
+ string = [Vector((-w, 0)), Vector((w, 0)), Vector((w, h)), Vector((-w, h))]
+
+ if self.right_string:
+ g.make_profile(string, int(self.idmat_string), "RIGHT", False, self.height, self.step_depth,
+ self.x_offset + 0.5 * self.width + self.string_offset,
+ self.string_alt, 0, verts, faces, matids, uvs)
+
+ if self.left_string:
+ g.make_profile(string, int(self.idmat_string), "LEFT", False, self.height, self.step_depth,
+ -self.x_offset + 0.5 * self.width + self.string_offset,
+ self.string_alt, 0, verts, faces, matids, uvs)
+
+ bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs, weld=True, clean=True)
+
+ # enable manipulators rebuild
+ if manipulable_refresh:
+ self.manipulable_refresh = True
+
+ self.restore_context(context)
+
+ def manipulable_setup(self, context):
+ """
+ TODO: Implement the setup part as per parent object basis
+
+ self.manipulable_disable(context)
+ o = context.active_object
+ for m in self.manipulators:
+ self.manip_stack.append(m.setup(context, o, self))
+
+ """
+ self.manipulable_disable(context)
+ o = context.active_object
+
+ self.setup_manipulators()
+
+ if self.presets is not 'STAIR_O':
+ for i, part in enumerate(self.parts):
+ if i >= self.n_parts:
+ break
+ if "S_" in part.type or self.presets in ['STAIR_USER']:
+ for j, m in enumerate(part.manipulators):
+ self.manip_stack.append(m.setup(context, o, part))
+
+ if self.presets in ['STAIR_U', 'STAIR_L']:
+ self.manip_stack.append(self.parts[1].manipulators[0].setup(context, o, self))
+
+ for m in self.manipulators:
+ self.manip_stack.append(m.setup(context, o, self))
+
+
+class ARCHIPACK_PT_stair(Panel):
+ bl_idname = "ARCHIPACK_PT_stair"
+ bl_label = "Stair"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ # bl_context = 'object'
+ bl_category = 'ArchiPack'
+
+ @classmethod
+ def poll(cls, context):
+ return archipack_stair.filter(context.active_object)
+
+ def draw(self, context):
+ prop = archipack_stair.datablock(context.active_object)
+ if prop is None:
+ return
+ scene = context.scene
+ layout = self.layout
+ row = layout.row(align=True)
+ row.operator('archipack.stair_manipulate', icon='HAND')
+ row = layout.row(align=True)
+ row.prop(prop, 'presets', text="")
+ box = layout.box()
+ # box.label(text="Styles")
+ row = box.row(align=True)
+ # row.menu("ARCHIPACK_MT_stair_preset", text=bpy.types.ARCHIPACK_MT_stair_preset.bl_label)
+ row.operator("archipack.stair_preset_menu", text=bpy.types.ARCHIPACK_OT_stair_preset_menu.bl_label)
+ row.operator("archipack.stair_preset", text="", icon='ZOOMIN')
+ row.operator("archipack.stair_preset", text="", icon='ZOOMOUT').remove_active = True
+ box = layout.box()
+ box.prop(prop, 'width')
+ box.prop(prop, 'height')
+ box.prop(prop, 'bottom_z')
+ box.prop(prop, 'x_offset')
+ # box.prop(prop, 'z_mode')
+ box = layout.box()
+ row = box.row()
+ if prop.parts_expand:
+ row.prop(prop, 'parts_expand', icon="TRIA_DOWN", icon_only=True, text="Parts", emboss=False)
+ if prop.presets == 'STAIR_USER':
+ box.prop(prop, 'n_parts')
+ if prop.presets != 'STAIR_USER':
+ row = box.row(align=True)
+ row.prop(prop, "left_shape", text="")
+ row.prop(prop, "right_shape", text="")
+ row = box.row()
+ row.prop(prop, "radius")
+ row = box.row()
+ if prop.presets == 'STAIR_O':
+ row.prop(prop, 'total_angle')
+ else:
+ row.prop(prop, 'da')
+ if prop.presets != 'STAIR_O':
+ for i, part in enumerate(prop.parts):
+ part.draw(layout, context, i, prop.presets == 'STAIR_USER')
+ else:
+ row.prop(prop, 'parts_expand', icon="TRIA_RIGHT", icon_only=True, text="Parts", emboss=False)
+
+ box = layout.box()
+ row = box.row()
+ if prop.steps_expand:
+ row.prop(prop, 'steps_expand', icon="TRIA_DOWN", icon_only=True, text="Steps", emboss=False)
+ box.prop(prop, 'steps_type')
+ box.prop(prop, 'step_depth')
+ box.prop(prop, 'nose_type')
+ box.prop(prop, 'nose_z')
+ box.prop(prop, 'nose_y')
+ else:
+ row.prop(prop, 'steps_expand', icon="TRIA_RIGHT", icon_only=True, text="Steps", emboss=False)
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.handrail_expand:
+ row.prop(prop, 'handrail_expand', icon="TRIA_DOWN", icon_only=True, text="Handrail", emboss=False)
+ else:
+ row.prop(prop, 'handrail_expand', icon="TRIA_RIGHT", icon_only=True, text="Handrail", emboss=False)
+
+ row.prop(prop, 'left_handrail')
+ row.prop(prop, 'right_handrail')
+
+ if prop.handrail_expand:
+ box.prop(prop, 'handrail_alt')
+ box.prop(prop, 'handrail_offset')
+ box.prop(prop, 'handrail_extend')
+ box.prop(prop, 'handrail_profil')
+ if prop.handrail_profil != 'CIRCLE':
+ box.prop(prop, 'handrail_x')
+ box.prop(prop, 'handrail_y')
+ else:
+ box.prop(prop, 'handrail_radius')
+ row = box.row(align=True)
+ row.prop(prop, 'handrail_slice_left')
+ row.prop(prop, 'handrail_slice_right')
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.string_expand:
+ row.prop(prop, 'string_expand', icon="TRIA_DOWN", icon_only=True, text="String", emboss=False)
+ else:
+ row.prop(prop, 'string_expand', icon="TRIA_RIGHT", icon_only=True, text="String", emboss=False)
+ row.prop(prop, 'left_string')
+ row.prop(prop, 'right_string')
+ if prop.string_expand:
+ box.prop(prop, 'string_x')
+ box.prop(prop, 'string_z')
+ box.prop(prop, 'string_alt')
+ box.prop(prop, 'string_offset')
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.post_expand:
+ row.prop(prop, 'post_expand', icon="TRIA_DOWN", icon_only=True, text="Post", emboss=False)
+ else:
+ row.prop(prop, 'post_expand', icon="TRIA_RIGHT", icon_only=True, text="Post", emboss=False)
+ row.prop(prop, 'left_post')
+ row.prop(prop, 'right_post')
+ if prop.post_expand:
+ box.prop(prop, 'post_corners')
+ if not prop.post_corners:
+ box.prop(prop, 'post_spacing')
+ box.prop(prop, 'post_x')
+ box.prop(prop, 'post_y')
+ box.prop(prop, 'post_z')
+ box.prop(prop, 'post_alt')
+ box.prop(prop, 'post_offset_x')
+ row = box.row(align=True)
+ row.prop(prop, 'user_defined_post_enable', text="")
+ row.prop_search(prop, "user_defined_post", scene, "objects", text="")
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.subs_expand:
+ row.prop(prop, 'subs_expand', icon="TRIA_DOWN", icon_only=True, text="Subs", emboss=False)
+ else:
+ row.prop(prop, 'subs_expand', icon="TRIA_RIGHT", icon_only=True, text="Subs", emboss=False)
+
+ row.prop(prop, 'left_subs')
+ row.prop(prop, 'right_subs')
+ if prop.subs_expand:
+ box.prop(prop, 'subs_spacing')
+ box.prop(prop, 'subs_x')
+ box.prop(prop, 'subs_y')
+ box.prop(prop, 'subs_z')
+ box.prop(prop, 'subs_alt')
+ box.prop(prop, 'subs_offset_x')
+ box.prop(prop, 'subs_bottom')
+ row = box.row(align=True)
+ row.prop(prop, 'user_defined_subs_enable', text="")
+ row.prop_search(prop, "user_defined_subs", scene, "objects", text="")
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.panel_expand:
+ row.prop(prop, 'panel_expand', icon="TRIA_DOWN", icon_only=True, text="Panels", emboss=False)
+ else:
+ row.prop(prop, 'panel_expand', icon="TRIA_RIGHT", icon_only=True, text="Panels", emboss=False)
+ row.prop(prop, 'left_panel')
+ row.prop(prop, 'right_panel')
+ if prop.panel_expand:
+ box.prop(prop, 'panel_dist')
+ box.prop(prop, 'panel_x')
+ box.prop(prop, 'panel_z')
+ box.prop(prop, 'panel_alt')
+ box.prop(prop, 'panel_offset_x')
+
+ box = layout.box()
+ row = box.row(align=True)
+ if prop.rail_expand:
+ row.prop(prop, 'rail_expand', icon="TRIA_DOWN", icon_only=True, text="Rails", emboss=False)
+ else:
+ row.prop(prop, 'rail_expand', icon="TRIA_RIGHT", icon_only=True, text="Rails", emboss=False)
+ row.prop(prop, 'left_rail')
+ row.prop(prop, 'right_rail')
+ if prop.rail_expand:
+ box.prop(prop, 'rail_n')
+ for i in range(prop.rail_n):
+ box = layout.box()
+ box.label(text="Rail " + str(i + 1))
+ box.prop(prop, 'rail_x', index=i)
+ box.prop(prop, 'rail_z', index=i)
+ box.prop(prop, 'rail_alt', index=i)
+ box.prop(prop, 'rail_offset', index=i)
+ box.prop(prop.rail_mat[i], 'index', text="")
+
+ box = layout.box()
+ row = box.row()
+
+ if prop.idmats_expand:
+ row.prop(prop, 'idmats_expand', icon="TRIA_DOWN", icon_only=True, text="Materials", emboss=False)
+ box.prop(prop, 'idmat_top')
+ box.prop(prop, 'idmat_side')
+ box.prop(prop, 'idmat_bottom')
+ box.prop(prop, 'idmat_step_side')
+ box.prop(prop, 'idmat_step_front')
+ box.prop(prop, 'idmat_raise')
+ box.prop(prop, 'idmat_handrail')
+ box.prop(prop, 'idmat_panel')
+ box.prop(prop, 'idmat_post')
+ box.prop(prop, 'idmat_subs')
+ box.prop(prop, 'idmat_string')
+ else:
+ row.prop(prop, 'idmats_expand', icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False)
+
+
+# ------------------------------------------------------------------
+# Define operator class to create object
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_stair(ArchipackCreateTool, Operator):
+ bl_idname = "archipack.stair"
+ bl_label = "Stair"
+ bl_description = "Create a Stair"
+ bl_category = 'Archipack'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def create(self, context):
+ m = bpy.data.meshes.new("Stair")
+ o = bpy.data.objects.new("Stair", m)
+ d = m.archipack_stair.add()
+ context.scene.objects.link(o)
+ o.select = True
+ context.scene.objects.active = o
+ self.load_preset(d)
+ self.add_material(o)
+ m.auto_smooth_angle = 0.20944
+ return o
+
+ # -----------------------------------------------------
+ # Execute
+ # -----------------------------------------------------
+ def execute(self, context):
+ if context.mode == "OBJECT":
+ bpy.ops.object.select_all(action="DESELECT")
+ o = self.create(context)
+ o.location = context.scene.cursor_location
+ o.select = True
+ context.scene.objects.active = o
+ self.manipulate()
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
+ return {'CANCELLED'}
+
+# ------------------------------------------------------------------
+# Define operator class to manipulate object
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_stair_manipulate(Operator):
+ bl_idname = "archipack.stair_manipulate"
+ bl_label = "Manipulate"
+ bl_description = "Manipulate"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(self, context):
+ return archipack_stair.filter(context.active_object)
+
+ def invoke(self, context, event):
+ d = archipack_stair.datablock(context.active_object)
+ d.manipulable_invoke(context)
+ return {'FINISHED'}
+
+
+# ------------------------------------------------------------------
+# Define operator class to load / save presets
+# ------------------------------------------------------------------
+
+
+class ARCHIPACK_OT_stair_preset_menu(PresetMenuOperator, Operator):
+ bl_idname = "archipack.stair_preset_menu"
+ bl_label = "Stair style"
+ preset_subdir = "archipack_stair"
+
+
+class ARCHIPACK_OT_stair_preset(ArchipackPreset, Operator):
+ """Add a Stair Preset"""
+ bl_idname = "archipack.stair_preset"
+ bl_label = "Add Stair Style"
+ preset_menu = "ARCHIPACK_OT_stair_preset_menu"
+
+ @property
+ def blacklist(self):
+ return ['manipulators']
+
+ """
+ 'presets', 'n_parts', 'parts', 'width', 'height', 'radius',
+ 'total_angle', 'da',
+ """
+
+
+def register():
+ bpy.utils.register_class(archipack_stair_material)
+ bpy.utils.register_class(archipack_stair_part)
+ bpy.utils.register_class(archipack_stair)
+ Mesh.archipack_stair = CollectionProperty(type=archipack_stair)
+ bpy.utils.register_class(ARCHIPACK_PT_stair)
+ bpy.utils.register_class(ARCHIPACK_OT_stair)
+ bpy.utils.register_class(ARCHIPACK_OT_stair_preset_menu)
+ bpy.utils.register_class(ARCHIPACK_OT_stair_preset)
+ bpy.utils.register_class(ARCHIPACK_OT_stair_manipulate)
+
+
+def unregister():
+ bpy.utils.unregister_class(archipack_stair_material)
+ bpy.utils.unregister_class(archipack_stair_part)
+ bpy.utils.unregister_class(archipack_stair)
+ del Mesh.archipack_stair
+ bpy.utils.unregister_class(ARCHIPACK_PT_stair)
+ bpy.utils.unregister_class(ARCHIPACK_OT_stair)
+ bpy.utils.unregister_class(ARCHIPACK_OT_stair_preset_menu)
+ bpy.utils.unregister_class(ARCHIPACK_OT_stair_preset)
+ bpy.utils.unregister_class(ARCHIPACK_OT_stair_manipulate)