Skip to content
Snippets Groups Projects
object_primitives.py 63.1 KiB
Newer Older
# SPDX-License-Identifier: GPL-2.0-or-later

# <pep8 compliant>

""" Get POV-Ray specific objects In and Out of Blender """
Maurice Raybaud's avatar
Maurice Raybaud committed
from math import pi, cos, sin
import os.path
Maurice Raybaud's avatar
Maurice Raybaud committed
import bpy
from bpy_extras.object_utils import object_data_add
Maurice Raybaud's avatar
Maurice Raybaud committed
from bpy.utils import register_class, unregister_class
from bpy.types import Operator
    StringProperty,
    BoolProperty,
    IntProperty,
    FloatProperty,
    FloatVectorProperty,
    EnumProperty,
from mathutils import Vector, Matrix

Maurice Raybaud's avatar
Maurice Raybaud committed
    """Translate some object level POV statements from Blender UI
    to POV syntax and write to exported file """

    # Maybe return that string to be added instead of directly written.

    '''XXX WIP
    # import .object_mesh_topology.write_object_csg_inside_vector
    write_object_csg_inside_vector(ob, file)
Maurice Raybaud's avatar
Maurice Raybaud committed
    '''

    if ob.pov.hollow:
        File.write("\thollow\n")
    if ob.pov.double_illuminate:
        File.write("\tdouble_illuminate\n")
    if ob.pov.sturm:
        File.write("\tsturm\n")
    if ob.pov.no_shadow:
        File.write("\tno_shadow\n")
    if ob.pov.no_image:
        File.write("\tno_image\n")
    if ob.pov.no_reflection:
        File.write("\tno_reflection\n")
    if ob.pov.no_radiosity:
        File.write("\tno_radiosity\n")
    if ob.pov.inverse:
        File.write("\tinverse\n")
    if ob.pov.hierarchy:
        File.write("\thierarchy\n")

    # XXX, Commented definitions
    '''
    if scene.pov.photon_enable:
        File.write("photons {\n")
        if ob.pov.target:
            File.write("target %.4g\n"%ob.pov.target_value)
        if ob.pov.refraction:
            File.write("refraction on\n")
        if ob.pov.reflection:
            File.write("reflection on\n")
        if ob.pov.pass_through:
            File.write("pass_through\n")
        File.write("}\n")
    if ob.pov.object_ior > 1:
        File.write("interior {\n")
        File.write("ior %.4g\n"%ob.pov.object_ior)
        if scene.pov.photon_enable and ob.pov.target and ob.pov.refraction and ob.pov.dispersion:
            File.write("ior %.4g\n"%ob.pov.dispersion_value)
            File.write("ior %s\n"%ob.pov.dispersion_samples)
        if scene.pov.photon_enable == False:
            File.write("caustics %.4g\n"%ob.pov.fake_caustics_power)
    '''


def pov_define_mesh(mesh, verts, edges, faces, name, hide_geometry=True):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Generate proxy mesh."""
    if mesh is None:
        mesh = bpy.data.meshes.new(name)
    mesh.from_pydata(verts, edges, faces)
    mesh.update()
    mesh.validate(
        verbose=False
    )  # Set it to True to see debug messages (helps ensure you generate valid geometry).
    if hide_geometry:
        mesh.vertices.foreach_set("hide", [True] * len(mesh.vertices))
        mesh.edges.foreach_set("hide", [True] * len(mesh.edges))
        mesh.polygons.foreach_set("hide", [True] * len(mesh.polygons))
    return mesh


class POVRAY_OT_lathe_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV lathe using a screw modifier."""
    bl_idname = "pov.addlathe"
    bl_label = "Lathe"
    bl_description = "adds lathe"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}
        # ayers=[False]*20
        # layers[0]=True
Campbell Barton's avatar
Campbell Barton committed
        bpy.ops.curve.primitive_bezier_curve_add(
            location=context.scene.cursor.location,
Campbell Barton's avatar
Campbell Barton committed
            rotation=(0, 0, 0),
        ob = context.view_layer.objects.active
Campbell Barton's avatar
Campbell Barton committed
        ob_data = ob.data
        ob.name = ob_data.name = "PovLathe"
        ob_data.dimensions = '2D'
        ob_data.transform(Matrix.Rotation(-pi / 2.0, 4, 'Z'))
        ob.pov.object_as = 'LATHE'
Maurice Raybaud's avatar
Maurice Raybaud committed
        self.report(
            {'INFO'}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
        )
        ob.pov.curveshape = "lathe"
        bpy.ops.object.modifier_add(type='SCREW')
Campbell Barton's avatar
Campbell Barton committed
        mod = ob.modifiers[-1]
        mod.axis = 'Y'
        mod.show_render = False
        return {'FINISHED'}


def pov_superellipsoid_define(context, op, ob):
    """Create the proxy mesh of a POV superellipsoid using pov_superellipsoid_define()."""
Maurice Raybaud's avatar
Maurice Raybaud committed
    if op:
        mesh = None
Maurice Raybaud's avatar
Maurice Raybaud committed
        u = op.se_u
        v = op.se_v
        n1 = op.se_n1
        n2 = op.se_n2
        edit = op.se_edit
        se_param1 = n2  # op.se_param1
        se_param2 = n1  # op.se_param2
Maurice Raybaud's avatar
Maurice Raybaud committed
    else:
Maurice Raybaud's avatar
Maurice Raybaud committed
        mesh = ob.data
Maurice Raybaud's avatar
Maurice Raybaud committed
        u = ob.pov.se_u
        v = ob.pov.se_v
        n1 = ob.pov.se_n1
        n2 = ob.pov.se_n2
        edit = ob.pov.se_edit
        se_param1 = ob.pov.se_param1
        se_param2 = ob.pov.se_param2

    verts = []
    stepSegment = 360 / v * pi / 180
    stepRing = pi / u
    angSegment = 0
    angRing = -pi / 2
    step = 0
    for ring in range(0, u - 1):
Maurice Raybaud's avatar
Maurice Raybaud committed
        angRing += stepRing
        for segment in range(0, v):
Maurice Raybaud's avatar
Maurice Raybaud committed
            step += 1
            angSegment += stepSegment
            x = r * (abs(cos(angRing)) ** n1) * (abs(cos(angSegment)) ** n2)
            if (cos(angRing) < 0 < cos(angSegment)) or (
                    cos(angRing) > 0 > cos(angSegment)
Maurice Raybaud's avatar
Maurice Raybaud committed
                x = -x
            y = r * (abs(cos(angRing)) ** n1) * (abs(sin(angSegment)) ** n2)
            if (cos(angRing) < 0 < sin(angSegment)) or (
                    cos(angRing) > 0 > sin(angSegment)
Maurice Raybaud's avatar
Maurice Raybaud committed
                y = -y
            z = r * (abs(sin(angRing)) ** n1)
Maurice Raybaud's avatar
Maurice Raybaud committed
            if sin(angRing) < 0:
                z = -z
            x = round(x, 4)
            y = round(y, 4)
            z = round(z, 4)
            verts.append((x, y, z))
Maurice Raybaud's avatar
Maurice Raybaud committed
    if edit == 'TRIANGLES':
        verts.append((0, 0, 1))
        verts.append((0, 0, -1))
    for i in range(0, u - 2):
        m = i * v
        for p in range(0, v):
            if p < v - 1:
                face = (m + p, 1 + m + p, v + 1 + m + p, v + m + p)
            if p == v - 1:
                face = (m + p, m, v + m, v + m + p)
Maurice Raybaud's avatar
Maurice Raybaud committed
    if edit == 'TRIANGLES':
        indexUp = len(verts) - 2
        indexDown = len(verts) - 1
        indexStartDown = len(verts) - 2 - v
        for i in range(0, v):
            if i < v - 1:
                face = (indexDown, i, i + 1)
Maurice Raybaud's avatar
Maurice Raybaud committed
                faces.append(face)
            if i == v - 1:
                face = (indexDown, i, 0)
Maurice Raybaud's avatar
Maurice Raybaud committed
                faces.append(face)
        for i in range(0, v):
            if i < v - 1:
                face = (indexUp, i + indexStartDown, i + indexStartDown + 1)
Maurice Raybaud's avatar
Maurice Raybaud committed
                faces.append(face)
            if i == v - 1:
                face = (indexUp, i + indexStartDown, indexStartDown)
Maurice Raybaud's avatar
Maurice Raybaud committed
                faces.append(face)
    if edit == 'NGONS':
        face = []
        for i in range(0, v):
Maurice Raybaud's avatar
Maurice Raybaud committed
            face.append(i)
        faces.append(face)
        face = []
        indexUp = len(verts) - 1
        for i in range(0, v):
            face.append(indexUp - i)
Maurice Raybaud's avatar
Maurice Raybaud committed
        faces.append(face)
    mesh = pov_define_mesh(mesh, verts, [], faces, "SuperEllipsoid")
Maurice Raybaud's avatar
Maurice Raybaud committed
    if not ob:
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob = object_data_add(context, mesh, operator=None)
        # engine = context.scene.render.engine what for?
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob = context.object
        ob.name = ob.data.name = "PovSuperellipsoid"
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob.pov.object_as = 'SUPERELLIPSOID'
        ob.pov.se_param1 = n2
        ob.pov.se_param2 = n1

        ob.pov.se_u = u
        ob.pov.se_v = v
        ob.pov.se_n1 = n1
        ob.pov.se_n2 = n2
        ob.pov.se_edit = edit

        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
class POVRAY_OT_superellipsoid_add(Operator):
    """Add the representation of POV superellipsoid using the pov_superellipsoid_define()."""
    bl_idname = "pov.addsuperellipsoid"
    bl_label = "Add SuperEllipsoid"
    bl_description = "Create a SuperEllipsoid"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

Maurice Raybaud's avatar
Maurice Raybaud committed
    # Keep in sync within object_properties.py section Superellipsoid
    # as this allows interactive update
    #     If someone knows how to define operators' props from a func, I'd be delighted to learn it!
    # XXX ARE the first two used for import ? could we hide or suppress them otherwise?
Maurice Raybaud's avatar
Maurice Raybaud committed
    se_param1: FloatProperty(name="Parameter 1", description="", min=0.00, max=10.0, default=0.04)
Maurice Raybaud's avatar
Maurice Raybaud committed
    se_param2: FloatProperty(name="Parameter 2", description="", min=0.00, max=10.0, default=0.04)
Maurice Raybaud's avatar
Maurice Raybaud committed
        name="U-segments", description="radial segmentation", default=20, min=4, max=265
Maurice Raybaud's avatar
Maurice Raybaud committed
        name="V-segments", description="lateral segmentation", default=20, min=4, max=265
    )
    se_n1: FloatProperty(
        name="Ring manipulator",
        description="Manipulates the shape of the Ring",
        default=1.0,
        min=0.01,
        max=100.0,
    )
    se_n2: FloatProperty(
        name="Cross manipulator",
        description="Manipulates the shape of the cross-section",
        default=1.0,
        min=0.01,
        max=100.0,
    )
    se_edit: EnumProperty(
Maurice Raybaud's avatar
Maurice Raybaud committed
        items=[("NOTHING", "Nothing", ""), ("NGONS", "N-Gons", ""), ("TRIANGLES", "Triangles", "")],
        name="Fill up and down",
        description="",
        default='TRIANGLES',
    )

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        return engine in cls.COMPAT_ENGINES
    def execute(self, context):
Maurice Raybaud's avatar
Maurice Raybaud committed
            {'INFO'}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
class POVRAY_OT_superellipsoid_update(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Update the superellipsoid.

    Delete its previous proxy geometry and rerun pov_superellipsoid_define() function
    with the new parameters"""

    bl_idname = "pov.superellipsoid_update"
    bl_label = "Update"
    bl_description = "Update Superellipsoid"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        ob = context.object
Maurice Raybaud's avatar
Maurice Raybaud committed
        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES

    def execute(self, context):
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.reveal()
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.delete(type='VERT')
        bpy.ops.object.mode_set(mode="OBJECT")

        pov_superellipsoid_define(context, None, context.object)

        return {'FINISHED'}

Maurice Raybaud's avatar
Maurice Raybaud committed
def create_faces(vert_idx_1, vert_idx_2, closed=False, flipped=False):
    """Generate viewport proxy mesh data for some pov primitives"""
Maurice Raybaud's avatar
Maurice Raybaud committed
    if not vert_idx_1 or not vert_idx_2:
Maurice Raybaud's avatar
Maurice Raybaud committed
    if len(vert_idx_1) < 2 and len(vert_idx_2) < 2:
Maurice Raybaud's avatar
Maurice Raybaud committed
    if len(vert_idx_1) != len(vert_idx_2):
        if len(vert_idx_1) == 1 and len(vert_idx_2) > 1:
Maurice Raybaud's avatar
Maurice Raybaud committed
    total = len(vert_idx_2)
Maurice Raybaud's avatar
Maurice Raybaud committed
            face = [vert_idx_1[0], vert_idx_2[0], vert_idx_2[total - 1]]
Maurice Raybaud's avatar
Maurice Raybaud committed
                face.append(vert_idx_1[total - 1])
Maurice Raybaud's avatar
Maurice Raybaud committed
            face = [vert_idx_2[0], vert_idx_1[0]]
Maurice Raybaud's avatar
Maurice Raybaud committed
                face.append(vert_idx_1[total - 1])
            face.append(vert_idx_2[total - 1])
            faces.append(face)
    for num in range(total - 1):
        if flipped:
            if fan:
Maurice Raybaud's avatar
Maurice Raybaud committed
                face = [vert_idx_2[num], vert_idx_1[0], vert_idx_2[num + 1]]
Maurice Raybaud's avatar
Maurice Raybaud committed
                face = [vert_idx_2[num], vert_idx_1[num], vert_idx_1[num + 1], vert_idx_2[num + 1]]
Maurice Raybaud's avatar
Maurice Raybaud committed
                face = [vert_idx_1[0], vert_idx_2[num], vert_idx_2[num + 1]]
Maurice Raybaud's avatar
Maurice Raybaud committed
                face = [vert_idx_1[num], vert_idx_2[num], vert_idx_2[num + 1], vert_idx_1[num + 1]]
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Workaround to negative a, where the math.pow() method would return a ValueError."""
        return -((-a) ** b)
    return a ** b

def supertoroid(R, r, u, v, n1, n2):
    a = 2 * pi / u
    b = 2 * pi / v
        s = power(sin(i * a), n1)
        c = power(cos(i * a), n1)
            c2 = R + r * power(cos(j * b), n2)
            s2 = r * power(sin(j * b), n2)
Maurice Raybaud's avatar
Maurice Raybaud committed
            verts.append((c * c2, s * c2, s2))  # type as a (mathutils.Vector(c*c2,s*c2,s2))?
Maurice Raybaud's avatar
Maurice Raybaud committed
            f = create_faces(range((i - 1) * v, i * v), range(i * v, (i + 1) * v), closed=True)
Maurice Raybaud's avatar
Maurice Raybaud committed
    f = create_faces(range((u - 1) * v, u * v), range(v), closed=True)
Campbell Barton's avatar
Campbell Barton committed
def pov_supertorus_define(context, op, ob):
    """Get POV supertorus properties from operator (object creation/import) or data update."""
    if op:
        mesh = None
        st_R = op.st_R
        st_r = op.st_r
        st_u = op.st_u
        st_v = op.st_v
        st_n1 = op.st_n1
        st_n2 = op.st_n2
        st_ie = op.st_ie
        st_edit = op.st_edit

    else:
        assert ob
        mesh = ob.data
        st_R = ob.pov.st_major_radius
        st_r = ob.pov.st_minor_radius
        st_u = ob.pov.st_u
        st_v = ob.pov.st_v
        st_n1 = ob.pov.st_ring
        st_n2 = ob.pov.st_cross
        st_ie = ob.pov.st_ie
        st_edit = ob.pov.st_edit

    if st_ie:
        rad1 = (st_R + st_r) / 2
        rad2 = (st_R - st_r) / 2
        if rad2 > rad1:
            [rad1, rad2] = [rad2, rad1]
    else:
        rad1 = st_R
        rad2 = st_r
        if rad2 > rad1:
            rad1 = rad2
    verts, faces = supertoroid(rad1, rad2, st_u, st_v, st_n1, st_n2)
    mesh = pov_define_mesh(mesh, verts, [], faces, "PovSuperTorus", True)
    if not ob:
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob = object_data_add(context, mesh, operator=None)
        ob.pov.object_as = 'SUPERTORUS'
        ob.pov.st_major_radius = st_R
        ob.pov.st_minor_radius = st_r
        ob.pov.st_u = st_u
        ob.pov.st_v = st_v
        ob.pov.st_ring = st_n1
        ob.pov.st_cross = st_n2
        ob.pov.st_ie = st_ie
        ob.pov.st_edit = st_edit
class POVRAY_OT_supertorus_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV supertorus using the pov_supertorus_define() function."""

    bl_idname = "pov.addsupertorus"
    bl_label = "Add Supertorus"
    bl_description = "Create a SuperTorus"
    bl_options = {'REGISTER', 'UNDO'}
Campbell Barton's avatar
Campbell Barton committed
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    st_R: FloatProperty(
        name="big radius",
        description="The radius inside the tube",
        default=1.0,
        min=0.01,
        max=100.0,
    )
    st_r: FloatProperty(
Maurice Raybaud's avatar
Maurice Raybaud committed
        name="small radius", description="The radius of the tube", default=0.3, min=0.01, max=100.0
Maurice Raybaud's avatar
Maurice Raybaud committed
        name="U-segments", description="radial segmentation", default=16, min=3, max=265
Maurice Raybaud's avatar
Maurice Raybaud committed
        name="V-segments", description="lateral segmentation", default=8, min=3, max=265
    )
    st_n1: FloatProperty(
        name="Ring manipulator",
        description="Manipulates the shape of the Ring",
        default=1.0,
        min=0.01,
        max=100.0,
    )
    st_n2: FloatProperty(
        name="Cross manipulator",
        description="Manipulates the shape of the cross-section",
        default=1.0,
        min=0.01,
        max=100.0,
    )
    st_ie: BoolProperty(
Maurice Raybaud's avatar
Maurice Raybaud committed
        name="Use Int.+Ext. radii", description="Use internal and external radii", default=False
Maurice Raybaud's avatar
Maurice Raybaud committed
    st_edit: BoolProperty(name="", description="", default=False, options={'HIDDEN'})

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        return engine in cls.COMPAT_ENGINES

    def execute(self, context):
        pov_supertorus_define(context, self, None)

Maurice Raybaud's avatar
Maurice Raybaud committed
            {'INFO'}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
Campbell Barton's avatar
Campbell Barton committed
        return {'FINISHED'}
class POVRAY_OT_supertorus_update(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Update the supertorus.

    Delete its previous proxy geometry and rerun pov_supetorus_define() function
    with the new parameters"""
    bl_idname = "pov.supertorus_update"
    bl_label = "Update"
    bl_description = "Update SuperTorus"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        ob = context.object
Maurice Raybaud's avatar
Maurice Raybaud committed
        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES

    def execute(self, context):
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.reveal()
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.delete(type='VERT')
        bpy.ops.object.mode_set(mode="OBJECT")

        pov_supertorus_define(context, None, context.object)

        return {'FINISHED'}
# -----------------------------------------------------------------------------
class POVRAY_OT_loft_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Create the representation of POV loft using Blender curves."""

    bl_idname = "pov.addloft"
    bl_label = "Add Loft Data"
    bl_description = "Create a Curve data for Meshmaker"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}
    loft_n: IntProperty(
Maurice Raybaud's avatar
Maurice Raybaud committed
        name="Segments", description="Vertical segments", default=16, min=3, max=720
    )
    loft_rings_bottom: IntProperty(
        name="Bottom", description="Bottom rings", default=5, min=2, max=100
    )
Maurice Raybaud's avatar
Maurice Raybaud committed
    loft_rings_side: IntProperty(name="Side", description="Side rings", default=10, min=2, max=100)
    loft_thick: FloatProperty(
        name="Thickness",
        description="Manipulates the shape of the Ring",
        default=0.3,
        min=0.01,
        max=1.0,
    )
Maurice Raybaud's avatar
Maurice Raybaud committed
    loft_r: FloatProperty(name="Radius", description="Radius", default=1, min=0.01, max=10)
    loft_height: FloatProperty(
        name="Height",
        description="Manipulates the shape of the Ring",
        default=2,
        min=0.01,
        max=10.0,
    )

    def execute(self, context):
Maurice Raybaud's avatar
Maurice Raybaud committed
        loft_data = bpy.data.curves.new('Loft', type='CURVE')
        loft_data.dimensions = '3D'
        loft_data.resolution_u = 2
        # loft_data.show_normal_face = False # deprecated in 2.8
        thick = props.loft_thick
        side = props.loft_rings_side
        bottom = props.loft_rings_bottom
        h = props.loft_height
        r = props.loft_r
        for i in range(bottom + 1):
                x = r0 * cos(angle)
                y = r0 * sin(angle)
                coords.append((x, y, z))
                angle += pi * 2 / n
            r0 += distB
Maurice Raybaud's avatar
Maurice Raybaud committed
            nurbs = loft_data.splines.new('NURBS')
            nurbs.points.add(len(coords) - 1)
Maurice Raybaud's avatar
Maurice Raybaud committed
            for c, coord in enumerate(coords):
Maurice Raybaud's avatar
Maurice Raybaud committed
                nurbs.points[c].co = (x, y, z, 1)
                x = r * cos(angle)
                y = r * sin(angle)
                coords.append((x, y, z))
                angle += pi * 2 / n
Maurice Raybaud's avatar
Maurice Raybaud committed
            nurbs = loft_data.splines.new('NURBS')
            nurbs.points.add(len(coords) - 1)
Maurice Raybaud's avatar
Maurice Raybaud committed
            for c, coord in enumerate(coords):
Maurice Raybaud's avatar
Maurice Raybaud committed
                nurbs.points[c].co = (x, y, z, 1)
                x = r * cos(angle)
                y = r * sin(angle)
                coords.append((x, y, z))
                angle += pi * 2 / n
Maurice Raybaud's avatar
Maurice Raybaud committed
            nurbs = loft_data.splines.new('NURBS')
            nurbs.points.add(len(coords) - 1)
Maurice Raybaud's avatar
Maurice Raybaud committed
            for c, coord in enumerate(coords):
Maurice Raybaud's avatar
Maurice Raybaud committed
                nurbs.points[c].co = (x, y, z, 1)
            z -= h / side
        z = (-h / 2) + thick
        distB = (r - 0.00001) / bottom
        for i in range(bottom + 1):
                x = r * cos(angle)
                y = r * sin(angle)
                coords.append((x, y, z))
                angle += pi * 2 / n
            r -= distB
Maurice Raybaud's avatar
Maurice Raybaud committed
            nurbs = loft_data.splines.new('NURBS')
            nurbs.points.add(len(coords) - 1)
Maurice Raybaud's avatar
Maurice Raybaud committed
            for c, coord in enumerate(coords):
Maurice Raybaud's avatar
Maurice Raybaud committed
                nurbs.points[c].co = (x, y, z, 1)
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob = bpy.data.objects.new('Loft_shape', loft_data)
Maurice Raybaud's avatar
Maurice Raybaud committed
        scn.collection.objects.link(ob)
        context.view_layer.objects.active = ob
        ob.select_set(True)
class POVRAY_OT_plane_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV infinite plane using just a very big Blender Plane.

    Flag its primitive type with a specific pov.object_as attribute and lock edit mode
    to keep proxy consistency by hiding edit geometry."""

    bl_idname = "pov.addplane"
    bl_label = "Plane"
    bl_description = "Add Plane"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.mesh.primitive_plane_add(size=10000)
        ob = context.object
        ob.name = ob.data.name = 'PovInfinitePlane'
        bpy.ops.object.mode_set(mode="EDIT")
Maurice Raybaud's avatar
Maurice Raybaud committed
            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
        bpy.ops.object.shade_smooth()
        ob.pov.object_as = "PLANE"
        return {'FINISHED'}
class POVRAY_OT_box_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV box using a simple Blender mesh cube.

    Flag its primitive type with a specific pov.object_as attribute and lock edit mode
    to keep proxy consistency by hiding edit geometry."""

    bl_idname = "pov.addbox"
    bl_label = "Box"
    bl_description = "Add Box"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
Maurice Raybaud's avatar
Maurice Raybaud committed
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.mesh.primitive_cube_add()
        ob = context.object
        ob.name = ob.data.name = 'PovBox'
        bpy.ops.object.mode_set(mode="EDIT")
Maurice Raybaud's avatar
Maurice Raybaud committed
            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
        ob.pov.object_as = "BOX"
        return {'FINISHED'}


def pov_cylinder_define(context, op, ob, radius, loc, loc_cap):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Pick POV cylinder properties either from creation operator, import, or data update """
        loc = bpy.context.scene.cursor.location
        loc_cap[2] = loc[2] + 2
    vec = Vector(loc_cap) - Vector(loc)
    depth = vec.length
    rot = Vector((0, 0, 1)).rotation_difference(vec)  # Rotation from Z axis.
    trans = rot @ Vector(
        (0, 0, depth / 2)
    )  # Such that origin is at center of the base of the cylinder.
    roteuler = rot.to_euler()
    if not ob:
        bpy.ops.object.add(type='MESH', location=loc)
        ob = context.object
        ob.name = ob.data.name = "PovCylinder"
        ob.pov.cylinder_radius = radius
        ob.pov.cylinder_location_cap = vec
        ob.pov.object_as = "CYLINDER"
    else:
        ob.location = loc

    bpy.ops.object.mode_set(mode="EDIT")
    bpy.ops.mesh.reveal()
    bpy.ops.mesh.select_all(action='SELECT')
    bpy.ops.mesh.delete(type='VERT')
    bpy.ops.mesh.primitive_cylinder_add(
Maurice Raybaud's avatar
Maurice Raybaud committed
        radius=radius, depth=depth, location=loc, rotation=roteuler, end_fill_type='NGON'
    )  # 'NOTHING'
    bpy.ops.transform.translate(value=trans)

    bpy.ops.mesh.hide(unselected=False)
    bpy.ops.object.mode_set(mode="OBJECT")
    bpy.ops.object.shade_smooth()


class POVRAY_OT_cylinder_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV cylinder using pov_cylinder_define() function.

    Use imported_cyl_loc when this operator is run by POV importer."""
    bl_idname = "pov.addcylinder"
    bl_label = "Cylinder"
    bl_description = "Add Cylinder"
    bl_options = {'REGISTER', 'UNDO'}
Maurice Raybaud's avatar
Maurice Raybaud committed
    # Keep in sync within object_properties.py section Cylinder
    # as this allows interactive update
    R: FloatProperty(name="Cylinder radius", min=0.00, max=10.0, default=1.0)
    imported_cyl_loc: FloatVectorProperty(
        name="Imported Pov base location", precision=6, default=(0.0, 0.0, 0.0)
    )
    imported_cyl_loc_cap: FloatVectorProperty(
        name="Imported Pov cap location", precision=6, default=(0.0, 0.0, 2.0)
    )
    def execute(self, context):
Campbell Barton's avatar
Campbell Barton committed
        ob = context.object
        # layers = 20*[False]
        # layers[0] = True
        if ob:
            if ob.pov.imported_cyl_loc:
                LOC = ob.pov.imported_cyl_loc
            if ob.pov.imported_cyl_loc_cap:
                LOC_CAP = ob.pov.imported_cyl_loc_cap
        elif not props.imported_cyl_loc:
            LOC_CAP = LOC = bpy.context.scene.cursor.location
            LOC_CAP[2] += 2.0
            LOC = props.imported_cyl_loc
            LOC_CAP = props.imported_cyl_loc_cap
Maurice Raybaud's avatar
Maurice Raybaud committed
                "This native POV-Ray primitive " "won't have any vertex to show in edit mode",

        pov_cylinder_define(context, self, None, self.R, LOC, LOC_CAP)

        return {'FINISHED'}


class POVRAY_OT_cylinder_update(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Update the POV cylinder.

    Delete its previous proxy geometry and rerun pov_cylinder_define() function
    with the new parameters"""

    bl_idname = "pov.cylinder_update"
    bl_label = "Update"
    bl_description = "Update Cylinder"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        return (
            ob
            and ob.data
            and ob.type == 'MESH'
            and ob.pov.object_as == "CYLINDER"
            and engine in cls.COMPAT_ENGINES
        )

    def execute(self, context):
        ob = context.object
        radius = ob.pov.cylinder_radius
        loc = ob.location
        loc_cap = loc + ob.pov.cylinder_location_cap

        pov_cylinder_define(context, None, ob, radius, loc, loc_cap)
# ----------------------------------- SPHERE---------------------------------- #
Maurice Raybaud's avatar
Maurice Raybaud committed
    """create the representation of POV sphere using a Blender icosphere.
Maurice Raybaud's avatar
Maurice Raybaud committed
    Its nice platonic solid curvature better represents pov rendertime
    tesselation than a UV sphere"""
Maurice Raybaud's avatar
Maurice Raybaud committed
    if op:
        R = op.R
        loc = bpy.context.scene.cursor.location
    else:
Maurice Raybaud's avatar
Maurice Raybaud committed
        R = ob.pov.sphere_radius
        # keep object rotation and location for the add object operator
Maurice Raybaud's avatar
Maurice Raybaud committed
        obrot = ob.rotation_euler
        # obloc = ob.location
Maurice Raybaud's avatar
Maurice Raybaud committed
        obscale = ob.scale
Maurice Raybaud's avatar
Maurice Raybaud committed
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.reveal()
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.delete(type='VERT')
        bpy.ops.mesh.primitive_ico_sphere_add(
Maurice Raybaud's avatar
Maurice Raybaud committed
            subdivisions=4, radius=ob.pov.sphere_radius, location=loc, rotation=obrot
        )
        # bpy.ops.transform.rotate(axis=obrot,orient_type='GLOBAL')
Maurice Raybaud's avatar
Maurice Raybaud committed
        bpy.ops.transform.resize(value=obscale)
        # bpy.ops.transform.rotate(axis=obrot, proportional_size=1)
Maurice Raybaud's avatar
Maurice Raybaud committed

        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
        bpy.ops.object.shade_smooth()
        # bpy.ops.transform.rotate(axis=obrot,orient_type='GLOBAL')
Maurice Raybaud's avatar
Maurice Raybaud committed

    if not ob:
Maurice Raybaud's avatar
Maurice Raybaud committed
        bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, radius=R, location=loc)
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob = context.object
        ob.name = ob.data.name = "PovSphere"
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob.pov.object_as = "SPHERE"
        ob.pov.sphere_radius = R
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
class POVRAY_OT_sphere_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV sphere using pov_sphere_define() function.

    Use imported_loc when this operator is run by POV importer."""

    bl_idname = "pov.addsphere"
    bl_label = "Sphere"
    bl_description = "Add Sphere Shape"
    bl_options = {'REGISTER', 'UNDO'}
Maurice Raybaud's avatar
Maurice Raybaud committed
    # Keep in sync within object_properties.py section Sphere
    # as this allows interactive update
    R: FloatProperty(name="Sphere radius", min=0.00, max=10.0, default=0.5)
    imported_loc: FloatVectorProperty(
        name="Imported Pov location", precision=6, default=(0.0, 0.0, 0.0)
    )
    def execute(self, context):
        elif not props.imported_loc:
            LOC = bpy.context.scene.cursor.location
        else:
            LOC = props.imported_loc
            self.report(
                {'INFO'},
                "This native POV-Ray primitive " "won't have any vertex to show in edit mode",
            )
    #  layers = 20*[False]
    #  layers[0] = True

    # bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, radius=ob.pov.sphere_radius)
    # ob = context.object
    # bpy.ops.object.mode_set(mode="EDIT")
    # self.report({'INFO'}, "This native POV-Ray primitive "
    # "won't have any vertex to show in edit mode")
    # bpy.ops.mesh.hide(unselected=False)
    # bpy.ops.object.mode_set(mode="OBJECT")
    # bpy.ops.object.shade_smooth()
    # ob.pov.object_as = "SPHERE"
    # ob.name = ob.data.name = 'PovSphere'
    # return {'FINISHED'}


class POVRAY_OT_sphere_update(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Update the POV sphere.

    Delete its previous proxy geometry and rerun pov_sphere_define() function
    with the new parameters"""

    bl_idname = "pov.sphere_update"
    bl_label = "Update"
    bl_description = "Update Sphere"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        ob = context.object
Maurice Raybaud's avatar
Maurice Raybaud committed
        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES
Maurice Raybaud's avatar
Maurice Raybaud committed
        pov_sphere_define(context, None, context.object, context.object.location)
Campbell Barton's avatar
Campbell Barton committed
        return {'FINISHED'}

# ----------------------------------- CONE ---------------------------------- #
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV cone using pov_define_mesh() function.

    Blender cone does not offer the same features such as a second radius."""
    verts = []
    faces = []
    if op:
        mesh = None
        base = op.base
        cap = op.cap
        seg = op.seg
        height = op.height
    else:
        mesh = ob.data
        base = ob.pov.cone_base_radius
        cap = ob.pov.cone_cap_radius
        seg = ob.pov.cone_segments
Campbell Barton's avatar
Campbell Barton committed
        height = ob.pov.cone_height

    zc = height / 2
    zb = -zc
    angle = 2 * pi / seg
    t = 0
    for i in range(seg):
        xb = base * cos(t)
        yb = base * sin(t)
        xc = cap * cos(t)
        yc = cap * sin(t)
        verts.append((xb, yb, zb))
        verts.append((xc, yc, zc))
        t += angle
    for i in range(seg):
        f = i * 2
        if i == seg - 1:
            faces.append([0, 1, f + 1, f])
        else:
            faces.append([f + 2, f + 3, f + 1, f])
    if base != 0:
        base_face = []
        for i in range(seg - 1, -1, -1):
            p = i * 2
            base_face.append(p)
        faces.append(base_face)
    if cap != 0:
        cap_face = []
        for i in range(seg):
            p = i * 2 + 1
            cap_face.append(p)
        faces.append(cap_face)

    mesh = pov_define_mesh(mesh, verts, [], faces, "PovCone", True)
    if not ob:
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob = object_data_add(context, mesh, operator=None)
        ob.pov.object_as = "CONE"
        ob.pov.cone_base_radius = base
        ob.pov.cone_cap_radius = cap
        ob.pov.cone_height = height
        ob.pov.cone_base_z = zb
        ob.pov.cone_cap_z = zc


class POVRAY_OT_cone_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV cone using pov_cone_define() function."""

    bl_label = "Cone"
    bl_description = "Add Cone"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

Maurice Raybaud's avatar
Maurice Raybaud committed
    # Keep in sync within object_properties.py section Cone
    #     If someone knows how to define operators' props from a func, I'd be delighted to learn it!
    base: FloatProperty(
        name="Base radius",
        description="The first radius of the cone",
        default=1.0,
        min=0.01,
        max=100.0,
    )
    cap: FloatProperty(
        name="Cap radius",
        description="The second radius of the cone",
        default=0.3,
        min=0.0,
        max=100.0,
    )
    seg: IntProperty(
        name="Segments",
        description="Radial segmentation of the proxy mesh",
        default=16,
        min=3,
        max=265,
    )
    height: FloatProperty(
Maurice Raybaud's avatar
Maurice Raybaud committed
        name="Height", description="Height of the cone", default=2.0, min=0.01, max=100.0

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        return engine in cls.COMPAT_ENGINES
Maurice Raybaud's avatar
Maurice Raybaud committed
            {'INFO'}, "This native POV-Ray primitive" "won't have any vertex to show in edit mode"
class POVRAY_OT_cone_update(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Update the POV cone.

    Delete its previous proxy geometry and rerun pov_cone_define() function
    with the new parameters"""

    bl_idname = "pov.cone_update"
    bl_label = "Update"
    bl_description = "Update Cone"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        ob = context.object
Maurice Raybaud's avatar
Maurice Raybaud committed
        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES

    def execute(self, context):
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.reveal()
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.delete(type='VERT')
        bpy.ops.object.mode_set(mode="OBJECT")

        pov_cone_define(context, None, context.object)

        return {'FINISHED'}
# ----------------------------------- ISOSURFACES ----------------------------------- #
def pov_isosurface_view_define(context, op, ob, loc):
    """create the representation of POV isosurface using a Blender empty."""

    if op:
        eq = op.isosurface_eq

        loc = bpy.context.scene.cursor.location

    else:
        assert ob
        eq = ob.pov.isosurface_eq

        # keep object rotation and location for the add object operator
        obrot = ob.rotation_euler
        # obloc = ob.location
        obscale = ob.scale

        #bpy.ops.object.empty_add(type='CUBE', location=loc, rotation=obrot)
        bpy.ops.mesh.primitive_emptyvert_add()

        # bpy.ops.transform.rotate(axis=obrot,orient_type='GLOBAL')
        bpy.ops.transform.resize(value=obscale)
        # bpy.ops.transform.rotate(axis=obrot, proportional_size=1)
        bpy.ops.object.mode_set(mode="OBJECT")
    if not ob:
        #bpy.ops.object.empty_add(type='CUBE', location=loc)
        bpy.ops.mesh.primitive_emptyvert_add()
        ob = context.object
        ob.name = ob.data.name = "PovIsosurface"
        ob.pov.object_as = "ISOSURFACE_VIEW"
        ob.pov.isosurface_eq = eq
        ob.pov.contained_by = 'box'
        bpy.ops.object.mode_set(mode="OBJECT")

class POVRAY_OT_isosurface_add(Operator):
    """Add the representation of POV isosurface sphere by a Blender mesh icosphere.

    Flag its primitive type with a specific pov.object_as attribute and lock edit mode
    to keep proxy consistency by hiding edit geometry."""

    bl_idname = "pov.addisosurface"
    bl_label = "Generic Isosurface"
    bl_description = "Add Isosurface"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    # Keep in sync within object_properties.py section Sphere
    # as this allows interactive update
    isosurface_eq: StringProperty(
        name="f(x,y,z)=",
        description="Type the POV Isosurface function syntax for equation, "
        "pattern,etc. ruling an implicit surface to be rendered",
        default="sqrt(pow(x,2) + pow(y,2) + pow(z,2)) - 1.5",
    )
    imported_loc: FloatVectorProperty(
        name="Imported Pov location", precision=6, default=(0.0, 0.0, 0.0)
    )

    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        props = self.properties
        ob = context.object
        if ob:
            if ob.pov.imported_loc:
                LOC = ob.pov.imported_loc
        elif not props.imported_loc:
            LOC = bpy.context.scene.cursor.location
        else:
            LOC = props.imported_loc
        pov_isosurface_view_define(context, self, None, LOC)
        self.report(
            {'INFO'}, "This native POV-Ray primitive " "is only an abstract proxy in Blender"
        )
        return {'FINISHED'}



class POVRAY_OT_isosurface_update(Operator):
    """Update the POV isosurface.

    Rerun pov_isosurface_view_define() function
    with the new parameters"""

    bl_idname = "pov.isosurface_update"
    bl_label = "Update"
    bl_description = "Update Isosurface"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        ob = context.object
        return ob and ob.data and ob.type == 'ISOSURFACE_VIEW' and engine in cls.COMPAT_ENGINES

    def execute(self, context):

        pov_isosurface_view_define(context, None, context.object, context.object.location)

        return {'FINISHED'}


class POVRAY_OT_isosurface_box_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV isosurface box using also just a Blender mesh cube.

    Flag its primitive type with a specific pov.object_as attribute and lock edit mode
    to keep proxy consistency by hiding edit geometry."""

    bl_idname = "pov.addisosurfacebox"
    bl_label = "Isosurface Box"
    bl_description = "Add Isosurface contained by Box"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.mesh.primitive_cube_add()
Maurice Raybaud's avatar
Maurice Raybaud committed
            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
        ob.name = 'PovIsosurfaceBox'
class POVRAY_OT_isosurface_sphere_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV isosurface sphere by a Blender mesh icosphere.

    Flag its primitive type with a specific pov.object_as attribute and lock edit mode
    to keep proxy consistency by hiding edit geometry."""

    bl_idname = "pov.addisosurfacesphere"
    bl_label = "Isosurface Sphere"
    bl_description = "Add Isosurface contained by Sphere"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4)
Maurice Raybaud's avatar
Maurice Raybaud committed
            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
        bpy.ops.object.shade_smooth()
        ob.name = 'PovIsosurfaceSphere'
class POVRAY_OT_sphere_sweep_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV sphere_sweep using a Blender NURBS curve.

    Flag its primitive type with a specific ob.pov.curveshape attribute and
    leave access to edit mode to keep user editable handles."""

    bl_idname = "pov.addspheresweep"
    bl_label = "Sphere Sweep"
    bl_description = "Create Sphere Sweep along curve"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.curve.primitive_nurbs_curve_add()
        ob = context.object
        ob.name = ob.data.name = "PovSphereSweep"
        ob.pov.curveshape = "sphere_sweep"
        ob.data.bevel_depth = 0.02
        ob.data.bevel_resolution = 4
        ob.data.fill_mode = 'FULL'
        # ob.data.splines[0].order_u = 4
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV blob using a Blender meta ball.

    No need to flag its primitive type as meta are exported to blobs
    and leave access to edit mode to keep user editable thresholds."""

    bl_idname = "pov.addblobsphere"
    bl_label = "Blob Sphere"
    bl_description = "Add Blob Sphere"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.object.metaball_add(type='BALL')
class POVRAY_OT_blobcapsule_add(Operator):
    """Add the representation of POV blob using a Blender meta ball.

    No need to flag its primitive type as meta are exported to blobs
    and leave access to edit mode to keep user editable thresholds."""

    bl_idname = "pov.addblobcapsule"
    bl_label = "Blob Capsule"
    bl_description = "Add Blob Capsule"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.object.metaball_add(type='CAPSULE')
        ob = context.object
        ob.name = "PovBlob"
        return {'FINISHED'}


class POVRAY_OT_blobplane_add(Operator):
    """Add the representation of POV blob using a Blender meta ball.

    No need to flag its primitive type as meta are exported to blobs
    and leave access to edit mode to keep user editable thresholds."""

    bl_idname = "pov.addblobplane"
    bl_label = "Blob Plane"
    bl_description = "Add Blob Plane"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.object.metaball_add(type='PLANE')
        ob = context.object
        ob.name = "PovBlob"
        return {'FINISHED'}


class POVRAY_OT_blobellipsoid_add(Operator):
    """Add the representation of POV blob using a Blender meta ball.

    No need to flag its primitive type as meta are exported to blobs
    and leave access to edit mode to keep user editable thresholds."""

    bl_idname = "pov.addblobellipsoid"
    bl_label = "Blob Ellipsoid"
    bl_description = "Add Blob Ellipsoid"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.object.metaball_add(type='ELLIPSOID')
        ob = context.object
        ob.name = "PovBlob"
        return {'FINISHED'}


class POVRAY_OT_blobcube_add(Operator):
    """Add the representation of POV blob using a Blender meta ball.

    No need to flag its primitive type as meta are exported to blobs
    and leave access to edit mode to keep user editable thresholds."""

    bl_idname = "pov.addblobcube"
    bl_label = "Blob Cube"
    bl_description = "Add Blob Cube"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    def execute(self, context):
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.object.metaball_add(type='CUBE')
        ob = context.object
        ob.name = "PovBlob"
        return {'FINISHED'}


class POVRAY_OT_rainbow_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV rainbow using a Blender spot light.

    Rainbows indeed propagate along a visibility cone.
    Flag its primitive type with a specific ob.pov.object_as attribute
    and leave access to edit mode to keep user editable handles.
    Add a constraint to orient it towards camera because POV Rainbows
    are view dependant and having it always initially visible is less
    confusing """

    bl_idname = "pov.addrainbow"
    bl_label = "Rainbow"
    bl_description = "Add Rainbow"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        bpy.ops.object.light_add(type='SPOT', radius=1)
        ob = context.object
        ob.data.show_cone = False
        ob.data.spot_blend = 0.5
        # ob.data.shadow_buffer_clip_end = 0 # deprecated in 2.8
        ob.data.shadow_buffer_clip_start = 4 * cam.location.length
Campbell Barton's avatar
Campbell Barton committed
        ob.data.energy = 0
        ob.name = ob.data.name = "PovRainbow"
        ob.pov.object_as = "RAINBOW"
        # obj = context.object
        bpy.ops.object.constraint_add(type='DAMPED_TRACK')

        ob.constraints["Damped Track"].target = cam
        ob.constraints["Damped Track"].track_axis = 'TRACK_NEGATIVE_Z'
        ob.location = -cam.location

        # refocus on the actual rainbow
        bpy.context.view_layer.objects.active = ob
        ob.select_set(True)
class POVRAY_OT_height_field_add(bpy.types.Operator, ImportHelper):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV height_field using a displaced grid.

    texture slot fix and displace modifier will be needed because noise
    displace operator was deprecated in 2.8"""

    bl_idname = "pov.addheightfield"
    bl_label = "Height Field"
    bl_description = "Add Height Field"
Maurice Raybaud's avatar
Maurice Raybaud committed
    # Keep in sync within object_properties.py section HeightFields
    # as this allows interactive update

    # 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",
    # options={'HIDDEN'},
    # )
Maurice Raybaud's avatar
Maurice Raybaud committed
    quality: IntProperty(name="Quality", description="", default=100, min=1, max=100)
    hf_filename: StringProperty(maxlen=1024)
Maurice Raybaud's avatar
Maurice Raybaud committed
    hf_gamma: FloatProperty(name="Gamma", description="Gamma", min=0.0001, max=20.0, default=1.0)
Maurice Raybaud's avatar
Maurice Raybaud committed
    hf_premultiplied: BoolProperty(name="Premultiplied", description="Premultiplied", default=True)
    hf_smooth: BoolProperty(name="Smooth", description="Smooth", default=False)
    hf_water: FloatProperty(
Maurice Raybaud's avatar
Maurice Raybaud committed
        name="Water Level", description="Wather Level", min=0.00, max=1.00, default=0.0
Maurice Raybaud's avatar
Maurice Raybaud committed
    hf_hierarchy: BoolProperty(name="Hierarchy", description="Height field hierarchy", default=True)

    def execute(self, context):
        props = self.properties
        impath = bpy.path.abspath(self.filepath)
        img = bpy.data.images.load(impath)
        im_name = img.name
        im_name, file_extension = os.path.splitext(im_name)
        hf_tex = bpy.data.textures.new('%s_hf_image' % im_name, type='IMAGE')
        mat = bpy.data.materials.new('Tex_%s_hf' % im_name)
Maurice Raybaud's avatar
Maurice Raybaud committed
        hf_slot = mat.pov_texture_slots.add()
        hf_slot.texture = hf_tex.name
        # layers = 20*[False]
        # layers[0] = True
        res = 100 / quality
        w, h = hf_tex.image.size[:]
        w = int(w / res)
        h = int(h / res)
Maurice Raybaud's avatar
Maurice Raybaud committed
        bpy.ops.mesh.primitive_grid_add(x_subdivisions=w, y_subdivisions=h, size=0.5)
        ob.name = ob.data.name = '%s' % im_name
        ob.data.materials.append(mat)
        bpy.ops.object.mode_set(mode="EDIT")
        # bpy.ops.mesh.noise(factor=1) # TODO replace by displace modifier, noise deprecated in 2.8
        # needs a loop to select by index?
        # bpy.ops.object.material_slot_remove()
        # material just left there for now
Maurice Raybaud's avatar
Maurice Raybaud committed
        mat.pov_texture_slots.clear()
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
        ob.pov.object_as = 'HEIGHT_FIELD'
Maurice Raybaud's avatar
Maurice Raybaud committed
        # POV-Ray will soon use only forwards slashes on every OS and already can
        forward_impath = impath.replace(os.sep, '/')
        ob.pov.hf_filename = forward_impath
# ----------------------------------- TORUS ----------------------------------- #
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV torus using just a Blender torus.
Maurice Raybaud's avatar
Maurice Raybaud committed
    Picking properties either from creation operator, import, or data update.
Maurice Raybaud's avatar
Maurice Raybaud committed
    But flag its primitive type with a specific pov.object_as attribute and lock edit mode
    to keep proxy consistency by hiding edit geometry."""
Maurice Raybaud's avatar
Maurice Raybaud committed
    if op:
        mas = op.mas
        mis = op.mis
        mar = op.mar
        mir = op.mir
    else:
Maurice Raybaud's avatar
Maurice Raybaud committed
        mas = ob.pov.torus_major_segments
        mis = ob.pov.torus_minor_segments
        mar = ob.pov.torus_major_radius
        mir = ob.pov.torus_minor_radius
        # keep object rotation and location for the add object operator
Maurice Raybaud's avatar
Maurice Raybaud committed
        obrot = ob.rotation_euler
        obloc = ob.location
Maurice Raybaud's avatar
Maurice Raybaud committed
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.reveal()
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.delete(type='VERT')
        bpy.ops.mesh.primitive_torus_add(
            rotation=obrot,
            location=obloc,
            major_segments=mas,
            minor_segments=mis,
            major_radius=mar,
            minor_radius=mir,
        )
Maurice Raybaud's avatar
Maurice Raybaud committed
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")

    if not ob:
        bpy.ops.mesh.primitive_torus_add(
Maurice Raybaud's avatar
Maurice Raybaud committed
            major_segments=mas, minor_segments=mis, major_radius=mar, minor_radius=mir
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob = context.object
        ob.name = ob.data.name = "PovTorus"
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob.pov.object_as = "TORUS"
        ob.pov.torus_major_segments = mas
        ob.pov.torus_minor_segments = mis
        ob.pov.torus_major_radius = mar
        ob.pov.torus_minor_radius = mir
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
class POVRAY_OT_torus_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV torus using using pov_torus_define() function."""

    bl_idname = "pov.addtorus"
    bl_label = "Torus"
    bl_description = "Add Torus"
    bl_options = {'REGISTER', 'UNDO'}
Maurice Raybaud's avatar
Maurice Raybaud committed
    # Keep in sync within object_properties.py section Torus
    # as this allows interactive update
    mas: IntProperty(name="Major Segments", description="", default=48, min=3, max=720)
    mis: IntProperty(name="Minor Segments", description="", default=12, min=3, max=720)
    mar: FloatProperty(name="Major Radius", description="", default=1.0)
    mir: FloatProperty(name="Minor Radius", description="", default=0.25)

    def execute(self, context):
        props = self.properties
        mar = props.mar
        mir = props.mir
        mas = props.mas
        mis = props.mis
        pov_torus_define(context, self, None)
Maurice Raybaud's avatar
Maurice Raybaud committed
            {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
class POVRAY_OT_torus_update(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Update the POV torus.

    Delete its previous proxy geometry and rerun pov_torus_define() function
    with the new parameters"""

    bl_idname = "pov.torus_update"
    bl_label = "Update"
    bl_description = "Update Torus"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        ob = context.object
Maurice Raybaud's avatar
Maurice Raybaud committed
        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES

    def execute(self, context):

        pov_torus_define(context, None, context.object)

Campbell Barton's avatar
Campbell Barton committed
        return {'FINISHED'}

# -----------------------------------------------------------------------------
class POVRAY_OT_prism_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV prism using using an extruded curve."""

    bl_idname = "pov.addprism"
    bl_label = "Prism"
    bl_description = "Create Prism"
    bl_options = {'REGISTER', 'UNDO'}
Maurice Raybaud's avatar
Maurice Raybaud committed
    prism_n: IntProperty(name="Sides", description="Number of sides", default=5, min=3, max=720)
    prism_r: FloatProperty(name="Radius", description="Radius", default=1.0)

    def execute(self, context):
Maurice Raybaud's avatar
Maurice Raybaud committed
        loft_data = bpy.data.curves.new('Prism', type='CURVE')
        loft_data.dimensions = '2D'
        loft_data.resolution_u = 2
        # loft_data.show_normal_face = False
        loft_data.extrude = 2
        n = props.prism_n
        r = props.prism_r
            x = r * cos(angle)
            y = r * sin(angle)
            coords.append((x, y, z))
            angle += pi * 2 / n
Maurice Raybaud's avatar
Maurice Raybaud committed
        poly = loft_data.splines.new('POLY')
        poly.points.add(len(coords) - 1)
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob = bpy.data.objects.new('Prism_shape', loft_data)
Maurice Raybaud's avatar
Maurice Raybaud committed
        scn.collection.objects.link(ob)
        context.view_layer.objects.active = ob
        ob.select_set(True)
        ob.pov.curveshape = "prism"
        ob.name = ob.data.name = "Prism"
        return {'FINISHED'}
# ----------------------------------- PARAMETRIC ----------------------------------- #
Campbell Barton's avatar
Campbell Barton committed
def pov_parametric_define(context, op, ob):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV parametric surfaces by math surface from add mesh extra objects addon.
Maurice Raybaud's avatar
Maurice Raybaud committed
    Picking properties either from creation operator, import, or data update.
    But flag its primitive type with a specific pov.object_as attribute and lock edit mode
    to keep proxy consistency by hiding edit geometry."""
Maurice Raybaud's avatar
Maurice Raybaud committed
    if op:
        u_min = op.u_min
        u_max = op.u_max
        v_min = op.v_min
        v_max = op.v_max
        x_eq = op.x_eq
        y_eq = op.y_eq
        z_eq = op.z_eq
Maurice Raybaud's avatar
Maurice Raybaud committed
    else:
Maurice Raybaud's avatar
Maurice Raybaud committed
        u_min = ob.pov.u_min
        u_max = ob.pov.u_max
        v_min = ob.pov.v_min
        v_max = ob.pov.v_max
        x_eq = ob.pov.x_eq
        y_eq = ob.pov.y_eq
        z_eq = ob.pov.z_eq

        # keep object rotation and location for the updated object
Maurice Raybaud's avatar
Maurice Raybaud committed
        obloc = ob.location
        obrot = ob.rotation_euler  # In radians
        # Parametric addon has no loc rot, some extra work is needed
        # in case cursor has moved
Maurice Raybaud's avatar
Maurice Raybaud committed
        curloc = bpy.context.scene.cursor.location
Maurice Raybaud's avatar
Maurice Raybaud committed
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.reveal()
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.delete(type='VERT')
        bpy.ops.mesh.primitive_xyz_function_surface(
            x_eq=x_eq,
            y_eq=y_eq,
            z_eq=z_eq,
            range_u_min=u_min,
            range_u_max=u_max,
            range_v_min=v_min,
            range_v_max=v_max,
        )
Maurice Raybaud's avatar
Maurice Raybaud committed
        bpy.ops.mesh.select_all(action='SELECT')
        # extra work:
        bpy.ops.transform.translate(value=(obloc - curloc), proportional_size=1)
Maurice Raybaud's avatar
Maurice Raybaud committed
        # XXX TODO : https://devtalk.blender.org/t/bpy-ops-transform-rotate-option-axis/6235/7
        # to complete necessary extra work rotation, after updating from blender version > 2.92
        # update and uncomment below, but simple axis deprecated since 2.8
        # bpy.ops.transform.rotate(axis=obrot, proportional_size=1)
Maurice Raybaud's avatar
Maurice Raybaud committed

        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")

    if not ob:
        bpy.ops.mesh.primitive_xyz_function_surface(
            x_eq=x_eq,
            y_eq=y_eq,
            z_eq=z_eq,
            range_u_min=u_min,
            range_u_max=u_max,
            range_v_min=v_min,
            range_v_max=v_max,
        )
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob = context.object
        ob.name = ob.data.name = "PovParametric"
Maurice Raybaud's avatar
Maurice Raybaud committed
        ob.pov.object_as = "PARAMETRIC"

        ob.pov.u_min = u_min
        ob.pov.u_max = u_max
        ob.pov.v_min = v_min
        ob.pov.v_max = v_max
        ob.pov.x_eq = x_eq
        ob.pov.y_eq = y_eq
        ob.pov.z_eq = z_eq

        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
class POVRAY_OT_parametric_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the representation of POV parametric surfaces using pov_parametric_define() function."""
    bl_idname = "pov.addparametric"
    bl_label = "Parametric"
    bl_description = "Add Paramertic"
    bl_options = {'REGISTER', 'UNDO'}
Maurice Raybaud's avatar
Maurice Raybaud committed
    # Keep in sync within object_properties.py section Parametric primitive
    # as this allows interactive update
    u_min: FloatProperty(name="U Min", description="", default=0.0)
    v_min: FloatProperty(name="V Min", description="", default=0.0)
    u_max: FloatProperty(name="U Max", description="", default=6.28)
    v_max: FloatProperty(name="V Max", description="", default=12.57)
    x_eq: StringProperty(maxlen=1024, default="cos(v)*(1+cos(u))*sin(v/8)")
    y_eq: StringProperty(maxlen=1024, default="sin(u)*sin(v/8)+cos(v/8)*1.5")
    z_eq: StringProperty(maxlen=1024, default="sin(v)*(1+cos(u))*sin(v/8)")

    def execute(self, context):
        props = self.properties
        u_min = props.u_min
        v_min = props.v_min
        u_max = props.u_max
        v_max = props.v_max
        x_eq = props.x_eq
        y_eq = props.y_eq
        z_eq = props.z_eq
        try:
            pov_parametric_define(context, self, None)
            self.report(
                {'INFO'}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
            )
        except AttributeError:
            self.report(
                {'INFO'}, "Please enable Add Mesh: Extra Objects addon"
            )
class POVRAY_OT_parametric_update(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Update the representation of POV parametric surfaces.

    Delete its previous proxy geometry and rerun pov_parametric_define() function
    with the new parameters"""
    bl_idname = "pov.parametric_update"
    bl_label = "Update"
    bl_description = "Update parametric object"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    @classmethod
    def poll(cls, context):
        engine = context.scene.render.engine
        ob = context.object
Maurice Raybaud's avatar
Maurice Raybaud committed
        return ob and ob.data and ob.type == 'MESH' and engine in cls.COMPAT_ENGINES

    def execute(self, context):

        pov_parametric_define(context, None, context.object)

        return {'FINISHED'}
# -----------------------------------------------------------------------------
class POVRAY_OT_shape_polygon_to_circle_add(Operator):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Add the proxy mesh for POV Polygon to circle lofting macro"""

    bl_idname = "pov.addpolygontocircle"
    bl_label = "Polygon To Circle Blending"
    bl_description = "Add Polygon To Circle Blending Surface"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}
Maurice Raybaud's avatar
Maurice Raybaud committed
    # Keep in sync within object_properties.py section PolygonToCircle properties
    # as this allows interactive update
    polytocircle_resolution: IntProperty(
        name="Resolution", description="", default=3, min=0, max=256
    )
Maurice Raybaud's avatar
Maurice Raybaud committed
    polytocircle_ngon: IntProperty(name="NGon", description="", min=3, max=64, default=5)
    polytocircle_ngonR: FloatProperty(name="NGon Radius", description="", default=0.3)
    polytocircle_circleR: FloatProperty(name="Circle Radius", description="", default=1.0)

    def execute(self, context):
        props = self.properties
        ngon = props.polytocircle_ngon
        ngonR = props.polytocircle_ngonR
        circleR = props.polytocircle_circleR
        resolution = props.polytocircle_resolution
        # layers = 20*[False]
        # layers[0] = True
        bpy.ops.mesh.primitive_circle_add(
            vertices=ngon, radius=ngonR, fill_type='NGON', enter_editmode=True
        )
        bpy.ops.transform.translate(value=(0, 0, 1))
        bpy.ops.mesh.subdivide(number_cuts=resolution)
        numCircleVerts = ngon + (ngon * resolution)
        bpy.ops.mesh.primitive_circle_add(
Maurice Raybaud's avatar
Maurice Raybaud committed
            vertices=numCircleVerts, radius=circleR, fill_type='NGON', enter_editmode=True
        bpy.ops.transform.translate(value=(0, 0, -1))
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.bridge_edge_loops()
        if ngon < 5:
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.mesh.primitive_circle_add(
Maurice Raybaud's avatar
Maurice Raybaud committed
                vertices=ngon, radius=ngonR, fill_type='TRIFAN', enter_editmode=True
            bpy.ops.transform.translate(value=(0, 0, 1))
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.remove_doubles()
        bpy.ops.object.mode_set(mode='OBJECT')
        ob = context.object
        ob.name = "Polygon_To_Circle"
        ob.pov.object_as = 'POLYCIRCLE'
        ob.pov.ngon = ngon
        ob.pov.ngonR = ngonR
        ob.pov.circleR = circleR
        bpy.ops.object.mode_set(mode="EDIT")
        bpy.ops.mesh.hide(unselected=False)
        bpy.ops.object.mode_set(mode="OBJECT")
        return {'FINISHED'}
classes = (
    POVRAY_OT_lathe_add,
    POVRAY_OT_superellipsoid_add,
    POVRAY_OT_superellipsoid_update,
    POVRAY_OT_supertorus_add,
    POVRAY_OT_supertorus_update,
    POVRAY_OT_loft_add,
    POVRAY_OT_plane_add,
    POVRAY_OT_box_add,
    POVRAY_OT_cylinder_add,
    POVRAY_OT_cylinder_update,
    POVRAY_OT_sphere_add,
    POVRAY_OT_sphere_update,
    POVRAY_OT_cone_add,
    POVRAY_OT_cone_update,
    POVRAY_OT_isosurface_add,
    POVRAY_OT_isosurface_update,
    POVRAY_OT_isosurface_box_add,
    POVRAY_OT_isosurface_sphere_add,
    POVRAY_OT_sphere_sweep_add,
    POVRAY_OT_blobsphere_add,
    POVRAY_OT_blobcapsule_add,
    POVRAY_OT_blobplane_add,
    POVRAY_OT_blobellipsoid_add,
    POVRAY_OT_blobcube_add,
    POVRAY_OT_rainbow_add,
    POVRAY_OT_height_field_add,
    POVRAY_OT_torus_add,
    POVRAY_OT_torus_update,
    POVRAY_OT_prism_add,
    POVRAY_OT_parametric_add,
    POVRAY_OT_parametric_update,
    POVRAY_OT_shape_polygon_to_circle_add,
)


def register():
    for cls in classes:
        register_class(cls)


def unregister():
    for cls in reversed(classes):
        unregister_class(cls)