Skip to content
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

[QUESTION]: How to get the material/node of a specific duplicated object #590

Open
AlcatrAAz opened this issue Jun 6, 2022 · 11 comments
Open
Assignees
Labels
first answer provided question Question, not yet a bug ;)

Comments

@AlcatrAAz
Copy link

Hello everyone,

I have a question how I get the material or better a specific node of a specific duplicated object? In my case I load an object (with one material) from a .blend file and afterwards a duplicate it 100x (including the material). If I change one thing in the material it will change it for all duplicated objects. But I want to change/randomize for example the hue for every duplicated object, so that I have 100 objects with the same material but with different hues. Is this possible or do I have to create for every duplicated object a new material?

Any tips are apreciated :)

@AlcatrAAz AlcatrAAz added the bug Something isn't working label Jun 6, 2022
@cornerfarmer cornerfarmer self-assigned this Jun 7, 2022
@cornerfarmer
Copy link
Member

Hey @AlcatrAAz,

there is currently no single bproc method that can duplicate all materials of a given object (although we maybe should add one), however you can simply duplicate each material of your duplicated object obj manually:

for i, material in enumerate(obj.get_materials()):
    material_dup = material.duplicate()
    obj.set_material(i, material_dup)
@cornerfarmer cornerfarmer added question Question, not yet a bug ;) first answer provided and removed bug Something isn't working labels Jun 7, 2022
@AlcatrAAz
Copy link
Author

Hey @cornerfarmer

thank you for your fast answer! Okay this would be a workaround to create a material for every object.

My question was more something like this:
If I duplicate one object 100x, then I have 100x Objects with the same material. If I change one thing in the material it will change it for every object. But is there a way to get access to a node in the material directly from an specific object.

Instead of something like this:
bpy.data.materials["case"].node_tree.nodes["Hue Saturation Value"].inputs['Hue'].default_value =random.uniform(0, 1)

More like this:
duplicated_object1."get_node_from_Material_from_this object.["Hue Saturation Value"].inputs['Hue'].default_value =random.uniform(0, 1)
duplicated_object2."get_node_from_Material_from_this object.["Hue Saturation Value"].inputs['Hue'].default_value =random.uniform(0, 1)

So that I can change only this specific node at this specific object and all other Materials of the duplicated objects remain the same.

@cornerfarmer
Copy link
Member

Hey @AlcatrAAz,

when duplicating objects in blender, the material is not duplicated, but instead the same material is linked to all duplicated objects. Therefore, you cannot access the individual material of one specific object, as simply there is none.

If you duplicate the materials, as I described above, then each object has its individual material and you can set object specific parameters via:

obj.get_materials()[0].nodes["Hue Saturation Value"].inputs['Hue'].default_value = random.uniform(0, 1)
@themasterlink
Copy link
Contributor

Hey,

one thing to understand is that a material is its own entity and is only linked to an object. So, when duplicate an object the link to this one existing material is also copied, this means no new material is created.

This also means that a material can be linked to a hundred objects, which all then use the same material.

If you now want that each object has its own material, you have to create it, as @cornerfarmer already showed.

for obj in list_of_all_duplicated_objects:  # iterate over all objects
    for i, material in enumerate(obj.get_materials()):  # duplicate each material of each object 
        material_dup = material.duplicate()
        obj.set_material(i, material_dup)

Afterwards you can iterate over all objects again and set whatever value you want.

for obj in list_of_all_duplicated_objects:  # iterate over all objects
    for node in obj.get_nodes_with_type("ShaderNodeHueSaturation"):
        node.inputs["Hue"].default_value = random.uniform(0, 1)

Check this document here:

https://github.com/DLR-RM/BlenderProc/blob/main/blenderproc/python/types/MaterialUtility.py
https://docs.blender.org/api/current/bpy.types.ShaderNodeHueSaturation.html#bpy.types.ShaderNodeHueSaturation

PS: I did not test this code.

Best,
Max

@themasterlink
Copy link
Contributor

I assume this is closed now

@AlcatrAAz
Copy link
Author

Hey @cornerfarmer @themasterlink

sorry for the late replay, yes it worked and your help was very helpful.

Thank you!

@MartinSmeyer
Copy link
Member

In BlenderProc, Materials can be set for MeshObject instances. However, in Blender materials are independent of objects. If we duplicate a MeshObject in BlenderProc, it just duplicates a reference to the same Material. In OOP it's quite unexpected that changing an instance attribute can change another instance of the same type as happened here and in #792

So we would propose a breaking change but with an option to keep the current behavior, i.e.

def duplicate(self, duplicate_materials: bool = True, duplicate_children: bool = True) -> "MeshObject":

Any thoughts?

@MartinSmeyer MartinSmeyer reopened this Jan 30, 2023
@MartinSmeyer
Copy link
Member

As a user I would expect that I need to change all MeshObject materials and not just one, so I don't think we would break many people's code.

@Sebastian-Jung
Copy link
Contributor

👍🏽 I would appreciate duplication to create an independent instance that does not influence other objects.

@cornerfarmer
Copy link
Member

In OOP it's quite unexpected that changing an instance attribute can change another instance

I wouldn't say thats uncommon in OOP. If you say mesh objects have references to material objects, then it is of course plausible that two mesh objects reference the same material. We also model it that way in blenderproc, we have a class MeshObject and a class Material. So material attributes are not really MeshObject attributes.

Also, the material sharing between different objects is quite common in blenderproc. For example in the front3D loader each material is loaded only once and then shared between objects: see https://github.com/DLR-RM/BlenderProc/blob/main/blenderproc/python/loader/Front3DLoader.py#L184
The reason for that is to prevent having thousands of duplicate materials flying around.

I agree there should be definitely a parameter in the duplicate function which allows duplicating the materials. The only question is how to set the default value.
Given that we had this problem now multiple times, I tend to agree to set it to True 👍

@MartinSmeyer
Copy link
Member

Yes, I see the reason for not duplicating the materials in some cases such as Front3D and we should definitely keep that option.

Given that we had this problem now multiple times, I tend to agree to set it to True 👍

Sounds good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
first answer provided question Question, not yet a bug ;)
5 participants