Animall: move to own dir, to add translations later
[blender-addons.git] / render_povray / model_primitives_topology.py
blob9870fc46d24602182c07a0126e5513f742d97cb0
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 """ Get POV-Ray specific objects In and Out of Blender """
5 from math import pi, cos, sin
6 import os.path
7 import bpy
8 from bpy_extras.object_utils import object_data_add
9 from bpy_extras.io_utils import ImportHelper
10 from bpy.utils import register_class, unregister_class
11 from bpy.types import Operator
13 from bpy.props import (
14 StringProperty,
15 BoolProperty,
16 IntProperty,
17 FloatProperty,
18 FloatVectorProperty,
19 EnumProperty,
22 from mathutils import Vector, Matrix
24 from . import model_primitives
26 class POV_OT_lathe_add(Operator):
27 """Add the representation of POV lathe using a screw modifier."""
29 bl_idname = "pov.addlathe"
30 bl_label = "Lathe"
31 bl_description = "adds lathe"
32 bl_options = {'REGISTER', 'UNDO'}
33 COMPAT_ENGINES = {"POVRAY_RENDER"}
35 def execute(self, context):
36 # ayers=[False]*20
37 # layers[0]=True
38 bpy.ops.curve.primitive_bezier_curve_add(
39 location=context.scene.cursor.location,
40 rotation=(0, 0, 0),
41 # layers=layers,
43 ob = context.view_layer.objects.active
44 ob_data = ob.data
45 ob.name = ob_data.name = "PovLathe"
46 ob_data.dimensions = "2D"
47 ob_data.transform(Matrix.Rotation(-pi / 2.0, 4, "Z"))
48 ob.pov.object_as = "LATHE"
49 self.report(
50 {"INFO"}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
52 ob.pov.curveshape = "lathe"
53 bpy.ops.object.modifier_add(type="SCREW")
54 mod = ob.modifiers[-1]
55 mod.axis = "Y"
56 mod.show_render = False
57 ob.update_tag() # as prop set via python not updated in depsgraph
58 return {"FINISHED"}
61 def pov_superellipsoid_define(context, op, ob):
62 """Create the proxy mesh of a POV superellipsoid using pov_superellipsoid_define()."""
64 if op:
65 mesh = None
67 u = op.se_u
68 v = op.se_v
69 n1 = op.se_n1
70 n2 = op.se_n2
71 edit = op.se_edit
72 se_param1 = n2 # op.se_param1
73 se_param2 = n1 # op.se_param2
75 else:
76 assert ob
77 mesh = ob.data
79 u = ob.pov.se_u
80 v = ob.pov.se_v
81 n1 = ob.pov.se_n1
82 n2 = ob.pov.se_n2
83 edit = ob.pov.se_edit
84 se_param1 = ob.pov.se_param1
85 se_param2 = ob.pov.se_param2
87 verts = []
88 r = 1
90 stepSegment = 360 / v * pi / 180
91 stepRing = pi / u
92 angSegment = 0
93 angRing = -pi / 2
95 step = 0
96 for ring in range(0, u - 1):
97 angRing += stepRing
98 for segment in range(0, v):
99 step += 1
100 angSegment += stepSegment
101 x = r * (abs(cos(angRing)) ** n1) * (abs(cos(angSegment)) ** n2)
102 if (cos(angRing) < 0 < cos(angSegment)) or (cos(angRing) > 0 > cos(angSegment)):
103 x = -x
104 y = r * (abs(cos(angRing)) ** n1) * (abs(sin(angSegment)) ** n2)
105 if (cos(angRing) < 0 < sin(angSegment)) or (cos(angRing) > 0 > sin(angSegment)):
106 y = -y
107 z = r * (abs(sin(angRing)) ** n1)
108 if sin(angRing) < 0:
109 z = -z
110 x = round(x, 4)
111 y = round(y, 4)
112 z = round(z, 4)
113 verts.append((x, y, z))
114 if edit == "TRIANGLES":
115 verts.extend([(0, 0, 1),(0, 0, -1)])
117 faces = []
119 for i in range(0, u - 2):
120 m = i * v
121 for p in range(0, v):
122 if p < v - 1:
123 face = (m + p, 1 + m + p, v + 1 + m + p, v + m + p)
124 if p == v - 1:
125 face = (m + p, m, v + m, v + m + p)
126 faces.append(face)
127 if edit == "TRIANGLES":
128 indexUp = len(verts) - 2
129 indexDown = len(verts) - 1
130 indexStartDown = len(verts) - 2 - v
131 for i in range(0, v):
132 if i < v - 1:
133 face = (indexDown, i, i + 1)
134 faces.append(face)
135 if i == v - 1:
136 face = (indexDown, i, 0)
137 faces.append(face)
138 for i in range(0, v):
139 if i < v - 1:
140 face = (indexUp, i + indexStartDown, i + indexStartDown + 1)
141 faces.append(face)
142 if i == v - 1:
143 face = (indexUp, i + indexStartDown, indexStartDown)
144 faces.append(face)
145 if edit == "NGONS":
146 face = list(range(v))
147 faces.append(face)
148 face = []
149 indexUp = len(verts) - 1
150 for i in range(0, v):
151 face.append(indexUp - i)
152 faces.append(face)
153 mesh = model_primitives.pov_define_mesh(mesh, verts, [], faces, "SuperEllipsoid")
155 if not ob:
156 ob = object_data_add(context, mesh, operator=None)
157 # engine = context.scene.render.engine what for?
158 ob = context.object
159 ob.name = ob.data.name = "PovSuperellipsoid"
160 ob.pov.se_param1 = n2
161 ob.pov.se_param2 = n1
163 ob.pov.se_u = u
164 ob.pov.se_v = v
165 ob.pov.se_n1 = n1
166 ob.pov.se_n2 = n2
167 ob.pov.se_edit = edit
169 bpy.ops.object.mode_set(mode="EDIT")
170 bpy.ops.mesh.hide(unselected=False)
171 bpy.ops.object.mode_set(mode="OBJECT")
172 ob.data.auto_smooth_angle = 1.3
173 bpy.ops.object.shade_smooth()
174 ob.pov.object_as = "SUPERELLIPSOID"
175 ob.update_tag() # as prop set via python not updated in depsgraph
177 class POV_OT_superellipsoid_add(Operator):
178 """Add the representation of POV superellipsoid using the pov_superellipsoid_define()."""
180 bl_idname = "pov.addsuperellipsoid"
181 bl_label = "Add SuperEllipsoid"
182 bl_description = "Create a SuperEllipsoid"
183 bl_options = {'REGISTER', 'UNDO'}
184 COMPAT_ENGINES = {"POVRAY_RENDER"}
186 # Keep in sync within model_properties.py section Superellipsoid
187 # as this allows interactive update
188 # If someone knows how to define operators' props from a func, I'd be delighted to learn it!
189 # XXX ARE the first two used for import ? could we hide or suppress them otherwise?
190 se_param1: FloatProperty(name="Parameter 1", description="", min=0.00, max=10.0, default=0.04)
192 se_param2: FloatProperty(name="Parameter 2", description="", min=0.00, max=10.0, default=0.04)
194 se_u: IntProperty(
195 name="U-segments", description="radial segmentation", default=20, min=4, max=265
197 se_v: IntProperty(
198 name="V-segments", description="lateral segmentation", default=20, min=4, max=265
200 se_n1: FloatProperty(
201 name="Ring manipulator",
202 description="Manipulates the shape of the Ring",
203 default=1.0,
204 min=0.01,
205 max=100.0,
207 se_n2: FloatProperty(
208 name="Cross manipulator",
209 description="Manipulates the shape of the cross-section",
210 default=1.0,
211 min=0.01,
212 max=100.0,
214 se_edit: EnumProperty(
215 items=[("NOTHING", "Nothing", ""), ("NGONS", "N-Gons", ""), ("TRIANGLES", "Triangles", "")],
216 name="Fill up and down",
217 description="",
218 default="TRIANGLES",
221 @classmethod
222 def poll(cls, context):
223 engine = context.scene.render.engine
224 return engine in cls.COMPAT_ENGINES
226 def execute(self, context):
227 pov_superellipsoid_define(context, self, None)
229 self.report(
230 {"INFO"}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
233 return {"FINISHED"}
236 class POV_OT_superellipsoid_update(Operator):
237 """Update the superellipsoid.
239 Delete its previous proxy geometry and rerun pov_superellipsoid_define() function
240 with the new parameters"""
242 bl_idname = "pov.superellipsoid_update"
243 bl_label = "Update"
244 bl_description = "Update Superellipsoid"
245 bl_options = {'REGISTER', 'UNDO'}
246 COMPAT_ENGINES = {"POVRAY_RENDER"}
248 @classmethod
249 def poll(cls, context):
250 engine = context.scene.render.engine
251 ob = context.object
252 return ob and ob.data and ob.type == "MESH" and engine in cls.COMPAT_ENGINES
254 def execute(self, context):
255 bpy.ops.object.mode_set(mode="EDIT")
256 bpy.ops.mesh.reveal()
257 bpy.ops.mesh.select_all(action="SELECT")
258 bpy.ops.mesh.delete(type="VERT")
259 bpy.ops.object.mode_set(mode="OBJECT")
261 pov_superellipsoid_define(context, None, context.object)
263 return {"FINISHED"}
266 def create_faces(vert_idx_1, vert_idx_2, closed=False, flipped=False):
267 """Generate viewport proxy mesh data for some pov primitives"""
268 faces = []
269 if not vert_idx_1 or not vert_idx_2:
270 return None
271 if len(vert_idx_1) < 2 and len(vert_idx_2) < 2:
272 return None
273 fan = False
274 if len(vert_idx_1) != len(vert_idx_2):
275 if len(vert_idx_1) == 1 and len(vert_idx_2) > 1:
276 fan = True
277 else:
278 return None
279 total = len(vert_idx_2)
280 if closed:
281 if flipped:
282 face = [vert_idx_1[0], vert_idx_2[0], vert_idx_2[total - 1]]
283 if not fan:
284 face.append(vert_idx_1[total - 1])
285 else:
286 face = [vert_idx_2[0], vert_idx_1[0]]
287 if not fan:
288 face.append(vert_idx_1[total - 1])
289 face.append(vert_idx_2[total - 1])
290 faces.append(face)
292 for num in range(total - 1):
293 if flipped:
294 if fan:
295 face = [vert_idx_2[num], vert_idx_1[0], vert_idx_2[num + 1]]
296 else:
297 face = [vert_idx_2[num], vert_idx_1[num], vert_idx_1[num + 1], vert_idx_2[num + 1]]
298 elif fan:
299 face = [vert_idx_1[0], vert_idx_2[num], vert_idx_2[num + 1]]
300 else:
301 face = [vert_idx_1[num], vert_idx_2[num], vert_idx_2[num + 1], vert_idx_1[num + 1]]
302 faces.append(face)
303 return faces
306 def power(a, b):
307 """Workaround to negative a, where the math.pow() method would return a ValueError."""
308 return -((-a) ** b) if a < 0 else a**b
311 def supertoroid(R, r, u, v, n1, n2):
312 a = 2 * pi / u
313 b = 2 * pi / v
314 verts = []
315 faces = []
316 for i in range(u):
317 s = power(sin(i * a), n1)
318 c = power(cos(i * a), n1)
319 for j in range(v):
320 c2 = R + r * power(cos(j * b), n2)
321 s2 = r * power(sin(j * b), n2)
322 verts.append((c * c2, s * c2, s2)) # type as a (mathutils.Vector(c*c2,s*c2,s2))?
323 if i > 0:
324 f = create_faces(range((i - 1) * v, i * v), range(i * v, (i + 1) * v), closed=True)
325 faces.extend(f)
326 f = create_faces(range((u - 1) * v, u * v), range(v), closed=True)
327 faces.extend(f)
328 return verts, faces
331 def pov_supertorus_define(context, op, ob):
332 """Get POV supertorus properties from operator (object creation/import) or data update."""
333 if op:
334 mesh = None
335 st_R = op.st_R
336 st_r = op.st_r
337 st_u = op.st_u
338 st_v = op.st_v
339 st_n1 = op.st_n1
340 st_n2 = op.st_n2
341 st_ie = op.st_ie
342 st_edit = op.st_edit
344 else:
345 assert ob
346 mesh = ob.data
347 st_R = ob.pov.st_major_radius
348 st_r = ob.pov.st_minor_radius
349 st_u = ob.pov.st_u
350 st_v = ob.pov.st_v
351 st_n1 = ob.pov.st_ring
352 st_n2 = ob.pov.st_cross
353 st_ie = ob.pov.st_ie
354 st_edit = ob.pov.st_edit
356 if st_ie:
357 rad1 = (st_R + st_r) / 2
358 rad2 = (st_R - st_r) / 2
359 if rad2 > rad1:
360 [rad1, rad2] = [rad2, rad1]
361 else:
362 rad1 = st_R
363 rad2 = st_r
364 if rad2 > rad1:
365 rad1 = rad2
366 verts, faces = supertoroid(rad1, rad2, st_u, st_v, st_n1, st_n2)
367 mesh = model_primitives.pov_define_mesh(mesh, verts, [], faces, "PovSuperTorus", True)
368 if not ob:
369 ob = object_data_add(context, mesh, operator=None)
370 ob.pov.object_as = "SUPERTORUS"
371 ob.pov.st_major_radius = st_R
372 ob.pov.st_minor_radius = st_r
373 ob.pov.st_u = st_u
374 ob.pov.st_v = st_v
375 ob.pov.st_ring = st_n1
376 ob.pov.st_cross = st_n2
377 ob.pov.st_ie = st_ie
378 ob.pov.st_edit = st_edit
379 ob.update_tag() # as prop set via python not updated in depsgraph
382 class POV_OT_supertorus_add(Operator):
383 """Add the representation of POV supertorus using the pov_supertorus_define() function."""
385 bl_idname = "pov.addsupertorus"
386 bl_label = "Add Supertorus"
387 bl_description = "Create a SuperTorus"
388 bl_options = {'REGISTER', 'UNDO'}
389 COMPAT_ENGINES = {"POVRAY_RENDER"}
391 st_R: FloatProperty(
392 name="big radius",
393 description="The radius inside the tube",
394 default=1.0,
395 min=0.01,
396 max=100.0,
398 st_r: FloatProperty(
399 name="small radius", description="The radius of the tube", default=0.3, min=0.01, max=100.0
401 st_u: IntProperty(
402 name="U-segments", description="radial segmentation", default=16, min=3, max=265
404 st_v: IntProperty(
405 name="V-segments", description="lateral segmentation", default=8, min=3, max=265
407 st_n1: FloatProperty(
408 name="Ring manipulator",
409 description="Manipulates the shape of the Ring",
410 default=1.0,
411 min=0.01,
412 max=100.0,
414 st_n2: FloatProperty(
415 name="Cross manipulator",
416 description="Manipulates the shape of the cross-section",
417 default=1.0,
418 min=0.01,
419 max=100.0,
421 st_ie: BoolProperty(
422 name="Use Int.+Ext. radii", description="Use internal and external radii", default=False
424 st_edit: BoolProperty(name="", description="", default=False, options={"HIDDEN"})
426 @classmethod
427 def poll(cls, context):
428 engine = context.scene.render.engine
429 return engine in cls.COMPAT_ENGINES
431 def execute(self, context):
432 pov_supertorus_define(context, self, None)
434 self.report(
435 {"INFO"}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
437 return {"FINISHED"}
440 class POV_OT_supertorus_update(Operator):
441 """Update the supertorus.
443 Delete its previous proxy geometry and rerun pov_supetorus_define() function
444 with the new parameters"""
446 bl_idname = "pov.supertorus_update"
447 bl_label = "Update"
448 bl_description = "Update SuperTorus"
449 bl_options = {'REGISTER', 'UNDO'}
450 COMPAT_ENGINES = {"POVRAY_RENDER"}
452 @classmethod
453 def poll(cls, context):
454 engine = context.scene.render.engine
455 ob = context.object
456 return ob and ob.data and ob.type == "MESH" and engine in cls.COMPAT_ENGINES
458 def execute(self, context):
459 bpy.ops.object.mode_set(mode="EDIT")
460 bpy.ops.mesh.reveal()
461 bpy.ops.mesh.select_all(action="SELECT")
462 bpy.ops.mesh.delete(type="VERT")
463 bpy.ops.object.mode_set(mode="OBJECT")
465 pov_supertorus_define(context, None, context.object)
467 return {"FINISHED"}
470 # -----------------------------------------------------------------------------
471 class POV_OT_loft_add(Operator):
472 """Create the representation of POV loft using Blender curves."""
474 bl_idname = "pov.addloft"
475 bl_label = "Add Loft Data"
476 bl_description = "Create a Curve data for Meshmaker"
477 bl_options = {'REGISTER', 'UNDO'}
478 COMPAT_ENGINES = {"POVRAY_RENDER"}
480 loft_n: IntProperty(
481 name="Segments", description="Vertical segments", default=16, min=3, max=720
483 loft_rings_bottom: IntProperty(
484 name="Bottom", description="Bottom rings", default=5, min=2, max=100
486 loft_rings_side: IntProperty(name="Side", description="Side rings", default=10, min=2, max=100)
487 loft_thick: FloatProperty(
488 name="Thickness",
489 description="Manipulates the shape of the Ring",
490 default=0.3,
491 min=0.01,
492 max=1.0,
494 loft_r: FloatProperty(name="Radius", description="Radius", default=1, min=0.01, max=10)
495 loft_height: FloatProperty(
496 name="Height",
497 description="Manipulates the shape of the Ring",
498 default=2,
499 min=0.01,
500 max=10.0,
503 def execute(self, context):
505 props = self.properties
506 loft_data = bpy.data.curves.new("Loft", type="CURVE")
507 loft_data.dimensions = "3D"
508 loft_data.resolution_u = 2
509 # loft_data.show_normal_face = False # deprecated in 2.8
510 n = props.loft_n
511 thick = props.loft_thick
512 side = props.loft_rings_side
513 bottom = props.loft_rings_bottom
514 h = props.loft_height
515 r = props.loft_r
516 distB = r / bottom
517 r0 = 0.00001
518 z = -h / 2
519 print("New")
520 for i in range(bottom + 1):
521 coords = []
522 angle = 0
523 for p in range(n):
524 x = r0 * cos(angle)
525 y = r0 * sin(angle)
526 coords.append((x, y, z))
527 angle += pi * 2 / n
528 r0 += distB
529 nurbs = loft_data.splines.new("NURBS")
530 nurbs.points.add(len(coords) - 1)
531 for c, coord in enumerate(coords):
532 x, y, z = coord
533 nurbs.points[c].co = (x, y, z, 1)
534 nurbs.use_cyclic_u = True
535 for i in range(side):
536 z += h / side
537 coords = []
538 angle = 0
539 for p in range(n):
540 x = r * cos(angle)
541 y = r * sin(angle)
542 coords.append((x, y, z))
543 angle += pi * 2 / n
544 nurbs = loft_data.splines.new("NURBS")
545 nurbs.points.add(len(coords) - 1)
546 for c, coord in enumerate(coords):
547 x, y, z = coord
548 nurbs.points[c].co = (x, y, z, 1)
549 nurbs.use_cyclic_u = True
550 r -= thick
551 for i in range(side):
552 coords = []
553 angle = 0
554 for p in range(n):
555 x = r * cos(angle)
556 y = r * sin(angle)
557 coords.append((x, y, z))
558 angle += pi * 2 / n
559 nurbs = loft_data.splines.new("NURBS")
560 nurbs.points.add(len(coords) - 1)
561 for c, coord in enumerate(coords):
562 x, y, z = coord
563 nurbs.points[c].co = (x, y, z, 1)
564 nurbs.use_cyclic_u = True
565 z -= h / side
566 z = (-h / 2) + thick
567 distB = (r - 0.00001) / bottom
568 for i in range(bottom + 1):
569 coords = []
570 angle = 0
571 for p in range(n):
572 x = r * cos(angle)
573 y = r * sin(angle)
574 coords.append((x, y, z))
575 angle += pi * 2 / n
576 r -= distB
577 nurbs = loft_data.splines.new("NURBS")
578 nurbs.points.add(len(coords) - 1)
579 for c, coord in enumerate(coords):
580 x, y, z = coord
581 nurbs.points[c].co = (x, y, z, 1)
582 nurbs.use_cyclic_u = True
583 ob = bpy.data.objects.new("Loft_shape", loft_data)
584 scn = bpy.context.scene
585 scn.collection.objects.link(ob)
586 context.view_layer.objects.active = ob
587 ob.select_set(True)
588 ob.pov.curveshape = "loft"
589 return {"FINISHED"}
592 # ----------------------------------- ISOSURFACES ----------------------------------- #
595 def pov_isosurface_view_define(context, op, ob, loc):
596 """create the representation of POV isosurface using a Blender empty."""
598 if op:
599 eq = op.isosurface_eq
601 loc = bpy.context.scene.cursor.location
603 else:
604 assert ob
605 eq = ob.pov.isosurface_eq
607 # keep object rotation and location for the add object operator
608 obrot = ob.rotation_euler
609 # obloc = ob.location
610 obscale = ob.scale
612 # bpy.ops.object.empty_add(type='CUBE', location=loc, rotation=obrot)
613 bpy.ops.mesh.primitive_emptyvert_add()
615 # bpy.ops.transform.rotate(axis=obrot,orient_type='GLOBAL')
616 bpy.ops.transform.resize(value=obscale)
617 # bpy.ops.transform.rotate(axis=obrot, proportional_size=1)
618 bpy.ops.object.mode_set(mode="OBJECT")
619 if not ob:
620 # bpy.ops.object.empty_add(type='CUBE', location=loc)
621 bpy.ops.mesh.primitive_emptyvert_add()
622 ob = context.object
623 ob.name = ob.data.name = "PovIsosurface"
624 ob.pov.object_as = "ISOSURFACE_VIEW"
625 ob.pov.isosurface_eq = eq
626 ob.pov.contained_by = "box"
627 bpy.ops.object.mode_set(mode="OBJECT")
628 ob.update_tag() # as prop set via python not updated in depsgraph
631 class POV_OT_isosurface_add(Operator):
632 """Add the representation of POV isosurface sphere by a Blender mesh icosphere.
634 Flag its primitive type with a specific pov.object_as attribute and lock edit mode
635 to keep proxy consistency by hiding edit geometry."""
637 bl_idname = "pov.addisosurface"
638 bl_label = "Generic Isosurface"
639 bl_description = "Add Isosurface"
640 bl_options = {'REGISTER', 'UNDO'}
641 COMPAT_ENGINES = {"POVRAY_RENDER"}
643 # Keep in sync within model_properties.py section Sphere
644 # as this allows interactive update
645 isosurface_eq: StringProperty(
646 name="f(x,y,z)=",
647 description="Type the POV Isosurface function syntax for equation, "
648 "pattern,etc. ruling an implicit surface to be rendered",
649 default="sqrt(pow(x,2) + pow(y,2) + pow(z,2)) - 1.5",
651 imported_loc: FloatVectorProperty(
652 name="Imported Pov location", precision=6, default=(0.0, 0.0, 0.0)
655 def execute(self, context):
656 # layers = 20*[False]
657 # layers[0] = True
658 props = self.properties
659 if ob := context.object:
660 if ob.pov.imported_loc:
661 LOC = ob.pov.imported_loc
662 elif not props.imported_loc:
663 LOC = bpy.context.scene.cursor.location
664 else:
665 LOC = props.imported_loc
666 try:
667 pov_isosurface_view_define(context, self, None, LOC)
668 self.report(
669 {"INFO"}, "This native POV-Ray primitive " "is only an abstract proxy in Blender"
671 except AttributeError:
672 self.report({"INFO"}, "Please enable Add Mesh: Extra Objects addon")
673 return {"FINISHED"}
676 class POV_OT_isosurface_update(Operator):
677 """Update the POV isosurface.
679 Rerun pov_isosurface_view_define() function
680 with the new parameters"""
682 bl_idname = "pov.isosurface_update"
683 bl_label = "Update"
684 bl_description = "Update Isosurface"
685 bl_options = {'REGISTER', 'UNDO'}
686 COMPAT_ENGINES = {"POVRAY_RENDER"}
688 @classmethod
689 def poll(cls, context):
690 engine = context.scene.render.engine
691 ob = context.object
692 return ob and ob.data and ob.type == "ISOSURFACE_VIEW" and engine in cls.COMPAT_ENGINES
694 def execute(self, context):
696 pov_isosurface_view_define(context, None, context.object, context.object.location)
698 return {"FINISHED"}
701 class POV_OT_isosurface_box_add(Operator):
702 """Add the representation of POV isosurface box using also just a Blender mesh cube.
704 Flag its primitive type with a specific pov.object_as attribute and lock edit mode
705 to keep proxy consistency by hiding edit geometry."""
707 bl_idname = "pov.addisosurfacebox"
708 bl_label = "Isosurface Box"
709 bl_description = "Add Isosurface contained by Box"
710 bl_options = {'REGISTER', 'UNDO'}
711 COMPAT_ENGINES = {"POVRAY_RENDER"}
713 def execute(self, context):
714 # layers = 20*[False]
715 # layers[0] = True
716 bpy.ops.mesh.primitive_cube_add()
717 ob = context.object
718 bpy.ops.object.mode_set(mode="EDIT")
719 self.report(
720 {"INFO"}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
722 bpy.ops.mesh.hide(unselected=False)
723 bpy.ops.object.mode_set(mode="OBJECT")
724 ob.pov.object_as = "ISOSURFACE_NODE"
725 ob.pov.contained_by = "box"
726 ob.name = "PovIsosurfaceBox"
727 ob.update_tag() # as prop set via python not updated in depsgraph
728 return {"FINISHED"}
731 class POV_OT_isosurface_sphere_add(Operator):
732 """Add the representation of POV isosurface sphere by a Blender mesh icosphere.
734 Flag its primitive type with a specific pov.object_as attribute and lock edit mode
735 to keep proxy consistency by hiding edit geometry."""
737 bl_idname = "pov.addisosurfacesphere"
738 bl_label = "Isosurface Sphere"
739 bl_description = "Add Isosurface contained by Sphere"
740 bl_options = {'REGISTER', 'UNDO'}
741 COMPAT_ENGINES = {"POVRAY_RENDER"}
743 def execute(self, context):
744 # layers = 20*[False]
745 # layers[0] = True
746 bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4)
747 ob = context.object
748 bpy.ops.object.mode_set(mode="EDIT")
749 self.report(
750 {"INFO"}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
752 bpy.ops.mesh.hide(unselected=False)
753 bpy.ops.object.mode_set(mode="OBJECT")
754 bpy.ops.object.shade_smooth()
755 ob.pov.object_as = "ISOSURFACE_NODE"
756 ob.pov.contained_by = "sphere"
757 ob.name = "PovIsosurfaceSphere"
758 ob.update_tag() # as prop set via python not updated in depsgraph
759 return {"FINISHED"}
762 class POV_OT_sphere_sweep_add(Operator):
763 """Add the representation of POV sphere_sweep using a Blender NURBS curve.
765 Flag its primitive type with a specific ob.pov.curveshape attribute and
766 leave access to edit mode to keep user editable handles."""
768 bl_idname = "pov.addspheresweep"
769 bl_label = "Sphere Sweep"
770 bl_description = "Create Sphere Sweep along curve"
771 bl_options = {'REGISTER', 'UNDO'}
772 COMPAT_ENGINES = {"POVRAY_RENDER"}
774 def execute(self, context):
775 # layers = 20*[False]
776 # layers[0] = True
777 bpy.ops.curve.primitive_nurbs_curve_add()
778 ob = context.object
779 ob.name = ob.data.name = "PovSphereSweep"
780 ob.pov.curveshape = "sphere_sweep"
781 ob.data.bevel_depth = 0.02
782 ob.data.bevel_resolution = 4
783 ob.data.fill_mode = "FULL"
784 # ob.data.splines[0].order_u = 4
786 return {"FINISHED"}
789 class POV_OT_blobsphere_add(Operator):
790 """Add the representation of POV blob using a Blender meta ball.
792 No need to flag its primitive type as meta are exported to blobs
793 and leave access to edit mode to keep user editable thresholds."""
795 bl_idname = "pov.addblobsphere"
796 bl_label = "Blob Sphere"
797 bl_description = "Add Blob Sphere"
798 bl_options = {'REGISTER', 'UNDO'}
799 COMPAT_ENGINES = {"POVRAY_RENDER"}
801 def execute(self, context):
802 # layers = 20*[False]
803 # layers[0] = True
804 bpy.ops.object.metaball_add(type="BALL")
805 ob = context.object
806 ob.name = "PovBlob"
807 return {"FINISHED"}
810 class POV_OT_blobcapsule_add(Operator):
811 """Add the representation of POV blob using a Blender meta ball.
813 No need to flag its primitive type as meta are exported to blobs
814 and leave access to edit mode to keep user editable thresholds."""
816 bl_idname = "pov.addblobcapsule"
817 bl_label = "Blob Capsule"
818 bl_description = "Add Blob Capsule"
819 bl_options = {'REGISTER', 'UNDO'}
820 COMPAT_ENGINES = {"POVRAY_RENDER"}
822 def execute(self, context):
823 # layers = 20*[False]
824 # layers[0] = True
825 bpy.ops.object.metaball_add(type="CAPSULE")
826 ob = context.object
827 ob.name = "PovBlob"
828 return {"FINISHED"}
831 class POV_OT_blobplane_add(Operator):
832 """Add the representation of POV blob using a Blender meta ball.
834 No need to flag its primitive type as meta are exported to blobs
835 and leave access to edit mode to keep user editable thresholds."""
837 bl_idname = "pov.addblobplane"
838 bl_label = "Blob Plane"
839 bl_description = "Add Blob Plane"
840 bl_options = {'REGISTER', 'UNDO'}
841 COMPAT_ENGINES = {"POVRAY_RENDER"}
843 def execute(self, context):
844 # layers = 20*[False]
845 # layers[0] = True
846 bpy.ops.object.metaball_add(type="PLANE")
847 ob = context.object
848 ob.name = "PovBlob"
849 return {"FINISHED"}
852 class POV_OT_blobellipsoid_add(Operator):
853 """Add the representation of POV blob using a Blender meta ball.
855 No need to flag its primitive type as meta are exported to blobs
856 and leave access to edit mode to keep user editable thresholds."""
858 bl_idname = "pov.addblobellipsoid"
859 bl_label = "Blob Ellipsoid"
860 bl_description = "Add Blob Ellipsoid"
863 def execute(self, context):
864 # layers = 20*[False]
865 # layers[0] = True
866 bpy.ops.object.metaball_add(type="ELLIPSOID")
867 ob = context.object
868 ob.name = "PovBlob"
869 return {"FINISHED"}
872 class POV_OT_blobcube_add(Operator):
873 """Add the representation of POV blob using a Blender meta ball.
875 No need to flag its primitive type as meta are exported to blobs
876 and leave access to edit mode to keep user editable thresholds."""
878 bl_idname = "pov.addblobcube"
879 bl_label = "Blob Cube"
880 bl_description = "Add Blob Cube"
881 bl_options = {'REGISTER', 'UNDO'}
882 COMPAT_ENGINES = {"POVRAY_RENDER"}
884 def execute(self, context):
885 # layers = 20*[False]
886 # layers[0] = True
887 bpy.ops.object.metaball_add(type="CUBE")
888 ob = context.object
889 ob.name = "PovBlob"
890 return {"FINISHED"}
893 class POV_OT_height_field_add(bpy.types.Operator, ImportHelper):
894 """Add the representation of POV height_field using a displaced grid.
896 texture slot fix and displace modifier will be needed because noise
897 displace operator was deprecated in 2.8"""
899 bl_idname = "pov.addheightfield"
900 bl_label = "Height Field"
901 bl_description = "Add Height Field"
902 bl_options = {'REGISTER', 'UNDO'}
903 COMPAT_ENGINES = {"POVRAY_RENDER"}
905 # Keep in sync within model_properties.py section HeightFields
906 # as this allows interactive update
908 # filename_ext = ".png"
910 # filter_glob = StringProperty(
911 # default="*.exr;*.gif;*.hdr;*.iff;*.jpeg;*.jpg;*.pgm;*.png;*.pot;*.ppm;*.sys;*.tga;*.tiff;*.EXR;*.GIF;*.HDR;*.IFF;*.JPEG;*.JPG;*.PGM;*.PNG;*.POT;*.PPM;*.SYS;*.TGA;*.TIFF",
912 # options={'HIDDEN'},
914 quality: IntProperty(name="Quality", description="", default=100, min=1, max=100)
915 hf_filename: StringProperty(maxlen=1024)
917 hf_gamma: FloatProperty(name="Gamma", description="Gamma", min=0.0001, max=20.0, default=1.0)
919 hf_premultiplied: BoolProperty(name="Premultiplied", description="Premultiplied", default=True)
921 hf_smooth: BoolProperty(name="Smooth", description="Smooth", default=False)
923 hf_water: FloatProperty(
924 name="Water Level", description="Wather Level", min=0.00, max=1.00, default=0.0
927 hf_hierarchy: BoolProperty(name="Hierarchy", description="Height field hierarchy", default=True)
929 def execute(self, context):
930 props = self.properties
931 impath = bpy.path.abspath(self.filepath)
932 img = bpy.data.images.load(impath)
933 im_name = img.name
934 im_name, file_extension = os.path.splitext(im_name)
935 hf_tex = bpy.data.textures.new("%s_hf_image" % im_name, type="IMAGE")
936 hf_tex.image = img
937 mat = bpy.data.materials.new("Tex_%s_hf" % im_name)
938 hf_slot = mat.pov_texture_slots.add()
939 hf_slot.texture = hf_tex.name
940 # layers = 20*[False]
941 # layers[0] = True
942 quality = props.quality
943 res = 100 / quality
944 w, h = hf_tex.image.size[:]
945 w = int(w / res)
946 h = int(h / res)
947 bpy.ops.mesh.primitive_grid_add(x_subdivisions=w, y_subdivisions=h, size=0.5)
948 ob = context.object
949 ob.name = ob.data.name = "%s" % im_name
950 ob.data.materials.append(mat)
951 bpy.ops.object.mode_set(mode="EDIT")
952 # bpy.ops.mesh.noise(factor=1) # TODO replace by displace modifier, noise deprecated in 2.8
953 bpy.ops.object.mode_set(mode="OBJECT")
955 # needs a loop to select by index?
956 # bpy.ops.object.material_slot_remove()
957 # material just left there for now
959 mat.pov_texture_slots.clear()
960 bpy.ops.object.mode_set(mode="EDIT")
961 bpy.ops.mesh.hide(unselected=False)
962 bpy.ops.object.mode_set(mode="OBJECT")
963 ob.pov.object_as = "HEIGHT_FIELD"
964 # POV-Ray will soon use only forwards slashes on every OS and already can
965 forward_impath = impath.replace(os.sep, "/")
966 ob.pov.hf_filename = forward_impath
967 ob.update_tag() # as prop set via python not updated in depsgraph
968 return {"FINISHED"}
971 # ----------------------------------- PARAMETRIC ----------------------------------- #
972 def pov_parametric_define(context, op, ob):
973 """Add the representation of POV parametric surfaces by math surface from add mesh extra objects addon.
975 Picking properties either from creation operator, import, or data update.
976 But flag its primitive type with a specific pov.object_as attribute and lock edit mode
977 to keep proxy consistency by hiding edit geometry."""
978 if op:
979 u_min = op.u_min
980 u_max = op.u_max
981 v_min = op.v_min
982 v_max = op.v_max
983 x_eq = op.x_eq
984 y_eq = op.y_eq
985 z_eq = op.z_eq
987 else:
988 assert ob
989 u_min = ob.pov.u_min
990 u_max = ob.pov.u_max
991 v_min = ob.pov.v_min
992 v_max = ob.pov.v_max
993 x_eq = ob.pov.x_eq
994 y_eq = ob.pov.y_eq
995 z_eq = ob.pov.z_eq
997 # keep object rotation and location for the updated object
998 obloc = ob.location
999 obrot = ob.rotation_euler # In radians
1000 # Parametric addon has no loc rot, some extra work is needed
1001 # in case cursor has moved
1002 curloc = bpy.context.scene.cursor.location
1004 bpy.ops.object.mode_set(mode="EDIT")
1005 bpy.ops.mesh.reveal()
1006 bpy.ops.mesh.select_all(action="SELECT")
1007 bpy.ops.mesh.delete(type="VERT")
1008 bpy.ops.mesh.primitive_xyz_function_surface(
1009 x_eq=x_eq,
1010 y_eq=y_eq,
1011 z_eq=z_eq,
1012 range_u_min=u_min,
1013 range_u_max=u_max,
1014 range_v_min=v_min,
1015 range_v_max=v_max,
1017 bpy.ops.mesh.select_all(action="SELECT")
1018 # extra work:
1019 bpy.ops.transform.translate(value=(obloc - curloc), proportional_size=1)
1020 # XXX TODO : https://devtalk.blender.org/t/bpy-ops-transform-rotate-option-axis/6235/7
1021 # to complete necessary extra work rotation, after updating from blender version > 2.92
1022 # update and uncomment below, but simple axis deprecated since 2.8
1023 # bpy.ops.transform.rotate(axis=obrot, proportional_size=1)
1025 bpy.ops.mesh.hide(unselected=False)
1026 bpy.ops.object.mode_set(mode="OBJECT")
1028 if not ob:
1029 bpy.ops.mesh.primitive_xyz_function_surface(
1030 x_eq=x_eq,
1031 y_eq=y_eq,
1032 z_eq=z_eq,
1033 range_u_min=u_min,
1034 range_u_max=u_max,
1035 range_v_min=v_min,
1036 range_v_max=v_max,
1038 ob = context.object
1039 ob.name = ob.data.name = "PovParametric"
1041 ob.pov.u_min = u_min
1042 ob.pov.u_max = u_max
1043 ob.pov.v_min = v_min
1044 ob.pov.v_max = v_max
1045 ob.pov.x_eq = x_eq
1046 ob.pov.y_eq = y_eq
1047 ob.pov.z_eq = z_eq
1049 bpy.ops.object.mode_set(mode="EDIT")
1050 bpy.ops.mesh.hide(unselected=False)
1051 bpy.ops.object.mode_set(mode="OBJECT")
1052 ob.data.auto_smooth_angle = 0.6
1053 bpy.ops.object.shade_smooth()
1054 ob.pov.object_as = "PARAMETRIC"
1055 ob.update_tag() # as prop set via python not updated in depsgraph
1056 return{'FINISHED'}
1058 class POV_OT_parametric_add(Operator):
1059 """Add the representation of POV parametric surfaces using pov_parametric_define() function."""
1061 bl_idname = "pov.addparametric"
1062 bl_label = "Parametric"
1063 bl_description = "Add Paramertic"
1064 bl_options = {'REGISTER', 'UNDO'}
1065 COMPAT_ENGINES = {"POVRAY_RENDER"}
1067 # Keep in sync within model_properties.py section Parametric primitive
1068 # as this allows interactive update
1069 u_min: FloatProperty(name="U Min", description="", default=0.0)
1070 v_min: FloatProperty(name="V Min", description="", default=0.0)
1071 u_max: FloatProperty(name="U Max", description="", default=6.28)
1072 v_max: FloatProperty(name="V Max", description="", default=12.57)
1073 x_eq: StringProperty(maxlen=1024, default="cos(v)*(1+cos(u))*sin(v/8)")
1074 y_eq: StringProperty(maxlen=1024, default="sin(u)*sin(v/8)+cos(v/8)*1.5")
1075 z_eq: StringProperty(maxlen=1024, default="sin(v)*(1+cos(u))*sin(v/8)")
1077 def execute(self, context):
1078 props = self.properties
1079 u_min = props.u_min
1080 v_min = props.v_min
1081 u_max = props.u_max
1082 v_max = props.v_max
1083 x_eq = props.x_eq
1084 y_eq = props.y_eq
1085 z_eq = props.z_eq
1086 try:
1087 pov_parametric_define(context, self, None)
1088 self.report(
1089 {"INFO"},
1090 "This native POV-Ray primitive " "won't have any vertex to show in edit mode",
1092 except AttributeError:
1093 self.report({"INFO"}, "Please enable Add Mesh: Extra Objects addon")
1094 return {"FINISHED"}
1097 class POV_OT_parametric_update(Operator):
1098 """Update the representation of POV parametric surfaces.
1100 Delete its previous proxy geometry and rerun pov_parametric_define() function
1101 with the new parameters"""
1103 bl_idname = "pov.parametric_update"
1104 bl_label = "Update"
1105 bl_description = "Update parametric object"
1106 bl_options = {'REGISTER', 'UNDO'}
1107 COMPAT_ENGINES = {"POVRAY_RENDER"}
1109 @classmethod
1110 def poll(cls, context):
1111 engine = context.scene.render.engine
1112 ob = context.object
1113 return ob and ob.data and ob.type == "MESH" and engine in cls.COMPAT_ENGINES
1115 def execute(self, context):
1117 pov_parametric_define(context, None, context.object)
1119 return {"FINISHED"}
1122 # -----------------------------------------------------------------------------
1125 class POV_OT_polygon_to_circle_add(Operator):
1126 """Add the proxy mesh for POV Polygon to circle lofting macro"""
1128 bl_idname = "pov.addpolygontocircle"
1129 bl_label = "Polygon To Circle Blending"
1130 bl_description = "Add Polygon To Circle Blending Surface"
1131 bl_options = {'REGISTER', 'UNDO'}
1132 COMPAT_ENGINES = {"POVRAY_RENDER"}
1134 # Keep in sync within model_properties.py section PolygonToCircle properties
1135 # as this allows interactive update
1136 polytocircle_resolution: IntProperty(
1137 name="Resolution", description="", default=3, min=0, max=256
1139 polytocircle_ngon: IntProperty(name="NGon", description="", min=3, max=64, default=5)
1140 polytocircle_ngonR: FloatProperty(name="NGon Radius", description="", default=0.3)
1141 polytocircle_circleR: FloatProperty(name="Circle Radius", description="", default=1.0)
1143 def execute(self, context):
1144 props = self.properties
1145 ngon = props.polytocircle_ngon
1146 ngonR = props.polytocircle_ngonR
1147 circleR = props.polytocircle_circleR
1148 resolution = props.polytocircle_resolution
1149 # layers = 20*[False]
1150 # layers[0] = True
1151 bpy.ops.mesh.primitive_circle_add(
1152 vertices=ngon, radius=ngonR, fill_type="NGON", enter_editmode=True
1154 bpy.ops.transform.translate(value=(0, 0, 1))
1155 bpy.ops.mesh.subdivide(number_cuts=resolution)
1156 numCircleVerts = ngon + (ngon * resolution)
1157 bpy.ops.mesh.select_all(action="DESELECT")
1158 bpy.ops.mesh.primitive_circle_add(
1159 vertices=numCircleVerts, radius=circleR, fill_type="NGON", enter_editmode=True
1161 bpy.ops.transform.translate(value=(0, 0, -1))
1162 bpy.ops.mesh.select_all(action="SELECT")
1163 bpy.ops.mesh.bridge_edge_loops()
1164 if ngon < 5:
1165 bpy.ops.mesh.select_all(action="DESELECT")
1166 bpy.ops.mesh.primitive_circle_add(
1167 vertices=ngon, radius=ngonR, fill_type="TRIFAN", enter_editmode=True
1169 bpy.ops.transform.translate(value=(0, 0, 1))
1170 bpy.ops.mesh.select_all(action="SELECT")
1171 bpy.ops.mesh.remove_doubles()
1172 bpy.ops.object.mode_set(mode="OBJECT")
1173 ob = context.object
1174 ob.name = "Polygon_To_Circle"
1175 ob.pov.ngon = ngon
1176 ob.pov.ngonR = ngonR
1177 ob.pov.circleR = circleR
1178 bpy.ops.object.mode_set(mode="EDIT")
1179 bpy.ops.mesh.hide(unselected=False)
1180 bpy.ops.object.mode_set(mode="OBJECT")
1181 #ob.data.auto_smooth_angle = 0.1
1182 #bpy.ops.object.shade_smooth()
1183 ob.pov.object_as = "POLYCIRCLE"
1184 ob.update_tag() # as prop set via python not updated in depsgraph
1185 return {"FINISHED"}
1188 classes = (
1189 POV_OT_lathe_add,
1190 POV_OT_superellipsoid_add,
1191 POV_OT_superellipsoid_update,
1192 POV_OT_supertorus_add,
1193 POV_OT_supertorus_update,
1194 POV_OT_loft_add,
1195 POV_OT_isosurface_add,
1196 POV_OT_isosurface_update,
1197 POV_OT_isosurface_box_add,
1198 POV_OT_isosurface_sphere_add,
1199 POV_OT_sphere_sweep_add,
1200 POV_OT_blobsphere_add,
1201 POV_OT_blobcapsule_add,
1202 POV_OT_blobplane_add,
1203 POV_OT_blobellipsoid_add,
1204 POV_OT_blobcube_add,
1205 POV_OT_height_field_add,
1206 POV_OT_parametric_add,
1207 POV_OT_parametric_update,
1208 POV_OT_polygon_to_circle_add,
1212 def register():
1213 for cls in classes:
1214 register_class(cls)
1217 def unregister():
1218 for cls in reversed(classes):
1219 unregister_class(cls)