Skip to content
Snippets Groups Projects
model_primitives.py 25.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • # SPDX-License-Identifier: GPL-2.0-or-later
    
    """ Get POV-Ray specific objects In and Out of Blender """
    from math import pi, cos, sin
    import os.path
    import bpy
    from bpy_extras.object_utils import object_data_add
    from bpy_extras.io_utils import ImportHelper
    from bpy.utils import register_class, unregister_class
    from bpy.types import Operator
    
    from bpy.props import (
        StringProperty,
        BoolProperty,
        IntProperty,
        FloatProperty,
        FloatVectorProperty,
        EnumProperty,
    )
    
    from mathutils import Vector, Matrix
    
    
    # import collections
    
    
    def write_object_modifiers(ob, File):
        """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 .model_all.write_object_csg_inside_vector
        write_object_csg_inside_vector(ob, file)
        """
    
        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):
        """Generate proxy mesh."""
        if mesh is None:
            mesh = bpy.data.meshes.new(name)
        mesh.from_pydata(verts, edges, faces)
        # Function Arguments change : now bpy.types.Mesh.update (calc_edges, calc_edges_loose,
        # calc_loop_triangles), was (calc_edges, calc_tessface)
    
    
        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 POV_OT_plane_add(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'}
        COMPAT_ENGINES = {"POVRAY_RENDER"}
    
        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")
            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"
    
            ob.update_tag() # as prop set via python not updated in depsgraph
    
            return {"FINISHED"}
    
    
    class POV_OT_box_add(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'}
        COMPAT_ENGINES = {"POVRAY_RENDER"}
    
        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"
    
            ob.update_tag() # as prop set via python not updated in depsgraph
    
            return {"FINISHED"}
    
    
    def pov_cylinder_define(context, op, ob, radius, loc, loc_cap):
        """Pick POV cylinder properties either from creation operator, import, or data update"""
        if op:
            cy_rad = op.cy_rad
            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.data.use_auto_smooth = True
            ob.pov.object_as = "CYLINDER"
    
            ob.update_tag() # as prop set via python not updated in depsgraph
    
    
        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 POV_OT_cylinder_add(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_options = {'REGISTER', 'UNDO'}
        bl_idname = "pov.addcylinder"
        bl_label = "Cylinder"
        bl_description = "Add Cylinder"
    
        COMPAT_ENGINES = {"POVRAY_RENDER"}
    
        # Keep in sync within model_properties.py section Cylinder
        # as this allows interactive update
        cy_rad: 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
            cy_rad = props.cy_rad
            if ob := context.object:
                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
            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.cy_rad, LOC, LOC_CAP)
    
            return {"FINISHED"}
    
    
    class POV_OT_cylinder_update(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
        tessellation than a UV sphere"""
    
        if op:
            sphe_rad = op.sphe_rad
            loc = bpy.context.scene.cursor.location
        else:
            assert ob
            sphe_rad = 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.transform.rotate(axis=obrot,orient_type='GLOBAL')
    
        if not ob:
            bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, radius=sphe_rad, location=loc)
            ob = context.object
            ob.name = ob.data.name = "PovSphere"
            ob.pov.sphere_radius = sphe_rad
            bpy.ops.object.mode_set(mode="EDIT")
            bpy.ops.mesh.hide(unselected=False)
            bpy.ops.object.mode_set(mode="OBJECT")
            ob.data.use_auto_smooth = True
            bpy.ops.object.shade_smooth()
            ob.pov.object_as = "SPHERE"
    
            ob.update_tag() # as prop set via python not updated in depsgraph
    
    
    
    class POV_OT_sphere_add(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'}
        COMPAT_ENGINES = {"POVRAY_RENDER"}
    
        # Keep in sync within model_properties.py section Sphere
        # as this allows interactive update
        sphe_rad: 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
            sphe_rad = props.sphe_rad
            if ob := context.object:
                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
                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.update_tag() # as prop set via python not updated in depsgraph
    
        # ob.name = ob.data.name = 'PovSphere'
        # return {'FINISHED'}
    
    
    class POV_OT_sphere_update(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 ob.pov.object_as == "SPHERE"
                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.extend([(xb, yb, zb), (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 = [i * 2 for i in range(seg - 1, -1, -1)]
            faces.append(base_face)
        if cap != 0:
            cap_face = [i * 2 + 1 for i in range(seg)]
            faces.append(cap_face)
    
        mesh = pov_define_mesh(mesh, verts, [], faces, "PovCone", True)
        if not ob:
            ob = object_data_add(context, mesh, operator=None)
            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
            ob.data.use_auto_smooth = True
            bpy.ops.object.shade_smooth()
            ob.pov.object_as = "CONE"
    
            ob.update_tag() # as prop set via python not updated in depsgraph
    
    
    class POV_OT_cone_add(Operator):
        """Add the representation of POV cone using pov_cone_define() function."""
    
        bl_idname = "pov.addcone"
        bl_label = "Cone"
        bl_description = "Add Cone"
        bl_options = {'REGISTER', 'UNDO'}
        COMPAT_ENGINES = {"POVRAY_RENDER"}
    
        # Keep in sync within model_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(
            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 POV_OT_cone_update(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 ob.pov.object_as == "CONE"
                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"}
    
    
    class POV_OT_rainbow_add(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'}
        COMPAT_ENGINES = {"POVRAY_RENDER"}
    
        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"
    
            ob.update_tag() # as prop set via python not updated in depsgraph
    
    
            # 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"}
    
    
    # ----------------------------------- TORUS ----------------------------------- #
    def pov_torus_define(context, op, ob):
        """Add the representation of POV torus using just a Blender torus.
    
        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."""
    
        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.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")
            ob.data.use_auto_smooth = True
            ob.data.auto_smooth_angle = 0.6
            bpy.ops.object.shade_smooth()
            ob.pov.object_as = "TORUS"
    
            ob.update_tag() # as prop set via python not updated in depsgraph
    
    
    class POV_OT_torus_add(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'}
        COMPAT_ENGINES = {"POVRAY_RENDER"}
    
        # Keep in sync within model_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)
            self.report(
                {"INFO"}, "This native POV-Ray primitive " "won't have any vertex to show in edit mode"
            )
            return {"FINISHED"}
    
    
    class POV_OT_torus_update(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 ob.pov.object_as == "TORUS"
                and engine in cls.COMPAT_ENGINES
            )
    
        def execute(self, context):
    
            pov_torus_define(context, None, context.object)
    
            return {"FINISHED"}
    
    
    # -----------------------------------------------------------------------------
    
    
    class POV_OT_prism_add(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'}
        COMPAT_ENGINES = {"POVRAY_RENDER"}
    
        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
            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
            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 = loft_data.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", loft_data)
            scn = bpy.context.scene
            scn.collection.objects.link(ob)
            context.view_layer.objects.active = ob
            ob.select_set(True)
            bpy.ops.object.mode_set(mode="OBJECT")
            bpy.ops.object.shade_flat()
            ob.data.fill_mode = 'BOTH'
            ob.pov.curveshape = "prism"
            ob.name = ob.data.name = "Prism"
            return {"FINISHED"}
    
    
    classes = (
        POV_OT_plane_add,
        POV_OT_box_add,
        POV_OT_cylinder_add,
        POV_OT_cylinder_update,
        POV_OT_sphere_add,
        POV_OT_sphere_update,
        POV_OT_cone_add,
        POV_OT_cone_update,
        POV_OT_rainbow_add,
        POV_OT_torus_add,
        POV_OT_torus_update,
        POV_OT_prism_add,
    )
    
    
    def register():
        for cls in classes:
            register_class(cls)
    
    
    def unregister():
        for cls in reversed(classes):
            unregister_class(cls)