3
$\begingroup$

I would like to ask for help with an issue I'm having regarding a Python script to enable or disable the camera property (visible_camera) in the Ray Visibility settings (found in the Object Properties tab), for the object in a specific Collection Info node when it is connected to a Replace Material node.

I have made several room templates (collections with planes for walls, the floor, etc and a door) due to having several templates the object collection that is assigned to the Collection Info node is dynamic.

For extra clarity, the camera Ray Visibility property of each room template's Door collection has to be toggled by the Python script for different View Layers, because render cameras have to be able to look through the backside of the door in certain angles.

This is the code I currently have for toggling the camera ray visibility property:

 #Define room objects for visibility change

    def visibility(Door: bool, Spotlight: bool):
        for i in bpy.data.collections['Door_{}'.format(room)].objects:
            i.visible_camera = Door
        for i in bpy.data.collections['Spotlight_{}'.format(room)].objects:
            i.visible_camera = Spotlight

This is the relative part of the code to determine for which View Layers the camera ray visiblity property should be true or false:

    #Define visibility for each template camera and view layer 
            
            visibility_map_Camera_1 = {
                'Perspective': (False, True),
                'Sideview': (True, True),
                'Frontview': (False, True),
                'Topview': (True, False),  }
            
            visibility_map_Camera_2 = {
                'Perspective': (False, True),
                'Sideview': (True, True),
                'Frontview': (False, True),
                'Topview': (True, False),  }
             
            #all other cameras.. in total there are 6#
        
    # Iterate over each frame
        for frame in range(start_frame, end_frame):
            room = file.iloc[frame]['RoomTemplate']
            bpy.context.scene.frame_set(frame)
        
            # Determine which camera's view layer map to use
            camera = file.iloc[frame]['Camera']
            
            if camera == '1':
                view_layer_camera_map = view_layer_camera_map_Camera_1
                visibility_map = visibility_map_Camera_1
                z_position_map = z_position_map_Camera_1
            elif camera == '2':
                view_layer_camera_map = view_layer_camera_map_Camera_2
                visibility_map = visibility_map_Camera_2
                z_position_map = z_position_map_Camera_2

            #all other cameras.. in total there are 6#
    
# Iterate over each view layer
        for view_layer_name in view_layer_camera_map:
        # Get visibility settings from the map for the current view layer
        visibility_settings = visibility_map.get(view_layer_name, (True, True))  # Default to (True, True) if not specified
        z_position_settings = z_position_map.get(view_layer_name, (None, None))  # Default to (0.0, 0.0) if not specified
        
        # Apply visibility settings based on the current view layer and camera
        visibility(visibility_settings[0], visibility_settings[1])

When the Collection Info node is not connected to a Replace Material node everything is working perfectly as can be seen in the images below (the door is hidden in 2 of the 4 views).

Perspective, door hidden

Sideview

Frontview, door hidden

Topview

The Spreadsheet window also shows that the currently used room template's door collection assigned to the Collection Info node is recognized by the Geometry Nodes:

Collection found by Geometry Nodes

But here the issue arises, if I connect the Door Collection Info node to a Replace Material node (to be able to change the color of the door handle), the door remains visible in every View Layer render because the Python script doesn't work anymore.

I also noticed that in the Spreadsheet window, the name gets changed to simply Geometry as can be seen in the image below:

Geometry Nodes after connecting Replace Material node

In the images below can be seen that the color of the door handle is changed to gold but also that the door remains visible in every image instead of only 2 out of 4 as shown previously.

Perspective, door visible

Sideview 2

Frontview, door visible

Topview 2

My knowledge of Python is very limited, because of that I would like to ask if someone understands what the issue could be or knows how to fix this?

Edit: It is important that the geometry of the door and the handle remain visible in reflections, because of that I was trying to toggle the camera property of the Ray Visibility settings. The Replace Material node is being used because the color (material assignment) of the door handle should be the same as the material that is assigned to eg. faucets in the room.

Thanks in advance!

$\endgroup$
2
  • 2
    $\begingroup$ The camera ray visibility is a property on the object that holds the geometry. If you are using instances, they inherit the properties of the instances objects, but if you realize the instances, which you do when you change the material assignment, all the geometry will follow the setting of the object that holds the geometry nodes modifier. You may want to add a special attribute and use a shader to set some of the geometry transparent ? $\endgroup$
    – Gorgious
    Commented Sep 17 at 15:20
  • $\begingroup$ @Gorgious That makes sense. Thanks for the explanation about what happens with an instance when you do something with it in geometry nodes. The special attribute that you mentioned, would that be a Custom Property added to the geometry, in my case, the door objects? And will adding a Custom Property make it possible to search for the objects with Python to be able to change the camera ray visibility? $\endgroup$
    – Joeyvm
    Commented Sep 18 at 7:05

2 Answers 2

1
$\begingroup$

You may add a custom attribute on each instanced object's geometry that will be used in the shader. Here's a generalized example you can iterate from :

import bpy

attr_name = "show_me"

my_list_of_objects_to_show = [bpy.data.objects["Cube"]]
my_list_of_objects_to_hide = [bpy.data.objects["Cube.001"], bpy.data.objects["Cube.002"]]

def ensure_attribute(mesh):
    attribute = mesh.attributes.get(attr_name)
    if not attribute:
        attribute = mesh.attributes.new(name=attr_name, type="BOOLEAN", domain="POINT")
    return attribute

def fill_attribute(attribute, value):
    attribute.data.foreach_set("value", [value] * len(attribute.data))

for obj in my_list_of_objects_to_show:
    attribute = ensure_attribute(obj.data)
    fill_attribute(attribute, True)

for obj in my_list_of_objects_to_hide:
    attribute = ensure_attribute(obj.data)
    fill_attribute(attribute, False)

Then in your shader simply use the attribute to switch between a transparent display and the regular display. Note Cycles and EEVEE deal with transparency differently, you might want to look into increasing the maximum transparent light bounces if you're using Cycles.

enter image description here

Further reading

BTW the python guidelines state that variable names should be self_explanatory, eg i stands for integer, so when you assign an object to i it can be confusing. Similary, assigning a boolean value to Door is confusing for two reasons : One would assume it is a construct representing a door, not a boolean, and python follows the snake_case naming convention, so using a capitalized letter for a variable name means it is a class.

$\endgroup$
5
  • $\begingroup$ Interesting approach, I think that could work indeed. Will this also work on instanced (Collection Info node) geometry that gets altered by a Replace Material node? And will adding a custom attribute make it possible for Python to "find" the instanced geometry to change object properties? Or will it never be possible to change object properties of instanced geometry after "realizing" the instance by connecting it to eg. a Replace Material node, like you mentioned before? $\endgroup$
    – Joeyvm
    Commented Sep 18 at 10:21
  • $\begingroup$ The reason I'm asking is because the door and the door handle have to stay visible in reflections, because of that I was changing the camera property in the ray visibility settings before, but I'm not sure if that is a property that can be changed by this approach, what do you think about that? $\endgroup$
    – Joeyvm
    Commented Sep 18 at 10:23
  • $\begingroup$ Hello, just FYI I'd like to respectfully inform you that it's better practice to put all the information in the original question, so as not to move the goal post as people answer it, this is not really a back-and-forth type of website :) I can confirm that this will work on realized instanced objects, and on non-realized instanced objects, you just need to ensure that the shader on all objects implement the switch by attribute. Then in the shader node tree, the output of the Attribute node is basically a value alternating between 0 and 1, so you can combine this value with any other node, $\endgroup$
    – Gorgious
    Commented Sep 18 at 11:03
  • 1
    $\begingroup$ especially any output of the Light path node. You can select only the Is Camera Ray output and multiply it with the Attribute output to hide the direct contribution of the "hidden" meshes $\endgroup$
    – Gorgious
    Commented Sep 18 at 11:04
  • $\begingroup$ Do you think there could be an object / geometry based approach instead of adding attributes to a shader? Because I've given it some thought and honestly I don't think it will work in my case, due to the Replace Material node that has to change the material assignment on the door depending on what material is used for other objects in the room (eg. the faucets). If I add the attribute to every material shader I will expect that it also hides other objects that have the same material which is not ideal. $\endgroup$
    – Joeyvm
    Commented Sep 18 at 13:14
0
$\begingroup$

I have managed to find a workaround by using Python to change the material assignment on the door collections instead of using the Replace Material node.

In the Geometry Nodes modifier the door objects of each room template get added by a Collection Info node as it was done initially, so this remains unchanged. Because of that, the original Python code, that toggles the camera property in the Ray Visibility settings of each door collection also keeps working as intended.

A CSV file is used to load in objects and change the room's template for every row (or frame in Blender).

The material that has to replace the door's default material is dependent on the data in a column in the CSV file and has to be loaded from an Asset Library (which gets set at the start of the script). To achieve this I have specified which column has to be read and which material index slot has to be replaced so that the door handle can have the same material as eg. faucets or other metal objects in the room while maintaining the visibility toggle per View Layer or camera.

This seems to work perfectly. I'll paste an example of the code below:

def replace_material_in_slot_1(object_name, new_material_name):
    obj = bpy.data.objects.get(object_name)
        if obj and obj.type == 'MESH':
        #Fetch the new material directly from the asset library
        new_material = bpy.data.materials.get(new_material_name)
    
    if new_material:
        #Replace the material in slot 1 (if it exists)
        if len(obj.data.materials) > 1:  # Ensure there's a second slot (slot index 1)
            obj.data.materials[1] = new_material
            print(f"Replaced material in slot 1 of {object_name} with {new_material_name}")
        else:
            print(f"{object_name} does not have a material slot 1.")
    else:
        print(f"New material {new_material_name} not found.")
else:
    print(f"Object {object_name} is not a mesh or doesn't exist.")

def load_room_and_door(row):
room = file.iloc[row]['YourRoomTemplateName']

#Load room
nodes['GeoNodesRoomCollectionInfoName'].inputs['Collection'].default_value= bpy.data.collections.get('{}'.format(room))

#Load door
nodes['Door'].inputs[0].default_value= bpy.data.collections['{}'.format(room)].children.get('Door_{}'.format(room))
#Replace material for the door handle using 'YourSpecificReplacementMaterial' column
door_object = bpy.data.collections[f'Door_{room}'].objects.get(f'Door_{room}')
if door_object:
    new_material = file.iloc[row]['YourSpecificReplacementMaterial']
    replace_material_in_slot_1(door_object.name, new_material)

def handler(scene):
frame = scene.frame_current
load_room_and_door(frame)

bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_pre.append(handler)
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .