1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 # ----------------------------------------------------------
22 # Automatic generation of stairs
23 # Author: Antonio Vazquez (antonioya)
25 # ----------------------------------------------------------
26 # noinspection PyUnresolvedReferences
28 from math
import radians
, sin
, cos
29 from bpy
.types
import Operator
30 from bpy
.props
import FloatProperty
, BoolProperty
, IntProperty
, EnumProperty
31 from .achm_tools
import *
34 # ------------------------------------------------------------------
37 # ------------------------------------------------------------------
38 class AchmStairs(Operator
):
39 bl_idname
= "mesh.archimesh_stairs"
41 bl_description
= "Stairs Generator"
42 bl_category
= 'Archimesh'
43 bl_options
= {'REGISTER', 'UNDO'}
48 ('1', "Rectangular", ""),
52 description
="Type of steps",
54 radio
= FloatProperty(
57 default
=0.20, precision
=3,
58 description
='Radius factor for rounded',
61 name
="Include deformation handles",
62 description
="Include a curve to modify the stairs curve",
66 step_num
= IntProperty(
67 name
='Number of steps',
70 description
='Number total of steps',
72 max_width
= FloatProperty(
75 default
=1, precision
=3,
76 description
='Step maximum width',
78 depth
= FloatProperty(
81 default
=0.30, precision
=3,
82 description
='Depth of the step',
84 shift
= FloatProperty(
87 default
=1, precision
=3,
88 description
='Step shift in Y axis',
90 thickness
= FloatProperty(
93 default
=0.03, precision
=3,
94 description
='Step thickness',
97 name
="Variable width",
98 description
="Steps are not equal in width",
103 description
="Close all steps side to make a solid structure",
106 min_width
= FloatProperty(
109 default
=1, precision
=3,
110 description
='Step minimum width',
113 height
= FloatProperty(
116 default
=0.14, precision
=3,
117 description
='Step height',
119 front_gap
= FloatProperty(
124 description
='Front gap',
126 side_gap
= FloatProperty(
129 default
=0, precision
=3,
130 description
='Side gap',
132 crt_mat
= BoolProperty(
133 name
="Create default Cycles materials",
134 description
="Create default materials for Cycles render",
138 # -----------------------------------------------------
139 # Draw (create UI interface)
140 # -----------------------------------------------------
141 # noinspection PyUnusedLocal
142 def draw(self
, context
):
144 space
= bpy
.context
.space_data
145 if not space
.local_view
:
146 # Imperial units warning
147 if bpy
.context
.scene
.unit_settings
.system
== "IMPERIAL":
149 row
.label("Warning: Imperial units not supported", icon
='COLOR_RED')
153 row
.prop(self
, 'model')
154 if self
.model
== "2":
155 row
.prop(self
, 'radio')
157 box
.prop(self
, 'step_num')
159 row
.prop(self
, 'max_width')
160 row
.prop(self
, 'depth')
161 row
.prop(self
, 'shift')
163 row
.prop(self
, 'back')
164 row
.prop(self
, 'sizev')
166 row
.prop(self
, 'curve')
168 if self
.sizev
is True:
169 row
.prop(self
, 'min_width')
173 row
.prop(self
, 'thickness')
174 row
.prop(self
, 'height')
176 row
.prop(self
, 'front_gap')
177 if self
.model
== "1":
178 row
.prop(self
, 'side_gap')
181 if not context
.scene
.render
.engine
== 'CYCLES':
183 box
.prop(self
, 'crt_mat')
186 row
.label("Warning: Operator does not work in local view mode", icon
='ERROR')
188 # -----------------------------------------------------
190 # -----------------------------------------------------
191 # noinspection PyUnusedLocal
192 def execute(self
, context
):
193 if bpy
.context
.mode
== "OBJECT":
194 create_stairs_mesh(self
)
197 self
.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
201 # ------------------------------------------------------------------------------
203 # All custom values are passed using self container (self.myvariable)
204 # ------------------------------------------------------------------------------
205 def create_stairs_mesh(self
):
208 for o
in bpy
.data
.objects
:
212 bpy
.ops
.object.select_all(False)
214 # ------------------------
216 # ------------------------
217 mydata
= create_stairs(self
, "Stairs")
219 mystairs
.select
= True
220 bpy
.context
.scene
.objects
.active
= mystairs
221 remove_doubles(mystairs
)
222 set_normals(mystairs
)
223 set_modifier_mirror(mystairs
, "X")
224 # ------------------------
225 # Create curve handles
226 # ------------------------
228 x
= mystairs
.location
.x
229 y
= mystairs
.location
.y
230 z
= mystairs
.location
.z
235 myp
= [((0, 0, 0), (- 0.25, 0, 0), (0.25, 0, 0)),
236 ((x1
, 0, 0), (x1
- 0.25, 0, 0), (x1
+ 0.25, 0, 0))] # double element
237 mycurve
= create_bezier("Stairs_handle", myp
, (x
, y
, z
))
238 set_modifier_curve(mystairs
, mycurve
)
240 # ------------------------
242 # ------------------------
243 if self
.crt_mat
and bpy
.context
.scene
.render
.engine
== 'CYCLES':
245 mat
= create_diffuse_material("Stairs_material", False, 0.8, 0.8, 0.8)
246 set_material(mystairs
, mat
)
248 bpy
.ops
.object.select_all(False)
249 mystairs
.select
= True
250 bpy
.context
.scene
.objects
.active
= mystairs
255 # ------------------------------------------------------------------------------
256 # Create rectangular Stairs
257 # ------------------------------------------------------------------------------
258 def create_stairs(self
, objname
):
264 lastpoint
= (0, 0, 0)
265 for s
in range(0, self
.step_num
):
266 if self
.model
== "1":
267 mydata
= create_rect_step(self
, lastpoint
, myvertex
, myfaces
, index
, s
)
268 if self
.model
== "2":
269 mydata
= create_round_step(self
, lastpoint
, myvertex
, myfaces
, index
, s
)
271 lastpoint
= mydata
[1]
273 mesh
= bpy
.data
.meshes
.new(objname
)
274 myobject
= bpy
.data
.objects
.new(objname
, mesh
)
276 myobject
.location
= bpy
.context
.scene
.cursor_location
277 bpy
.context
.scene
.objects
.link(myobject
)
279 mesh
.from_pydata(myvertex
, [], myfaces
)
280 mesh
.update(calc_edges
=True)
282 return myobject
, lastpoint
285 # ------------------------------------------------------------------------------
286 # Create rectangular step
287 # ------------------------------------------------------------------------------
288 def create_rect_step(self
, origin
, myvertex
, myfaces
, index
, step
):
293 max_depth
= y
+ self
.depth
294 if self
.back
is True:
295 max_depth
= self
.depth
* self
.step_num
297 # calculate width (no side gap)
298 if self
.sizev
is False:
299 width
= self
.max_width
/ 2
301 width
= (self
.max_width
/ 2) - (step
* (((self
.max_width
- self
.min_width
) / 2) / self
.step_num
))
304 myvertex
.extend([(x
, y
, z
), (x
, y
, z
+ self
.height
), (x
+ width
, y
, z
+ self
.height
), (x
+ width
, y
, z
)])
305 val
= y
+ self
.thickness
306 myvertex
.extend([(x
, val
, z
), (x
, val
, z
+ self
.height
), (x
+ width
, val
, z
+ self
.height
), (x
+ width
, val
, z
)])
308 myfaces
.extend([(i
+ 0, i
+ 1, i
+ 2, i
+ 3), (i
+ 4, i
+ 5, i
+ 6, i
+ 7), (i
+ 0, i
+ 3, i
+ 7, i
+ 4),
309 (i
+ 1, i
+ 2, i
+ 6, i
+ 5), (i
+ 0, i
+ 1, i
+ 5, i
+ 4), (i
+ 3, i
+ 2, i
+ 6, i
+ 7)])
311 myvertex
.extend([(x
+ width
, max_depth
, z
+ self
.height
), (x
+ width
, max_depth
, z
)])
312 myfaces
.extend([(i
+ 7, i
+ 6, i
+ 8, i
+ 9)])
314 # calculate width (side gap)
315 width
= width
+ self
.side_gap
317 # Horizontal Rectangle
319 myvertex
.extend([(x
, y
- self
.front_gap
, z
), (x
, max_depth
, z
), (x
+ width
, max_depth
, z
),
320 (x
+ width
, y
- self
.front_gap
, z
)])
321 z
= z
+ self
.thickness
322 myvertex
.extend([(x
, y
- self
.front_gap
, z
), (x
, max_depth
, z
), (x
+ width
, max_depth
, z
),
323 (x
+ width
, y
- self
.front_gap
, z
)])
324 myfaces
.extend([(i
+ 0, i
+ 1, i
+ 2, i
+ 3), (i
+ 4, i
+ 5, i
+ 6, i
+ 7), (i
+ 0, i
+ 3, i
+ 7, i
+ 4),
325 (i
+ 1, i
+ 2, i
+ 6, i
+ 5), (i
+ 3, i
+ 2, i
+ 6, i
+ 7)])
328 y
= y
+ (self
.depth
* self
.shift
)
333 # ------------------------------------------------------------------------------
334 # Create rounded step
335 # ------------------------------------------------------------------------------
336 def create_round_step(self
, origin
, myvertex
, myfaces
, index
, step
):
342 li
= [radians(270), radians(288), radians(306), radians(324), radians(342),
345 max_width
= self
.max_width
346 max_depth
= y
+ self
.depth
347 if self
.back
is True:
348 max_depth
= self
.depth
* self
.step_num
351 if self
.sizev
is True:
352 max_width
= max_width
- (step
* ((self
.max_width
- self
.min_width
) / self
.step_num
))
355 # ------------------------------------
357 # ------------------------------------
359 width
= half
- (half
* self
.radio
)
360 myradio
= half
- width
362 myvertex
.extend([(x
, y
, z
), (x
, y
, z
+ self
.height
)])
365 pos_x
= (cos(e
) * myradio
) + x
+ width
- myradio
366 pos_y
= (sin(e
) * myradio
) + y
+ myradio
368 myvertex
.extend([(pos_x
, pos_y
, z
), (pos_x
, pos_y
, z
+ self
.height
)])
371 myvertex
.extend([(x
+ width
, max_depth
, z
), (x
+ width
, max_depth
, z
+ self
.height
)])
373 myfaces
.extend([(i
, i
+ 1, i
+ 3, i
+ 2), (i
+ 2, i
+ 3, i
+ 5, i
+ 4), (i
+ 4, i
+ 5, i
+ 7, i
+ 6),
374 (i
+ 6, i
+ 7, i
+ 9, i
+ 8),
375 (i
+ 8, i
+ 9, i
+ 11, i
+ 10), (i
+ 10, i
+ 11, i
+ 13, i
+ 12), (i
+ 12, i
+ 13, i
+ 15, i
+ 14)])
378 # ------------------------------------
380 # ------------------------------------
381 # calculate width gap
382 width
= half
+ self
.front_gap
- (half
* self
.radio
)
386 myvertex
.extend([(x
, y
- self
.front_gap
, z
), (x
, y
- self
.front_gap
, z
+ self
.thickness
)])
389 pos_x
= (cos(e
) * myradio
) + x
+ width
- myradio
390 pos_y
= (sin(e
) * myradio
) + y
+ myradio
- self
.front_gap
392 myvertex
.extend([(pos_x
, pos_y
, z
), (pos_x
, pos_y
, z
+ self
.thickness
)])
395 myvertex
.extend([(pos_x
, max_depth
, z
), (pos_x
, max_depth
, z
+ self
.thickness
),
396 (x
, max_depth
, z
), (x
, max_depth
, z
+ self
.thickness
)])
398 myfaces
.extend([(i
, i
+ 1, i
+ 3, i
+ 2), (i
+ 2, i
+ 3, i
+ 5, i
+ 4), (i
+ 4, i
+ 5, i
+ 7, i
+ 6),
399 (i
+ 6, i
+ 7, i
+ 9, i
+ 8),
400 (i
+ 8, i
+ 9, i
+ 11, i
+ 10), (i
+ 10, i
+ 11, i
+ 13, i
+ 12), (i
+ 12, i
+ 13, i
+ 15, i
+ 14),
401 (i
, i
+ 2, i
+ 4, i
+ 6, i
+ 8, i
+ 10, i
+ 12, i
+ 14, i
+ 16),
402 (i
+ 1, i
+ 3, i
+ 5, i
+ 7, i
+ 9, i
+ 11, i
+ 13, i
+ 15, i
+ 17),
403 (i
+ 14, i
+ 15, i
+ 17, i
+ 16)])
406 z
= z
+ self
.thickness
409 y
= y
+ (self
.depth
* self
.shift
)
414 # ------------------------------------------------------------------------------
415 # Create bezier curve
416 # ------------------------------------------------------------------------------
417 def create_bezier(objname
, points
, origin
):
418 curvedata
= bpy
.data
.curves
.new(name
=objname
, type='CURVE')
419 curvedata
.dimensions
= '3D'
421 myobject
= bpy
.data
.objects
.new(objname
, curvedata
)
422 myobject
.location
= origin
423 myobject
.rotation_euler
[2] = radians(90)
425 bpy
.context
.scene
.objects
.link(myobject
)
427 polyline
= curvedata
.splines
.new('BEZIER')
428 polyline
.bezier_points
.add(len(points
) - 1)
430 for idx
, (knot
, h1
, h2
) in enumerate(points
):
431 point
= polyline
.bezier_points
[idx
]
433 point
.handle_left
= h1
434 point
.handle_right
= h2
435 point
.handle_left_type
= 'FREE'
436 point
.handle_right_type
= 'FREE'