1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Original by Buerbaum Martin (Pontiac), Elod Csirmaz
8 from mathutils
import *
10 from bpy
.types
import Operator
11 from bpy
.props
import (
19 # List of safe functions for eval()
20 safe_list
= ['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh',
21 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot',
22 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians',
23 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'gcd']
25 # Use the list to filter the local namespace
26 safe_dict
= dict((k
, globals().get(k
, None)) for k
in safe_list
)
27 safe_dict
['math'] = math
28 safe_dict
['numpy'] = safe_dict
['np'] = numpy
29 safe_dict
['lcm'] = numpy
.lcm
30 safe_dict
['max'] = max
31 safe_dict
['min'] = min
34 # Stores the values of a list of properties and the
35 # operator id in a property group ('recall_op') inside the object
36 # Could (in theory) be used for non-objects.
37 # Note: Replaces any existing property group with the same name!
38 # ob ... Object to store the properties in
39 # op ... The operator that should be used
40 # op_args ... A dictionary with valid Blender
41 # properties (operator arguments/parameters)
44 # Create a new mesh (object) from verts/edges/faces
45 # verts/edges/faces ... List of vertices/edges/faces for the
46 # new mesh (as used in from_pydata)
47 # name ... Name of the new mesh (& object)
49 def create_mesh_object(context
, verts
, edges
, faces
, name
):
52 mesh
= bpy
.data
.meshes
.new(name
)
54 # Make a mesh from a list of verts/edges/faces
55 mesh
.from_pydata(verts
, edges
, faces
)
57 # Update mesh geometry after adding stuff
60 from bpy_extras
import object_utils
61 return object_utils
.object_data_add(context
, mesh
, operator
=None)
64 # A very simple "bridge" tool
66 def createFaces(vertIdx1
, vertIdx2
, closed
=False, flipped
=False):
69 if not vertIdx1
or not vertIdx2
:
72 if len(vertIdx1
) < 2 and len(vertIdx2
) < 2:
76 if (len(vertIdx1
) != len(vertIdx2
)):
77 if (len(vertIdx1
) == 1 and len(vertIdx2
) > 1):
85 # Bridge the start with the end
92 face
.append(vertIdx1
[total
- 1])
96 face
= [vertIdx2
[0], vertIdx1
[0]]
98 face
.append(vertIdx1
[total
- 1])
99 face
.append(vertIdx2
[total
- 1])
102 # Bridge the rest of the faces
103 for num
in range(total
- 1):
106 face
= [vertIdx2
[num
], vertIdx1
[0], vertIdx2
[num
+ 1]]
108 face
= [vertIdx2
[num
], vertIdx1
[num
],
109 vertIdx1
[num
+ 1], vertIdx2
[num
+ 1]]
113 face
= [vertIdx1
[0], vertIdx2
[num
], vertIdx2
[num
+ 1]]
115 face
= [vertIdx1
[num
], vertIdx2
[num
],
116 vertIdx2
[num
+ 1], vertIdx1
[num
+ 1]]
122 class AddZFunctionSurface(Operator
):
123 bl_idname
= "mesh.primitive_z_function_surface"
124 bl_label
= "Add Z Function Surface"
125 bl_description
= "Add a surface defined defined by a function z=f(x,y)"
126 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
128 equation
: StringProperty(
130 description
="Equation for z=f(x,y)",
131 default
="1 - ( x**2 + y**2 )"
134 name
="X Subdivisions",
135 description
="Number of vertices in x direction",
141 name
="Y Subdivisions",
142 description
="Number of vertices in y direction",
147 size_x
: FloatProperty(
149 description
="Size of the x axis",
155 size_y
: FloatProperty(
157 description
="Size of the y axis",
164 def execute(self
, context
):
165 equation
= self
.equation
174 delta_x
= size_x
/ (div_x
- 1)
175 delta_y
= size_y
/ (div_y
- 1)
176 start_x
= -(size_x
/ 2.0)
177 start_y
= -(size_y
/ 2.0)
184 compile(equation
, __file__
, 'eval'),
185 {"__builtins__": None},
189 # WARNING is used to prevent the constant pop-up spam
190 self
.report({'WARNING'},
191 "Error parsing expression: {} "
192 "(Check the console for more info)".format(equation
))
193 print("\n[Add Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
197 for row_x
in range(div_x
):
199 x
= start_x
+ row_x
* delta_x
201 for row_y
in range(div_y
):
202 y
= start_y
+ row_y
* delta_y
208 # Try to evaluate the equation.
210 z
= float(eval(*expr_args
))
213 self
.report({'WARNING'},
214 "Error evaluating expression: {} "
215 "(Check the console for more info)".format(equation
))
216 print("\n[Add Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
220 edgeloop_cur
.append(len(verts
))
221 verts
.append((x
, y
, z
))
223 if len(edgeloop_prev
) > 0:
224 faces_row
= createFaces(edgeloop_prev
, edgeloop_cur
)
225 faces
.extend(faces_row
)
227 edgeloop_prev
= edgeloop_cur
229 base
= create_mesh_object(context
, verts
, [], faces
, "Z Function")
231 self
.report({'WARNING'}, "Z Equation - No expression is given")
238 def xyz_function_surface_faces(self
, x_eq
, y_eq
, z_eq
,
239 range_u_min
, range_u_max
, range_u_step
, wrap_u
,
240 range_v_min
, range_v_max
, range_v_step
, wrap_v
,
241 a_eq
, b_eq
, c_eq
, f_eq
, g_eq
, h_eq
, n
, close_v
):
246 # Distance of each step in Blender Units
247 uStep
= (range_u_max
- range_u_min
) / range_u_step
248 vStep
= (range_v_max
- range_v_min
) / range_v_step
250 # Number of steps in the vertex creation loops.
251 # Number of steps is the number of faces
252 # => Number of points is +1 unless wrapped.
253 uRange
= range_u_step
+ 1
254 vRange
= range_v_step
+ 1
264 compile(x_eq
, __file__
.replace(".py", "_x.py"), 'eval'),
265 {"__builtins__": None},
268 compile(y_eq
, __file__
.replace(".py", "_y.py"), 'eval'),
269 {"__builtins__": None},
272 compile(z_eq
, __file__
.replace(".py", "_z.py"), 'eval'),
273 {"__builtins__": None},
276 compile(a_eq
, __file__
.replace(".py", "_a.py"), 'eval'),
277 {"__builtins__": None},
280 compile(b_eq
, __file__
.replace(".py", "_b.py"), 'eval'),
281 {"__builtins__": None},
284 compile(c_eq
, __file__
.replace(".py", "_c.py"), 'eval'),
285 {"__builtins__": None},
288 compile(f_eq
, __file__
.replace(".py", "_f.py"), 'eval'),
289 {"__builtins__": None},
292 compile(g_eq
, __file__
.replace(".py", "_g.py"), 'eval'),
293 {"__builtins__": None},
296 compile(h_eq
, __file__
.replace(".py", "_h.py"), 'eval'),
297 {"__builtins__": None},
301 self
.report({'WARNING'}, "Error parsing expression(s) - "
302 "Check the console for more info")
303 print("\n[Add X, Y, Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
306 for vN
in range(vRange
):
307 v
= range_v_min
+ (vN
* vStep
)
309 for uN
in range(uRange
):
310 u
= range_u_min
+ (uN
* uStep
)
317 # Try to evaluate the equations.
319 safe_dict
['a'] = float(eval(*expr_args_a
))
320 safe_dict
['b'] = float(eval(*expr_args_b
))
321 safe_dict
['c'] = float(eval(*expr_args_c
))
322 safe_dict
['f'] = float(eval(*expr_args_f
))
323 safe_dict
['g'] = float(eval(*expr_args_g
))
324 safe_dict
['h'] = float(eval(*expr_args_h
))
327 float(eval(*expr_args_x
)),
328 float(eval(*expr_args_y
)),
329 float(eval(*expr_args_z
))))
332 self
.report({'WARNING'}, "Error evaluating expression(s) - "
333 "Check the console for more info")
334 print("\n[Add X, Y, Z Function Surface]:\n\n", traceback
.format_exc(limit
=1))
337 for vN
in range(range_v_step
):
340 if wrap_v
and (vNext
>= vRange
):
343 for uN
in range(range_u_step
):
346 if wrap_u
and (uNext
>= uRange
):
349 faces
.append([(vNext
* uRange
) + uNext
,
350 (vNext
* uRange
) + uN
,
352 (vN
* uRange
) + uNext
])
354 if close_v
and wrap_u
and (not wrap_v
):
355 for uN
in range(1, range_u_step
- 1):
358 range_u_step
- 1 - uN
,
359 range_u_step
- 2 - uN
])
361 range_v_step
* uRange
,
362 range_v_step
* uRange
+ uN
,
363 range_v_step
* uRange
+ uN
+ 1])
368 # Original Script "Parametric.py" by Ed Mackey.
369 # -> http://www.blinken.com/blender-plugins.php
370 # Partly converted for Blender 2.5 by tuga3d.
373 # x = sin(2*pi*u)*sin(pi*v)
374 # y = cos(2*pi*u)*sin(pi*v)
380 # x = 1.2**v*(sin(u)**2 *sin(v))
381 # y = 1.2**v*(sin(u)*cos(u))
382 # z = 1.2**v*(sin(u)**2 *cos(v))
388 class AddXYZFunctionSurface(Operator
):
389 bl_idname
= "mesh.primitive_xyz_function_surface"
390 bl_label
= "Add X, Y, Z Function Surface"
391 bl_description
= ("Add a surface defined defined by 3 functions:\n"
392 "x=F1(u,v), y=F2(u,v) and z=F3(u,v)")
393 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
395 x_eq
: StringProperty(
397 description
="Equation for x=F(u,v). "
398 "Also available: n, a, b, c, f, g, h",
399 default
="cos(v)*(1+cos(u))*sin(v/8)"
401 y_eq
: StringProperty(
403 description
="Equation for y=F(u,v). "
404 "Also available: n, a, b, c, f, g, h",
405 default
="sin(u)*sin(v/8)+cos(v/8)*1.5"
407 z_eq
: StringProperty(
409 description
="Equation for z=F(u,v). "
410 "Also available: n, a, b, c, f, g, h",
411 default
="sin(v)*(1+cos(u))*sin(v/8)"
413 range_u_min
: FloatProperty(
415 description
="Minimum U value. Lower boundary of U range",
420 range_u_max
: FloatProperty(
422 description
="Maximum U value. Upper boundary of U range",
427 range_u_step
: IntProperty(
429 description
="U Subdivisions",
434 wrap_u
: BoolProperty(
436 description
="U Wrap around",
439 range_v_min
: FloatProperty(
441 description
="Minimum V value. Lower boundary of V range",
446 range_v_max
: FloatProperty(
448 description
="Maximum V value. Upper boundary of V range",
453 range_v_step
: IntProperty(
455 description
="V Subdivisions",
460 wrap_v
: BoolProperty(
462 description
="V Wrap around",
465 close_v
: BoolProperty(
467 description
="Create faces for first and last "
468 "V values (only if U is wrapped)",
472 name
="Number of objects (n=0..N-1)",
473 description
="The parameter n will be the index "
474 "of the current object, 0 to N-1",
479 a_eq
: StringProperty(
480 name
="A helper function",
481 description
="Equation for a=F(u,v). Also available: n",
484 b_eq
: StringProperty(
485 name
="B helper function",
486 description
="Equation for b=F(u,v). Also available: n",
489 c_eq
: StringProperty(
490 name
="C helper function",
491 description
="Equation for c=F(u,v). Also available: n",
494 f_eq
: StringProperty(
495 name
="F helper function",
496 description
="Equation for f=F(u,v). Also available: n, a, b, c",
499 g_eq
: StringProperty(
500 name
="G helper function",
501 description
="Equation for g=F(u,v). Also available: n, a, b, c",
504 h_eq
: StringProperty(
505 name
="H helper function",
506 description
="Equation for h=F(u,v). Also available: n, a, b, c",
509 show_wire
: BoolProperty(
510 name
="Show wireframe",
512 description
="Add the object’s wireframe over solid drawing"
514 edit_mode
: BoolProperty(
515 name
="Show in edit mode",
517 description
="Show in edit mode"
520 def execute(self
, context
):
521 for n
in range(0, self
.n_eq
):
522 verts
, faces
= xyz_function_surface_faces(
547 obj
= create_mesh_object(context
, verts
, [], faces
, "XYZ Function")
550 context
.active_object
.show_wire
= True
552 context
.active_object
.show_wire
= False
555 bpy
.ops
.object.mode_set(mode
= 'EDIT')
557 bpy
.ops
.object.mode_set(mode
= 'OBJECT')