Skip to content

feat(mesh): Rework material manipulation #1083

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions blenderproc/api/material/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from blenderproc.python.material.MaterialLoaderUtility import add_alpha, add_alpha_channel_to_textures, \
add_alpha_texture_node, add_ambient_occlusion, add_base_color, add_bump, add_displacement, add_metal, \
add_normal, add_roughness, add_specular, change_to_texture_less_render, collect_all, connect_uv_maps, \
convert_to_materials, create_image_node, create, is_material_used, create_new_cc_material, \
create_procedural_texture, find_cc_material_by_name, create_material_from_texture
convert_to_material, convert_to_materials, create_image_node, create, is_material_used, \
create_new_cc_material, create_procedural_texture, find_cc_material_by_name, create_material_from_texture
from blenderproc.python.material.Dust import add_dust
8 changes: 7 additions & 1 deletion blenderproc/python/material/MaterialLoaderUtility.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,19 @@ def create(name: str) -> Material:
return Material(new_mat)


def convert_to_material(blender_material: bpy.types.Material) -> Material:
""" Converts the given blender material to bproc.Material
"""
return None if blender_material is None else Material(blender_material)


def convert_to_materials(blender_materials: List[Optional[bpy.types.Material]]) -> List[Optional[Material]]:
""" Converts the given list of blender materials to bproc.Material(s)
:param blender_materials: List of materials.
:return: The list of materials.
"""
return [(None if obj is None else Material(obj)) for obj in blender_materials]
return [convert_to_material(obj) for obj in blender_materials]


def find_cc_material_by_name(material_name: str, custom_properties: Dict[str, Any]) -> bpy.types.Material:
Expand Down
71 changes: 60 additions & 11 deletions blenderproc/python/types/MeshObjectUtility.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,88 @@ class MeshObject(Entity):
Every instance of this class is a mesh which can be rendered in the scene. It can have multiple materials and
different configurations of vertices with faces and edges.
"""
def materials(self) -> int:
""" Returns the number of material slots of the object.

def get_materials(self) -> List[Optional[Material]]:
""" Returns the materials used by the mesh.
:return: The number of material slots.
"""
return len(self.blender_obj.material_slots)

def get_material_slot_link(self, index: int) -> str:
""" Returns whether object's or object's data material is used in the material slot.

:return: "DATA" if the material slot is linked to data material or "OBJECT" otherwise.
"""
return self.blender_obj.material_slots[index].link

def set_material_slot_link(self, index: int, link: str):
""" Sets whether object's or object's data material is used in the material slot.
Available: ["DATA", "OBJECT"]. Type: str
"""
self.blender_obj.material_slots[index].link = link

def get_material(self, index: int, link = "VISIBLE") -> Material:
""" Returns the material used by the object.

:link: The mode specifying whether to get material linked to the object or object's data.
Available: ["DATA", "OBJECT", "VISIBLE"]. Type: str
:return: A list of materials.
"""
link = link.upper()
if link == "VISIBLE" and self.get_material_slot_link(index) == "DATA":
link = "DATA"
link2get = "OBJECT" if link == "VISIBLE" else link

link2return = self.get_material_slot_link(index)
self.set_material_slot_link(index, link2get)
material = self.blender_obj.material_slots[index].material
self.set_material_slot_link(index, link2return)

# If there is no material in the `OBJECT` slot then the 'DATA' material is displayed.
if material is None and link == "VISIBLE":
return self.get_material(index, "DATA")
else:
return MaterialLoaderUtility.convert_to_material(material)

def get_materials(self, link = "VISIBLE") -> List[Optional[Material]]:
""" Returns the materials used by the object.

:link: The mode specifying whether to get materials linked to the object or object's data.
Available: ["DATA", "OBJECT", "VISIBLE"]. Type: str
:return: A list of materials.
"""
return MaterialLoaderUtility.convert_to_materials(self.blender_obj.data.materials)
return [self.get_material(index, link) for index in range(self.materials())]

def has_materials(self) -> bool:
"""
Returns True if the object has material slots. This does not necessarily mean any `Material` is assigned to it.

:return: True if the object has material slots.
"""
return len(self.blender_obj.data.materials) > 0
return self.materials() > 0

def set_material(self, index: int, material: Material):
""" Sets the given material at the given index of the objects material list.
def set_material(self, index: int, material: Material, link="DATA"):
""" Sets the given material at the given index of the object's material list.

:param index: The index to set the material to.
:param material: The material to set.
:link: The mode specifying whether to link material to the object or object's data.
Available: ["DATA", "OBJECT"]. Type: str
"""
self.blender_obj.data.materials[index] = material.blender_obj
keep_link = self.get_material_slot_link(index)
self.set_material_slot_link(index, link)
self.blender_obj.material_slots[index].material = None if material is None else material.blender_obj
self.set_material_slot_link(index, keep_link)

def add_material(self, material: Material):
""" Adds a new material to the object.
""" Adds a new material to the object's data.

:param material: The material to add.
"""
self.blender_obj.data.materials.append(material.blender_obj)

def new_material(self, name: str) -> Material:
""" Creates a new material and adds it to the object.
""" Creates a new material and adds it to the object's data.

:param name: The name of the new material.
"""
Expand All @@ -68,11 +117,11 @@ def new_material(self, name: str) -> Material:
return new_mat

def clear_materials(self):
""" Removes all materials from the object. """
""" Removes all materials from the object's data. """
self.blender_obj.data.materials.clear()

def replace_materials(self, material: bpy.types.Material):
""" Replaces all materials of the object with the given new material.
""" Replaces all materials of the object's data with the given new material.

:param material: A material that should exclusively be used as new material for the object.
"""
Expand Down
40 changes: 40 additions & 0 deletions tests/testMeshObject.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import blenderproc as bproc

import unittest

from blenderproc.python.tests.SilentMode import SilentMode
from blenderproc.python.types.MeshObjectUtility import create_primitive
from blenderproc.python.material import MaterialLoaderUtility


class UnitTestCheckUtility(unittest.TestCase):
def test_materials(self):
bproc.clean_up(True)

mat1 = MaterialLoaderUtility.create("mat1")
mat2 = MaterialLoaderUtility.create("mat2")

obj = create_primitive("CUBE")
self.assertTrue(obj.has_materials() == False)

obj.add_material(mat1)
self.assertTrue(obj.materials() == 1)
self.assertTrue(obj.get_material_slot_link(0) == "DATA")
self.assertTrue(obj.get_material(0, "DATA").get_name() == "mat1")
self.assertTrue(obj.get_material(0, "OBJECT") is None)
self.assertTrue(obj.get_material(0, "VISIBLE").get_name() == "mat1")

obj.set_material(0, mat2, "OBJECT")
self.assertTrue(obj.get_material(0, "DATA").get_name() == "mat1")
self.assertTrue(obj.get_material(0, "OBJECT").get_name() == "mat2")
self.assertTrue(obj.get_material(0, "VISIBLE").get_name() == "mat1")

obj.set_material_slot_link(0, "OBJECT")
self.assertTrue(obj.get_material(0, "DATA").get_name() == "mat1")
self.assertTrue(obj.get_material(0, "OBJECT").get_name() == "mat2")
self.assertTrue(obj.get_material(0, "VISIBLE").get_name() == "mat2")

obj.set_material(0, None, "OBJECT")
self.assertTrue(obj.get_material(0, "DATA").get_name() == "mat1")
self.assertTrue(obj.get_material(0, "OBJECT") is None)
self.assertTrue(obj.get_material(0, "VISIBLE").get_name() == "mat1")