Update for changes in Blender's API
[blender-addons.git] / archimesh / achm_stairs_maker.py
blobd5b1f82e68877fde0e2e9c6ad70c1921ede0d400
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 #####
19 # <pep8 compliant>
21 # ----------------------------------------------------------
22 # Automatic generation of stairs
23 # Author: Antonio Vazquez (antonioya)
25 # ----------------------------------------------------------
26 # noinspection PyUnresolvedReferences
27 import bpy
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 # ------------------------------------------------------------------
35 # Define UI class
36 # Stairs
37 # ------------------------------------------------------------------
38 class AchmStairs(Operator):
39 bl_idname = "mesh.archimesh_stairs"
40 bl_label = "Stairs"
41 bl_description = "Stairs Generator"
42 bl_category = 'Archimesh'
43 bl_options = {'REGISTER', 'UNDO'}
45 # Define properties
46 model = EnumProperty(
47 items=(
48 ('1', "Rectangular", ""),
49 ('2', "Rounded", ""),
51 name="Model",
52 description="Type of steps",
54 radio = FloatProperty(
55 name='',
56 min=0.001, max=0.500,
57 default=0.20, precision=3,
58 description='Radius factor for rounded',
60 curve = BoolProperty(
61 name="Include deformation handles",
62 description="Include a curve to modify the stairs curve",
63 default=False,
66 step_num = IntProperty(
67 name='Number of steps',
68 min=1, max=1000,
69 default=3,
70 description='Number total of steps',
72 max_width = FloatProperty(
73 name='Width',
74 min=0.001, max=10,
75 default=1, precision=3,
76 description='Step maximum width',
78 depth = FloatProperty(
79 name='Depth',
80 min=0.001, max=10,
81 default=0.30, precision=3,
82 description='Depth of the step',
84 shift = FloatProperty(
85 name='Shift',
86 min=0.001, max=1,
87 default=1, precision=3,
88 description='Step shift in Y axis',
90 thickness = FloatProperty(
91 name='Thickness',
92 min=0.001, max=10,
93 default=0.03, precision=3,
94 description='Step thickness',
96 sizev = BoolProperty(
97 name="Variable width",
98 description="Steps are not equal in width",
99 default=False,
101 back = BoolProperty(
102 name="Close sides",
103 description="Close all steps side to make a solid structure",
104 default=False,
106 min_width = FloatProperty(
107 name='',
108 min=0.001, max=10,
109 default=1, precision=3,
110 description='Step minimum width',
113 height = FloatProperty(
114 name='height',
115 min=0.001, max=10,
116 default=0.14, precision=3,
117 description='Step height',
119 front_gap = FloatProperty(
120 name='Front',
121 min=0, max=10,
122 default=0.03,
123 precision=3,
124 description='Front gap',
126 side_gap = FloatProperty(
127 name='Side',
128 min=0, max=10,
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",
135 default=True,
138 # -----------------------------------------------------
139 # Draw (create UI interface)
140 # -----------------------------------------------------
141 # noinspection PyUnusedLocal
142 def draw(self, context):
143 layout = self.layout
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":
148 row = layout.row()
149 row.label("Warning: Imperial units not supported", icon='COLOR_RED')
151 box = layout.box()
152 row = box.row()
153 row.prop(self, 'model')
154 if self.model == "2":
155 row.prop(self, 'radio')
157 box.prop(self, 'step_num')
158 row = box.row()
159 row.prop(self, 'max_width')
160 row.prop(self, 'depth')
161 row.prop(self, 'shift')
162 row = box.row()
163 row.prop(self, 'back')
164 row.prop(self, 'sizev')
165 row = box.row()
166 row.prop(self, 'curve')
167 # all equal
168 if self.sizev is True:
169 row.prop(self, 'min_width')
171 box = layout.box()
172 row = box.row()
173 row.prop(self, 'thickness')
174 row.prop(self, 'height')
175 row = box.row()
176 row.prop(self, 'front_gap')
177 if self.model == "1":
178 row.prop(self, 'side_gap')
180 box = layout.box()
181 if not context.scene.render.engine == 'CYCLES':
182 box.enabled = False
183 box.prop(self, 'crt_mat')
184 else:
185 row = layout.row()
186 row.label("Warning: Operator does not work in local view mode", icon='ERROR')
188 # -----------------------------------------------------
189 # Execute
190 # -----------------------------------------------------
191 # noinspection PyUnusedLocal
192 def execute(self, context):
193 if bpy.context.mode == "OBJECT":
194 create_stairs_mesh(self)
195 return {'FINISHED'}
196 else:
197 self.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
198 return {'CANCELLED'}
201 # ------------------------------------------------------------------------------
202 # Generate mesh data
203 # All custom values are passed using self container (self.myvariable)
204 # ------------------------------------------------------------------------------
205 def create_stairs_mesh(self):
207 # deactivate others
208 for o in bpy.data.objects:
209 if o.select is True:
210 o.select = False
212 bpy.ops.object.select_all(False)
214 # ------------------------
215 # Create stairs
216 # ------------------------
217 mydata = create_stairs(self, "Stairs")
218 mystairs = mydata[0]
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 # ------------------------
227 if self.curve:
228 x = mystairs.location.x
229 y = mystairs.location.y
230 z = mystairs.location.z
232 last = mydata[1]
233 x1 = last[1] # use y
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 # ------------------------
241 # Create materials
242 # ------------------------
243 if self.crt_mat and bpy.context.scene.render.engine == 'CYCLES':
244 # Stairs material
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
252 return
255 # ------------------------------------------------------------------------------
256 # Create rectangular Stairs
257 # ------------------------------------------------------------------------------
258 def create_stairs(self, objname):
260 myvertex = []
261 myfaces = []
262 index = 0
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)
270 index = mydata[0]
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):
289 x = origin[0]
290 y = origin[1]
291 z = origin[2]
292 i = index
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
300 else:
301 width = (self.max_width / 2) - (step * (((self.max_width - self.min_width) / 2) / self.step_num))
303 # Vertical Rectangle
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)])
310 # Side plane
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)])
313 i += 10
314 # calculate width (side gap)
315 width = width + self.side_gap
317 # Horizontal Rectangle
318 z = z + self.height
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)])
326 i += 8
327 # remap origin
328 y = y + (self.depth * self.shift)
330 return i, (x, y, z)
333 # ------------------------------------------------------------------------------
334 # Create rounded step
335 # ------------------------------------------------------------------------------
336 def create_round_step(self, origin, myvertex, myfaces, index, step):
337 x = origin[0]
338 y = origin[1]
339 z = origin[2]
340 pos_x = None
341 i = index
342 li = [radians(270), radians(288), radians(306), radians(324), radians(342),
343 radians(0)]
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
350 # Resize for width
351 if self.sizev is True:
352 max_width = max_width - (step * ((self.max_width - self.min_width) / self.step_num))
354 half = max_width / 2
355 # ------------------------------------
356 # Vertical
357 # ------------------------------------
358 # calculate width
359 width = half - (half * self.radio)
360 myradio = half - width
362 myvertex.extend([(x, y, z), (x, y, z + self.height)])
363 # Round
364 for e in li:
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)])
370 # back point
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)])
377 i += 16
378 # ------------------------------------
379 # Horizontal
380 # ------------------------------------
381 # calculate width gap
382 width = half + self.front_gap - (half * self.radio)
384 z = z + self.height
385 # Vertical
386 myvertex.extend([(x, y - self.front_gap, z), (x, y - self.front_gap, z + self.thickness)])
387 # Round
388 for e in li:
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)])
394 # back points
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)])
405 i += 18
406 z = z + self.thickness
408 # remap origin
409 y = y + (self.depth * self.shift)
411 return i, (x, y, z)
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]
432 point.co = knot
433 point.handle_left = h1
434 point.handle_right = h2
435 point.handle_left_type = 'FREE'
436 point.handle_right_type = 'FREE'
438 return myobject