Blender BIM hide isolating in context with Python
I was trying the IFC Search panel,
I wanted to see if I could make a very simple script, which automatically hide isolates the elements. Instead of clicking an extra button. Also for my own learning purposes.
This was my attempt, trying to hide isolate an IfcWall
When I change the class from IfcWall to IfcBeam it says the context is incorrect, probably because I already have something selected.
RuntimeError: Operator bpy.ops.object.hide_view_clear.poll() failed, context is incorrect
Error: Python script failed, check the message in the system console
After searching I found this post.
I found this function in the documentation to check if the context is correct.
@classmethod
def poll(cls, context):
return context.object is not None
How would I use this function? What values should go into the parameters cls and context?
Comments
cls and context are mandatory signature for poll functions, basically you may give the first argument the name you want, but as this method is a "class" method, and does not require an instance of the operator to run, by convention we use "cls" to refer to the class of the method - by opposition to "self" for instances methods.
When blender check if the operator is in the right context, it does pass context as argument and the function must return true or false if the context is correct for the operation.
Isolate mode in blender "local_view" is available as shortcut : numpad "/", but looks like you are trying to achieve the opposite ?
Thanks for your answer, I am not trying to hide the object, but isolate them and show them.
I am trying to do something really simple ( I think), but I get confused by these forum posts:
https://blenderartists.org/t/blender-2-8-python-hide-unhide-objects/1141228
https://blender.stackexchange.com/questions/36281/bpy-context-selected-objects-context-object-has-no-attribute-selected-objects
What I am trying to achieve,
" and show instantly the new IFC class.
I tried to put bpy.context in poll function, it does not work. I am doing something fundamentally wrong I think.
Basically poll() get context from blender at call time, so you have the context without bpy. things required.
The operator hide_view probably require an active 3d view (mouse cursor over the area) in order to work so if you have a button anywhere else than in 3d view, you must override the operator's context and pass the right area.
ctx = context.copy()
ctx['area'] = ... the 3d view area
ctx['selected_objects'] = ..
bpy.ops.object.hide_view_set(ctx, unselected=True)
Another solution may be to rely on visibility state of objects instead,
o.hide_set(state=True)
If you feel like copy pasting code, @stephen_l 's suggestion can be seen in use in the BCF module where we have to efficiently hide and isolate elements for each BCF viewpoint:
You should always try to avoid using operators in code. They are really finnicky because they usually require a particular context, and they are extremely slow because they force a reevaluation (and even a redraw of the interface ?) every time they are evaluated. Usually you can get away with lower-level API like
obj.hide_set(state)
andobj.hide_get()
.@Gorgious that is advice I would also encourage in the majority of situations. However, I recall when writing that particular portion, when dealing with the visibility of typically 10,000-100,000 objects as you would in a federated model, a Python loop with a lower level call was unfortunately magnitudes slower than the operator-based context hack. I would love to know a faster way especially as toggling BCF viewpoints is a big part of the user experience in model coordination.
Edit: oh, just realised I left a comment in there because I figured other devs might have the same concern: https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/src/blenderbim/blenderbim/bim/module/bcf/operator.py#L877-L878
@Moult Have you tried
foreach_get
andforeach_set
?https://docs.blender.org/api/latest/bpy.types.bpy_prop_collection.html#bpy.types.bpy_prop_collection.foreach_set
I read that they're as fast as C to batch-change or batch-get values from
bpy_prop_collection
objects.Coupled with the numpy library I think we could replace calls to operators.
Very simple example :
bpy.data.objects.foreach_set("hide_viewport", (True,) * len(bpy.data.objects))
Will hide every object from viewport.
Only weird thing is it doesn't update the viewport. Toggling the parent collection does the trick.
bpy.data.collections["Collection"].hide_viewport = True
bpy.data.collections["Collection"].hide_viewport = False
Tested with ~12.000 objects :
for obj in bpy.data.objects:
obj.hide_viewport = True
takes 7,5 sec
bpy.data.objects.foreach_set("hide_viewport", (True,) * len(bpy.data.objects))
takes 0,03 sec@Gorgious aaah yes I didn't try with
foreach_get/set
! I think you're right that could be a better approach. Would you be interested in checking if it is faster than the current operator implementation? That comment should definitely be updated to say "TODO: try out foreach".Yeah sure I'll poke around ! :)
Informative thread :-)
I tried this:
It selects the windows

But I get this message in the console
What is the correct syntax? Do I need to add an extra argument in the foreach_set ? Or am I using the function wrong?
Turns out it's more complicated than I expected and since the eye icon visibility is governed per view layer and not the object, we can't use
foreach_get
orforeach_set
. Same thing for the selection state. Meaning an object can have n(view layers) visibility states and selection states.That being said the problem there is you need to provide a boolean array with exactly the same length as the collection that's being iterated over. Moreover operators never return objects, but a set giving information whether it executed correctly.
I would rather write :
Since it's not as straightforward as I initially thought and I don't have the data to test against thousands of Ifc objects, I can't say whether or not it's worth the hassle... :)
So if you have a list of ifcguids in Python.
What's the fastest way to select all the IFC elements and hide all the unselected IFC elements in Blender in a Python script?
I now select it like this:
Takes quite a while for this IFC model, for example, selecting the IfcCovering takes 66.707590341568 secondsseconds:

Is there a faster way of selecting a group of IfcGuids, how does the IFC search function does it?
How does this fare ?
Or maybe this, which hides all the other objects :
Thank you for your answer, I am getting confused though.
When I use
It selects the elements, but slow, this took two seconds:

After everything is selected I use shift + H to hide the unselected elements.

After a mouse click in the view I can click and inspect the elements as I wish.
It's a slow method so I tried this
It's indeed faster and I can see in the outline it indeed does hides the elements from the guid list. However I needed the inverse.


I wanted to highlight the elements from the guid list, not hide them. That script seems to select everything and it's faster. That's also confusing.
When I used Shift + H to hide the remaining objects I get to see nothing (obviously). But when pressing Alt + H to reveal everything the elements from the guid list are still hidden.
Oh, right sorry my bad, it should be
obj.hide_viewport = data.get("GlobalId", False) not in guid_list
(orobj.hide_viewport = not data.get("GlobalId", False) in guid_list
).The reason it doesn't get un-hidden when using ALT H is because
hide_viewport
property is a global hidden property (monitor icon), whereas the eye icon is a local hidden property. If you've ever used Autocad it's kind of the same as the light bulb icon and the sun icon. To the point, ALT H will locally unhide objects, but it won't change their global hiden-ness (or however it's called ? ><) .By the way, to get access to the monitor icon in the outliner expand the sieve icon in the top right and check the monitor icon.


Then :
Note this is the exact same property as the one you toggle in the object's visibility properties :
The eye icon is a little bit more complicated to use, since it can also rely on a specific view layer, but by default it's the active one. Here's a suggestion :
The reason it seems (and certainly is) faster is because using blender operators (expression begginning with
bpy.ops
) is notoriously slow. They are by essence user interface operators, and they kind of force a redraw of the interface and a re-calculation of some parameters every time they are executed. You won't notice it when you execute it 1, 10 or 20 times, but for hundreds of calls the hiccup will be significant. Whenever possible it's usually recommended to use lower level API calls. That's also one of the reasons why the BlenderBim logic is getting uncoupled from blender operators by Dion since a few commits already. This allows users to bypass the limitations of blender operators.@Gorgious
Thank you so much! I never knew about the screen icon. Only started learning Blender three months ago.

It works.
But if ALT H doesn't work. How do I make everything visible again?
Haha yeah, I wouldn't worry about it, I've been using it extensively for 3 years and I still discover things every other day :)
I don't know if you noticed, but collections also have visibility toggles. Using SHIFT + Click on a collection toggle will propagate to all its children. So you can SHIFT + double click on a collection toggle to uncheck then recheck all its children (alternatively, and it may be faster if you have a lot of objects in the collection, you can simply click on the collection toggle to deactivate it and only it, then SHIFT + Click on the toggle to unhide objects.)
Or via python
How would I toggle the monitor icon with Python?
I am using
bpy.context.space_data.show_restrict_column_viewport = False
or
bpy.context.space_data.show_restrict_column_viewport = True
But the Blender console is giving me this error
Do you mean toggling the display of the icon inside the outliner, or toggle the state of a particular object ? If it's the object :
obj.hide_viewport = True # or False
If it's the outliner toggle visibility, it's a little more involved :
First get the outliner editor area
Then get the outliner space (it only has one)
You motivated me to post a Q&A on BSE. :)
Very nice, you have been extremely helpful and I learned a lot from this discussion.

The function now works exactly as I intended.
My pleasure :)
Don't hesitate to ping me if you have any Blender related questions !