# # How to create a texture property
#
# This tutorial demonstrates how to create a texture property.
# ## What is a textur property?
# A texture property (also named material), gathers 3 notions:
# the surface optical property (SOP), the texture and the volume optical property (VOP).
# The property is then applied to a geometry (like bodies, faces).
#
# ## Prerequisites
#
# ### Perform imports

# +
import os
from pathlib import Path

from ansys.speos.core import Face, Project, Speos, launcher
from ansys.speos.core.generic.parameters import MeshData
from ansys.speos.core.generic.version_checker import server_version_checker
from ansys.speos.core.kernel.client import (
    default_docker_channel,
)
from ansys.speos.core.sensor import SensorRadiance
from ansys.speos.core.simulation import SimulationInverse
from ansys.speos.core.source import SourceAmbientEnvironment

# -

# ### Define constants
#
#  help ensure consistency and avoid repetition throughout the example.

HOSTNAME = "localhost"
GRPC_PORT = 50098  # Be sure the Speos GRPC Server has been started on this port.
USE_DOCKER = True  # Set to False if you're running this example locally as a Notebook.

# ## Define helper functions


def create_helper_geometries(project: Project):
    """Create bodies and faces."""

    def create_rect_face(my_body, name, pos, x, y) -> Face:
        face = my_body.create_face(name=name)
        face.vertices = [
            pos[0],
            pos[1],
            pos[2],
            pos[0],
            pos[1] + y,
            pos[2],
            pos[0] + x,
            pos[1],
            pos[2],
            pos[0] + x,
            pos[1] + y,
            pos[2],
        ]
        face.facets = [0, 1, 2, 1, 2, 3]
        face.normals = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0]
        return face

    root_part = project.create_root_part()
    data = {"bodies": [], "faces": [], "rp": root_part}
    data["bodies"].append(root_part.create_body(name="TheBody0"))
    data["bodies"].append(root_part.create_body(name="TheBody1"))
    data["bodies"].append(root_part.create_body(name="TheBody2"))
    data["bodies"].append(root_part.create_body(name="TheBody3"))
    data["bodies"].append(root_part.create_body(name="TheBody4"))
    data["bodies"].append(root_part.create_body(name="TheBody5"))
    data["faces"].append(create_rect_face(data["bodies"][0], "face0_0", [0, 0, 0], 5, 5))
    data["faces"].append(create_rect_face(data["bodies"][1], "Face1_0", [6, 0, 0], 5, 10))
    data["faces"].append(create_rect_face(data["bodies"][2], "Face2_0", [12, 0, 0], 10, 5))
    data["faces"].append(create_rect_face(data["bodies"][3], "Face3_0", [0, -6, 0], 5, 5))
    data["faces"].append(create_rect_face(data["bodies"][4], "Face4_0", [6, -6, 0], 5, 5))
    data["faces"].append(create_rect_face(data["bodies"][5], "Face5_0", [12, -6, 0], 5, 5))
    root_part.commit()
    return data


# ## Model Setup
#
# ### Load assets
# The assets used to run this example are available in the
# [PySpeos repository](https://github.com/ansys/pyspeos/) on GitHub.
#
# > **Note:** Make sure you
# > have downloaded simulation assets and set ``assets_data_path``
# > to point to the assets folder.

if USE_DOCKER:  # Running on the remote server.
    assets_data_path = Path("/app") / "assets"
else:
    assets_data_path = Path("/path/to/your/download/assets/directory")

# ## Connect to the RPC Server
# This Python client connects to a server where the Speos engine
# is running as a service. In this example, the server and
# client are the same machine. The launch_local_speos_rpc_method can
# be used to start a local instance of the service.

if USE_DOCKER:
    speos = Speos(channel=default_docker_channel(port=GRPC_PORT))
else:
    speos = launcher.launch_local_speos_rpc_server(port=GRPC_PORT)

# ### Create a new project
#
# The only way to create an optical property using the core layer, is to create it from a project.
# The ``Project`` class is instantiated by passing a ``Speos`` instance

p = Project(speos=speos)
print(p)

# ## Add geometries
#
# we use the helper function to create a variety of rectangular geometries to allow the application
# of textures

data = create_helper_geometries(p)
bodies = data["bodies"]
faces = data["faces"]
p.preview()

# ## Create Ambient source

src = p.create_source(name="Ambient", feature_type=SourceAmbientEnvironment)
src.luminance = 1000
src.image_file_uri = Path(assets_data_path) / "uffizi_cross.hdr"
src.set_predefined_color_space().set_color_space_srgb()
src.zenith_direction = [0.0, 0.0, 1.0]
src.north_direction = [1.0, 0.0, 0.0]
src.commit()

# ## Create Radiance Sensor

ssr = p.create_sensor(name="Radiance", feature_type=SensorRadiance)
ssr.axis_system = [11, 0, 10, 1, 0, 0, 0, 1, 0, 0, 0, 1]
ssr.integration_angle = 5
ssr.dimensions.x_start = -8
ssr.dimensions.x_end = 8
ssr.dimensions.x_sampling = 200
ssr.dimensions.y_start = -8
ssr.dimensions.y_end = 8
ssr.dimensions.y_sampling = 200
ssr.focal = 10
wv = ssr.set_type_spectral().set_wavelengths_range()
wv.start = 400
wv.end = 800
wv.sampling = 13
ssr.commit()

# ## Create Inverse Simulation with define Texture normalization

sim = p.create_simulation(name="Inverse", feature_type=SimulationInverse)
sim.sensor_paths = ["Radiance"]
sim.source_paths = ["Ambient"]
sim.set_texture_normalization_none()
sim.commit()


# ## Preview the project setup

p.preview()


# ### Create Texture Properties via UV Mapping
#
## Activate texture settings inside optical property
# Here we create a default optical property for the planner square shape surface
# The dimension of surface is 5 x 5.

opt = p.create_optical_property(name="optical_property")
opt.set_volume_none()
opt.set_surface_mirror().reflectance = 0
opt.geometries = [faces[0]]
opt.commit()
print(opt)

# ## Run Simulation and open result
# Image result shows black surface as mirror reflectance is 0.

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")

# Here we activate the texture and create the first texture layer
# By default, each texture layer is set as mirror with 100% reflectance.
# No mapping method has been defined yet.
# No image texture and normal map are applied.

texture_layer_1 = opt.create_texture_layer()
opt.commit()
print(len(opt.texture))
print(opt)

# Run simulation and result shows mirror surface as the default texture layer
# Image result shows mirror surface as mirror reflectance is 100.

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")

# Here we the second texture layer
# This is additional texture layer stack on top of the first texture layer.
# the texture layer order is following the order of creation.
# Similarly, texture layer is set as mirror without image and normal map.

texture_layer_2 = opt.create_texture_layer()
opt.commit()
print(len(opt.texture))
print(opt)

# Here we can delete 1 layer, e.g. the first layer

texture_layer_1.delete()
opt.commit()
print(len(opt.texture))  # only has 1 texture layer left
print(opt)

# User can NOT delete the last layer as this is last layer.
# The following lines demon the error message.

try:
    opt.texture[0].delete()
except Exception as e:
    print("This is the last texture layer, it can not be deleted: {}".format(e))

# User can still use the texture layer as SOP to apply surface
# optical property as usual. Use image texture and normal map as None.

new_texture_layer = opt.create_texture_layer()
opt.texture[0].set_surface_library().file_uri = (
    Path(assets_data_path) / "Texture.1.speos" / "100% transparent.simplescattering"
)
opt.texture[0].set_image_texture_to_none()
opt.texture[0].set_normal_map_to_none()
opt.commit()

# Run simulation and result shows a fully transparent surface as defined in
# simplescattering file.

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")

# ### Create Texture Properties via UV Mapping or MeshData
#
# ## Create Texture Property by UV Mapping
# User can create some UV Mappings method as using:
# planar, cubic, spherical or cylindrical UV mapping method.
# Here, we create another texture layer.
# With the previously remained texture layer, in total, there are 2 texture layers.

texture_layer_3 = opt.create_texture_layer()
texture_layer_3.set_surface_library().file_uri = Path(assets_data_path) / "L100 2.simplescattering"

# UV mapping axis system will locate the center of the texture image,
# in this example:
# texture image center: x = 0, y = 0, z = 0
# x-axis direction: vector = [0, 0, 1] is used for projection of the texture image.
# y-axis direction: vector = [0, 1, 0] is used for top direction of the texture image.
#
# Light will interact the last layer first.
# texture image as checkerboard where alpha value is 0 for passing the ray toward the
# subsequent layer -> fully transparent layer where alpha value is not 0 for interacting
# with the L100 2.simplescattering material.

texture_layer_3.set_image_texture().set_uv_mapping_planar().axis_system = [
    0,
    0,
    0,
    0,
    0,
    1,
    0,
    1,
    0,
    1,
    0,
    0,
]
texture_layer_3.image_texture.image_file_uri = (
    Path(assets_data_path) / "Texture.1.speos" / "white_trans_checkerboard.png"
)
texture_layer_3.image_texture.repeat_v = False
texture_layer_3.image_texture.repeat_u = False
texture_layer_3.image_texture.uv_mapping.u_length = 2.5
texture_layer_3.image_texture.uv_mapping.v_length = 2.5
texture_layer_3.set_normal_map_to_none()
opt.commit()

# Run simulation and result shows white in the checkerboard area while
# transparent at the other area.

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")

# Move the texture image up in the y direction and run result.
# The new center of texture image is [0, 2.5, 0]

texture_layer_3.image_texture.uv_mapping.axis_system = [0, 2.5, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0]
opt.commit()

results = sim.compute_CPU()  # use GPU using "compute_GPU" method
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")

# Run simulation and result pattern will be repeated.

texture_layer_3.image_texture.repeat_v = True
texture_layer_3.image_texture.repeat_u = True
opt.commit()

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")

# Run simulation and result pattern will be enlarged.

texture_layer_3.image_texture.uv_mapping.u_length = 10
texture_layer_3.image_texture.uv_mapping.v_length = 10
opt.commit()

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")


# ## Create Texture Property by data
# When texture is create by data the image gets positioned on the geometry using
# the uv information stored in the mesh vertices data attribute on the face.


# ## Apply vertices data for all faces except the first
#
# we create image locations for each vertex and provide these to each face to position the texture
# on the Geometry. we give for each vertex the u,v location of the image
# data structure for the MeshData:
# Texture coordinates uv: (u1 v1 u2 v2 ...) with u1 and v1 the coordinates for the first vertex.
# Typically ranging from 0.0 to 1.0, where (0.0 0.0) is the bottom-left and (1.0 1.0) is the
# top-right of the texture.
# In this section we create different mappings by playing with the u value assigned to the vertices.
# The V value is kept unchanged for all faces as we use a picture which has stripes along the v
# direction and playing with v would induce no change in the result.


face1_0 = faces[1]  # vertical rectangular with base as 5 and height as 10.
# face1_0 is made of 4 vertices:
# 1st vertice at location (6, 0, 0)
# 2nd vertice at location (6, 10, 0)
# 3rd vertice at location (11, 0, 0)
# 4th vertice at location (6, 10, 0)
face1_0.vertices_data = [
    MeshData(
        name="uv_0",
        data=[
            0.0,
            1.0,  # 1st vertice at (6, 0, 0) => texture image (0, 1) top left corner
            0.0,
            0.0,  # 2nd vertice at (6, 10, 0) => texture image (0, 0) button left corner
            1.0,
            1.0,  # 3rd vertice at (11, 0, 0) => texture image (1, 1) top right corner
            1.0,
            0.0,  # 4th vertice at (6, 10, 0) => texture image (0, 0) button right corner
        ],
    )  # full picture
]
data["rp"].commit()
print(face1_0)

opt_2 = p.create_optical_property(name="optical_property.2")
opt_2.set_volume_none()
opt_2.geometries = [
    face1_0.geo_path,
]

opt_2_layer_1 = opt_2.create_texture_layer()
opt_2_layer_1.set_surface_library().file_uri = Path(assets_data_path) / "L100 2.simplescattering"
opt_2_layer_1.set_image_texture().image_file_uri = (
    Path(assets_data_path) / "Texture.1.speos" / "textureColors_half.jpg"
)
opt_2_layer_1.image_texture.set_uv_mapping_by_data()

# Select which meshdata assign to the face is used to position the image on the geometry.
# Here we have only created one meshdata with uv coordinates but if there were several
# you could select which one to use for the mapping

opt_2_layer_1.image_texture.uv_mapping.vertices_data_index = 0
opt_2_layer_1.image_texture.repeat_u = False
opt_2_layer_1.image_texture.repeat_v = False
opt_2.commit()

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")


# Modify the UV mapping MeshData, the following example flip the texture image upside-down.
# Until 26R1 SP1 included, it is known behaviour that, previous vertices data will not
# be removed, but just appending new MeshData.
# This is corrected in the RPC version: 26R1 SP2.

uv_1 = MeshData(
    name="uv_1",
    data=[
        0.0,
        0.0,  # 1st vertice at (6, 0, 0) => texture image (0, 1) button left corner
        0.0,
        1.0,  # 2nd vertice at (6, 10, 0) => texture image (0, 0) top left corner
        1.0,
        0.0,  # 3rd vertice at (11, 0, 0) => texture image (1, 1) button right corner
        1.0,
        1.0,  # 4th vertice at (6, 10, 0) => texture image (0, 0) top right corner
    ],
)  # full picture

if server_version_checker.is_version_supported(2026, 1, 2):
    face1_0.vertices_data = face1_0.vertices_data + [uv_1]
else:
    face1_0.vertices_data = [uv_1]
data["rp"].commit()
print(face1_0)

# To use the new MeshData, user can choose the new MeshData by setting the index.
# Here we set index to 1 to select the new MeshData we just created.

opt_2_layer_1.image_texture.uv_mapping.vertices_data_index = 1
opt_2.commit()

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")


# The following rotation the texture image by 90 degree.

uv_2 = MeshData(
    name="uv_2",
    data=[
        0.0,
        1.0,  # 1st vertice at (6, 0, 0) => texture image (0, 1) button left corner
        1.0,
        1.0,  # 2nd vertice at (6, 10, 0) => texture image (0, 0) top left corner
        0.0,
        0.0,  # 3rd vertice at (11, 0, 0) => texture image (1, 1) button right corner
        1.0,
        0.0,  # 4th vertice at (6, 10, 0) => texture image (0, 0) top right corner
    ],
)  # full picture
if server_version_checker.is_version_supported(2026, 1, 2):
    face1_0.vertices_data = face1_0.vertices_data + [uv_2]
else:
    face1_0.vertices_data = [uv_2]
data["rp"].commit()
print(face1_0)

# To use the new MeshData, user can choose the new MeshData by setting the index.
# Here we set index to 2 to select the new MeshData we just created.

opt_2_layer_1.image_texture.uv_mapping.vertices_data_index = 2
opt_2.commit()

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")


# The following examples shows using partial texture image, e.g. half or quarter.

face2_0 = faces[2]  # horizontal rectangular with base as 10 and height as 5.
# 1st vertice at location (12, 0, 0)
# 2nd vertice at location (12, 5, 0)
# 3rd vertice at location (22, 0, 0)
# 4th vertice at location (22, 5, 0)
face2_0.vertices_data = [
    MeshData(
        name="uv_0",
        data=[
            0.0,
            1.0,  # 1st vertice / Left bottom corner mesh -> left top corner of texture image
            0.0,
            0.0,  # 2nd vertice / Left top corner mesh -> left bottom corner of texture image
            1.0,
            1.0,  # 3rd vertice / Right bottom corner mesh -> right top corner of texture image
            1.0,
            0.0,  # 4th vertice / Right top corner mesh -> right bottom corner of texture image
        ],
    )  # full picture
]

# Here we play with the u: from 0.0 to 0.5
# first half of the texture image (half image in the left) is projected to the square surface.
# i.e. red, blue, yellow

face3_0 = faces[3]  # square with dimension 5 by 5.
face3_0.vertices_data = [
    MeshData(
        name="uv_0",
        data=[
            0.0,
            1.0,  # 1st vertice / Left bottom corner mesh -> left top corner of texture image
            0.0,
            0.0,  # 2nd vertice / Left top corner mesh -> left bottom corner of texture image
            0.5,
            1.0,  # 3rd vertice / Right bottom corner mesh -> middle top corner of texture image
            0.5,
            0.0,  # 4th vertice / Right top corner mesh -> middle bottom corner of texture image
        ],
    )
]

# Here we play with the u: from 0.5 to 1.0
# second half of the image (half image in the right) is projected to the square surface
# i.e. pink, green, purple

face4_0 = faces[4]
face4_0.vertices_data = [
    MeshData(
        name="uv_0",
        data=[
            0.5,
            1.0,  # 1st vertice / Left bottom corner mesh -> middle top corner of texture image
            0.5,
            0.0,  # 2nd vertice / Left top corner mesh -> middle bottom corner of texture image
            1.0,
            1.0,  # 3rd vertice / Right bottom corner mesh -> Right top corner of texture image
            1.0,
            0.0,  # 4th vertice / Right top corner mesh -> Right bottom corner of texture image
        ],
    )
]

# Here we play with the u: from 4/6 to 5/6
# green only

face5_0 = faces[5]
face5_0.vertices_data = [
    MeshData(name="uv_0", data=[4 / 6, 1.0, 4 / 6, 0.0, 5 / 6, 1.0, 5 / 6, 0.0])
]
data["rp"].commit()


opt_3 = p.create_optical_property(name="optical_property.3")
opt_3.set_volume_none()
opt_3.geometries = [
    face2_0.geo_path,
    face3_0.geo_path,
    face4_0.geo_path,
    face5_0.geo_path,
]
opt_3.commit()

opt_3_layer_1 = opt_3.create_texture_layer()
opt_3_layer_1.set_surface_library().file_uri = Path(assets_data_path) / "L100 2.simplescattering"
opt_3_layer_1.set_image_texture().image_file_uri = Path(assets_data_path) / "textureColors.jpg"
opt_3_layer_1.image_texture.set_uv_mapping_by_data()

# Select which MeshData assign to the face is used to position the image on the geometry.
# Here we have only created one meshdata with uv coordinates but if there were several
# you could select which one to use for the mapping

opt_3_layer_1.image_texture.uv_mapping.vertices_data_index = 0
opt_3_layer_1.image_texture.repeat_u = False
opt_3_layer_1.image_texture.repeat_v = False
opt_3.commit()

results = sim.compute_CPU()
# Method available only on Windows OS or with Speos 2026 R1.2 or higher,
# which supports opening XMP results as images regardless of the OS.
if os.name == "nt" or server_version_checker.is_version_supported(2026, 1, 2):
    from ansys.speos.core.workflow.open_result import open_result_image

    open_result_image(simulation_feature=sim, result_name="Radiance.xmp")
