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