import os
import re
from random import choice
import bpy
from blenderproc.python.modules.loader.LoaderInterface import LoaderInterface
from blenderproc.python.modules.utility.Config import Config
from blenderproc.python.utility.Utility import Utility
[docs]class RockEssentialsTextureSampler(LoaderInterface):
"""
Samples a random texture data from the provided list and sets the images to each selected object (ground tiles
created by constructor.RockEssentialsGroundConstructor) if they have a RE-specific material assigned (they have
it applied by default if ground tile was constructed by aforementioned constructor module).
Example 1: For all ground planes matching a name pattern select a random set of textures with custom ambient
occlusion, displacements strength and UV map scaling factor values.
.. code-block:: yaml
{
"module": "materials.RockEssentialsTextureSampler",
"config": {
"selector": {
"provider": "getter.Entity",
"conditions": {
"name": "Gr_Plane.*",
"type": "MESH"
}
},
"textures": [
{
"path": "<args:0>/Rock Essentials/Ground Textures/Pebbles/RDTGravel001/",
"uv_scaling": 2,
"ambient_occlusion": [0.5, 0.5, 0.5, 1],
"displacement_strength": 1.5,
"images": {
"color": "RDTGravel001_COL_VAR1_3K.jpg",
"roughness": "RDTGravel001_GLOSS_3K.jpg",
"reflection": "RDTGravel001_REFL_3K.jpg",
"normal": "RDTGravel001_NRM_3K.jpg",
"displacement": "RDTGravel001_DISP16_3K.tif"
}
},
{
"path": "<args:0>/Rock Essentials/Ground Textures/Pebbles/RDTGroundForest002/",
"uv_scaling": 4,
"ambient_occlusion": [0.7, 0.7, 0.7, 1],
"displacement_strength": 0.5,
"images": {
"color": "RDTGroundForest002_COL_VAR1_3K.jpg",
"roughness": "RDTGroundForest002_GLOSS_3K.jpg",
"reflection": "RDTGroundForest002_REFL_3K.jpg",
"normal": "RDTGroundForest002_NRM_3K.jpg",
"displacement": "RDTGroundForest002_DISP16_3K.tif"
}
}
]
}
}
**Ground plane config**:
.. list-table::
:widths: 25 100 10
:header-rows: 1
* - Parameter
- Description
- Type
* - selector
- Objects (ground planes) with RE-specific material applied.
- Provider
* - textures
- A list of dicts with texture data: images, path to the images, etc.
- list
**Texture data**:
.. list-table::
:widths: 25 100 10
:header-rows: 1
* - Parameter
- Description
- Type
* - path
- Path to a directory containing maps required for recreating texture.
- string
* - ambient_occlusion
- Ambient occlusion [R, G, B, A] color vector for a ground tile material's shader. Default: [1, 1, 1, 1].
- mathutils.Vector
* - uv_scaling
- Scaling factor of the UV map. Default: 1.
- float
* - displacement_strength
- Strength of a plane's displacement modifier. Default: 1.
- float
* - images/color
- Full name of a color map image.
- string
* - images/roughness
- Full name of a roughness map image.
- string
* - images/reflection
- Full name of a reflection map image.
- string
* - images/normal
- Full name of a normal map image.
- string
* - images/displacement
- Full name of a displacement map image.
- string
"""
def __init__(self, config):
LoaderInterface.__init__(self, config)
# set a RE-specific material name pattern to look for in the selected objects
self.target_material = "re_ground_mat.*"
[docs] def run(self):
""" Sets a random texture from the provided list for each selected object if it has a re-material assigned.
1. For all selected ground tiles.
2. Get a random texture from the defined list.
3. Load images.
4. Assign them to a material.
"""
# get list of textures
textures = self.config.get_list("textures")
# get objects to set textures to. It is implied that one is selecting the ground planes by the name that was
# defined in the config of the constructor.RockEssentialsGroundConstructor config
ground_tiles = self.config.get_list("selector")
# for each selected ground tile (plane)
for ground_tile in ground_tiles:
# get a random texture
selected_texture = self._get_random_texture(textures)
# load the images
images, uv_scaling, ambient_occlusion, displacement_strength = self._load_images(selected_texture)
# set images
self._set_textures(ground_tile, images, uv_scaling, ambient_occlusion, displacement_strength)
[docs] def _get_random_texture(self, textures):
""" Chooses a random texture data from the provided list.
:param textures: Texture data. Type: list.
:return: Selected texture data. Type: Config.
"""
selected_dict = choice(textures)
selected_texture = Config(selected_dict)
return selected_texture
[docs] def _load_images(self, selected_texture):
""" Loads images that are used as color, roughness, reflection, normal, and displacement maps.
:param selected_texture: Selected texture data. Type: Config.
:return: loaded_images: Loaded images. Type: dict.
:return: uv_scaling: Scaling factor of the UV map. Type: float.
:return: ambient_occlusion: Ambient occlusion color vector. Type: mathutils.Vector.
:return: displacement_strength: Strength of a plane's displacement modifier. Type: float.
"""
loaded_images = {}
# get path to image folder
path = selected_texture.get_string("path")
# get uv layer scaling factor
uv_scaling = selected_texture.get_float("uv_scaling", 1)
# get ambient occlusion vector
ambient_occlusion = selected_texture.get_list("ambient_occlusion", [1, 1, 1, 1])
# get displacement modifier strength
displacement_strength = selected_texture.get_float("displacement_strength", 1)
# get dict of format {may type: full map name}
maps = selected_texture.get_raw_dict("images")
# check if dict contains all required maps
for key, value in maps.items():
# open image
bpy.ops.image.open(filepath=os.path.join(path + value), directory=path)
# if map type is not 'color' - set colorspace to 'Non-Color'
if key != "color":
bpy.data.images[value].colorspace_settings.name = 'Non-Color'
# update return dict
loaded_images.update({key: bpy.data.images.get(value)})
return loaded_images, uv_scaling, ambient_occlusion, displacement_strength
[docs] def _set_textures(self, ground_tile, images, uv_scaling, ambient_occlusion, displacement_strength):
""" Sets available loaded images to a texture of a current processed ground tile.
:param ground_tile: Ground tile (plane). Type: bpy.types.Object.
:param images: Loaded images of a chosen texture. Type: dict.
:param uv_scaling: Scaling factor for the UV layer of the tile. Type: float.
"""
# get a target material in case ground tile has more than one
for material in ground_tile.data.materials.items():
if re.fullmatch(self.target_material, material[0]):
mat_obj = bpy.data.materials[material[0]]
else:
raise Exception("No RE material " + self.target_material + " found in selected objects. Check if "
"constructor.RockEssentialsGroundConstructor module was run at least once and created "
"at least one ground tile!")
# get the node tree of the current material
nodes = mat_obj.node_tree.nodes
# get all Image Texture nodes in the tree
image_texture_nodes = Utility.get_nodes_with_type(nodes, 'ShaderNodeTexImage')
# for each Image Texture node set a texture (image) if one was loaded
for node in image_texture_nodes:
if node.label in images.keys():
node.image = images[node.label]
# get texture name for a displacement modifier of the current ground tile
texture_name = ground_tile.name + "_texture"
# if displacement map (image) was provided and loaded - set it to the modifier
if "displacement" in images.keys():
bpy.data.textures[texture_name].image = images['displacement']
# set ambient occlusion
nodes.get("Group").inputs["AO"].default_value = ambient_occlusion
# set displacement modifier strength
bpy.context.object.modifiers["Displace"].strength = displacement_strength
# and scale the texture
for point in ground_tile.data.uv_layers.active.data[:]:
point.uv = point.uv * uv_scaling