Is there a better way to raycast to an unevaluated mesh than what I'm doing?

I have a mesh with modifiers (subdivision, bevel) and need to raycast in edit mode to the unevaluated mesh below. I can’t create a BVH tree because it’s just too slow… I’m doing about 10 raycasts per second and creating a BVH tree takes about 40ms which adds up to a LOT of hitching.

On the other hand, scene.ray_cast takes only 1-2ms, but returns the evaluated mesh, so you end up getting face indices that don’t exist in edit mode (and subsequently won’t work with bmesh for obvious reasons).

My current solution is to loop through all of the modifiers on the object during invoke() and disable show_in_editmode, then turn them back on when the operator is finished.

This solution is working great with two minor caveats: 1) for some reason, scene.ray_cast will still return the evaluated mesh after turning off show_in_edit mode (likely a deferred update happening) so you have to wait for the frame after disabling modifiers to raycast. 2) it is a bit visually jarring for the user to have the mesh ‘pop’ to its unmodified state and then ‘pop’ back.

TLDR: I have a workable solution, but it’s not perfect. I’m curious if any of you clever chaps have some other ideas for me. Ideally scene.ray_cast() would just have a flag to return unevaluated geometry but in my search I have found no such option.

1 Like

You could maybe use scene_eval to get unevaluated geometry.

1 Like

This technically works, but it requires any edit mode changes to be pushed back to the object first. If a user makes a bunch of edits and then this raycast happens afterward it will be using data from whatever existed when they entered edit mode…

Thinking about it a bit more, bpy.ops.ed.flush_edits() during invoke() should have pretty good performance on objects that are not very dense. a quick test shows I can get about 17ms on a mesh with 62k faces, but it jumps to 73ms on a mesh with 250k faces so it’s directly proportional to the amount of geometry it has to process. I could fall back to the ‘disable modifiers’ method for these extreme cases.

it’s something to think about anyway, i guess i need to decide if I want to maintain these two paths or just accept the imperfect solution I have. thanks for the idea, got my brain going this morning if nothing else! :slight_smile:

Ah dang, didn’t think of that.
I just remembered going down the api rabbit hole that is the depsgraph a while back, trying to find the elusive deform cage of a mesh and that sprung to mind.

If you want to localize the update to just one mesh obj.update_from_editmode() should be just as fast as flush_edits.

1 Like

I was having this same issue now. Did you end up using the context.view_layer.depsgraph.scene_eval.raycast() method?

I am using scene.raycast as well and doing literally the exact same thing. Collect object modifiers/disable and then raycast to get proper face index. But as you said, I was getting stale/wrong mesh data, if you stayed in edit mode/deleted faces, then ran my script again/scene raycast.

I THINK running the obj.update_from_editmode(), in my code fixed the issue though… but i have to do more testing.

But is it better to use context.view_layer.depsgraph.scene_eval.raycast() and avoid all this stuff? Not have to disable modifiers then?

For my tool, it does help to disable the modifiers though i guess, so the user has a better idea of what they are selecting/what is real geo i guess/base geo.

Only thing that sucks is with scene raycast, I was also getting issues where it was hitting objects not visible when are you in Isolate Mode…
So I had to loop my raycast, and if its object not “visible” in isolate, it hides those to unhide later. So that works, but then when you commit/cancel the operator modal, it can hitch as it has to unhide potentially 5-10 Highpoly meshes.

Edit: Using obj.update_from_editmode() seems to have fixed my issues, at least in many tests so far. But we will see. But I’m still curious about what I said above.

ultimately I didn’t like having two logic paths to maintain, so I ended up just using the faster method (disabling/enabling modifiers).

1 Like

ok, cool. Thanks for the reply! Yeah. I might just leave what I got for now.

Why not create a BVH tree only once and use that throughout the lifetime of the operator? Or does the geometry change or something that requires updating it? Or did I understand this incorrectly?

for my purposes, the geometry has to be considered ‘dirty’ from one raycast to the next. I experimented with caching it and it worked in most situations, but occasionally it would end up referencing the wrong face index, or the face’s location would be different, etc.

honestly, I wish the python API just exposed a precomputed BVH tree for a bmesh rather than having to construct one from scratch. Blender is obviously using a BVH tree (or similar data structure) on the C side of things for normal selections in edit mode, I don’t know why it’s not made available for Python as pass-through data. My guess would be the old historical ‘memory’ argument that gets made anytime something like this is brought up, but to that I say- just let us pass a True/False argument when we’re creating the bmesh if we also want the BVH tree that goes with it. even better, just make a BVH tree property for BMesh that is always up to date any time you run bmesh.ops or modify the bmesh data manually.

Ah, ok, thanks for the info.

That would be nice. It would also be great to have a way to get the element closest to cursor without using ray cast at all. (I don’t know what purpose you’re using it for, though.) Anyway, I’m now aware of anything better than what you’re doing already :frowning:

I am pretty sure selections inside Blender are not done using BVH or a similar data structure. At any rate,as far as I know, there is no constantly available bvh tree for a bmesh. Some operators build one in order to accomplish their task, but those are ephemeral. I would guess the reason is not memory but rather the computation cost of constantly keeping it up to date in the face of geometry edits.

Has anybody noticed that this raycast method returns evaluated geometry lately? I haven’t needed to use it in a while- but for a recent project I thought it would be the perfect fit and I’m getting results for geometry that doesn’t exist in edit mode. IE) a default cube with a bevel modifier will return the beveled geometry face indices rather than the underlying cube. Has something changed with scene_eval.ray_cast() or am I just completely misremembering how it worked?

Can confirm the behavior changed, but it’s weird that scene_eval returned unevaluated geometry in the past.

Now at least, the raycast follows the toggle of modifiers in the viewport.

1 Like