Cleanup: simplify file name incrementing logic
[blender-addons.git] / archipack / archipack_stair.py
blob9fbcdfe2436af965505f968ffe4184788b40603c
1 # -*- coding:utf-8 -*-
3 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
19 # ##### END GPL LICENSE BLOCK #####
21 # <pep8 compliant>
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
26 # ----------------------------------------------------------
27 # noinspection PyUnresolvedReferences
28 import bpy
29 # noinspection PyUnresolvedReferences
30 from bpy.types import Operator, PropertyGroup, Mesh, Panel
31 from bpy.props import (
32 FloatProperty, BoolProperty, IntProperty, CollectionProperty,
33 StringProperty, EnumProperty, FloatVectorProperty
35 from .bmesh_utils import BmeshEdit as bmed
36 from .panel import Panel as Lofter
37 from mathutils import Vector, Matrix
38 from math import sin, cos, pi, floor, acos
39 from .archipack_manipulator import Manipulable, archipack_manipulator
40 from .archipack_2d import Line, Arc
41 from .archipack_preset import ArchipackPreset, PresetMenuOperator
42 from .archipack_object import ArchipackCreateTool, ArchipackObject
45 class 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
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
66 def set_height(self, step_height, z0):
67 self.step_height = step_height
68 self.z0 = z0
70 @property
71 def height(self):
72 return self.n_step * self.step_height
74 @property
75 def top_offset(self):
76 return self.t_step / self.step_depth
78 @property
79 def top(self):
80 return self.z0 + self.height
82 @property
83 def left_length(self):
84 return self.get_length("LEFT")
86 @property
87 def right_length(self):
88 return self.get_length("RIGHT")
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
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)])
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)])
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))
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))
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)
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)
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)
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)
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
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]
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]])
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()
261 def _make_nose(self, i, s, verts, faces, matids, uvs, nose_y):
263 t = self.t_step * i
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)
270 if self.z_mode == 'LINEAR':
271 return rM
273 f = len(verts)
275 tl = t - nose_y / self.get_length("LEFT")
276 tr = t - nose_y / self.get_length("RIGHT")
278 t2, part, dz, shape = self.get_part(tl, "LEFT")
279 p0 = part.lerp(t2)
280 self.p3d_left(verts, p0, s, t2)
282 t2, part, dz, shape = self.get_part(tr, "RIGHT")
283 p1 = part.lerp(t2)
284 self.p3d_right(verts, p1, s, t2)
286 start = 3
287 end = 6
288 offset = 10
290 # left, top, right
291 matids.extend([self.idmat_step_side,
292 self.idmat_top,
293 self.idmat_step_side])
295 faces += [(f + j, f + j + 1, f + j + offset + 1, f + j + offset) for j in range(start, end)]
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)
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)]
305 uvs.append([(0, 0), (0, v), (u, v), (u, 0)])
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)]
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)])
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)])
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))
328 uvs.append([(0, w), (v, w), (v, 0), (0, 0)])
330 matids.append(self.idmat_side)
331 faces.append((f + 3, f + 13, f + 12))
332 uvs.append([(0, 0), (u, 0), (u, w)])
334 matids.append(self.idmat_side)
335 faces.append((f + 6, f + 17, f + 16))
336 uvs.append([(0, 0), (u, w), (u, 0)])
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
345 def make_faces(self, f, rM, verts, faces, matids, uvs):
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])
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
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)]
389 self.project_uv(rM, uvs, verts, [f + start + s, f + start + s + 1,
390 f + start + s + offset + 1, f + start + s + offset])
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)]
395 self.project_uv(rM, uvs, verts, [f + end, f + start, f + offset + start, f + offset + end])
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))
401 class 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)
408 def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
410 rM = self._make_nose(i, i, verts, faces, matids, uvs, nose_y)
412 t0 = self.t_step * i
414 f = len(verts)
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)
421 t1 = t0 + self.t_step
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)
428 self.make_faces(f, rM, verts, faces, matids, uvs)
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)])
435 def get_length(self, side):
436 return self.length
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]
451 def n_posts(self, post_spacing, side, respect_edges):
452 return self.steps(post_spacing)
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'
462 class 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):
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)
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
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
510 def _make_step(self, t_step, i, s, verts, landing=False):
512 tb = t_step * i
514 f = len(verts)
516 t, part, dz, shape = self.get_part(tb, "LEFT")
517 p = part.lerp(t)
518 self.p3d_left(verts, p, s, tb, landing)
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
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
572 def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
574 # open stair with closed face
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)
593 self._make_step(self.t_step, i + 1, i, verts)
594 self.make_faces(f, rM, verts, faces, matids, uvs)
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)])
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
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')
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))
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
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)
689 class 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'):
693 StraightStair.__init__(self, p, v, left_offset, right_offset, steps_type,
694 nose_type, z_mode, nose_z, bottom_z)
696 self.last_type = last_type
698 @property
699 def height(self):
700 return 0
702 @property
703 def top_offset(self):
704 return self.t_step / self.v.length
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
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
722 def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
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)
729 f = len(verts)
730 j = 0
731 t0 = self.t_step * i
733 p = self.l_line.lerp(t0)
734 self.p3d_left(verts, p, j, t0)
736 p = self.r_line.lerp(t0)
737 self.p3d_right(verts, p, j, t0)
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')
743 p = self.r_line.lerp(t1)
744 self.p3d_right(verts, p, j, t1, self.next_type != 'LANDING')
746 self.make_faces(f, rM, verts, faces, matids, uvs)
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)])
753 def straight_landing(self, length):
754 return Stair.straight_landing(self, length, last_type='LANDING')
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')
760 def get_z(self, t, mode):
761 if mode == 'STEP':
762 return self.z0 + self.step_height
763 else:
764 return self.z0
767 class 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'):
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)
774 self.last_type = last_type
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
786 @property
787 def height(self):
788 return 0
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
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
813 def make_step(self, i, verts, faces, matids, uvs, nose_y=0):
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)
820 f = len(verts)
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)
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)
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)])
843 def straight_landing(self, length):
844 return Stair.straight_landing(self, length, last_type='LANDING')
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')
850 def get_z(self, t, mode):
851 if mode == 'STEP':
852 return self.z0 + self.step_height
853 else:
854 return self.z0
857 class 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
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):
871 self.steps_type = steps_type
872 if len(self.stairs) < 1:
873 s = None
874 else:
875 s = self.stairs[-1]
877 if "S_" not in type:
878 self.sum_da += da
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
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
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
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)
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'
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)])
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)])
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]
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
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"):
1035 n, dz, zs, zl = post
1036 slope = dz * post_y
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]
1052 self.get_user_defined_post(tM, z0, z1, z2, dz, post_z, verts, faces, matids, uvs)
1053 return
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])
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
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
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')
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
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):
1146 n_steps = self.n_steps(step_depth)
1147 self.set_height(height / n_steps)
1148 n_stairs = len(self.stairs) - 1
1149 subs = []
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
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
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
1191 t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)
1193 # space between posts
1194 sp = stair.get_length(side)
1195 # post size
1196 t_post = post_y / sp
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
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)
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
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)
1240 t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)
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)
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)
1251 for i, post in enumerate(l_posts):
1252 self.get_post(post, x, y, z, altitude, 0, mat, verts, faces, matids, uvs)
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):
1257 n_steps = self.n_steps(step_depth)
1258 self.set_height(height / n_steps)
1259 subs = []
1260 n_stairs = len(self.stairs) - 1
1262 if side == "LEFT":
1263 offset = move_x - x_offset
1264 else:
1265 offset = move_x + x_offset
1267 for s, stair in enumerate(self.stairs):
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)
1283 # space between posts
1284 sp = stair.get_length(side)
1286 t_step, n_step = stair.n_posts(post_spacing, side, respect_edges)
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
1294 # post size
1295 t_post = (post_y + panel_dist) / sp
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)
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):
1321 params = [(stair.z_mode, stair.l_shape, stair.r_shape,
1322 stair.bottom_z, stair.steps_type) for stair in self.stairs]
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
1359 def make_profile(self, profile, idmat, side, slice, height, step_depth,
1360 x_offset, z_offset, extend, verts, faces, matids, uvs):
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)
1370 n_steps = self.n_steps(step_depth)
1371 self.set_height(height / n_steps)
1373 n_stairs = len(self.stairs) - 1
1375 if n_stairs < 0:
1376 return
1378 sections = []
1379 sections.append([])
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)
1386 for s, stair in enumerate(self.stairs):
1387 n_step = 1
1388 is_circle = False
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
1403 if is_circle:
1404 n_step = 3 * stair.n_step
1406 t_step = 1 / n_step
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
1426 if s == n_stairs:
1427 n_step += 1
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]
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)
1446 t = 1 + extend / self.stairs[-1].length
1447 self.stairs[-1].get_lerp_vect(sections[-1], side, 1, t, True)
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
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
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')
1493 def set_matids(self, id_materials):
1494 for stair in self.stairs:
1495 stair.set_matids(id_materials)
1498 def update(self, context):
1499 self.update(context)
1502 def update_manipulators(self, context):
1503 self.update(context, manipulable_refresh=True)
1506 def 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
1538 materials_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)
1548 class archipack_stair_material(PropertyGroup):
1549 index : EnumProperty(
1550 items=materials_enum,
1551 default='4',
1552 update=update
1555 def find_datablock_in_selection(self, context):
1557 find witch selected object this instance belongs to
1558 provide support for "copy to selected"
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
1569 def update(self, context):
1570 props = self.find_datablock_in_selection(context)
1571 if props is not None:
1572 props.update(context)
1575 class 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)
1585 default='S_STAIR',
1586 update=update_manipulators
1588 length : FloatProperty(
1589 name="Length",
1590 min=0.01,
1591 default=2.0,
1592 unit='LENGTH', subtype='DISTANCE',
1593 update=update
1595 radius : FloatProperty(
1596 name="Radius",
1597 min=0.01,
1598 default=0.7,
1599 unit='LENGTH', subtype='DISTANCE',
1600 update=update
1602 da : FloatProperty(
1603 name="Angle",
1604 min=-pi,
1605 max=pi,
1606 default=pi / 2,
1607 subtype='ANGLE', unit='ROTATION',
1608 update=update
1610 left_shape : EnumProperty(
1611 items=(
1612 ('RECTANGLE', 'Straight', '', 0),
1613 ('CIRCLE', 'Curved ', '', 1)
1615 default='RECTANGLE',
1616 update=update
1618 right_shape : EnumProperty(
1619 items=(
1620 ('RECTANGLE', 'Straight', '', 0),
1621 ('CIRCLE', 'Curved ', '', 1)
1623 default='RECTANGLE',
1624 update=update
1626 manipulators : CollectionProperty(type=archipack_manipulator)
1628 def find_datablock_in_selection(self, context):
1630 find witch selected object this instance belongs to
1631 provide support for "copy to selected"
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
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)
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")
1671 class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
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
1680 step_depth : FloatProperty(
1681 name="Going",
1682 min=0.2,
1683 default=0.25,
1684 unit='LENGTH', subtype='DISTANCE',
1685 update=update
1687 width : FloatProperty(
1688 name="Width",
1689 min=0.01,
1690 default=1.2,
1691 unit='LENGTH', subtype='DISTANCE',
1692 update=update
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
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
1708 x_offset : FloatProperty(
1709 name="Offset",
1710 default=0.0, precision=2, step=1,
1711 unit='LENGTH', subtype='DISTANCE',
1712 update=update
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
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
1728 radius : FloatProperty(
1729 name="Radius",
1730 min=0.5,
1731 default=0.7,
1732 unit='LENGTH', subtype='DISTANCE',
1733 update=update
1735 da : FloatProperty(
1736 name="Angle",
1737 min=-pi,
1738 max=pi,
1739 default=pi / 2,
1740 subtype='ANGLE', unit='ROTATION',
1741 update=update
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
1751 steps_type : EnumProperty(
1752 name="Steps",
1753 items=(
1754 ('CLOSED', 'Closed', '', 0),
1755 ('FULL', 'Full height', '', 1),
1756 ('OPEN', 'Open ', '', 2)
1758 default='CLOSED',
1759 update=update
1761 nose_type : EnumProperty(
1762 name="Nosing",
1763 items=(
1764 ('STRAIGHT', 'Straight', '', 0),
1765 ('OBLIQUE', 'Oblique', '', 1),
1767 default='STRAIGHT',
1768 update=update
1770 left_shape : EnumProperty(
1771 items=(
1772 ('RECTANGLE', 'Straight', '', 0),
1773 ('CIRCLE', 'Curved ', '', 1)
1775 default='RECTANGLE',
1776 update=update
1778 right_shape : EnumProperty(
1779 items=(
1780 ('RECTANGLE', 'Straight', '', 0),
1781 ('CIRCLE', 'Curved ', '', 1)
1783 default='RECTANGLE',
1784 update=update
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)
1793 default='STANDARD',
1794 update=update
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),
1804 default='STAIR_I', update=update_preset
1806 left_post : BoolProperty(
1807 name='left',
1808 default=True,
1809 update=update
1811 right_post : BoolProperty(
1812 name='right',
1813 default=True,
1814 update=update
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
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
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
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
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
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
1858 post_corners : BoolProperty(
1859 name="Only on edges",
1860 update=update,
1861 default=False
1863 user_defined_post_enable : BoolProperty(
1864 name="User",
1865 update=update,
1866 default=True
1868 user_defined_post : StringProperty(
1869 name="User defined",
1870 update=update
1872 idmat_post : EnumProperty(
1873 name="Post",
1874 items=materials_enum,
1875 default='4',
1876 update=update
1878 left_subs : BoolProperty(
1879 name='left',
1880 default=False,
1881 update=update
1883 right_subs : BoolProperty(
1884 name='right',
1885 default=False,
1886 update=update
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
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
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
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
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
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
1930 subs_bottom : EnumProperty(
1931 name="Bottom",
1932 items=(
1933 ('STEP', 'Follow step', '', 0),
1934 ('LINEAR', 'Linear', '', 1),
1936 default='STEP',
1937 update=update
1939 user_defined_subs_enable : BoolProperty(
1940 name="User",
1941 update=update,
1942 default=True
1944 user_defined_subs : StringProperty(
1945 name="User defined",
1946 update=update
1948 idmat_subs : EnumProperty(
1949 name="Subs",
1950 items=materials_enum,
1951 default='4',
1952 update=update
1954 left_panel : BoolProperty(
1955 name='left',
1956 default=True,
1957 update=update
1959 right_panel : BoolProperty(
1960 name='right',
1961 default=True,
1962 update=update
1964 panel_alt : FloatProperty(
1965 name="Altitude",
1966 default=0.25, precision=2, step=1,
1967 unit='LENGTH', subtype='DISTANCE',
1968 update=update
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
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
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
1991 panel_offset_x : FloatProperty(
1992 name="Offset",
1993 default=0.0, precision=2, step=1,
1994 unit='LENGTH', subtype='DISTANCE',
1995 update=update
1997 idmat_panel : EnumProperty(
1998 name="Panels",
1999 items=materials_enum,
2000 default='5',
2001 update=update
2003 left_rail : BoolProperty(
2004 name="left",
2005 update=update,
2006 default=False
2008 right_rail : BoolProperty(
2009 name="right",
2010 update=update,
2011 default=False
2013 rail_n : IntProperty(
2014 name="#",
2015 default=1,
2016 min=0,
2017 max=31,
2018 update=update
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
2028 size=31,
2029 min=0.001,
2030 precision=2, step=1,
2031 unit='LENGTH',
2032 update=update
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
2042 size=31,
2043 min=0.001,
2044 precision=2, step=1,
2045 unit='LENGTH',
2046 update=update
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
2056 size=31,
2057 precision=2, step=1,
2058 unit='LENGTH',
2059 update=update
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
2069 size=31,
2070 precision=2, step=1,
2071 unit='LENGTH',
2072 update=update
2074 rail_mat : CollectionProperty(type=archipack_stair_material)
2076 left_handrail : BoolProperty(
2077 name="left",
2078 update=update,
2079 default=True
2081 right_handrail : BoolProperty(
2082 name="right",
2083 update=update,
2084 default=True
2086 handrail_offset : FloatProperty(
2087 name="Offset",
2088 default=0.0, precision=2, step=1,
2089 unit='LENGTH', subtype='DISTANCE',
2090 update=update
2092 handrail_alt : FloatProperty(
2093 name="Altitude",
2094 default=1.0, precision=2, step=1,
2095 unit='LENGTH', subtype='DISTANCE',
2096 update=update
2098 handrail_extend : FloatProperty(
2099 name="Extend",
2100 default=0.1, precision=2, step=1,
2101 unit='LENGTH', subtype='DISTANCE',
2102 update=update
2104 handrail_slice_left : BoolProperty(
2105 name='Slice',
2106 default=True,
2107 update=update
2109 handrail_slice_right : BoolProperty(
2110 name='Slice',
2111 default=True,
2112 update=update
2114 handrail_profil : EnumProperty(
2115 name="Profil",
2116 items=(
2117 ('SQUARE', 'Square', '', 0),
2118 ('CIRCLE', 'Circle', '', 1),
2119 ('COMPLEX', 'Circle over square', '', 2)
2121 default='SQUARE',
2122 update=update
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
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
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
2146 left_string : BoolProperty(
2147 name="left",
2148 update=update,
2149 default=False
2151 right_string : BoolProperty(
2152 name="right",
2153 update=update,
2154 default=False
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
2163 string_z : FloatProperty(
2164 name="Height",
2165 default=0.3, precision=2, step=1,
2166 unit='LENGTH', subtype='DISTANCE',
2167 update=update
2169 string_offset : FloatProperty(
2170 name="Offset",
2171 default=0.0, precision=2, step=1,
2172 unit='LENGTH', subtype='DISTANCE',
2173 update=update
2175 string_alt : FloatProperty(
2176 name="Altitude",
2177 default=-0.04, precision=2, step=1,
2178 unit='LENGTH', subtype='DISTANCE',
2179 update=update
2182 idmat_bottom : EnumProperty(
2183 name="Bottom",
2184 items=materials_enum,
2185 default='1',
2186 update=update
2188 idmat_raise : EnumProperty(
2189 name="Raise",
2190 items=materials_enum,
2191 default='1',
2192 update=update
2194 idmat_step_front : EnumProperty(
2195 name="Step front",
2196 items=materials_enum,
2197 default='3',
2198 update=update
2200 idmat_top : EnumProperty(
2201 name="Top",
2202 items=materials_enum,
2203 default='3',
2204 update=update
2206 idmat_side : EnumProperty(
2207 name="Side",
2208 items=materials_enum,
2209 default='1',
2210 update=update
2212 idmat_step_side : EnumProperty(
2213 name="Step Side",
2214 items=materials_enum,
2215 default='3',
2216 update=update
2218 idmat_handrail : EnumProperty(
2219 name="Handrail",
2220 items=materials_enum,
2221 default='3',
2222 update=update
2224 idmat_string : EnumProperty(
2225 name="String",
2226 items=materials_enum,
2227 default='3',
2228 update=update
2231 # UI layout related
2232 parts_expand : BoolProperty(
2233 default=False
2235 steps_expand : BoolProperty(
2236 default=False
2238 rail_expand : BoolProperty(
2239 default=False
2241 idmats_expand : BoolProperty(
2242 default=False
2244 handrail_expand : BoolProperty(
2245 default=False
2247 string_expand : BoolProperty(
2248 default=False
2250 post_expand : BoolProperty(
2251 default=False
2253 panel_expand : BoolProperty(
2254 default=False
2256 subs_expand : BoolProperty(
2257 default=False
2260 auto_update : BoolProperty(
2261 options={'SKIP_SAVE'},
2262 default=True,
2263 update=update_manipulators
2266 def setup_manipulators(self):
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))
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'
2283 def update_parts(self):
2285 # remove rails materials
2286 for i in range(len(self.rail_mat), self.rail_n, -1):
2287 self.rail_mat.remove(i - 1)
2289 # add rails
2290 for i in range(len(self.rail_mat), self.rail_n):
2291 self.rail_mat.add()
2293 # remove parts
2294 for i in range(len(self.parts), self.n_parts, -1):
2295 self.parts.remove(i - 1)
2297 # add parts
2298 for i in range(len(self.parts), self.n_parts):
2299 self.parts.add()
2301 self.setup_manipulators()
2303 def update(self, context, manipulable_refresh=False):
2305 o = self.find_in_selection(context, self.auto_update)
2307 if o is None:
2308 return
2310 # clean up manipulators before any data model change
2311 if manipulable_refresh:
2312 self.manipulable_disable(context)
2314 self.update_parts()
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)]
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
2330 width_left = 0.5 * self.width - self.x_offset
2331 width_right = 0.5 * self.width + self.x_offset
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)])
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)
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
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)
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)
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
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)
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)
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)
2406 # reset user def posts
2407 g.user_defined_post = None
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)
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)
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)
2427 g.user_defined_post = None
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)
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)
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)
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)
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)]]
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)]
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)
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)
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))]
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)
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)
2498 bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs, weld=True, clean=True)
2500 # enable manipulators rebuild
2501 if manipulable_refresh:
2502 self.manipulable_refresh = True
2504 self.restore_context(context)
2506 def manipulable_setup(self, context):
2508 TODO: Implement the setup part as per parent object basis
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))
2516 self.manipulable_disable(context)
2517 o = context.active_object
2519 self.setup_manipulators()
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))
2529 if self.presets in ['STAIR_U', 'STAIR_L']:
2530 self.manip_stack.append(self.parts[1].manipulators[0].setup(context, o, self))
2532 for m in self.manipulators:
2533 self.manip_stack.append(m.setup(context, o, self))
2536 class 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'
2544 @classmethod
2545 def poll(cls, context):
2546 return archipack_stair.filter(context.active_object)
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)
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)
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)
2613 row.prop(prop, 'left_handrail')
2614 row.prop(prop, 'right_handrail')
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')
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')
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="")
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)
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="")
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')
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="")
2720 box = layout.box()
2721 row = box.row()
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)
2740 # ------------------------------------------------------------------
2741 # Define operator class to create object
2742 # ------------------------------------------------------------------
2745 class 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'}
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
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'}
2780 # ------------------------------------------------------------------
2781 # Define operator class to manipulate object
2782 # ------------------------------------------------------------------
2785 class ARCHIPACK_OT_stair_manipulate(Operator):
2786 bl_idname = "archipack.stair_manipulate"
2787 bl_label = "Manipulate"
2788 bl_description = "Manipulate"
2789 bl_options = {'REGISTER', 'UNDO'}
2791 @classmethod
2792 def poll(self, context):
2793 return archipack_stair.filter(context.active_object)
2795 def invoke(self, context, event):
2796 d = archipack_stair.datablock(context.active_object)
2797 d.manipulable_invoke(context)
2798 return {'FINISHED'}
2801 # ------------------------------------------------------------------
2802 # Define operator class to load / save presets
2803 # ------------------------------------------------------------------
2806 class 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"
2813 class 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"
2819 @property
2820 def blacklist(self):
2821 return ['manipulators']
2824 def 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)
2836 def 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)