Skip to content
Snippets Groups Projects
  • Maurice Raybaud's avatar
    d1b824f3
    POV: fix some nested code and further files structure cleanup · d1b824f3
    Maurice Raybaud authored
    * FIX: wrongly nested pov braces made the default outpout file fail
    * FIX: use agnostic metallic property rather than create a duplicate
    * FIX: some 2.8 deprecated properties rewired in spec;diff; emit;ambient
    * FIX: clean up, hierarchize and redesign Global Settings ui panel
    * FIX: re-wire world background alpha to agnostic prop and redo its ui
    * FIX: wrong nested pov braces making the default outpout file fail
    * FIX: use agnostic metallic property rather than create a duplicate
    * FIX: reduced arguments numbers by imports and relocating variables
    * FIX: use more list comprehesions to reduce nested conditions levels
    * FIX: use more consistent class names but cleanup still not finished
    * FIX: use single quotes for enums preferably to distinguish strings
    * FIX: basic level of nodes based material (diffuse color) broken API
    * FIX: blurry reflection corner case caused output file to fail
    * FIX: added context managing ("with") syntaxes reducing crash cases
    ___________________________________________________________
    
    * ADD: model_all.py file to extract mostly object level loop and utils
    * ADD: model_meta_topology.py file to extract metaballs export
    * ADD: object_primitives_topology.py to extract pov compound primitives
    * ADD: nodes_fn.py file to extract main node exporting function
    * ADD: nodes_gui.py file to extract node operators and menus
    * ADD: nodes_properties.py file to extract nodes sub parameters
    * ADD: particles_properties.py to extract particles and fx parameters
    * ADD: render_core.py to extract main RenderEngine inheriting class(es)
    * ADD: shading_ray_properties.py to extract pathtraced shader parameters
    * ADD: texturing_procedural.py to extract algorithmic texture influences
    ___________________________________________________________
    
    * UPDATE: workspace tools icons and a couple of other icons choices
    * RENAME: pov.add.polygontocircle.dat macro workspace tool icon
    * RENAME: base_ui.py to ui_core.py
    * RENAME: shading_nodes.py to nodes.py
    * RENAME: df3_library.py to voxel_lib.py to make dot lookup inform more
    * RENAME: object_mesh_topology.py to model_poly_topology.py
    * RENAME: object_curve_topology.py to model_curve_topology.py
    * RENAME: object_gui.py to model_gui.py
    * RENAME: object_primitives.py to model_primitives.py
    * RENAME: object_properties.py to model_properties.py
    * RENAME: object_particles.py to particles.py
    d1b824f3
    History
    POV: fix some nested code and further files structure cleanup
    Maurice Raybaud authored
    * FIX: wrongly nested pov braces made the default outpout file fail
    * FIX: use agnostic metallic property rather than create a duplicate
    * FIX: some 2.8 deprecated properties rewired in spec;diff; emit;ambient
    * FIX: clean up, hierarchize and redesign Global Settings ui panel
    * FIX: re-wire world background alpha to agnostic prop and redo its ui
    * FIX: wrong nested pov braces making the default outpout file fail
    * FIX: use agnostic metallic property rather than create a duplicate
    * FIX: reduced arguments numbers by imports and relocating variables
    * FIX: use more list comprehesions to reduce nested conditions levels
    * FIX: use more consistent class names but cleanup still not finished
    * FIX: use single quotes for enums preferably to distinguish strings
    * FIX: basic level of nodes based material (diffuse color) broken API
    * FIX: blurry reflection corner case caused output file to fail
    * FIX: added context managing ("with") syntaxes reducing crash cases
    ___________________________________________________________
    
    * ADD: model_all.py file to extract mostly object level loop and utils
    * ADD: model_meta_topology.py file to extract metaballs export
    * ADD: object_primitives_topology.py to extract pov compound primitives
    * ADD: nodes_fn.py file to extract main node exporting function
    * ADD: nodes_gui.py file to extract node operators and menus
    * ADD: nodes_properties.py file to extract nodes sub parameters
    * ADD: particles_properties.py to extract particles and fx parameters
    * ADD: render_core.py to extract main RenderEngine inheriting class(es)
    * ADD: shading_ray_properties.py to extract pathtraced shader parameters
    * ADD: texturing_procedural.py to extract algorithmic texture influences
    ___________________________________________________________
    
    * UPDATE: workspace tools icons and a couple of other icons choices
    * RENAME: pov.add.polygontocircle.dat macro workspace tool icon
    * RENAME: base_ui.py to ui_core.py
    * RENAME: shading_nodes.py to nodes.py
    * RENAME: df3_library.py to voxel_lib.py to make dot lookup inform more
    * RENAME: object_mesh_topology.py to model_poly_topology.py
    * RENAME: object_curve_topology.py to model_curve_topology.py
    * RENAME: object_gui.py to model_gui.py
    * RENAME: object_primitives.py to model_primitives.py
    * RENAME: object_properties.py to model_properties.py
    * RENAME: object_particles.py to particles.py
model_poly_topology.py 26.45 KiB
# SPDX-License-Identifier: GPL-2.0-or-later

# <pep8 compliant>

"""Translate to POV the control point compound geometries.

Here polygon meshes as POV mesh2 objects.
"""

import bpy
from . import texturing
from .scenography import image_format, img_map, img_map_transforms
from .shading import write_object_material_interior
from .model_primitives import write_object_modifiers

def write_object_csg_inside_vector(ob, file):
    """Write inside vector for use by pov CSG, only once per object using boolean"""
    has_csg_inside_vector = False
    for modif in ob.modifiers:
        if not has_csg_inside_vector and modif.type == "BOOLEAN" and ob.pov.boolean_mod == "POV":
            file.write(
                "\tinside_vector <%.6g, %.6g, %.6g>\n"
                % (
                    ob.pov.inside_vector[0],
                    ob.pov.inside_vector[1],
                    ob.pov.inside_vector[2],
                )
            )
            has_csg_inside_vector = True

def export_mesh(file,
                ob,
                povdataname,
                material_names_dictionary,
                unpacked_images,
                tab_level,
                tab_write,
                linebreaksinlists):
    from .render import (
        string_strip_hyphen,
        tab,
        comments,
        preview_dir,
    )

    ob_eval = ob  # not sure this is needed in case to_mesh_clear could damage obj ?
    importance = ob.pov.importance_value

    try:
        me = ob_eval.to_mesh()

    # Here identify the exception for mesh object with no data: Runtime-Error ?
    # So we can write something for the dataname or maybe treated "if not me" below
    except BaseException as e:
        print(e.__doc__)
        print("An exception occurred: {}".format(e))
        # also happens when curves cant be made into meshes because of no-data
        return False  # To continue object loop
    if me:
        me.calc_loop_triangles()
        me_materials = me.materials
        me_faces = me.loop_triangles[:]
        # --- numpytest
        # me_looptris = me.loops

        # Below otypes = ['int32'] is a 32-bit signed integer number numpy datatype
        # get_v_index = np.vectorize(lambda l: l.vertex_index, otypes = ['int32'], cache = True)
        # faces_verts_idx = get_v_index(me_looptris)

    # if len(me_faces)==0:
    # tab_write(file, "\n//dummy sphere to represent empty mesh location\n")
    # tab_write(file, "#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n" % povdataname)

    if not me or not me_faces:
        tab_write(file, "\n//dummy sphere to represent empty mesh location\n")
        tab_write(
            file,
            "#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n"
            % povdataname,
        )
        return False  # To continue object loop

    uv_layers = me.uv_layers
    if len(uv_layers) > 0:
        if me.uv_layers.active and uv_layers.active.data:
            uv_layer = uv_layers.active.data
    else:
        uv_layer = None

    try:
        # vcol_layer = me.vertex_colors.active.data
        vcol_layer = me.vertex_colors.active.data
    except AttributeError:
        vcol_layer = None

    faces_verts = [f.vertices[:] for f in me_faces]
    faces_normals = [f.normal[:] for f in me_faces]
    verts_normals = [v.normal[:] for v in me.vertices]

    # Use named declaration to allow reference e.g. for baking. MR
    file.write("\n")
    tab_write(file, "#declare %s =\n" % povdataname)
    tab_write(file, "mesh2 {\n")
    tab_write(file, "vertex_vectors {\n")
    tab_write(file, "%d" % len(me.vertices))  # vert count

    tab_str = tab * tab_level
    for v in me.vertices:
        if linebreaksinlists:
            file.write(",\n")
            file.write(tab_str + "<%.6f, %.6f, %.6f>" % v.co[:])  # vert count
        else:
            file.write(", ")
            file.write("<%.6f, %.6f, %.6f>" % v.co[:])  # vert count
        # tab_write(file, "<%.6f, %.6f, %.6f>" % v.co[:])  # vert count
    file.write("\n")
    tab_write(file, "}\n")

    # Build unique Normal list
    uniqueNormals = {}
    for fi, f in enumerate(me_faces):
        fv = faces_verts[fi]
        # [-1] is a dummy index, use a list so we can modify in place
        if f.use_smooth:  # Use vertex normals
            for v in fv:
                key = verts_normals[v]
                uniqueNormals[key] = [-1]
        else:  # Use face normal
            key = faces_normals[fi]
            uniqueNormals[key] = [-1]

    tab_write(file, "normal_vectors {\n")
    tab_write(file, "%d" % len(uniqueNormals))  # vert count
    idx = 0
    tab_str = tab * tab_level
    for no, index in uniqueNormals.items():
        if linebreaksinlists:
            file.write(",\n")
            file.write(tab_str + "<%.6f, %.6f, %.6f>" % no)  # vert count
        else:
            file.write(", ")
            file.write("<%.6f, %.6f, %.6f>" % no)  # vert count
        index[0] = idx
        idx += 1
    file.write("\n")
    tab_write(file, "}\n")
    # Vertex colors
    vertCols = {}  # Use for material colors also.

    if uv_layer:
        # Generate unique UV's
        uniqueUVs = {}
        # n = 0
        for f in me_faces:  # me.faces in 2.7
            uvs = [uv_layer[loop_index].uv[:] for loop_index in f.loops]

            for uv in uvs:
                uniqueUVs[uv[:]] = [-1]

        tab_write(file, "uv_vectors {\n")
        # print unique_uvs
        tab_write(file, "%d" % len(uniqueUVs))  # vert count
        idx = 0
        tab_str = tab * tab_level
        for uv, index in uniqueUVs.items():
            if linebreaksinlists:
                file.write(",\n")
                file.write(tab_str + "<%.6f, %.6f>" % uv)
            else:
                file.write(", ")
                file.write("<%.6f, %.6f>" % uv)
            index[0] = idx
            idx += 1
        """
        else:
            # Just add 1 dummy vector, no real UV's
            tab_write(file, '1') # vert count
            file.write(',\n\t\t<0.0, 0.0>')
        """
        file.write("\n")
        tab_write(file, "}\n")
    if me.vertex_colors:
        # Write down vertex colors as a texture for each vertex
        tab_write(file, "texture_list {\n")
        tab_write(
            file, "%d\n" % (len(me_faces) * 3)
        )  # assumes we have only triangles
        VcolIdx = 0
        if comments:
            file.write(
                "\n  //Vertex colors: one simple pigment texture per vertex\n"
            )
        for fi, f in enumerate(me_faces):
            # annoying, index may be invalid
            material_index = f.material_index
            try:
                material = me_materials[material_index]
            except BaseException as e:
                print(e.__doc__)
                print("An exception occurred: {}".format(e))
                material = None
            if (
                material
                # and material.pov.use_vertex_color_paint
            ):  # Or maybe Always use vertex color when there is some for now

                cols = [vcol_layer[loop_index].color[:] for loop_index in f.loops]

                for col in cols:
                    key = (
                        col[0],
                        col[1],
                        col[2],
                        material_index,
                    )  # Material index!
                    VcolIdx += 1
                    vertCols[key] = [VcolIdx]
                    if linebreaksinlists:
                        tab_write(
                            file,
                            "texture {pigment{ color srgb <%6f,%6f,%6f> }}\n"
                            % (col[0], col[1], col[2]),
                        )
                    else:
                        tab_write(
                            file,
                            "texture {pigment{ color srgb <%6f,%6f,%6f> }}"
                            % (col[0], col[1], col[2]),
                        )
                        tab_str = tab * tab_level
            else:
                if material:
                    # Multiply diffuse with SSS Color
                    if material.pov_subsurface_scattering.use:
                        diffuse_color = [
                            i * j
                            for i, j in zip(
                                material.pov_subsurface_scattering.color[:],
                                material.diffuse_color[:],
                            )
                        ]
                        key = (
                            diffuse_color[0],
                            diffuse_color[1],
                            diffuse_color[2],
                            material_index,
                        )
                        vertCols[key] = [-1]
                    else:
                        diffuse_color = material.diffuse_color[:]
                        key = (
                            diffuse_color[0],
                            diffuse_color[1],
                            diffuse_color[2],
                            material_index,
                        )
                        vertCols[key] = [-1]

        tab_write(file, "\n}\n")
        # Face indices
        tab_write(file, "\nface_indices {\n")
        tab_write(file, "%d" % (len(me_faces)))  # faces count
        tab_str = tab * tab_level

        for fi, f in enumerate(me_faces):
            fv = faces_verts[fi]
            material_index = f.material_index

            if vcol_layer:
                cols = [vcol_layer[loop_index].color[:] for loop_index in f.loops]

            if not me_materials or (
                me_materials[material_index] is None
            ):  # No materials
                if linebreaksinlists:
                    file.write(",\n")
                    # vert count
                    file.write(tab_str + "<%d,%d,%d>" % (fv[0], fv[1], fv[2]))
                else:
                    file.write(", ")
                    file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2]))  # vert count
            else:
                material = me_materials[material_index]
                if me.vertex_colors:  # and material.pov.use_vertex_color_paint:
                    # Color per vertex - vertex color

                    col1 = cols[0]
                    col2 = cols[1]
                    col3 = cols[2]

                    ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
                    ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
                    ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
                else:
                    # Color per material - flat material color
                    if material.pov_subsurface_scattering.use:
                        diffuse_color = [
                            i * j
                            for i, j in zip(
                                material.pov_subsurface_scattering.color[:],
                                material.diffuse_color[:],
                            )
                        ]
                    else:
                        diffuse_color = material.diffuse_color[:]
                    ci1 = ci2 = ci3 = vertCols[
                        diffuse_color[0],
                        diffuse_color[1],
                        diffuse_color[2],
                        f.material_index,
                    ][0]
                    # ci are zero based index so we'll subtract 1 from them
                if linebreaksinlists:
                    file.write(",\n")
                    file.write(
                        tab_str
                        + "<%d,%d,%d>, %d,%d,%d"
                        % (
                            fv[0],
                            fv[1],
                            fv[2],
                            ci1 - 1,
                            ci2 - 1,
                            ci3 - 1,
                        )
                    )  # vert count
                else:
                    file.write(", ")
                    file.write(
                        "<%d,%d,%d>, %d,%d,%d"
                        % (
                            fv[0],
                            fv[1],
                            fv[2],
                            ci1 - 1,
                            ci2 - 1,
                            ci3 - 1,
                        )
                    )  # vert count

        file.write("\n")
        tab_write(file, "}\n")

        # normal_indices indices
        tab_write(file, "normal_indices {\n")
        tab_write(file, "%d" % (len(me_faces)))  # faces count
        tab_str = tab * tab_level
        for fi, fv in enumerate(faces_verts):

            if me_faces[fi].use_smooth:
                if linebreaksinlists:
                    file.write(",\n")
                    file.write(
                        tab_str
                        + "<%d,%d,%d>"
                        % (
                            uniqueNormals[verts_normals[fv[0]]][0],
                            uniqueNormals[verts_normals[fv[1]]][0],
                            uniqueNormals[verts_normals[fv[2]]][0],
                        )
                    )  # vert count
                else:
                    file.write(", ")
                    file.write(
                        "<%d,%d,%d>"
                        % (
                            uniqueNormals[verts_normals[fv[0]]][0],
                            uniqueNormals[verts_normals[fv[1]]][0],
                            uniqueNormals[verts_normals[fv[2]]][0],
                        )
                    )  # vert count
            else:
                idx = uniqueNormals[faces_normals[fi]][0]
                if linebreaksinlists:
                    file.write(",\n")
                    file.write(
                        tab_str + "<%d,%d,%d>" % (idx, idx, idx)
                    )  # vert count
                else:
                    file.write(", ")
                    file.write("<%d,%d,%d>" % (idx, idx, idx))  # vert count

        file.write("\n")
        tab_write(file, "}\n")

        if uv_layer:
            tab_write(file, "uv_indices {\n")
            tab_write(file, "%d" % (len(me_faces)))  # faces count
            tab_str = tab * tab_level
            for f in me_faces:
                uvs = [uv_layer[loop_index].uv[:] for loop_index in f.loops]

                if linebreaksinlists:
                    file.write(",\n")
                    file.write(
                        tab_str
                        + "<%d,%d,%d>"
                        % (
                            uniqueUVs[uvs[0]][0],
                            uniqueUVs[uvs[1]][0],
                            uniqueUVs[uvs[2]][0],
                        )
                    )
                else:
                    file.write(", ")
                    file.write(
                        "<%d,%d,%d>"
                        % (
                            uniqueUVs[uvs[0]][0],
                            uniqueUVs[uvs[1]][0],
                            uniqueUVs[uvs[2]][0],
                        )
                    )

            file.write("\n")
            tab_write(file, "}\n")

        # XXX BOOLEAN MODIFIER
        write_object_csg_inside_vector(ob, file)

        if me.materials:
            try:
                material = me.materials[0]  # dodgy
                write_object_material_interior(file, material, ob, tab_write)
            except IndexError:
                print(me)

        # POV object modifiers such as
        # hollow / sturm / double_illuminate etc.
        write_object_modifiers(ob, file)

        # Importance for radiosity sampling added here:
        tab_write(file, "radiosity { \n")
        tab_write(file, "importance %3g \n" % importance)
        tab_write(file, "}\n")

        tab_write(file, "}\n")  # End of mesh block
    else:
        facesMaterials = []  # WARNING!!!!!!!!!!!!!!!!!!!!!!
        if me_materials:
            new_me_faces_mat_idx = (f for f in me_faces if f.material_index not in
                                       facesMaterials)
            for f in new_me_faces_mat_idx:
                facesMaterials.append(f.material_index)
        # No vertex colors, so write material colors as vertex colors

        for i, material in enumerate(me_materials):
            if (
                material and material.pov.material_use_nodes is False
            ):  # WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                # Multiply diffuse with SSS Color
                if material.pov_subsurface_scattering.use:
                    diffuse_color = [
                        i * j
                        for i, j in zip(
                            material.pov_subsurface_scattering.color[:],
                            material.diffuse_color[:],
                        )
                    ]
                    key = (
                        diffuse_color[0],
                        diffuse_color[1],
                        diffuse_color[2],
                        i,
                    )  # i == f.mat
                    vertCols[key] = [-1]
                else:
                    diffuse_color = material.diffuse_color[:]
                    key = (
                        diffuse_color[0],
                        diffuse_color[1],
                        diffuse_color[2],
                        i,
                    )  # i == f.mat
                    vertCols[key] = [-1]

                idx = 0
                texturing.local_material_names = []
                for col, index in vertCols.items():
                    # if me_materials:
                    mater = me_materials[col[3]]
                    if me_materials is not None:
                        texturing.write_texture_influence(
                            file,
                            mater,
                            material_names_dictionary,
                            image_format,
                            img_map,
                            img_map_transforms,
                            tab_write,
                            comments,
                            col,
                            preview_dir,
                            unpacked_images,
                        )
                    # ------------------------------------------------
                    index[0] = idx
                    idx += 1

        # Vert Colors
        tab_write(file, "texture_list {\n")
        # In case there's is no material slot, give at least one texture
        # (an empty one so it uses pov default)
        if len(vertCols) != 0:
            tab_write(
                file, "%s" % (len(vertCols))
            )  # vert count
        else:
            tab_write(file, "1")
        # below "material" alias, added check obj.active_material
        # to avoid variable referenced before assignment error
        try:
            material = ob.active_material
        except IndexError:
            # when no material slot exists,
            material = None

        # WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        if (
            material
            and ob.active_material is not None
            and not material.pov.material_use_nodes
            and not material.use_nodes
        ):
            if material.pov.replacement_text != "":
                file.write("\n")
                file.write(" texture{%s}\n" % material.pov.replacement_text)

            else:
                # Loop through declared materials list
                # global local_material_names
                for cMN in texturing.local_material_names:
                    if material != "Default":
                        file.write("\n texture{MAT_%s}\n" % cMN)
                        # use string_strip_hyphen(material_names_dictionary[material]))
                        # or Something like that to clean up the above?
        elif material and material.pov.material_use_nodes:
            for index in facesMaterials:
                faceMaterial = string_strip_hyphen(
                    bpy.path.clean_name(me_materials[index].name)
                )
                file.write("\n texture{%s}\n" % faceMaterial)
        # END!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        elif vertCols:
            for cMN in vertCols: # or in texturing.local_material_names:
                # if possible write only one, though
                file.write(" texture{}\n")
        else:
            file.write(" texture{}\n")
        tab_write(file, "}\n")

        # Face indices
        tab_write(file, "face_indices {\n")
        tab_write(file, "%d" % (len(me_faces)))  # faces count
        tab_str = tab * tab_level

        for fi, f in enumerate(me_faces):
            fv = faces_verts[fi]
            material_index = f.material_index

            if vcol_layer:
                cols = [vcol_layer[loop_index].color[:] for loop_index in f.loops]

            if (
                not me_materials or me_materials[material_index] is None
            ):  # No materials
                if linebreaksinlists:
                    file.write(",\n")
                    # vert count
                    file.write(tab_str + "<%d,%d,%d>" % (fv[0], fv[1], fv[2]))
                else:
                    file.write(", ")
                    file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2]))  # vert count
            else:
                material = me_materials[material_index]
                ci1 = ci2 = ci3 = f.material_index
                if me.vertex_colors:  # and material.pov.use_vertex_color_paint:
                    # Color per vertex - vertex color

                    col1 = cols[0]
                    col2 = cols[1]
                    col3 = cols[2]

                    ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
                    ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
                    ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
                elif material.pov.material_use_nodes:
                    ci1 = ci2 = ci3 = 0
                else:
                    # Color per material - flat material color
                    if material.pov_subsurface_scattering.use:
                        diffuse_color = [
                            i * j
                            for i, j in zip(
                                material.pov_subsurface_scattering.color[:],
                                material.diffuse_color[:],
                            )
                        ]
                    else:
                        diffuse_color = material.diffuse_color[:]
                    ci1 = ci2 = ci3 = vertCols[
                        diffuse_color[0],
                        diffuse_color[1],
                        diffuse_color[2],
                        f.material_index,
                    ][0]

                if linebreaksinlists:
                    file.write(",\n")
                    file.write(
                        tab_str
                        + "<%d,%d,%d>, %d,%d,%d"
                        % (fv[0], fv[1], fv[2], ci1, ci2, ci3)
                    )  # vert count
                else:
                    file.write(", ")
                    file.write(
                        "<%d,%d,%d>, %d,%d,%d"
                        % (fv[0], fv[1], fv[2], ci1, ci2, ci3)
                    )  # vert count

        file.write("\n")
        tab_write(file, "}\n")

        # normal_indices indices
        tab_write(file, "normal_indices {\n")
        tab_write(file, "%d" % (len(me_faces)))  # faces count
        tab_str = tab * tab_level
        for fi, fv in enumerate(faces_verts):
            if me_faces[fi].use_smooth:
                if linebreaksinlists:
                    file.write(",\n")
                    file.write(
                        tab_str
                        + "<%d,%d,%d>"
                        % (
                            uniqueNormals[verts_normals[fv[0]]][0],
                            uniqueNormals[verts_normals[fv[1]]][0],
                            uniqueNormals[verts_normals[fv[2]]][0],
                        )
                    )  # vert count
                else:
                    file.write(", ")
                    file.write(
                        "<%d,%d,%d>"
                        % (
                            uniqueNormals[verts_normals[fv[0]]][0],
                            uniqueNormals[verts_normals[fv[1]]][0],
                            uniqueNormals[verts_normals[fv[2]]][0],
                        )
                    )  # vert count
            else:
                idx = uniqueNormals[faces_normals[fi]][0]
                if linebreaksinlists:
                    file.write(",\n")
                    file.write(
                        tab_str + "<%d,%d,%d>" % (idx, idx, idx)
                    )  # vertcount
                else:
                    file.write(", ")
                    file.write("<%d,%d,%d>" % (idx, idx, idx))  # vert count

        file.write("\n")
        tab_write(file, "}\n")

        if uv_layer:
            tab_write(file, "uv_indices {\n")
            tab_write(file, "%d" % (len(me_faces)))  # faces count
            tab_str = tab * tab_level
            for f in me_faces:
                uvs = [uv_layer[loop_index].uv[:] for loop_index in f.loops]

                if linebreaksinlists:
                    file.write(",\n")
                    file.write(
                        tab_str
                        + "<%d,%d,%d>"
                        % (
                            uniqueUVs[uvs[0]][0],
                            uniqueUVs[uvs[1]][0],
                            uniqueUVs[uvs[2]][0],
                        )
                    )
                else:
                    file.write(", ")
                    file.write(
                        "<%d,%d,%d>"
                        % (
                            uniqueUVs[uvs[0]][0],
                            uniqueUVs[uvs[1]][0],
                            uniqueUVs[uvs[2]][0],
                        )
                    )

            file.write("\n")
            tab_write(file, "}\n")

        # XXX BOOLEAN
        write_object_csg_inside_vector(ob, file)
        if me.materials:
            try:
                material = me.materials[0]  # dodgy
                write_object_material_interior(file, material, ob, tab_write)
            except IndexError:
                print(me)

        # POV object modifiers such as
        # hollow / sturm / double_illuminate etc.
        write_object_modifiers(ob, file)

        # Importance for radiosity sampling added here:
        tab_write(file, "radiosity { \n")
        tab_write(file, "importance %3g \n" % importance)
        tab_write(file, "}\n")

        tab_write(file, "}\n")  # End of mesh block

    ob_eval.to_mesh_clear()
    return True