Skip to content
Snippets Groups Projects
shading.py 82 KiB
Newer Older
# SPDX-License-Identifier: GPL-2.0-or-later
Maurice Raybaud's avatar
Maurice Raybaud committed
"""Translate complex shaders to exported POV textures."""

def write_object_material_interior(material, ob, tab_write):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Translate some object level material from Blender UI (VS data level)

    to POV interior{} syntax and write it to exported file.
    This is called in object_mesh_topology.export_meshes
Maurice Raybaud's avatar
Maurice Raybaud committed
    """
    # DH - modified some variables to be function local, avoiding RNA write
    # this should be checked to see if it is functionally correct

    # Commented out: always write IOR to be able to use it for SSS, Fresnel reflections...
    # if material and material.transparency_method == 'RAYTRACE':
    if material:
        # But there can be only one!
        if material.pov_subsurface_scattering.use:  # SSS IOR get highest priority
            tab_write("interior {\n")
            tab_write("ior %.6f\n" % material.pov_subsurface_scattering.ior)
        # Then the raytrace IOR taken from raytrace transparency properties and used for
        # reflections if IOR Mirror option is checked.
        elif material.pov.mirror_use_IOR:
            tab_write("interior {\n")
            tab_write("ior %.6f\n" % material.pov_raytrace_transparency.ior)
        elif material.pov.transparency_method == 'Z_TRANSPARENCY':
            tab_write("interior {\n")
            tab_write("ior 1.0\n")
        else:
            tab_write("interior {\n")
            tab_write("ior %.6f\n" % material.pov_raytrace_transparency.ior)

        pov_fake_caustics = False
        pov_photons_refraction = False
        pov_photons_reflection = False

        if material.pov.photons_reflection:
            pov_photons_reflection = True
        if not material.pov.refraction_caustics:
            pov_fake_caustics = False
            pov_photons_refraction = False
        elif material.pov.refraction_type == "1":
            pov_fake_caustics = True
            pov_photons_refraction = False
        elif material.pov.refraction_type == "2":
            pov_fake_caustics = False
            pov_photons_refraction = True

        # If only Raytrace transparency is set, its IOR will be used for refraction, but user
        # can set up 'un-physical' fresnel reflections in raytrace mirror parameters.
        # Last, if none of the above is specified, user can set up 'un-physical' fresnel
        # reflections in raytrace mirror parameters. And pov IOR defaults to 1.
        if material.pov.caustics_enable:
            if pov_fake_caustics:
                tab_write("caustics %.3g\n" % material.pov.fake_caustics_power)
            if pov_photons_refraction:
                # Default of 1 means no dispersion
                tab_write("dispersion %.6f\n" % material.pov.photons_dispersion)
                tab_write("dispersion_samples %.d\n" % material.pov.photons_dispersion_samples)
        # TODO
        # Other interior args
        if material.pov.use_transparency and material.pov.transparency_method == 'RAYTRACE':
            # fade_distance
            # In Blender this value has always been reversed compared to what tooltip says.
            # 100.001 rather than 100 so that it does not get to 0
            # which deactivates the feature in POV
            tab_write(
                "fade_distance %.3g\n" % (100.001 - material.pov_raytrace_transparency.depth_max)
            )
            # fade_power
            tab_write("fade_power %.3g\n" % material.pov_raytrace_transparency.falloff)
            # fade_color
            tab_write("fade_color <%.3g, %.3g, %.3g>\n" % material.pov.interior_fade_color[:])

        # (variable) dispersion_samples (constant count for now)
        tab_write("}\n")
        if material.pov.photons_reflection or material.pov.refraction_type == "2":
            tab_write("photons{")
            tab_write("target %.3g\n" % ob.pov.spacing_multiplier)
            if not ob.pov.collect_photons:
                tab_write("collect off\n")
            if pov_photons_refraction:
                tab_write("refraction on\n")
            if pov_photons_reflection:
                tab_write("reflection on\n")
            tab_write("}\n")


def write_material(
    using_uberpov,
    DEF_MAT_NAME,
    tab_write,
    safety,
    comments,
    unique_name,
    material_names_dictionary,
    material
Maurice Raybaud's avatar
Maurice Raybaud committed
):
    """Translate Blender material to POV texture{} block and write in exported file."""
    # Assumes only called once on each material
    if material:
        name_orig = material.name
        name = material_names_dictionary[name_orig] = unique_name(
            bpy.path.clean_name(name_orig), material_names_dictionary
        # If saturation(.s) is not zero, then color is not grey, and has a tint
Maurice Raybaud's avatar
Maurice Raybaud committed
        colored_specular_found = (material.pov.specular_color.s > 0.0) and (
            material.pov.diffuse_shader != "MINNAERT"
        )
    else:
        name = name_orig = DEF_MAT_NAME
Maurice Raybaud's avatar
Maurice Raybaud committed
    # Several versions of the finish: ref_level_bound conditions are variations for specular/Mirror
    # texture channel map with alternative finish of 0 specular and no mirror reflection.
Maurice Raybaud's avatar
Maurice Raybaud committed
    # ref_level_bound=1 Means No specular nor Mirror reflection
    # ref_level_bound=2 Means translation of spec and mir levels for when no map influences them
    # ref_level_bound=3 Means Maximum Spec and Mirror
Maurice Raybaud's avatar
Maurice Raybaud committed
    def pov_has_no_specular_maps(ref_level_bound):
Maurice Raybaud's avatar
Maurice Raybaud committed
        """Translate Blender specular map influence to POV finish map trick and write to file."""
Maurice Raybaud's avatar
Maurice Raybaud committed
        if ref_level_bound == 1:
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write("//--No specular nor Mirror reflection--\n")
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write("\n")
            tab_write("#declare %s = finish {\n" % safety(name, ref_level_bound=1))
Maurice Raybaud's avatar
Maurice Raybaud committed
        elif ref_level_bound == 2:
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write(
                    "//--translation of spec and mir levels for when no map " "influences them--\n"
                )
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write("\n")
            tab_write("#declare %s = finish {\n" % safety(name, ref_level_bound=2))
Maurice Raybaud's avatar
Maurice Raybaud committed

Maurice Raybaud's avatar
Maurice Raybaud committed
        elif ref_level_bound == 3:
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write("//--Maximum Spec and Mirror--\n")
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write("\n")
            tab_write("#declare %s = finish {\n" % safety(name, ref_level_bound=3))
        if material:
            # POV-Ray 3.7 now uses two diffuse values respectively for front and back shading
            # (the back diffuse is like blender translucency)
Maurice Raybaud's avatar
Maurice Raybaud committed
            front_diffuse = material.pov.diffuse_intensity
            back_diffuse = material.pov.translucency
Maurice Raybaud's avatar
Maurice Raybaud committed
                # Total should not go above one
                if (front_diffuse + back_diffuse) <= 1.0:
Maurice Raybaud's avatar
Maurice Raybaud committed
                elif front_diffuse == back_diffuse:
                    # Try to respect the user's 'intention' by comparing the two values but
                    # bringing the total back to one.
Maurice Raybaud's avatar
Maurice Raybaud committed
                    front_diffuse = back_diffuse = 0.5
                # Let the highest value stay the highest value.
Maurice Raybaud's avatar
Maurice Raybaud committed
                elif front_diffuse > back_diffuse:
Maurice Raybaud's avatar
Maurice Raybaud committed
                    back_diffuse = min(back_diffuse, (1.0 - front_diffuse))
Maurice Raybaud's avatar
Maurice Raybaud committed
                    front_diffuse = min(front_diffuse, (1.0 - back_diffuse))

            # map hardness between 0.0 and 1.0
Maurice Raybaud's avatar
Maurice Raybaud committed
            roughness = 1.0 - ((material.pov.specular_hardness - 1.0) / 510.0)
            ## scale from 0.0 to 0.1
            roughness *= 0.1
            # add a small value because 0.0 is invalid.
Maurice Raybaud's avatar
Maurice Raybaud committed
            roughness += 1.0 / 511.0
            # ------------------------------ Diffuse Shader ------------------------------ #
Maurice Raybaud's avatar
Maurice Raybaud committed
            # Not used for Full spec (ref_level_bound=3) of the shader.
            if material.pov.diffuse_shader == "OREN_NAYAR" and ref_level_bound != 3:
                # Blender roughness is what is generally called oren nayar Sigma,
                # and brilliance in POV-Ray.
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write("brilliance %.3g\n" % (0.9 + material.roughness))
Maurice Raybaud's avatar
Maurice Raybaud committed
            if material.pov.diffuse_shader == "TOON" and ref_level_bound != 3:
                tab_write("brilliance %.3g\n" % (0.01 + material.diffuse_toon_smooth * 0.25))
                # Lower diffuse and increase specular for toon effect seems to look better
                # in POV-Ray.
Maurice Raybaud's avatar
Maurice Raybaud committed
                front_diffuse *= 0.5
Maurice Raybaud's avatar
Maurice Raybaud committed
            if material.pov.diffuse_shader == "MINNAERT" and ref_level_bound != 3:
                # tab_write("aoi %.3g\n" % material.darkness)
                pass  # let's keep things simple for now
Maurice Raybaud's avatar
Maurice Raybaud committed
            if material.pov.diffuse_shader == "FRESNEL" and ref_level_bound != 3:
                # tab_write("aoi %.3g\n" % material.diffuse_fresnel_factor)
                pass  # let's keep things simple for now
Maurice Raybaud's avatar
Maurice Raybaud committed
            if material.pov.diffuse_shader == "LAMBERT" and ref_level_bound != 3:
                # trying to best match lambert attenuation by that constant brilliance value
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write("brilliance 1\n")
Maurice Raybaud's avatar
Maurice Raybaud committed
            if ref_level_bound == 2:
                # ------------------------------ Specular Shader ------------------------------ #
                # No difference between phong and cook torrence in blender HaHa!
Maurice Raybaud's avatar
Maurice Raybaud committed
                if (
                    material.pov.specular_shader == "COOKTORR"
                    or material.pov.specular_shader == "PHONG"
                ):
                    tab_write("phong %.3g\n" % material.pov.specular_intensity)
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write("phong_size %.3g\n" % (material.pov.specular_hardness / 3.14))

                # POV-Ray 'specular' keyword corresponds to a Blinn model, without the ior.
Maurice Raybaud's avatar
Maurice Raybaud committed
                elif material.pov.specular_shader == "BLINN":
                    # Use blender Blinn's IOR just as some factor for spec intensity
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write(
                        "specular %.3g\n"
                        % (material.pov.specular_intensity * (material.pov.specular_ior / 4.0))
                    )
                    tab_write("roughness %.3g\n" % roughness)
                    # Could use brilliance 2(or varying around 2 depending on ior or factor) too.

                elif material.pov.specular_shader == "TOON":
                    tab_write("phong %.3g\n" % (material.pov.specular_intensity * 2.0))
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write("phong_size %.3g\n" % (0.1 + material.pov.specular_toon_smooth / 2.0))
Maurice Raybaud's avatar
Maurice Raybaud committed
                elif material.pov.specular_shader == "WARDISO":
                    # find best suited default constant for brilliance Use both phong and
                    # specular for some values.
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write(
                        "specular %.3g\n"
                        % (material.pov.specular_intensity / (material.pov.specular_slope + 0.0005))
                    )
                    # find best suited default constant for brilliance Use both phong and
                    # specular for some values.
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write("roughness %.4g\n" % (0.0005 + material.pov.specular_slope / 10.0))
                    # find best suited default constant for brilliance Use both phong and
                    # specular for some values.
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write("brilliance %.4g\n" % (1.8 - material.pov.specular_slope * 1.8))
            # -------------------------------------------------------------------------------- #
Maurice Raybaud's avatar
Maurice Raybaud committed
            elif ref_level_bound == 1:
                if (
                    material.pov.specular_shader == "COOKTORR"
                    or material.pov.specular_shader == "PHONG"
                ):
                    tab_write("phong 0\n")  # %.3g\n" % (material.pov.specular_intensity/5))
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write("phong_size %.3g\n" % (material.pov.specular_hardness / 3.14))

                # POV-Ray 'specular' keyword corresponds to a Blinn model, without the ior.
Maurice Raybaud's avatar
Maurice Raybaud committed
                elif material.pov.specular_shader == "BLINN":
                    # Use blender Blinn's IOR just as some factor for spec intensity
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write(
                        "specular %.3g\n"
                        % (material.pov.specular_intensity * (material.pov.specular_ior / 4.0))
                    )
                    tab_write("roughness %.3g\n" % roughness)
                    # Could use brilliance 2(or varying around 2 depending on ior or factor) too.

                elif material.pov.specular_shader == "TOON":
                    tab_write("phong %.3g\n" % (material.pov.specular_intensity * 2.0))
                    # use extreme phong_size
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write("phong_size %.3g\n" % (0.1 + material.pov.specular_toon_smooth / 2.0))
Maurice Raybaud's avatar
Maurice Raybaud committed
                elif material.pov.specular_shader == "WARDISO":
                    # find best suited default constant for brilliance Use both phong and
                    # specular for some values.
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write(
                        "specular %.3g\n"
                        % (material.pov.specular_intensity / (material.pov.specular_slope + 0.0005))
                    )
                    # find best suited default constant for brilliance Use both phong and
                    # specular for some values.
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write("roughness %.4g\n" % (0.0005 + material.pov.specular_slope / 10.0))
                    # find best suited default constant for brilliance Use both phong and
                    # specular for some values.
Maurice Raybaud's avatar
Maurice Raybaud committed
                    tab_write("brilliance %.4g\n" % (1.8 - material.pov.specular_slope * 1.8))
            elif ref_level_bound == 3:
                # Spec must be Max at ref_level_bound 3 so that white of mixing texture always shows specularity
                # That's why it's multiplied by 255. maybe replace by texture's brightest pixel value?
                if material.pov_texture_slots:
                    max_spec_factor = (
                            material.pov.specular_intensity
                            * material.pov.specular_color.v
                            * 255
                            * slot.specular_factor
Maurice Raybaud's avatar
Maurice Raybaud committed
                    )
                else:
                    max_spec_factor = (
                            material.pov.specular_intensity
                            * material.pov.specular_color.v
                            * 255
                    )
                tab_write("specular %.3g\n" % max_spec_factor)
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write("roughness %.3g\n" % (1 / material.pov.specular_hardness))
            tab_write("diffuse %.3g %.3g\n" % (front_diffuse, back_diffuse))

            tab_write("ambient %.3g\n" % material.pov.ambient)
            # POV-Ray blends the global value
Maurice Raybaud's avatar
Maurice Raybaud committed
            # tab_write("ambient rgb <%.3g, %.3g, %.3g>\n" % \
            #         tuple([c*material.pov.ambient for c in world.ambient_color]))
Maurice Raybaud's avatar
Maurice Raybaud committed
            tab_write("emission %.3g\n" % material.pov.emit)  # New in POV-Ray 3.7
Maurice Raybaud's avatar
Maurice Raybaud committed
            # POV-Ray just ignores roughness if there's no specular keyword
            # tab_write("roughness %.3g\n" % roughness)

            if material.pov.conserve_energy:
                # added for more realistic shading. Needs some checking to see if it
                # really works. --Maurice.
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write("conserve_energy\n")
Maurice Raybaud's avatar
Maurice Raybaud committed
            if colored_specular_found:
                tab_write("metallic\n")
Maurice Raybaud's avatar
Maurice Raybaud committed
            if ref_level_bound != 1:
                if material.pov_raytrace_mirror.use:
                    raytrace_mirror = material.pov_raytrace_mirror
                    if raytrace_mirror.reflect_factor:
Maurice Raybaud's avatar
Maurice Raybaud committed
                        tab_write("reflection {\n")
                        tab_write("rgb <%.3g, %.3g, %.3g>\n" % material.pov.mirror_color[:])
                        if material.pov.mirror_metallic:
                            tab_write("metallic %.3g\n" % raytrace_mirror.reflect_factor)
                        # Blurry reflections for UberPOV
                        if using_uberpov and raytrace_mirror.gloss_factor < 1.0:
Maurice Raybaud's avatar
Maurice Raybaud committed
                            # tab_write("#ifdef(unofficial) #if(unofficial = \"patch\") #if(patch(\"upov-reflection-roughness\") > 0)\n")
                            tab_write(
                                "roughness %.6f\n" % (0.000001 / raytrace_mirror.gloss_factor)
                            )
                            # tab_write("#end #end #end\n") # This and previous comment for backward compatibility, messier pov code
                        if material.pov.mirror_use_IOR:  # WORKING ?
                            # Removed from the line below: gives a more physically correct
                            # material but needs proper IOR. --Maurice
Maurice Raybaud's avatar
Maurice Raybaud committed
                            tab_write("fresnel 1 ")
                        tab_write(
                            "falloff %.3g exponent %.3g} "
                            % (raytrace_mirror.fresnel, raytrace_mirror.fresnel_factor)
                        )
            if material.pov_subsurface_scattering.use:
                subsurface_scattering = material.pov_subsurface_scattering
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write(
                    "subsurface { translucency <%.3g, %.3g, %.3g> }\n"
                    % (
                        (subsurface_scattering.radius[0]),
                        (subsurface_scattering.radius[1]),
                        (subsurface_scattering.radius[2]),
                    )
                )
Maurice Raybaud's avatar
Maurice Raybaud committed
                tab_write(
                    "irid { %.4g thickness %.4g turbulence %.4g }"
                    % (
                        material.pov.irid_amount,
                        material.pov.irid_thickness,
                        material.pov.irid_turbulence,
                    )
                )
Maurice Raybaud's avatar
Maurice Raybaud committed
            tab_write("diffuse 0.8\n")
            tab_write("phong 70.0\n")
Maurice Raybaud's avatar
Maurice Raybaud committed
            # tab_write("specular 0.2\n")

        # This is written into the object
Maurice Raybaud's avatar
Maurice Raybaud committed
        """
        if material and material.pov.transparency_method=='RAYTRACE':
            'interior { ior %.3g} ' % material.raytrace_transparency.ior
Maurice Raybaud's avatar
Maurice Raybaud committed
        """
Maurice Raybaud's avatar
Maurice Raybaud committed
        # tab_write("crand 1.0\n") # Sand granyness
        # tab_write("metallic %.6f\n" % material.spec)
        # tab_write("phong %.6f\n" % material.spec)
        # tab_write("phong_size %.6f\n" % material.spec)
        # tab_write("brilliance %.6f " % (material.pov.specular_hardness/256.0) # Like hardness
Maurice Raybaud's avatar
Maurice Raybaud committed
        tab_write("}\n\n")
Maurice Raybaud's avatar
Maurice Raybaud committed
    # ref_level_bound=2 Means translation of spec and mir levels for when no map influences them
    pov_has_no_specular_maps(ref_level_bound=2)

    if material:
        special_texture_found = False
        for t in material.pov_texture_slots:
Maurice Raybaud's avatar
Maurice Raybaud committed
            # index = material.pov.active_texture_index
Maurice Raybaud's avatar
Maurice Raybaud committed
            slot = material.pov_texture_slots[tmpidx]  # [index]
            povtex = slot.texture  # slot.name
Maurice Raybaud's avatar
Maurice Raybaud committed
            tex = bpy.data.textures[povtex]

Maurice Raybaud's avatar
Maurice Raybaud committed
            if t and t.use and tex is not None:
Maurice Raybaud's avatar
Maurice Raybaud committed
                if (tex.type == "IMAGE" and tex.image) or tex.type != "IMAGE":
                    # validPath
                    if (
                        t
                        and t.use
                        and (
                            t.use_map_specular
                            or t.use_map_raymir
                            or t.use_map_normal
                            or t.use_map_alpha
                        )
                    ):
                        special_texture_found = True
                        continue  # Some texture found

        if special_texture_found or colored_specular_found:
Maurice Raybaud's avatar
Maurice Raybaud committed
            # ref_level_bound=1 Means No specular nor Mirror reflection
            pov_has_no_specular_maps(ref_level_bound=1)

            # ref_level_bound=3 Means Maximum Spec and Mirror
            pov_has_no_specular_maps(ref_level_bound=3)
Maurice Raybaud's avatar
Maurice Raybaud committed

Maurice Raybaud's avatar
Maurice Raybaud committed
def export_pattern(texture):
Maurice Raybaud's avatar
Maurice Raybaud committed
    """Translate Blender procedural textures to POV patterns and write to pov file.
Maurice Raybaud's avatar
Maurice Raybaud committed
    Function Patterns can be used to better access sub components of a pattern like
Maurice Raybaud's avatar
Maurice Raybaud committed
    grey values for influence mapping
    """
    tex = texture
Maurice Raybaud's avatar
Maurice Raybaud committed
    pat = tex.pov
Maurice Raybaud's avatar
Maurice Raybaud committed
    pat_name = "PAT_%s" % string_strip_hyphen(bpy.path.clean_name(tex.name))
    mapping_dif = "translate <%.4g,%.4g,%.4g> scale <%.4g,%.4g,%.4g>" % (
        pat.tex_mov_x,
        pat.tex_mov_y,
        pat.tex_mov_z,
        1.0 / pat.tex_scale_x,
        1.0 / pat.tex_scale_y,
        1.0 / pat.tex_scale_z,
    )
    text_strg = ""

    def export_color_ramp(texture):
        tex = texture
Campbell Barton's avatar
Campbell Barton committed
        pat = tex.pov
Maurice Raybaud's avatar
Maurice Raybaud committed
        col_ramp_strg = "color_map {\n"
        num_color = 0
Maurice Raybaud's avatar
Maurice Raybaud committed
        for el in tex.color_ramp.elements:
Maurice Raybaud's avatar
Maurice Raybaud committed
            num_color += 1
Maurice Raybaud's avatar
Maurice Raybaud committed
            pos = el.position
Maurice Raybaud's avatar
Maurice Raybaud committed
            col = el.color
            col_r, col_g, col_b, col_a = col[0], col[1], col[2], 1 - col[3]
            if pat.tex_pattern_type not in {"checker", "hexagon", "square", "triangular", "brick"}:
                col_ramp_strg += "[%.4g color rgbf<%.4g,%.4g,%.4g,%.4g>] \n" % (
                    pos,
                    col_r,
                    col_g,
                    col_b,
                    col_a,
                )
            if pat.tex_pattern_type in {"brick", "checker"} and num_color < 3:
                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
            if pat.tex_pattern_type == "hexagon" and num_color < 4:
                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
            if pat.tex_pattern_type == "square" and num_color < 5:
                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)
            if pat.tex_pattern_type == "triangular" and num_color < 7:
                col_ramp_strg += "color rgbf<%.4g,%.4g,%.4g,%.4g> \n" % (col_r, col_g, col_b, col_a)

        col_ramp_strg += "} \n"
        # end color map
        return col_ramp_strg

    # much work to be done here only defaults translated for now:
    # pov noise_generator 3 means perlin noise
    if tex.type not in {"NONE", "IMAGE"} and pat.tex_pattern_type == "emulator":
        text_strg += "pigment {\n"
        # ------------------------- EMULATE BLENDER VORONOI TEXTURE ------------------------- #
Loading
Loading full blame...