1 # GPL # original by Buerbaum Martin (Pontiac), Elod Csirmaz
4 from mathutils
import *
6 from bpy
.types
import Operator
7 from bpy
.props
import (
15 # List of safe functions for eval()
16 safe_list
= ['math', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh',
17 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot',
18 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians',
19 'sin', 'sinh', 'sqrt', 'tan', 'tanh']
21 # Use the list to filter the local namespace
22 safe_dict
= dict((k
, globals().get(k
, None)) for k
in safe_list
)
25 # Stores the values of a list of properties and the
26 # operator id in a property group ('recall_op') inside the object
27 # Could (in theory) be used for non-objects.
28 # Note: Replaces any existing property group with the same name!
29 # ob ... Object to store the properties in
30 # op ... The operator that should be used
31 # op_args ... A dictionary with valid Blender
32 # properties (operator arguments/parameters)
35 # Create a new mesh (object) from verts/edges/faces
36 # verts/edges/faces ... List of vertices/edges/faces for the
37 # new mesh (as used in from_pydata)
38 # name ... Name of the new mesh (& object)
40 def create_mesh_object(context
, verts
, edges
, faces
, name
):
43 mesh
= bpy
.data
.meshes
.new(name
)
45 # Make a mesh from a list of verts/edges/faces
46 mesh
.from_pydata(verts
, edges
, faces
)
48 # Update mesh geometry after adding stuff
51 from bpy_extras
import object_utils
52 return object_utils
.object_data_add(context
, mesh
, operator
=None)
55 # A very simple "bridge" tool
57 def createFaces(vertIdx1
, vertIdx2
, closed
=False, flipped
=False):
60 if not vertIdx1
or not vertIdx2
:
63 if len(vertIdx1
) < 2 and len(vertIdx2
) < 2:
67 if (len(vertIdx1
) != len(vertIdx2
)):
68 if (len(vertIdx1
) == 1 and len(vertIdx2
) > 1):
76 # Bridge the start with the end
83 face
.append(vertIdx1
[total
- 1])
87 face
= [vertIdx2
[0], vertIdx1
[0]]
89 face
.append(vertIdx1
[total
- 1])
90 face
.append(vertIdx2
[total
- 1])
93 # Bridge the rest of the faces
94 for num
in range(total
- 1):
97 face
= [vertIdx2
[num
], vertIdx1
[0], vertIdx2
[num
+ 1]]
99 face
= [vertIdx2
[num
], vertIdx1
[num
],
100 vertIdx1
[num
+ 1], vertIdx2
[num
+ 1]]
104 face
= [vertIdx1
[0], vertIdx2
[num
], vertIdx2
[num
+ 1]]
106 face
= [vertIdx1
[num
], vertIdx2
[num
],
107 vertIdx2
[num
+ 1], vertIdx1
[num
+ 1]]
113 class AddZFunctionSurface(Operator
):
114 bl_idname
= "mesh.primitive_z_function_surface"
115 bl_label
= "Add Z Function Surface"
116 bl_description
= "Add a surface defined defined by a function z=f(x,y)"
117 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
119 equation
: StringProperty(
121 description
="Equation for z=f(x,y)",
122 default
="1 - ( x**2 + y**2 )"
125 name
="X Subdivisions",
126 description
="Number of vertices in x direction",
132 name
="Y Subdivisions",
133 description
="Number of vertices in y direction",
138 size_x
: FloatProperty(
140 description
="Size of the x axis",
146 size_y
: FloatProperty(
148 description
="Size of the y axis",
155 def execute(self
, context
):
156 equation
= self
.equation
165 delta_x
= size_x
/ (div_x
- 1)
166 delta_y
= size_y
/ (div_y
- 1)
167 start_x
= -(size_x
/ 2.0)
168 start_y
= -(size_y
/ 2.0)
175 compile(equation
, __file__
, 'eval'),
176 {"__builtins__": None},
180 # WARNING is used to prevent the constant pop-up spam
181 self
.report({'WARNING'},
182 "Error parsing expression: {} "
183 "(Check the console for more info)".format(equation
))
184 print("\n[Add Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
188 for row_x
in range(div_x
):
190 x
= start_x
+ row_x
* delta_x
192 for row_y
in range(div_y
):
193 y
= start_y
+ row_y
* delta_y
199 # Try to evaluate the equation.
201 z
= float(eval(*expr_args
))
204 self
.report({'WARNING'},
205 "Error evaluating expression: {} "
206 "(Check the console for more info)".format(equation
))
207 print("\n[Add Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
211 edgeloop_cur
.append(len(verts
))
212 verts
.append((x
, y
, z
))
214 if len(edgeloop_prev
) > 0:
215 faces_row
= createFaces(edgeloop_prev
, edgeloop_cur
)
216 faces
.extend(faces_row
)
218 edgeloop_prev
= edgeloop_cur
220 base
= create_mesh_object(context
, verts
, [], faces
, "Z Function")
222 self
.report({'WARNING'}, "Z Equation - No expression is given")
229 def xyz_function_surface_faces(self
, x_eq
, y_eq
, z_eq
,
230 range_u_min
, range_u_max
, range_u_step
, wrap_u
,
231 range_v_min
, range_v_max
, range_v_step
, wrap_v
,
232 a_eq
, b_eq
, c_eq
, f_eq
, g_eq
, h_eq
, n
, close_v
):
237 # Distance of each step in Blender Units
238 uStep
= (range_u_max
- range_u_min
) / range_u_step
239 vStep
= (range_v_max
- range_v_min
) / range_v_step
241 # Number of steps in the vertex creation loops.
242 # Number of steps is the number of faces
243 # => Number of points is +1 unless wrapped.
244 uRange
= range_u_step
+ 1
245 vRange
= range_v_step
+ 1
255 compile(x_eq
, __file__
.replace(".py", "_x.py"), 'eval'),
256 {"__builtins__": None},
259 compile(y_eq
, __file__
.replace(".py", "_y.py"), 'eval'),
260 {"__builtins__": None},
263 compile(z_eq
, __file__
.replace(".py", "_z.py"), 'eval'),
264 {"__builtins__": None},
267 compile(a_eq
, __file__
.replace(".py", "_a.py"), 'eval'),
268 {"__builtins__": None},
271 compile(b_eq
, __file__
.replace(".py", "_b.py"), 'eval'),
272 {"__builtins__": None},
275 compile(c_eq
, __file__
.replace(".py", "_c.py"), 'eval'),
276 {"__builtins__": None},
279 compile(f_eq
, __file__
.replace(".py", "_f.py"), 'eval'),
280 {"__builtins__": None},
283 compile(g_eq
, __file__
.replace(".py", "_g.py"), 'eval'),
284 {"__builtins__": None},
287 compile(h_eq
, __file__
.replace(".py", "_h.py"), 'eval'),
288 {"__builtins__": None},
292 self
.report({'WARNING'}, "Error parsing expression(s) - "
293 "Check the console for more info")
294 print("\n[Add X, Y, Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
297 for vN
in range(vRange
):
298 v
= range_v_min
+ (vN
* vStep
)
300 for uN
in range(uRange
):
301 u
= range_u_min
+ (uN
* uStep
)
308 # Try to evaluate the equations.
310 safe_dict
['a'] = float(eval(*expr_args_a
))
311 safe_dict
['b'] = float(eval(*expr_args_b
))
312 safe_dict
['c'] = float(eval(*expr_args_c
))
313 safe_dict
['f'] = float(eval(*expr_args_f
))
314 safe_dict
['g'] = float(eval(*expr_args_g
))
315 safe_dict
['h'] = float(eval(*expr_args_h
))
318 float(eval(*expr_args_x
)),
319 float(eval(*expr_args_y
)),
320 float(eval(*expr_args_z
))))
323 self
.report({'WARNING'}, "Error evaluating expression(s) - "
324 "Check the console for more info")
325 print("\n[Add X, Y, Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
328 for vN
in range(range_v_step
):
331 if wrap_v
and (vNext
>= vRange
):
334 for uN
in range(range_u_step
):
337 if wrap_u
and (uNext
>= uRange
):
340 faces
.append([(vNext
* uRange
) + uNext
,
341 (vNext
* uRange
) + uN
,
343 (vN
* uRange
) + uNext
])
345 if close_v
and wrap_u
and (not wrap_v
):
346 for uN
in range(1, range_u_step
- 1):
349 range_u_step
- 1 - uN
,
350 range_u_step
- 2 - uN
])
352 range_v_step
* uRange
,
353 range_v_step
* uRange
+ uN
,
354 range_v_step
* uRange
+ uN
+ 1])
359 # Original Script "Parametric.py" by Ed Mackey.
360 # -> http://www.blinken.com/blender-plugins.php
361 # Partly converted for Blender 2.5 by tuga3d.
364 # x = sin(2*pi*u)*sin(pi*v)
365 # y = cos(2*pi*u)*sin(pi*v)
371 # x = 1.2**v*(sin(u)**2 *sin(v))
372 # y = 1.2**v*(sin(u)*cos(u))
373 # z = 1.2**v*(sin(u)**2 *cos(v))
379 class AddXYZFunctionSurface(Operator
):
380 bl_idname
= "mesh.primitive_xyz_function_surface"
381 bl_label
= "Add X, Y, Z Function Surface"
382 bl_description
= ("Add a surface defined defined by 3 functions:\n"
383 "x=F1(u,v), y=F2(u,v) and z=F3(u,v)")
384 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
386 x_eq
: StringProperty(
388 description
="Equation for x=F(u,v). "
389 "Also available: n, a, b, c, f, g, h",
390 default
="cos(v)*(1+cos(u))*sin(v/8)"
392 y_eq
: StringProperty(
394 description
="Equation for y=F(u,v). "
395 "Also available: n, a, b, c, f, g, h",
396 default
="sin(u)*sin(v/8)+cos(v/8)*1.5"
398 z_eq
: StringProperty(
400 description
="Equation for z=F(u,v). "
401 "Also available: n, a, b, c, f, g, h",
402 default
="sin(v)*(1+cos(u))*sin(v/8)"
404 range_u_min
: FloatProperty(
406 description
="Minimum U value. Lower boundary of U range",
411 range_u_max
: FloatProperty(
413 description
="Maximum U value. Upper boundary of U range",
418 range_u_step
: IntProperty(
420 description
="U Subdivisions",
425 wrap_u
: BoolProperty(
427 description
="U Wrap around",
430 range_v_min
: FloatProperty(
432 description
="Minimum V value. Lower boundary of V range",
437 range_v_max
: FloatProperty(
439 description
="Maximum V value. Upper boundary of V range",
444 range_v_step
: IntProperty(
446 description
="V Subdivisions",
451 wrap_v
: BoolProperty(
453 description
="V Wrap around",
456 close_v
: BoolProperty(
458 description
="Create faces for first and last "
459 "V values (only if U is wrapped)",
463 name
="Number of objects (n=0..N-1)",
464 description
="The parameter n will be the index "
465 "of the current object, 0 to N-1",
470 a_eq
: StringProperty(
471 name
="A helper function",
472 description
="Equation for a=F(u,v). Also available: n",
475 b_eq
: StringProperty(
476 name
="B helper function",
477 description
="Equation for b=F(u,v). Also available: n",
480 c_eq
: StringProperty(
481 name
="C helper function",
482 description
="Equation for c=F(u,v). Also available: n",
485 f_eq
: StringProperty(
486 name
="F helper function",
487 description
="Equation for f=F(u,v). Also available: n, a, b, c",
490 g_eq
: StringProperty(
491 name
="G helper function",
492 description
="Equation for g=F(u,v). Also available: n, a, b, c",
495 h_eq
: StringProperty(
496 name
="H helper function",
497 description
="Equation for h=F(u,v). Also available: n, a, b, c",
500 show_wire
: BoolProperty(
501 name
="Show wireframe",
503 description
="Add the object’s wireframe over solid drawing"
505 edit_mode
: BoolProperty(
506 name
="Show in edit mode",
508 description
="Show in edit mode"
511 def execute(self
, context
):
512 for n
in range(0, self
.n_eq
):
513 verts
, faces
= xyz_function_surface_faces(
538 obj
= create_mesh_object(context
, verts
, [], faces
, "XYZ Function")
544 bpy
.ops
.object.mode_set(mode
= 'EDIT')
546 bpy
.ops
.object.mode_set(mode
= 'OBJECT')