1 # SPDX-FileCopyrightText: 2010-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Original by Buerbaum Martin (Pontiac), Elod Csirmaz
10 from mathutils
import *
12 from bpy
.types
import Operator
13 from bpy
.props
import (
21 # List of safe functions for eval()
22 safe_list
= ['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh',
23 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot',
24 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians',
25 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'gcd']
27 # Use the list to filter the local namespace
28 safe_dict
= dict((k
, globals().get(k
, None)) for k
in safe_list
)
29 safe_dict
['math'] = math
30 safe_dict
['numpy'] = safe_dict
['np'] = numpy
31 safe_dict
['lcm'] = numpy
.lcm
32 safe_dict
['max'] = max
33 safe_dict
['min'] = min
36 # Stores the values of a list of properties and the
37 # operator id in a property group ('recall_op') inside the object
38 # Could (in theory) be used for non-objects.
39 # Note: Replaces any existing property group with the same name!
40 # ob ... Object to store the properties in
41 # op ... The operator that should be used
42 # op_args ... A dictionary with valid Blender
43 # properties (operator arguments/parameters)
46 # Create a new mesh (object) from verts/edges/faces
47 # verts/edges/faces ... List of vertices/edges/faces for the
48 # new mesh (as used in from_pydata)
49 # name ... Name of the new mesh (& object)
51 def create_mesh_object(context
, verts
, edges
, faces
, name
):
54 mesh
= bpy
.data
.meshes
.new(name
)
56 # Make a mesh from a list of verts/edges/faces
57 mesh
.from_pydata(verts
, edges
, faces
)
59 # Update mesh geometry after adding stuff
62 from bpy_extras
import object_utils
63 return object_utils
.object_data_add(context
, mesh
, operator
=None)
66 # A very simple "bridge" tool
68 def createFaces(vertIdx1
, vertIdx2
, closed
=False, flipped
=False):
71 if not vertIdx1
or not vertIdx2
:
74 if len(vertIdx1
) < 2 and len(vertIdx2
) < 2:
78 if (len(vertIdx1
) != len(vertIdx2
)):
79 if (len(vertIdx1
) == 1 and len(vertIdx2
) > 1):
87 # Bridge the start with the end
94 face
.append(vertIdx1
[total
- 1])
98 face
= [vertIdx2
[0], vertIdx1
[0]]
100 face
.append(vertIdx1
[total
- 1])
101 face
.append(vertIdx2
[total
- 1])
104 # Bridge the rest of the faces
105 for num
in range(total
- 1):
108 face
= [vertIdx2
[num
], vertIdx1
[0], vertIdx2
[num
+ 1]]
110 face
= [vertIdx2
[num
], vertIdx1
[num
],
111 vertIdx1
[num
+ 1], vertIdx2
[num
+ 1]]
115 face
= [vertIdx1
[0], vertIdx2
[num
], vertIdx2
[num
+ 1]]
117 face
= [vertIdx1
[num
], vertIdx2
[num
],
118 vertIdx2
[num
+ 1], vertIdx1
[num
+ 1]]
124 class AddZFunctionSurface(Operator
):
125 bl_idname
= "mesh.primitive_z_function_surface"
126 bl_label
= "Add Z Function Surface"
127 bl_description
= "Add a surface defined defined by a function z=f(x,y)"
128 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
130 equation
: StringProperty(
132 description
="Equation for z=f(x,y)",
133 default
="1 - ( x**2 + y**2 )"
136 name
="X Subdivisions",
137 description
="Number of vertices in x direction",
143 name
="Y Subdivisions",
144 description
="Number of vertices in y direction",
149 size_x
: FloatProperty(
151 description
="Size of the x axis",
157 size_y
: FloatProperty(
159 description
="Size of the y axis",
166 def execute(self
, context
):
167 equation
= self
.equation
176 delta_x
= size_x
/ (div_x
- 1)
177 delta_y
= size_y
/ (div_y
- 1)
178 start_x
= -(size_x
/ 2.0)
179 start_y
= -(size_y
/ 2.0)
186 compile(equation
, __file__
, 'eval'),
187 {"__builtins__": None},
191 # WARNING is used to prevent the constant pop-up spam
192 self
.report({'WARNING'},
193 "Error parsing expression: {} "
194 "(Check the console for more info)".format(equation
))
195 print("\n[Add Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
199 for row_x
in range(div_x
):
201 x
= start_x
+ row_x
* delta_x
203 for row_y
in range(div_y
):
204 y
= start_y
+ row_y
* delta_y
210 # Try to evaluate the equation.
212 z
= float(eval(*expr_args
))
215 self
.report({'WARNING'},
216 "Error evaluating expression: {} "
217 "(Check the console for more info)".format(equation
))
218 print("\n[Add Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
222 edgeloop_cur
.append(len(verts
))
223 verts
.append((x
, y
, z
))
225 if len(edgeloop_prev
) > 0:
226 faces_row
= createFaces(edgeloop_prev
, edgeloop_cur
)
227 faces
.extend(faces_row
)
229 edgeloop_prev
= edgeloop_cur
231 base
= create_mesh_object(context
, verts
, [], faces
, "Z Function")
233 self
.report({'WARNING'}, "Z Equation - No expression is given")
240 def xyz_function_surface_faces(self
, x_eq
, y_eq
, z_eq
,
241 range_u_min
, range_u_max
, range_u_step
, wrap_u
,
242 range_v_min
, range_v_max
, range_v_step
, wrap_v
,
243 a_eq
, b_eq
, c_eq
, f_eq
, g_eq
, h_eq
, n
, close_v
):
248 # Distance of each step in Blender Units
249 uStep
= (range_u_max
- range_u_min
) / range_u_step
250 vStep
= (range_v_max
- range_v_min
) / range_v_step
252 # Number of steps in the vertex creation loops.
253 # Number of steps is the number of faces
254 # => Number of points is +1 unless wrapped.
255 uRange
= range_u_step
+ 1
256 vRange
= range_v_step
+ 1
266 compile(x_eq
, __file__
.replace(".py", "_x.py"), 'eval'),
267 {"__builtins__": None},
270 compile(y_eq
, __file__
.replace(".py", "_y.py"), 'eval'),
271 {"__builtins__": None},
274 compile(z_eq
, __file__
.replace(".py", "_z.py"), 'eval'),
275 {"__builtins__": None},
278 compile(a_eq
, __file__
.replace(".py", "_a.py"), 'eval'),
279 {"__builtins__": None},
282 compile(b_eq
, __file__
.replace(".py", "_b.py"), 'eval'),
283 {"__builtins__": None},
286 compile(c_eq
, __file__
.replace(".py", "_c.py"), 'eval'),
287 {"__builtins__": None},
290 compile(f_eq
, __file__
.replace(".py", "_f.py"), 'eval'),
291 {"__builtins__": None},
294 compile(g_eq
, __file__
.replace(".py", "_g.py"), 'eval'),
295 {"__builtins__": None},
298 compile(h_eq
, __file__
.replace(".py", "_h.py"), 'eval'),
299 {"__builtins__": None},
303 self
.report({'WARNING'}, "Error parsing expression(s) - "
304 "Check the console for more info")
305 print("\n[Add X, Y, Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
308 for vN
in range(vRange
):
309 v
= range_v_min
+ (vN
* vStep
)
311 for uN
in range(uRange
):
312 u
= range_u_min
+ (uN
* uStep
)
319 # Try to evaluate the equations.
321 safe_dict
['a'] = float(eval(*expr_args_a
))
322 safe_dict
['b'] = float(eval(*expr_args_b
))
323 safe_dict
['c'] = float(eval(*expr_args_c
))
324 safe_dict
['f'] = float(eval(*expr_args_f
))
325 safe_dict
['g'] = float(eval(*expr_args_g
))
326 safe_dict
['h'] = float(eval(*expr_args_h
))
329 float(eval(*expr_args_x
)),
330 float(eval(*expr_args_y
)),
331 float(eval(*expr_args_z
))))
334 self
.report({'WARNING'}, "Error evaluating expression(s) - "
335 "Check the console for more info")
336 print("\n[Add X, Y, Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
339 for vN
in range(range_v_step
):
342 if wrap_v
and (vNext
>= vRange
):
345 for uN
in range(range_u_step
):
348 if wrap_u
and (uNext
>= uRange
):
351 faces
.append([(vNext
* uRange
) + uNext
,
352 (vNext
* uRange
) + uN
,
354 (vN
* uRange
) + uNext
])
356 if close_v
and wrap_u
and (not wrap_v
):
357 for uN
in range(1, range_u_step
- 1):
360 range_u_step
- 1 - uN
,
361 range_u_step
- 2 - uN
])
363 range_v_step
* uRange
,
364 range_v_step
* uRange
+ uN
,
365 range_v_step
* uRange
+ uN
+ 1])
370 # Original Script "Parametric.py" by Ed Mackey.
371 # -> http://www.blinken.com/blender-plugins.php
372 # Partly converted for Blender 2.5 by tuga3d.
375 # x = sin(2*pi*u)*sin(pi*v)
376 # y = cos(2*pi*u)*sin(pi*v)
382 # x = 1.2**v*(sin(u)**2 *sin(v))
383 # y = 1.2**v*(sin(u)*cos(u))
384 # z = 1.2**v*(sin(u)**2 *cos(v))
390 class AddXYZFunctionSurface(Operator
):
391 bl_idname
= "mesh.primitive_xyz_function_surface"
392 bl_label
= "Add X, Y, Z Function Surface"
393 bl_description
= ("Add a surface defined defined by 3 functions:\n"
394 "x=F1(u,v), y=F2(u,v) and z=F3(u,v)")
395 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
397 x_eq
: StringProperty(
399 description
="Equation for x=F(u,v). "
400 "Also available: n, a, b, c, f, g, h",
401 default
="cos(v)*(1+cos(u))*sin(v/8)"
403 y_eq
: StringProperty(
405 description
="Equation for y=F(u,v). "
406 "Also available: n, a, b, c, f, g, h",
407 default
="sin(u)*sin(v/8)+cos(v/8)*1.5"
409 z_eq
: StringProperty(
411 description
="Equation for z=F(u,v). "
412 "Also available: n, a, b, c, f, g, h",
413 default
="sin(v)*(1+cos(u))*sin(v/8)"
415 range_u_min
: FloatProperty(
417 description
="Minimum U value. Lower boundary of U range",
422 range_u_max
: FloatProperty(
424 description
="Maximum U value. Upper boundary of U range",
429 range_u_step
: IntProperty(
431 description
="U Subdivisions",
436 wrap_u
: BoolProperty(
438 description
="U Wrap around",
441 range_v_min
: FloatProperty(
443 description
="Minimum V value. Lower boundary of V range",
448 range_v_max
: FloatProperty(
450 description
="Maximum V value. Upper boundary of V range",
455 range_v_step
: IntProperty(
457 description
="V Subdivisions",
462 wrap_v
: BoolProperty(
464 description
="V Wrap around",
467 close_v
: BoolProperty(
469 description
="Create faces for first and last "
470 "V values (only if U is wrapped)",
474 name
="Number of objects (n=0..N-1)",
475 description
="The parameter n will be the index "
476 "of the current object, 0 to N-1",
481 a_eq
: StringProperty(
482 name
="A helper function",
483 description
="Equation for a=F(u,v). Also available: n",
486 b_eq
: StringProperty(
487 name
="B helper function",
488 description
="Equation for b=F(u,v). Also available: n",
491 c_eq
: StringProperty(
492 name
="C helper function",
493 description
="Equation for c=F(u,v). Also available: n",
496 f_eq
: StringProperty(
497 name
="F helper function",
498 description
="Equation for f=F(u,v). Also available: n, a, b, c",
501 g_eq
: StringProperty(
502 name
="G helper function",
503 description
="Equation for g=F(u,v). Also available: n, a, b, c",
506 h_eq
: StringProperty(
507 name
="H helper function",
508 description
="Equation for h=F(u,v). Also available: n, a, b, c",
511 show_wire
: BoolProperty(
512 name
="Show wireframe",
514 description
="Add the object’s wireframe over solid drawing"
516 edit_mode
: BoolProperty(
517 name
="Show in edit mode",
519 description
="Show in edit mode"
522 def execute(self
, context
):
523 for n
in range(0, self
.n_eq
):
524 verts
, faces
= xyz_function_surface_faces(
549 obj
= create_mesh_object(context
, verts
, [], faces
, "XYZ Function")
552 context
.active_object
.show_wire
= True
554 context
.active_object
.show_wire
= False
557 bpy
.ops
.object.mode_set(mode
= 'EDIT')
559 bpy
.ops
.object.mode_set(mode
= 'OBJECT')