Correct Way to Update Object's Mesh with Bmesh in Edit Mode?

Does anyone know the correct way to update a mesh with Bmesh in Edit mode without leaving Edit mode? I need to temporarily remove a face, Object.scene.ray_cast and then restore the original mesh. So far the only way I’ve found to get ray_cast to hit the updated mesh is to exit and enter Edit mode but that often causes Blender to crash in my actual script. In my test script, toggling Edit mode does not crash, but I think it crashes because I’m doing it in a draw handler in the addon. Right now I’m doing something like:

bm = bmesh.from_edit_mesh(context.edit_object.data)
bmesh.ops.delete(bm, geom=[bm.faces[index]], context='FACES')

# this feels weird to me, I call update_edit_mesh but there's
# no reference to bm?
bmesh.update_edit_mesh(context.edit_object.data, loop_triangles=True, destructive=True)

# crashes Blender most of the time in my draw handler but if 
# I don't do this, the following ray_cast will still detect 
# the deleted face and then give me a face index with
# a huge value that isn't real
#bpy.ops.object.mode_set(mode='OBJECT')
#bpy.ops.object.mode_set(mode='EDIT')

result, _, _, index, _, _ = context.scene.ray_cast(depsgraph, origin, direction, distance=0.5 + 0.01)

I have to delete the face because Blender doesn’t appear to have a way for object.scene.ray_cast to ignore faces.

I figured out one piece of the puzzle. I need to also update the depsgraph for the next ray_cast. Once I do that, my test script functions without an edit mode toggle back and forth but it still crashes when the same code is in a draw handler.

depsgraph = context.evaluated_depsgraph_get()

Do you use a draw handler like this?
https://docs.blender.org/api/current/gpu.html#d-lines-with-single-color

I am not sure about how feasible is to run update code in this scope, that mutates the bpy.context, I might have never seen this.

In this way you might be far better to use the modal draw operator instead, so in this way the drawing/updating of the code happens exactly at the right moment. (Based on Blender’s internal update loop)
blender\4.0\scripts\templates_py\operator_modal_draw.py

However I have not tried to combine all of these three together raycastmesh updatedrawing.

If there is such problem that you are not able to perform such operation (if it still crashes) at once, you can try to split the operation to two modal operators raycast+mesh update and drawing

1 Like

Yeah

I’m only doing this because Blender doesn’t seem to have a way to ignore faces when ray casting. If there are two co-planar faces, identical in every way but one has a normal pointing in the Z positive and the other has a normal pointing in the Z negative, the only method I’ve found to detect both(because they’re co-planar, you could hit either since the ray_cast in Blender isn’t deterministic) is after hitting one, delete it, update the geometry, and ray cast again to see if the second one is present.

Is that really going to matter? It’s still using the same draw_handler_add as the example you provided above, it’s just enabling it when the operator is invoked instead of running all the time. I’ve tested the code in multiple ways and it always crashes in the same spot.

That’s a pretty good idea. I hadn’t thought of modal operators as something that can run without user interaction. I could run it in my workspace tool’s draw_cursor method.

Thank you for responding, if you have any more thoughts or ideas, I’m open to them. I’ve been try to work this out for awhile now.

Perhaps I remember someone else mentioning, having the same problem. I am very sure there is a workaround but I am not sure exactly to tell what.

From what I see quickly there are two other optional ways.
https://www.google.com/search?q=blender+raycast+ignore+backface

Option 1

added a small offset to the hit vector → used it as new origin for a second raycast
https://blender.stackexchange.com/questions/109539/how-to-ignore-backfaces-when-doing-a-raycast

Option 2

Maybe hiding them temporarily helps. → you hide it, cast the ray, unhide it
How to make the raycast ignore an object? - #7 by mano-wii

From what I see the least invasive technique is [2], if it works it would be really great. The other of option [1] looks like is very interesting, I don’t fully understand it though. If I am correct (based on what I understand) it means that if the normal looks points away from the camera (is backface) then you do a second raycast at this position. If I am correct, you do a recursive function call until you hit a face that points to the camera.

If both of these alternative ways fail, it means that you will have to revert back to the exact one you are in, that involves deleting faces. (Fingers crossed that [1] or [2] work better). :sweat_smile:

Yeah, I’ve gone through many iterations of searching and found both of those links. Unfortunately, neither of these work.

I’ve tested Option 1 and it doesn’t work because of floating point comparisons, it just misses the second face most of the time, even offsetting by tiny amounts. If you don’t offset at all, it just hits the first face again.

Option 2 doesn’t work because I’m working on one mesh in edit mode and that link is about ignoring other objects. context.scene.ray_cast doesn’t ignore hidden faces, it still hits them. Just re-tested toggling edit mode, in case that was a thing but it still hits hidden faces even if I hide them all.

So putting it in an operator that is called when the workspace tool’s draw_cursor method is called works, no more crashes. So thanks for that idea!

I ended up using a regular operator, I didn’t see the need for a modal one. I don’t know if this will be the long term solution though, since I’m now getting a different crash after awhile and I think it has to do with the undo_push/undo I’m using to restore the mesh. I really don’t trust Blender’s undo, since 99% of my crashes in normal use come from using it. The crash may be something else though, since it comes up around the same time as an error from something else I’ve been getting for awhile. Now I can finally move on to tackling that since I’ve fixed this one.

Thanks again!

1 Like

This is a real bummer that the alternate options didn’t work.

Another option would be to go with the same idea as deleting faces, but instead of deleting them, you will have to move them away from view. I get the feeling that hiding or deleting cause significant changes to the BMESH data. However moving operations are less intense as they have only to retain everything as it is.
This looks like it has a good potential as well.

The final solution, is to re-implement your own raycast function, so you can control everything about it and how it works. Unfortunately Blender’s raycast is weak. I have seen in many other engines or frameworks, but raycast should return a list of results that you can further filter them based on your needs, but not only one result that is the first hit as you get now.

1 Like

That’s a good idea too. I don’t know how easy it is to move a face through the API though. I think it’s the same series of function calls that cause the crash too: from_edit_mesh, update_edit_mesh, evaluated_despgraph_get, crash But maybe it’s not the same codepath internally if nothing was deleted.

This is what I had decided I was going to do next the night before you posted and I may still end up doing it. It’s not too bad performance wise because I can make assumptions about the geometry I’m checking since it isn’t arbitrary.

Yeah and for consistency with object mode ray casting, it should ignore hidden faces at least.

I used GPT to get an idea about how this is done but the math were not correct as the face was moving down instead of moving away. But anyway instead of spending more time to figure it out, I just went to copy the log output from the info window, as performing the transformation myself.

Code
import bpy
import bmesh
from mathutils import Vector


class MoveMeshForward(bpy.types.Operator):
    bl_idname = "mesh.move_mesh_forward"
    bl_label = "Move Mesh Forward"

    offset : bpy.props.FloatProperty(default=0.1)

    @classmethod
    def poll(cls, context):
        return context.mode == 'EDIT_MESH'

    def execute(self, context):
        print('move mesh forward')
        
        use_bmesh = False
        use_blendop = True
        
        if use_bmesh:
            print('WIP')
            return
        
            # get viewport forward vector
            viewport_forward = bpy.context.region_data.view_matrix @ Vector((0, 0, -1))

            # active object mesh
            bm = bmesh.from_edit_mesh(context.active_object.data)
            
            # iterate selected faces
            for face in bm.faces:
                if face.select:
                    print('iteracing face', face)                    
                    # get center of selected face
                    face_center = face.calc_center_median()
                    # move face along viewport forward vector                
                    #translation_vector = self.offset * viewport_forward
                    #bmesh.ops.translate(
                    #    bm,
                    #    verts=face.verts,
                    #    vec=translation_vector
                    #)

            # update the mesh
            bmesh.update_edit_mesh(obj.data)
            bpy.context.view_layer.update()
        

        if use_blendop:
            bm = bmesh.from_edit_mesh(context.active_object.data)
            if len([x for x in bm.faces if x.select]) == 0:
                self.report({'WARNING'}, 'No faces selected')
                return {'FINISHED'}
            
            bpy.ops.transform.translate(
                value=(0, 0, -10.000),
                orient_type='VIEW',
                orient_matrix_type='VIEW')        
        
        
        return {'FINISHED'}

bpy.utils.register_class(MoveMeshForward)

Yeah provided that you do the filtering early on as of hiding all backfacing geometry this would cut raycast checks about half. Then there are also other techniques to figure out if the face is within the viewport and reduce face iterations even further. However these are aggressive optimizations that you might figure out at a later stage. At this point you would only be concerned about doing direct iteration and check at once.

For posterity and in case you’re interested, I’ve found an alternative way to do this that doesn’t use ray_cast or deleting faces: bmesh.geometry.intersect_face_point. This returns every face that the projection of the point along each face’s normal intersects with. Combined with a distance check and a normal side check, it can filter down to the correct face pretty quickly. I’m probably going to move my code to this, just because of its read only nature.

1 Like

Wow, this was exactly what was needed. Nice find btw. :slight_smile: