# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

""" Get POV-Ray specific objects In and Out of Blender """

import bpy
import os.path
from bpy_extras.io_utils import ImportHelper
from bpy_extras import object_utils
from bpy.utils import register_class
from math import atan, pi, degrees, sqrt, cos, sin


from bpy.props import (
    StringProperty,
    BoolProperty,
    IntProperty,
    FloatProperty,
    FloatVectorProperty,
    EnumProperty,
    PointerProperty,
    CollectionProperty,
)

from mathutils import Vector, Matrix

# import collections


def pov_define_mesh(mesh, verts, edges, faces, name, hide_geometry=True):
    """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(bpy.types.Operator):
    """Add the representation of POV lathe using a screw modifier."""

    bl_idname = "pov.addlathe"
    bl_label = "Lathe"
    bl_options = {'REGISTER', 'UNDO'}
    bl_description = "adds lathe"

    def execute(self, context):
        # ayers=[False]*20
        # layers[0]=True
        bpy.ops.curve.primitive_bezier_curve_add(
            location=context.scene.cursor.location,
            rotation=(0, 0, 0),
            # layers=layers,
        )
        ob = context.view_layer.objects.active
        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'
        self.report({'INFO'}, "This native POV-Ray primitive")
        ob.pov.curveshape = "lathe"
        bpy.ops.object.modifier_add(type='SCREW')
        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 the pov_superellipsoid_define() function."""

    if op:
        mesh = None

        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

    else:
        assert ob
        mesh = ob.data

        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 = []
    r = 1

    stepSegment = 360 / v * pi / 180
    stepRing = pi / u
    angSegment = 0
    angRing = -pi / 2

    step = 0
    for ring in range(0, u - 1):
        angRing += stepRing
        for segment in range(0, v):
            step += 1
            angSegment += stepSegment
            x = r * (abs(cos(angRing)) ** n1) * (abs(cos(angSegment)) ** n2)
            if (cos(angRing) < 0 and cos(angSegment) > 0) or (
                cos(angRing) > 0 and cos(angSegment) < 0
            ):
                x = -x
            y = r * (abs(cos(angRing)) ** n1) * (abs(sin(angSegment)) ** n2)
            if (cos(angRing) < 0 and sin(angSegment) > 0) or (
                cos(angRing) > 0 and sin(angSegment) < 0
            ):
                y = -y
            z = r * (abs(sin(angRing)) ** n1)
            if sin(angRing) < 0:
                z = -z
            x = round(x, 4)
            y = round(y, 4)
            z = round(z, 4)
            verts.append((x, y, z))
    if edit == 'TRIANGLES':
        verts.append((0, 0, 1))
        verts.append((0, 0, -1))

    faces = []

    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)
            faces.append(face)
    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)
                faces.append(face)
            if i == v - 1:
                face = (indexDown, i, 0)
                faces.append(face)
        for i in range(0, v):
            if i < v - 1:
                face = (indexUp, i + indexStartDown, i + indexStartDown + 1)
                faces.append(face)
            if i == v - 1:
                face = (indexUp, i + indexStartDown, indexStartDown)
                faces.append(face)
    if edit == 'NGONS':
        face = []
        for i in range(0, v):
            face.append(i)
        faces.append(face)
        face = []
        indexUp = len(verts) - 1
        for i in range(0, v):
            face.append(indexUp - i)
        faces.append(face)
    mesh = pov_define_mesh(mesh, verts, [], faces, "SuperEllipsoid")

    if not ob:
        ob = object_utils.object_data_add(context, mesh, operator=None)
        # engine = context.scene.render.engine what for?
        ob = context.object
        ob.name = ob.data.name = "PovSuperellipsoid"
        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(bpy.types.Operator):
    """Add the representation of POV superellipsoid using the pov_superellipsoid_define() function."""

    bl_idname = "pov.addsuperellipsoid"
    bl_label = "Add SuperEllipsoid"
    bl_description = "Create a SuperEllipsoid"
    bl_options = {'REGISTER', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    # XXX Keep it in sync with __init__'s RenderPovSettingsConePrimitive
    #     If someone knows how to define operators' props from a func, I'd be delighted to learn it!
    se_param1: FloatProperty(
        name="Parameter 1", description="", min=0.00, max=10.0, default=0.04
    )

    se_param2: FloatProperty(
        name="Parameter 2", description="", min=0.00, max=10.0, default=0.04
    )

    se_u: IntProperty(
        name="U-segments",
        description="radial segmentation",
        default=20,
        min=4,
        max=265,
    )
    se_v: IntProperty(
        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(
        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):
        pov_superellipsoid_define(context, self, None)

        self.report(
            {'INFO'},
            "This native POV-Ray primitive won't have any vertex to show in edit mode",
        )

        return {'FINISHED'}


class POVRAY_OT_superellipsoid_update(bpy.types.Operator):
    """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
        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'}


def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
    faces = []
    if not vertIdx1 or not vertIdx2:
        return None
    if len(vertIdx1) < 2 and len(vertIdx2) < 2:
        return None
    fan = False
    if len(vertIdx1) != len(vertIdx2):
        if len(vertIdx1) == 1 and len(vertIdx2) > 1:
            fan = True
        else:
            return None
    total = len(vertIdx2)
    if closed:
        if flipped:
            face = [vertIdx1[0], vertIdx2[0], vertIdx2[total - 1]]
            if not fan:
                face.append(vertIdx1[total - 1])
            faces.append(face)

        else:
            face = [vertIdx2[0], vertIdx1[0]]
            if not fan:
                face.append(vertIdx1[total - 1])
            face.append(vertIdx2[total - 1])
            faces.append(face)
    for num in range(total - 1):
        if flipped:
            if fan:
                face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
            else:
                face = [
                    vertIdx2[num],
                    vertIdx1[num],
                    vertIdx1[num + 1],
                    vertIdx2[num + 1],
                ]
            faces.append(face)
        else:
            if fan:
                face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
            else:
                face = [
                    vertIdx1[num],
                    vertIdx2[num],
                    vertIdx2[num + 1],
                    vertIdx1[num + 1],
                ]
            faces.append(face)

    return faces


def power(a, b):
    if a < 0:
        return -((-a) ** b)
    return a ** b


def supertoroid(R, r, u, v, n1, n2):
    a = 2 * pi / u
    b = 2 * pi / v
    verts = []
    faces = []
    for i in range(u):
        s = power(sin(i * a), n1)
        c = power(cos(i * a), n1)
        for j in range(v):
            c2 = R + r * power(cos(j * b), n2)
            s2 = r * power(sin(j * b), n2)
            verts.append(
                (c * c2, s * c2, s2)
            )  # type as a (mathutils.Vector(c*c2,s*c2,s2))?
        if i > 0:
            f = createFaces(
                range((i - 1) * v, i * v),
                range(i * v, (i + 1) * v),
                closed=True,
            )
            faces.extend(f)
    f = createFaces(range((u - 1) * v, u * v), range(v), closed=True)
    faces.extend(f)
    return verts, faces


def pov_supertorus_define(context, op, ob):
    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:
        ob = object_utils.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(bpy.types.Operator):
    """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'}
    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(
        name="small radius",
        description="The radius of the tube",
        default=0.3,
        min=0.01,
        max=100.0,
    )
    st_u: IntProperty(
        name="U-segments",
        description="radial segmentation",
        default=16,
        min=3,
        max=265,
    )
    st_v: IntProperty(
        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(
        name="Use Int.+Ext. radii",
        description="Use internal and external radii",
        default=False,
    )
    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)

        self.report(
            {'INFO'},
            "This native POV-Ray primitive won't have any vertex to show in edit mode",
        )
        return {'FINISHED'}


class POVRAY_OT_supertorus_update(bpy.types.Operator):
    """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
        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(bpy.types.Operator):
    """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(
        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
    )
    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,
    )
    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):

        props = self.properties
        loftData = bpy.data.curves.new('Loft', type='CURVE')
        loftData.dimensions = '3D'
        loftData.resolution_u = 2
        # loftData.show_normal_face = False # deprecated in 2.8
        n = props.loft_n
        thick = props.loft_thick
        side = props.loft_rings_side
        bottom = props.loft_rings_bottom
        h = props.loft_height
        r = props.loft_r
        distB = r / bottom
        r0 = 0.00001
        z = -h / 2
        print("New")
        for i in range(bottom + 1):
            coords = []
            angle = 0
            for p in range(n):
                x = r0 * cos(angle)
                y = r0 * sin(angle)
                coords.append((x, y, z))
                angle += pi * 2 / n
            r0 += distB
            nurbs = loftData.splines.new('NURBS')
            nurbs.points.add(len(coords) - 1)
            for i, coord in enumerate(coords):
                x, y, z = coord
                nurbs.points[i].co = (x, y, z, 1)
            nurbs.use_cyclic_u = True
        for i in range(side):
            z += h / side
            coords = []
            angle = 0
            for p in range(n):
                x = r * cos(angle)
                y = r * sin(angle)
                coords.append((x, y, z))
                angle += pi * 2 / n
            nurbs = loftData.splines.new('NURBS')
            nurbs.points.add(len(coords) - 1)
            for i, coord in enumerate(coords):
                x, y, z = coord
                nurbs.points[i].co = (x, y, z, 1)
            nurbs.use_cyclic_u = True
        r -= thick
        for i in range(side):
            coords = []
            angle = 0
            for p in range(n):
                x = r * cos(angle)
                y = r * sin(angle)
                coords.append((x, y, z))
                angle += pi * 2 / n
            nurbs = loftData.splines.new('NURBS')
            nurbs.points.add(len(coords) - 1)
            for i, coord in enumerate(coords):
                x, y, z = coord
                nurbs.points[i].co = (x, y, z, 1)
            nurbs.use_cyclic_u = True
            z -= h / side
        z = (-h / 2) + thick
        distB = (r - 0.00001) / bottom
        for i in range(bottom + 1):
            coords = []
            angle = 0
            for p in range(n):
                x = r * cos(angle)
                y = r * sin(angle)
                coords.append((x, y, z))
                angle += pi * 2 / n
            r -= distB
            nurbs = loftData.splines.new('NURBS')
            nurbs.points.add(len(coords) - 1)
            for i, coord in enumerate(coords):
                x, y, z = coord
                nurbs.points[i].co = (x, y, z, 1)
            nurbs.use_cyclic_u = True
        ob = bpy.data.objects.new('Loft_shape', loftData)
        scn = bpy.context.scene
        scn.collection.objects.link(ob)
        context.view_layer.objects.active = ob
        ob.select_set(True)
        ob.pov.curveshape = "loft"
        return {'FINISHED'}


class POVRAY_OT_plane_add(bpy.types.Operator):
    """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=100000)
        ob = context.object
        ob.name = ob.data.name = 'PovInfinitePlane'
        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 = "PLANE"
        return {'FINISHED'}


class POVRAY_OT_box_add(bpy.types.Operator):
    """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):
        # 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")
        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")
        ob.pov.object_as = "BOX"
        return {'FINISHED'}


def pov_cylinder_define(context, op, ob, radius, loc, loc_cap):
    if op:
        R = op.R
        loc = bpy.context.scene.cursor.location
        loc_cap[0] = loc[0]
        loc_cap[1] = loc[1]
        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(
        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(bpy.types.Operator):
    """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'}

    # XXX Keep it in sync with __init__'s cylinder Primitive
    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):
        props = self.properties
        R = props.R
        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
        else:
            if not props.imported_cyl_loc:
                LOC_CAP = LOC = bpy.context.scene.cursor.location
                LOC_CAP[2] += 2.0
            else:
                LOC = props.imported_cyl_loc
                LOC_CAP = props.imported_cyl_loc_cap
            self.report(
                {'INFO'},
                "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(bpy.types.Operator):
    """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
        ob = context.object
        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)

        return {'FINISHED'}


################################SPHERE##########################################
def pov_sphere_define(context, op, ob, loc):
    """create the representation of POV sphere using a Blender icosphere.

    Its nice platonic solid curvature better represents pov rendertime
    tesselation than a UV sphere"""

    if op:
        R = op.R
        loc = bpy.context.scene.cursor.location
    else:
        assert ob
        R = ob.pov.sphere_radius

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

        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(
            subdivisions=4,
            radius=ob.pov.sphere_radius,
            location=loc,
            rotation=obrot,
        )
        # 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.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')

    if not ob:
        bpy.ops.mesh.primitive_ico_sphere_add(
            subdivisions=4, radius=R, location=loc
        )
        ob = context.object
        ob.name = ob.data.name = "PovSphere"
        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(bpy.types.Operator):
    """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'}

    # XXX Keep it in sync with __init__'s torus Primitive
    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):
        props = self.properties
        R = props.R
        ob = context.object

        if ob:
            if ob.pov.imported_loc:
                LOC = ob.pov.imported_loc
        else:
            if 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",
                )
        pov_sphere_define(context, self, None, LOC)

        return {'FINISHED'}

    # def execute(self,context):
    ## 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(bpy.types.Operator):
    """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
        return (
            ob
            and ob.data
            and ob.type == 'MESH'
            and engine in cls.COMPAT_ENGINES
        )

    def execute(self, context):

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

        return {'FINISHED'}


####################################CONE#######################################
def pov_cone_define(context, op, ob):
    """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:
        assert ob
        mesh = ob.data
        base = ob.pov.cone_base_radius
        cap = ob.pov.cone_cap_radius
        seg = ob.pov.cone_segments
        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:
        ob = object_utils.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(bpy.types.Operator):
    """Add the representation of POV cone using pov_cone_define() function."""

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

    # XXX Keep it in sync with __init__.py's RenderPovSettingsConePrimitive
    #     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(
        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

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

        self.report(
            {'INFO'},
            "This native POV-Ray primitive won't have any vertex to show in edit mode",
        )
        return {'FINISHED'}


class POVRAY_OT_cone_update(bpy.types.Operator):
    """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
        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##################################


class POVRAY_OT_isosurface_box_add(bpy.types.Operator):
    """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()
        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")
        ob.pov.object_as = "ISOSURFACE"
        ob.pov.contained_by = 'box'
        ob.name = 'PovIsosurfaceBox'
        return {'FINISHED'}


class POVRAY_OT_isosurface_sphere_add(bpy.types.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.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)
        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 = "ISOSURFACE"
        ob.pov.contained_by = 'sphere'
        ob.name = 'PovIsosurfaceSphere'
        return {'FINISHED'}


class POVRAY_OT_sphere_sweep_add(bpy.types.Operator):
    """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

        return {'FINISHED'}


class POVRAY_OT_blob_add(bpy.types.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.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')
        ob = context.object
        ob.name = "PovBlob"
        return {'FINISHED'}


class POVRAY_OT_rainbow_add(bpy.types.Operator):
    """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):
        cam = context.scene.camera
        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
        ob.data.distance = cam.location.length
        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)

        return {'FINISHED'}


class POVRAY_OT_height_field_add(bpy.types.Operator, ImportHelper):
    """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 "
    bl_options = {'REGISTER', 'UNDO'}

    # XXX Keep it in sync with __init__'s hf Primitive
    # filename_ext = ".png"

    # filter_glob = StringProperty(
    # 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'},
    # )
    quality: IntProperty(
        name="Quality", description="", default=100, min=1, max=100
    )
    hf_filename: StringProperty(maxlen=1024)

    hf_gamma: FloatProperty(
        name="Gamma", description="Gamma", min=0.0001, max=20.0, default=1.0
    )

    hf_premultiplied: BoolProperty(
        name="Premultiplied", description="Premultiplied", default=True
    )

    hf_smooth: BoolProperty(name="Smooth", description="Smooth", default=False)

    hf_water: FloatProperty(
        name="Water Level",
        description="Wather Level",
        min=0.00,
        max=1.00,
        default=0.0,
    )

    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')
        hf_tex.image = img
        mat = bpy.data.materials.new('Tex_%s_hf' % im_name)
        hf_slot = mat.pov_texture_slots.add()
        hf_slot.texture = hf_tex.name
        # layers = 20*[False]
        # layers[0] = True
        quality = props.quality
        res = 100 / quality
        w, h = hf_tex.image.size[:]
        w = int(w / res)
        h = int(h / res)
        bpy.ops.mesh.primitive_grid_add(
            x_subdivisions=w, y_subdivisions=h, size=0.5
        )
        ob = context.object
        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 a displace modifier as noise deprecated in 2.8
        bpy.ops.object.mode_set(mode="OBJECT")

        # needs a loop to select by index?
        # bpy.ops.object.material_slot_remove()
        # material just left there for now

        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'
        ob.pov.hf_filename = impath
        return {'FINISHED'}


############################TORUS############################################
def pov_torus_define(context, op, ob):
    """Add the representation of POV torus using just a Blender torus.

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

    if op:
        mas = op.mas
        mis = op.mis
        mar = op.mar
        mir = op.mir
    else:
        assert ob
        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
        obrot = ob.rotation_euler
        obloc = ob.location

        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,
        )

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

    if not ob:
        bpy.ops.mesh.primitive_torus_add(
            major_segments=mas,
            minor_segments=mis,
            major_radius=mar,
            minor_radius=mir,
        )
        ob = context.object
        ob.name = ob.data.name = "PovTorus"
        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(bpy.types.Operator):
    """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'}

    # XXX Keep it in sync with __init__'s torus Primitive
    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)
        self.report(
            {'INFO'},
            "This native POV-Ray primitive "
            "won't have any vertex to show in edit mode",
        )
        return {'FINISHED'}


class POVRAY_OT_torus_update(bpy.types.Operator):
    """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
        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)

        return {'FINISHED'}


###################################################################################


class POVRAY_OT_prism_add(bpy.types.Operator):
    """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'}

    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):

        props = self.properties
        loftData = bpy.data.curves.new('Prism', type='CURVE')
        loftData.dimensions = '2D'
        loftData.resolution_u = 2
        # loftData.show_normal_face = False
        loftData.extrude = 2
        n = props.prism_n
        r = props.prism_r
        coords = []
        z = 0
        angle = 0
        for p in range(n):
            x = r * cos(angle)
            y = r * sin(angle)
            coords.append((x, y, z))
            angle += pi * 2 / n
        poly = loftData.splines.new('POLY')
        poly.points.add(len(coords) - 1)
        for i, coord in enumerate(coords):
            x, y, z = coord
            poly.points[i].co = (x, y, z, 1)
        poly.use_cyclic_u = True

        ob = bpy.data.objects.new('Prism_shape', loftData)
        scn = bpy.context.scene
        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######################################
def pov_parametric_define(context, op, ob):
    """Add the representation of POV parametric surfaces by math surface from add mesh extra objects addon."""

    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

    else:
        assert ob
        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
        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
        curloc = bpy.context.scene.cursor.location

        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,
        )
        bpy.ops.mesh.select_all(action='SELECT')
        # extra work:
        bpy.ops.transform.translate(value=(obloc - curloc), proportional_size=1)
        bpy.ops.transform.rotate(axis=obrot, proportional_size=1)

        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,
        )
        ob = context.object
        ob.name = ob.data.name = "PovParametric"
        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(bpy.types.Operator):
    """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'}

    # XXX Keep it in sync with __init__'s Parametric primitive
    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

        pov_parametric_define(context, self, None)
        self.report(
            {'INFO'},
            "This native POV-Ray primitive "
            "won't have any vertex to show in edit mode",
        )
        return {'FINISHED'}


class POVRAY_OT_parametric_update(bpy.types.Operator):
    """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
        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(bpy.types.Operator):
    """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'}

    # XXX Keep it in sync with __init__'s polytocircle properties
    polytocircle_resolution: IntProperty(
        name="Resolution", description="", default=3, min=0, max=256
    )
    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.select_all(action='DESELECT')
        bpy.ops.mesh.primitive_circle_add(
            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(
                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'}


#############################IMPORT


class ImportPOV(bpy.types.Operator, ImportHelper):
    """Load Povray files"""

    bl_idname = "import_scene.pov"
    bl_label = "POV-Ray files (.pov/.inc)"
    bl_options = {'PRESET', 'UNDO'}
    COMPAT_ENGINES = {'POVRAY_RENDER'}

    # -----------
    # File props.
    files: CollectionProperty(
        type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'}
    )
    directory: StringProperty(
        maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'}
    )

    filename_ext = {".pov", ".inc"}
    filter_glob: StringProperty(default="*.pov;*.inc", options={'HIDDEN'})

    import_at_cur: BoolProperty(
        name="Import at Cursor Location",
        description="Ignore Object Matrix",
        default=False,
    )

    def execute(self, context):
        from mathutils import Matrix

        verts = []
        faces = []
        materials = []
        blendMats = []  ##############
        povMats = []  ##############
        colors = []
        matNames = []
        lenverts = None
        lenfaces = None
        suffix = -1
        name = 'Mesh2_%s' % suffix
        name_search = False
        verts_search = False
        faces_search = False
        plane_search = False
        box_search = False
        cylinder_search = False
        sphere_search = False
        cone_search = False
        tex_search = False  ##################
        cache = []
        matrixes = {}
        writematrix = False
        index = None
        value = None
        # filepov = bpy.path.abspath(self.filepath) #was used for single files

        def mat_search(cache):
            r = g = b = 0.5
            f = t = 0
            color = None

            for item, value in enumerate(cache):

                if value == 'texture':
                    pass

                if value == 'pigment':

                    if cache[item + 2] in {'rgb', 'srgb'}:
                        pass

                    elif cache[item + 2] in {'rgbf', 'srgbf'}:
                        pass

                    elif cache[item + 2] in {'rgbt', 'srgbt'}:
                        try:
                            r, g, b, t = (
                                float(cache[item + 3]),
                                float(cache[item + 4]),
                                float(cache[item + 5]),
                                float(cache[item + 6]),
                            )
                        except:
                            r = g = b = t = float(cache[item + 2])
                        color = (r, g, b, t)

                    elif cache[item + 2] in {'rgbft', 'srgbft'}:
                        pass

                    else:
                        pass

            if colors == [] or (colors != [] and color not in colors):
                colors.append(color)
                name = ob.name + "_mat"
                matNames.append(name)
                mat = bpy.data.materials.new(name)
                mat.diffuse_color = (r, g, b)
                mat.alpha = 1 - t
                if mat.alpha != 1:
                    mat.use_transparency = True
                ob.data.materials.append(mat)

            else:
                for i, value in enumerate(colors):
                    if color == value:
                        ob.data.materials.append(
                            bpy.data.materials[matNames[i]]
                        )

        for file in self.files:
            print("Importing file: " + file.name)
            filepov = self.directory + file.name
            for line in open(filepov):
                string = line.replace("{", " ")
                string = string.replace("}", " ")
                string = string.replace("<", " ")
                string = string.replace(">", " ")
                string = string.replace(",", " ")
                lw = string.split()
                lenwords = len(lw)
                if lw:
                    if lw[0] == "object":
                        writematrix = True
                    if writematrix:
                        if lw[0] not in {"object", "matrix"}:
                            index = lw[0]
                        if lw[0] in {"matrix"}:
                            value = [
                                float(lw[1]),
                                float(lw[2]),
                                float(lw[3]),
                                float(lw[4]),
                                float(lw[5]),
                                float(lw[6]),
                                float(lw[7]),
                                float(lw[8]),
                                float(lw[9]),
                                float(lw[10]),
                                float(lw[11]),
                                float(lw[12]),
                            ]
                            matrixes[index] = value
                            writematrix = False
            for line in open(filepov):
                S = line.replace("{", " { ")
                S = S.replace("}", " } ")
                S = S.replace(",", " ")
                S = S.replace("<", "")
                S = S.replace(">", " ")
                S = S.replace("=", " = ")
                S = S.replace(";", " ; ")
                S = S.split()
                lenS = len(S)
                for i, word in enumerate(S):
                    ##################Primitives Import##################
                    if word == 'cone':
                        cone_search = True
                        name_search = False
                    if cone_search:
                        cache.append(word)
                        if cache[-1] == '}':
                            try:
                                x0 = float(cache[2])
                                y0 = float(cache[3])
                                z0 = float(cache[4])
                                r0 = float(cache[5])
                                x1 = float(cache[6])
                                y1 = float(cache[7])
                                z1 = float(cache[8])
                                r1 = float(cache[9])
                                # Y is height in most pov files, not z
                                bpy.ops.pov.cone_add(
                                    base=r0, cap=r1, height=(y1 - y0)
                                )
                                ob = context.object
                                ob.location = (x0, y0, z0)
                                # ob.scale = (r,r,r)
                                mat_search(cache)
                            except (ValueError):
                                pass
                            cache = []
                            cone_search = False
                    if word == 'plane':
                        plane_search = True
                        name_search = False
                    if plane_search:
                        cache.append(word)
                        if cache[-1] == '}':
                            try:
                                bpy.ops.pov.addplane()
                                ob = context.object
                                mat_search(cache)
                            except (ValueError):
                                pass
                            cache = []
                            plane_search = False
                    if word == 'box':
                        box_search = True
                        name_search = False
                    if box_search:
                        cache.append(word)
                        if cache[-1] == '}':
                            try:
                                x0 = float(cache[2])
                                y0 = float(cache[3])
                                z0 = float(cache[4])
                                x1 = float(cache[5])
                                y1 = float(cache[6])
                                z1 = float(cache[7])
                                # imported_corner_1=(x0, y0, z0)
                                # imported_corner_2 =(x1, y1, z1)
                                center = (
                                    (x0 + x1) / 2,
                                    (y0 + y1) / 2,
                                    (z0 + z1) / 2,
                                )
                                bpy.ops.pov.addbox()
                                ob = context.object
                                ob.location = center
                                mat_search(cache)

                            except (ValueError):
                                pass
                            cache = []
                            box_search = False
                    if word == 'cylinder':
                        cylinder_search = True
                        name_search = False
                    if cylinder_search:
                        cache.append(word)
                        if cache[-1] == '}':
                            try:
                                x0 = float(cache[2])
                                y0 = float(cache[3])
                                z0 = float(cache[4])
                                x1 = float(cache[5])
                                y1 = float(cache[6])
                                z1 = float(cache[7])
                                imported_cyl_loc = (x0, y0, z0)
                                imported_cyl_loc_cap = (x1, y1, z1)

                                r = float(cache[8])

                                vec = Vector(imported_cyl_loc_cap) - Vector(
                                    imported_cyl_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.
                                # center = ((x0 + x1)/2,(y0 + y1)/2,(z0 + z1)/2)
                                scaleZ = (
                                    sqrt(
                                        (x1 - x0) ** 2
                                        + (y1 - y0) ** 2
                                        + (z1 - z0) ** 2
                                    )
                                    / 2
                                )
                                bpy.ops.pov.addcylinder(
                                    R=r,
                                    imported_cyl_loc=imported_cyl_loc,
                                    imported_cyl_loc_cap=imported_cyl_loc_cap,
                                )
                                ob = context.object
                                ob.location = (x0, y0, z0)
                                ob.rotation_euler = rot.to_euler()
                                ob.scale = (1, 1, scaleZ)

                                # scale data rather than obj?
                                # bpy.ops.object.mode_set(mode='EDIT')
                                # bpy.ops.mesh.reveal()
                                # bpy.ops.mesh.select_all(action='SELECT')
                                # bpy.ops.transform.resize(value=(1,1,scaleZ), orient_type='LOCAL')
                                # bpy.ops.mesh.hide(unselected=False)
                                # bpy.ops.object.mode_set(mode='OBJECT')

                                mat_search(cache)

                            except (ValueError):
                                pass
                            cache = []
                            cylinder_search = False
                    if word == 'sphere':
                        sphere_search = True
                        name_search = False
                    if sphere_search:
                        cache.append(word)
                        if cache[-1] == '}':
                            x = y = z = r = 0
                            try:
                                x = float(cache[2])
                                y = float(cache[3])
                                z = float(cache[4])
                                r = float(cache[5])

                            except (ValueError):
                                pass
                            except:
                                x = y = z = float(cache[2])
                                r = float(cache[3])
                            bpy.ops.pov.addsphere(R=r, imported_loc=(x, y, z))
                            ob = context.object
                            ob.location = (x, y, z)
                            ob.scale = (r, r, r)
                            mat_search(cache)
                            cache = []
                            sphere_search = False
                    ##################End Primitives Import##################
                    if word == '#declare':
                        name_search = True
                    if name_search:
                        cache.append(word)
                        if word == 'mesh2':
                            name_search = False
                            if cache[-2] == '=':
                                name = cache[-3]
                            else:
                                suffix += 1
                            cache = []
                        if word in {'texture', ';'}:
                            name_search = False
                            cache = []
                    if word == 'vertex_vectors':
                        verts_search = True
                    if verts_search:
                        cache.append(word)
                        if word == '}':
                            verts_search = False
                            lenverts = cache[2]
                            cache.pop()
                            cache.pop(0)
                            cache.pop(0)
                            cache.pop(0)
                            for i in range(int(lenverts)):
                                x = i * 3
                                y = (i * 3) + 1
                                z = (i * 3) + 2
                                verts.append(
                                    (
                                        float(cache[x]),
                                        float(cache[y]),
                                        float(cache[z]),
                                    )
                                )
                            cache = []
                    # if word == 'face_indices':
                    # faces_search = True
                    if word == 'texture_list':  ########
                        tex_search = True  #######
                    if tex_search:  #########
                        if (
                            word
                            not in {
                                'texture_list',
                                'texture',
                                '{',
                                '}',
                                'face_indices',
                            }
                            and word.isdigit() == False
                        ):  ##############
                            povMats.append(word)  #################
                    if word == 'face_indices':
                        tex_search = False  ################
                        faces_search = True
                    if faces_search:
                        cache.append(word)
                        if word == '}':
                            faces_search = False
                            lenfaces = cache[2]
                            cache.pop()
                            cache.pop(0)
                            cache.pop(0)
                            cache.pop(0)
                            lf = int(lenfaces)
                            var = int(len(cache) / lf)
                            for i in range(lf):
                                if var == 3:
                                    v0 = i * 3
                                    v1 = i * 3 + 1
                                    v2 = i * 3 + 2
                                    faces.append(
                                        (
                                            int(cache[v0]),
                                            int(cache[v1]),
                                            int(cache[v2]),
                                        )
                                    )
                                if var == 4:
                                    v0 = i * 4
                                    v1 = i * 4 + 1
                                    v2 = i * 4 + 2
                                    m = i * 4 + 3
                                    materials.append((int(cache[m])))
                                    faces.append(
                                        (
                                            int(cache[v0]),
                                            int(cache[v1]),
                                            int(cache[v2]),
                                        )
                                    )
                                if var == 6:
                                    v0 = i * 6
                                    v1 = i * 6 + 1
                                    v2 = i * 6 + 2
                                    m0 = i * 6 + 3
                                    m1 = i * 6 + 4
                                    m2 = i * 6 + 5
                                    materials.append(
                                        (
                                            int(cache[m0]),
                                            int(cache[m1]),
                                            int(cache[m2]),
                                        )
                                    )
                                    faces.append(
                                        (
                                            int(cache[v0]),
                                            int(cache[v1]),
                                            int(cache[v2]),
                                        )
                                    )
                            # mesh = pov_define_mesh(None, verts, [], faces, name, hide_geometry=False)
                            # ob = object_utils.object_data_add(context, mesh, operator=None)

                            me = bpy.data.meshes.new(name)  ########
                            ob = bpy.data.objects.new(name, me)  ##########
                            bpy.context.collection.objects.link(ob)  #########
                            me.from_pydata(verts, [], faces)  ############

                            for mat in bpy.data.materials:  ##############
                                blendMats.append(mat.name)  #############
                            for mName in povMats:  #####################
                                if mName not in blendMats:  ###########
                                    povMat = bpy.data.materials.new(
                                        mName
                                    )  #################
                                    mat_search(cache)
                                ob.data.materials.append(
                                    bpy.data.materials[mName]
                                )  ###################
                            if materials:  ##################
                                for i, val in enumerate(
                                    materials
                                ):  ####################
                                    try:  ###################
                                        ob.data.polygons[
                                            i
                                        ].material_index = (
                                            val
                                        )  ####################
                                    except TypeError:  ###################
                                        ob.data.polygons[
                                            i
                                        ].material_index = int(
                                            val[0]
                                        )  ##################

                            blendMats = []  #########################
                            povMats = []  #########################
                            materials = []  #########################
                            cache = []
                            name_search = True
                            if name in matrixes and self.import_at_cur == False:
                                global_matrix = Matrix.Rotation(
                                    pi / 2.0, 4, 'X'
                                )
                                ob = bpy.context.object
                                matrix = ob.matrix_world
                                v = matrixes[name]
                                matrix[0][0] = v[0]
                                matrix[1][0] = v[1]
                                matrix[2][0] = v[2]
                                matrix[0][1] = v[3]
                                matrix[1][1] = v[4]
                                matrix[2][1] = v[5]
                                matrix[0][2] = v[6]
                                matrix[1][2] = v[7]
                                matrix[2][2] = v[8]
                                matrix[0][3] = v[9]
                                matrix[1][3] = v[10]
                                matrix[2][3] = v[11]
                                matrix = global_matrix * ob.matrix_world
                                ob.matrix_world = matrix
                            verts = []
                            faces = []

                    # if word == 'pigment':
                    # try:
                    # #all indices have been incremented once to fit a bad test file
                    # r,g,b,t = float(S[2]),float(S[3]),float(S[4]),float(S[5])
                    # color = (r,g,b,t)

                    # except (IndexError):
                    # #all indices have been incremented once to fit alternate test file
                    # r,g,b,t = float(S[3]),float(S[4]),float(S[5]),float(S[6])
                    # color = (r,g,b,t)
                    # except UnboundLocalError:
                    # # In case no transmit is specified ? put it to 0
                    # r,g,b,t = float(S[2]),float(S[3]),float(S[4],0)
                    # color = (r,g,b,t)

                    # except (ValueError):
                    # color = (0.8,0.8,0.8,0)
                    # pass

                    # if colors == [] or (colors != [] and color not in colors):
                    # colors.append(color)
                    # name = ob.name+"_mat"
                    # matNames.append(name)
                    # mat = bpy.data.materials.new(name)
                    # mat.diffuse_color = (r,g,b)
                    # mat.alpha = 1-t
                    # if mat.alpha != 1:
                    # mat.use_transparency=True
                    # ob.data.materials.append(mat)
                    # print (colors)
                    # else:
                    # for i in range(len(colors)):
                    # if color == colors[i]:
                    # ob.data.materials.append(bpy.data.materials[matNames[i]])

        ##To keep Avogadro Camera angle:
        # for obj in bpy.context.view_layer.objects:
        # if obj.type == "CAMERA":
        # track = obj.constraints.new(type = "TRACK_TO")
        # track.target = ob
        # track.track_axis ="TRACK_NEGATIVE_Z"
        # track.up_axis = "UP_Y"
        # obj.location = (0,0,0)
        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_box_add,
    POVRAY_OT_isosurface_sphere_add,
    POVRAY_OT_sphere_sweep_add,
    POVRAY_OT_blob_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,
    ImportPOV,
)


def register():
    # from bpy.utils import register_class

    for cls in classes:
        register_class(cls)


def unregister():
    from bpy.utils import unregister_class

    for cls in classes:
        unregister_class(cls)
