Bug fix help: Object visibility out of sync with the outliner

I’m a first timer trying to solve a bug in Blender here. I actually looked at it a couple months ago, but got caught up and forgot about it. The issue may be reproduced as follows:-

  1. Open a blend file with at least one object in it, say the default cube.
  2. Create a new collection in the outliner as a child of the root collection.
  3. Bring up the view menu (is that what it’s called?) by hitting ‘n’ and selecting the view tab.
  4. Check local collections under ‘Collections’ and hide the collection you created. (Collection 1 by default)
  5. Move the default cube ‘Cube’ into the collection you created.
  6. The cube doesn’t become hidden.
  7. To get the desired result, unhide and then hide the local collection again.

This is my logical take on the issue:-

  1. The visibility of the objects in a local collection is calculated only when the visibility of the collection is changed.
  2. Visibility has to be calculated also after every change in the outliner.
  3. This does happen when local collections are disabled.

What I need to solve the issue:-

  1. I need to know what code is executed when a local collection’s visibility is changed.
  2. I need to know what function calculates the visibility of an object.

So I tried finding functions for the purpose by searching for keywords from the respective python commands (Search on QtCreator) for each UI element, but didn’t get any useful results. And then a good man on blender.chat pointed out that rna_collection.c would be a good place to look for what happens when collections are moved about. I don’t understand the code though. Would anyone help me find the code I need to work with? I’ve never worked on a large project before. Also, is it unusual for re-builds to take upto five minutes on a reasonable machine? It would be helpful if someone did a walk through on solving a beginner issue from a new comer’s perspective.

1 Like

When you had commented there, I had updated the task description to add this info:

Uncheck the collection “Exclude from view layer” box, and update occurs.

The tooltip of that message if developer extras are enabled in Preferences shows Scene .. exclude. The UI is in blender/release/scripts/startup/bl_ui/ file for this specific part: space_outliner.py . Note the string collection_exclude_set for further searches. Also, prefix is outliner. so that is useful too.

Switch to the IDE, regex search outliner.*collection_exclude_set. We reach outliner_collections.c. Function of interest is mostly ot->exec, which here is collection_view_layer_exec.

  1. I need to know what code is executed when a local collection’s visibility is changed.

I assume it’s about collection’s visibility in view layer. So that is collection_view_layer_exec

  1. I need to know what function calculates the visibility of an object.

It could be bitwise shift flags too, compared when required using bitwise and &. So searching for definitions with hide/ show/ visible in files containing outliner in the name would be useful.

Also, is it unusual for re-builds to take upto five minutes on a reasonable machine?

Run make lite in blender folder to get a tiny but essential-complete build. A similar search based question came up some time ago Distinguishing between a NURB and a Bezier curve

In addition to disabling features (or using make lite), I found three configurations sped up my builds on Linux: ninja, ccache, and lld. There are instructions on the wiki for configuring ninaj and ccache https://wiki.blender.org/wiki/Building_Blender/Options#Setup_For_Developers

For LLD set WITH_LINKER_LLD to ON and disable WITH_LINKER_GOLD in cmake. (requires installing lld). This will speed up the linking.

My incremental rebuilds are around 8-10 seconds with ninja, ccache, lld and many features (like cycles) disabled.

That helped! My build time is close to 20 seconds now.

1 Like

Could you tell me what the difference between ‘exclude from view layer’ (The check box next to collection name in the outliner) and ‘Disable from view layer’ (right click on collection name and navigate to view layer) is?

I ask you this because though they seem to have the same functionality (I’m not sure), they don’t share the same code. ‘Disable from view layer’ calls this function you were talking about -

But exclude from view layer doesn’t. And toggling ‘disable from view layer’ doesn’t fix the bug at hand. Only toggling ‘Exclude from view layer’, like you pointed out before, does. So I need to know the function that’s called there. It would also be helpful if you could let me know how you knew the string “collection_exclude_set” would be of use.

I admit I didn’t set breakpoints to check if the function collection_view_layer_exec runs or not.
However I just found that BKE_layer_collection_set_flag is executed when I “check” the exclude from view layer checkbox and also when I uncheck it. and it has two callers (in code limited to lite build). collection_view_layer_exec and outliner_unhide_all_exec. The latter is executed for the said check-uncheck process.

Though I still wonder if calling BKE_layer_collection_sync (or an even broader function BKE_scene_collection_sync) right after objects are moved would work or not. It’s worth a try.

I don’t know. @natecraddock is more knowledgeable here than me.

The prefix in python translate to C as SOMETHING_OT_ (like WM_OT, ANIM_OT, ) and suffix almost exactly the same. Also the tooltips are written in C too.

The difference here is how code is executed, the result is the same. Let’s start with the checkbox.

The checkbox is a UI button that is defined in outliner_draw.c:tselem_draw_layer_collection_enable_icon() The button is created with the call to uiDefIconButR_prop which has PointerRNA layer_collection_ptr and PropertyRNA *exclude_prop arguments. These are how the button is connected to the “exclude” property in rna_layer.c

In rna_layer.c you can see the “exclude” property defined starting at line 377. This is where the callback and update functions are connected to the property (as strings). These are what run when you click the button.

For the context menu entry things are a bit different. The outliner context menu is defined in space_outliner.py The Disable From View Layer menu entry calls outliner.collection_exclude_set. As @ankitm already said, we need to “translate” this to C operator spelling → OUTLINER_OT_collection_exclude_set in outliner_collections.c.

Here we can follow the operator’s exec function to collection_view_layer_exec. Here we can see similar code to the rna callbacks for the button, but here we also have some iterators over the tree to apply the flag to all selected outliner tree elements. The same syncing occurs here though.

Hopefully that helps as you are searching for the fix! :slight_smile:

I spent six hours or so today attempting the following changes to file editors/space_outliner/outliner_dragdrop.c, function outliner_item_drag_drop_invoke(line 871).

I tried both. Hard luck.

The functions I thought of interest here and tried were BKE_layer_collection_sync, DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS), and ED_object_base_active_refresh. All contained in the function rna_LayerCollection_exclude_update(line 302).

I also looked at object_edit.c, where the function that implements the eye icon for local collections resides. (Figured out that through the “translation process”). I tried calling a couple functions that seemed to update or refresh things, like DEG_id_tag_update and BKE_layer_collection_local_sync(Causes Blender to crash). I tried these because they come under a conditional that checks if the flag V3D_LOCAL_COLLECTIONS is enabled (line 294).

Sadly, nothing works. All I get is no difference or a crash. Other than trying assorted functions corresponding to GUI events that trigger a refresh of object visibility in the outlinerdragdrop.c file, is there any other approach I can take? I did get a lot more used to the codebase today, though.

I took a look in object_edit.c and BKE_layer_collection_local_sync(). Based on my limited understanding (looked at the commit where this was added https://developer.blender.org/rB92736a7b75920ffe4b8016a2d097ff8e36687c70), you need to replicate the behavior for all viewports. The eye toggle in the sidebar is part of a viewport which is why it can access the v3d context.

I think it would be best to fix this in BKE_collection_object_move so that moving objects between collections from outliner or other operators will properly update the viewport.

Also, the drag+drop in the outliner starts in outliner_item_drag_drop_invoke but the other operators in the file are what finish the action. Drags are managed by the window manager, started by one operator, and finished by a second depending on where they are dropped. See the bottom of the file outliner_dropboxes for those functions that set the valid outliner drop areas.

That’s all I’ve got for now. If I have more time I can try taking a deeper look,

Spoilers! https://developer.blender.org/P1535 this patch solves the bug. BUT

  • I don’t know how it works.
  • It’s very ugly ofc (just a copy paste).
  • It can be moved to the location Nate said, as you have the required collection from there which needs the flags to be set. Also remove the redundant update functions.
  • Sorry if I mislead you by this, and the solution turns out to be something elegant and far from what I’ve tried with sleepy eyes.

lol I wouldn’t know how my fix works either if I ever reach one. This seems to solve the issue except for the fact that if you change the collection to which an object belongs without using the outliner(use the M hotkey on an object), the visibility isn’t updated. So I guess I’ll try moving it to BKE_collection_object_move

I included the fix by @ankitm to function BKE_collection_object_move defined in collection.c. The bug appears completely solved now, with messy code and very little contribution on my part being the only topics of concern. I had to make a few other changes to do this.

  • @ankitm’s patch requires a ViewLayer which requires a bContext to be initialised - ViewLayer *view_layer = CTX_data_view_layer(C). //C is a pointer to a bContext.
    There are instances in the code where it was initialised with ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph). I didn’t try initialising with this, nor do I know the difference between the two.
    So I had to add bContext *C as one of the arguments to the BKE_collection_object_move function.

  • This forced me to add BKE_context.h as one of the includes in BKE_collection.h.

  • I obviously had to modify the other call to BKE_collection_object_move in line 1686 of object_edit.c.

I also went ahead and commented out a couple things in the patch that I figured were unnecessary and verified by building Blender. Could you tell me if there’s something wrong here or if any changes have to be made?
Diff is at https://developer.blender.org/P1537.

Best to make a diff and make changes there. When you feel it’s final , i.e:

  • make format has been run,
  • Comments if required are added.
  • commented out code has been removed

etc, then add reviewers.

Also setup Arcanist and use commands like:

  • arc diff / arc diff --update D1234
  • arc paste P1234 | git apply / git diff | arc paste

For the code: wouldn’t scene->view_layers.first (or all if required) work, instead of CTX...(C) ?
Also try to understand why it works, which flags make it work, can the double DEG update be skipped … it’s a hack now.
Also, iterating over all collections is not required… only src and dst need an update.


I did comment out one DEG update from your patch in the diff I posted earlier, leaving only one in the code.

That did work. We don’t need the bContext argument anymore.

True, but the function being called inside the loop, BKE_layer_collection_set_flag requires a LayerCollection object as an argument. I don’t really understand the difference between LayerCollection and Collection. LayerCollection is a struct with a Collection member. So assuming there’s a collection corresponding to each LayerCollection, I tried iterating over all the LayerCollectios and finding the appropriate LayerCollections that contain the source and destination Collections -

   LISTBASE_FOREACH (LayerCollection *, lc_iter, &lc_master->layer_collections) {
        if(lc_iter->collection == collection_dst){
            lc_src = lc_iter; //And then call BKE_layer_collection_set_flag with lc_src and lc_dst
        else if(lc_iter->collection == collection_src){
            lc_dst = lc_iter;

The bug appears solved this way at first, but after moving an object around more than once in the outliner, Blender crashes. Could you tell me if there are any alternative ways to use collection_src and collection_dst in calling the functions within the iterator?

In all, I managed to bring down the size of the fix quite a bit, but I can’t really make very calculated decisions without knowing what functions do what. Is there any documentation that doesn’t show itself to Google searches? I did try going through the function definitions.

Do you think this is one of the harder bugs to solve?

PS: I was trying to see if arc works properly (there was something wrong with the installation at first) and so ran arc diff, which unfortunately, created a differential. I did ‘Abandon revision’ in the comment box, but is there more I should do? Link to that is here

If there are no comments, there’s barely any documentation. https://wiki.blender.org/wiki/Source Read the commits which add the feature or the code itself.

You needed to press submit too. But there’s no need to. Update it if there’s any mistake.

I need clarification on this one thing that’s bugging me a bit. The differential needs revision, but the fix I submitted at first seems to be merged into master. Not the improved update that @ankitm made, but the original thing. Is it my knowledge of git that’s to blame? Still not quitting, by the way.

I hope you had not committed to your master locally. If so, this will work:

git checkout master
make update
arc patch D8342
git checkout arcpatch-D8342 # check that the branch name is correct.
# check the log to see commit history.
# make changes you want to make
# git commit with a message
arc diff --update D8342
# after some days
git checkout master 
make update
git checkout arcpatch-D8342
git rebase master
# more commits to patch branch, update the diff etc.

Sounds like a pun to me. but it’s most probably the lack of arcanist knowledge.


1 Like

@ankitm I see you’ve responded to the patch I made. Should I just wait now, to see if it gets accepted?

I just made some minor comments. Dalai will be reviewing the approach, accepting the revision & committing it.