Skip to content

Commit 9a55946

Browse files
committed
feat(MeshObject): Rework material manipulation
closes #1082
1 parent eaf6d86 commit 9a55946

File tree

4 files changed

+109
-14
lines changed

4 files changed

+109
-14
lines changed

blenderproc/api/material/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from blenderproc.python.material.MaterialLoaderUtility import add_alpha, add_alpha_channel_to_textures, \
22
add_alpha_texture_node, add_ambient_occlusion, add_base_color, add_bump, add_displacement, add_metal, \
33
add_normal, add_roughness, add_specular, change_to_texture_less_render, collect_all, connect_uv_maps, \
4-
convert_to_materials, create_image_node, create, is_material_used, create_new_cc_material, \
5-
create_procedural_texture, find_cc_material_by_name, create_material_from_texture
4+
convert_to_material, convert_to_materials, create_image_node, create, is_material_used, \
5+
create_new_cc_material, create_procedural_texture, find_cc_material_by_name, create_material_from_texture
66
from blenderproc.python.material.Dust import add_dust

blenderproc/python/material/MaterialLoaderUtility.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,19 @@ def create(name: str) -> Material:
3434
return Material(new_mat)
3535

3636

37+
def convert_to_material(blender_material: bpy.types.Material) -> Material:
38+
""" Converts the given blender material to bproc.Material
39+
"""
40+
return None if blender_material is None else Material(blender_material)
41+
42+
3743
def convert_to_materials(blender_materials: List[Optional[bpy.types.Material]]) -> List[Optional[Material]]:
3844
""" Converts the given list of blender materials to bproc.Material(s)
3945
4046
:param blender_materials: List of materials.
4147
:return: The list of materials.
4248
"""
43-
return [(None if obj is None else Material(obj)) for obj in blender_materials]
49+
return [convert_to_material(obj) for obj in blender_materials]
4450

4551

4652
def find_cc_material_by_name(material_name: str, custom_properties: Dict[str, Any]) -> bpy.types.Material:

blenderproc/python/types/MeshObjectUtility.py

+60-11
Original file line numberDiff line numberDiff line change
@@ -27,39 +27,88 @@ class MeshObject(Entity):
2727
Every instance of this class is a mesh which can be rendered in the scene. It can have multiple materials and
2828
different configurations of vertices with faces and edges.
2929
"""
30+
def materials(self) -> int:
31+
""" Returns the number of material slots of the object.
3032
31-
def get_materials(self) -> List[Optional[Material]]:
32-
""" Returns the materials used by the mesh.
33+
:return: The number of material slots.
34+
"""
35+
return len(self.blender_obj.material_slots)
36+
37+
def get_material_slot_link(self, index: int) -> str:
38+
""" Returns whether object's or object's data material is used in the material slot.
39+
40+
:return: "DATA" if the material slot is linked to data material or "OBJECT" otherwise.
41+
"""
42+
return self.blender_obj.material_slots[index].link
43+
44+
def set_material_slot_link(self, index: int, link: str):
45+
""" Sets whether object's or object's data material is used in the material slot.
46+
Available: ["DATA", "OBJECT"]. Type: str
47+
"""
48+
self.blender_obj.material_slots[index].link = link
49+
50+
def get_material(self, index: int, link = "VISIBLE") -> Material:
51+
""" Returns the material used by the object.
52+
53+
:link: The mode specifying whether to get material linked to the object or object's data.
54+
Available: ["DATA", "OBJECT", "VISIBLE"]. Type: str
55+
:return: A list of materials.
56+
"""
57+
link = link.upper()
58+
if link == "VISIBLE" and self.get_material_slot_link(index) == "DATA":
59+
link = "DATA"
60+
link2get = "OBJECT" if link == "VISIBLE" else link
61+
62+
link2return = self.get_material_slot_link(index)
63+
self.set_material_slot_link(index, link2get)
64+
material = self.blender_obj.material_slots[index].material
65+
self.set_material_slot_link(index, link2return)
66+
67+
# If there is no material in the `OBJECT` slot then the 'DATA' material is displayed.
68+
if material is None and link == "VISIBLE":
69+
return self.get_material(index, "DATA")
70+
else:
71+
return MaterialLoaderUtility.convert_to_material(material)
72+
73+
def get_materials(self, link = "VISIBLE") -> List[Optional[Material]]:
74+
""" Returns the materials used by the object.
3375
76+
:link: The mode specifying whether to get materials linked to the object or object's data.
77+
Available: ["DATA", "OBJECT", "VISIBLE"]. Type: str
3478
:return: A list of materials.
3579
"""
36-
return MaterialLoaderUtility.convert_to_materials(self.blender_obj.data.materials)
80+
return [self.get_material(index, link) for index in range(self.materials())]
3781

3882
def has_materials(self) -> bool:
3983
"""
4084
Returns True if the object has material slots. This does not necessarily mean any `Material` is assigned to it.
4185
4286
:return: True if the object has material slots.
4387
"""
44-
return len(self.blender_obj.data.materials) > 0
88+
return self.materials() > 0
4589

46-
def set_material(self, index: int, material: Material):
47-
""" Sets the given material at the given index of the objects material list.
90+
def set_material(self, index: int, material: Material, link="DATA"):
91+
""" Sets the given material at the given index of the object's material list.
4892
4993
:param index: The index to set the material to.
5094
:param material: The material to set.
95+
:link: The mode specifying whether to link material to the object or object's data.
96+
Available: ["DATA", "OBJECT"]. Type: str
5197
"""
52-
self.blender_obj.data.materials[index] = material.blender_obj
98+
keep_link = self.get_material_slot_link(index)
99+
self.set_material_slot_link(index, link)
100+
self.blender_obj.material_slots[index].material = None if material is None else material.blender_obj
101+
self.set_material_slot_link(index, keep_link)
53102

54103
def add_material(self, material: Material):
55-
""" Adds a new material to the object.
104+
""" Adds a new material to the object's data.
56105
57106
:param material: The material to add.
58107
"""
59108
self.blender_obj.data.materials.append(material.blender_obj)
60109

61110
def new_material(self, name: str) -> Material:
62-
""" Creates a new material and adds it to the object.
111+
""" Creates a new material and adds it to the object's data.
63112
64113
:param name: The name of the new material.
65114
"""
@@ -68,11 +117,11 @@ def new_material(self, name: str) -> Material:
68117
return new_mat
69118

70119
def clear_materials(self):
71-
""" Removes all materials from the object. """
120+
""" Removes all materials from the object's data. """
72121
self.blender_obj.data.materials.clear()
73122

74123
def replace_materials(self, material: bpy.types.Material):
75-
""" Replaces all materials of the object with the given new material.
124+
""" Replaces all materials of the object's data with the given new material.
76125
77126
:param material: A material that should exclusively be used as new material for the object.
78127
"""

tests/testMeshObject.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import blenderproc as bproc
2+
3+
import unittest
4+
5+
from blenderproc.python.tests.SilentMode import SilentMode
6+
from blenderproc.python.types.MeshObjectUtility import create_primitive
7+
from blenderproc.python.material import MaterialLoaderUtility
8+
9+
10+
class UnitTestCheckUtility(unittest.TestCase):
11+
def test_materials(self):
12+
bproc.clean_up(True)
13+
14+
mat1 = MaterialLoaderUtility.create("mat1")
15+
mat2 = MaterialLoaderUtility.create("mat2")
16+
17+
obj = create_primitive("CUBE")
18+
self.assertTrue(obj.has_materials() == False)
19+
20+
obj.add_material(mat1)
21+
self.assertTrue(obj.materials() == 1)
22+
self.assertTrue(obj.get_material_slot_link(0) == "DATA")
23+
self.assertTrue(obj.get_material(0, "DATA").get_name() == "mat1")
24+
self.assertTrue(obj.get_material(0, "OBJECT") is None)
25+
self.assertTrue(obj.get_material(0, "VISIBLE").get_name() == "mat1")
26+
27+
obj.set_material(0, mat2, "OBJECT")
28+
self.assertTrue(obj.get_material(0, "DATA").get_name() == "mat1")
29+
self.assertTrue(obj.get_material(0, "OBJECT").get_name() == "mat2")
30+
self.assertTrue(obj.get_material(0, "VISIBLE").get_name() == "mat1")
31+
32+
obj.set_material_slot_link(0, "OBJECT")
33+
self.assertTrue(obj.get_material(0, "DATA").get_name() == "mat1")
34+
self.assertTrue(obj.get_material(0, "OBJECT").get_name() == "mat2")
35+
self.assertTrue(obj.get_material(0, "VISIBLE").get_name() == "mat2")
36+
37+
obj.set_material(0, None, "OBJECT")
38+
self.assertTrue(obj.get_material(0, "DATA").get_name() == "mat1")
39+
self.assertTrue(obj.get_material(0, "OBJECT") is None)
40+
self.assertTrue(obj.get_material(0, "VISIBLE").get_name() == "mat1")

0 commit comments

Comments
 (0)