Sculpting events?

Hi

is there a way to get events when sculpting occurs ?

pressing the pen/mouse down → triggers a function with the original mesh data as parameter
releasing the pen/mouse → triggers a function with the new mesh data as parameter

I would need to know whish face gets splits into which new faces

thanks

I don’t know exactly if there are sculpting events or so.

The most possible technique is this:

Get 3D position cursor - #8 by const

thanks for replying, but I need to know mesh modifications when sculpting
could not find anything relevant

Technically, you have the dependency graph, which updates whenever mesh is modified. Actually, it’s modified whenever any data is changed, but it’ll work for your purposes. You’ll just have to do some filtering.

import bpy
import math
from bpy.app.handlers import persistent

@persistent
def get_pre_update_mesh(dummy):
    <do something>

@persistent
def get_post_update_mesh(dummy):
    <do something>
bpy.app.handlers.depsgraph_update_pre.append(get_pre_update_mesh)
bpy.app.handlers.depsgraph_update_post.append(get_post_update_mesh)

This code will allow you to do X before a mesh update is finalized, and Y after it’s finalized. In theory, it should allow you to do what you want and get the before and after mesh.

Cool idea, thanks for posting. With this hack you can actually see real update only when the vertices changes for good.

It might be quite slow depending on the number of thousands of vertices.

Initially I used the length of vertices as a check value, that would update only when entering-exiting dyntopo/sculptmode.

import bpy
import math
import bmesh
import hashlib
from bpy.app.handlers import persistent

update_count = 0
prev_hash = 0

def vertices_checksum(vertices) -> int:
    result = ''
    for v in vertices:
        result += str(v.co.x)
        result += str(v.co.y)
        result += str(v.co.z)
    return hash(result)

@persistent
def get_post_update_mesh(dummy):
    global update_count, prev_hash
    
    this_hash = vertices_checksum(bpy.context.object.data.vertices.values())
    
    if prev_hash != this_hash:
        prev_hash = this_hash
        update_count += 1
        print('updated mesh vertices:', update_count)

bpy.app.handlers.depsgraph_update_post.clear()
bpy.app.handlers.depsgraph_update_post.append(get_post_update_mesh)
1 Like

That actually seems like it would work perfectly. That speed issue you mentioned definitely might be an issue… I wonder if you could somehow only hash a portion of the vertices. Maybe using every other vertex for example. It’s unlikely that you’d ever affect just one one vertex in sculpt mode, so you should be able to hash half the vertices - alternating, of course - and still get reliable sculpting updates

1 Like

Actually, new thought… why bother hashing all the vertices, and wasting all that time with a for loop, when you could just get the surface area of the mesh? If the surface area stays the same, no sculpting has occurred, right? Surely just checking the surface area on each depsgraph_update to see if it’s changed would be faster and have the same result

Perhaps there could be a technique for this. If you can actually get the object change in a snap, rather than figure it out manually.

https://docs.blender.org/api/current/bpy.types.Depsgraph.html

But until that moment, when I will need something like this, I will revisit the thread.

wow, such a sudent rush of reply, thanks guys

yet I would have to go through all faces before vs after to figure which one have changed, so hasing it would just add more useless process

I am trying to update the sculpted mesh but it makes blender crash

is there something I am doing wrong with bmesh ?

class Post:
    def __init__( self, oldFaces ):
        self.oldFaces = oldFaces

    def __call__(self,scene,unknown):
        #the following lines make blender crash
        bm = bmesh.new()
        bm.from_mesh(bpy.context.active_object.data)
        # here I am supposed to modify the mesh
        bm.to_mesh(bpy.context.active_object.data)
        bm.free()

here is the main operator registering handlers (there to keep data between handlers calls)

class MyOperator_OT_Operator(bpy.types.Operator):
    bl_idname = "object.myoperator"
    bl_label = "SomeOperator"
    bl_options = {'REGISTER', 'UNDO'}
        
    def invoke(self, context, event):
        self.oldFaces = {}

        clearHandlers()

        bpy.app.handlers.depsgraph_update_pre.append(Pre(self.oldFaces))
        bpy.app.handlers.depsgraph_update_post.append(Post(self.oldFaces))

        return {'FINISHED'}

def register():
    bpy.utils.register_class(MyOperator_OT_Operator)

def unregister():
    bpy.utils.unregister_class(MyOperator_OT_Operator)

if __name__ == "__main__":
    register()

here is the exception

ExceptionCode         : EXCEPTION_ACCESS_VIOLATION
Exception Address     : 0x00007FF73B3CF77B
Exception Module      : blender.exe
Exception Flags       : 0x00000000
Exception Parameters  : 0x2
	Parameters[0] : 0x0000000000000000
	Parameters[1] : 0x0000014E63458FA0


Stack trace:
blender.exe         :0x00007FF73B3CF730  mesh_calc_ngon_normal
blender.exe         :0x00007FF73B429F60  pbvh_update_normals_accum_task_cb
blender.exe         :0x00007FF73F0FE050  BLI_task_parallel_range
blender.exe         :0x00007FF73B427080  BKE_pbvh_update_normals
blender.exe         :0x00007FF73B5D8870  DRW_mesh_batch_cache_create_requested
blender.exe         :0x00007FF73B5D0160  drw_batch_cache_generate_requested
blender.exe         :0x00007FF73B5AF9C0  drw_engines_cache_populate
blender.exe         :0x00007FF73B5AC570  DRW_draw_render_loop_ex
blender.exe         :0x00007FF73B5AD5A0  DRW_draw_view
blender.exe         :0x00007FF73BD0AE00  view3d_main_region_draw
blender.exe         :0x00007FF73B8F4E00  ED_region_do_draw
blender.exe         :0x00007FF73B4E0CA0  wm_draw_window_offscreen
blender.exe         :0x00007FF73B4E0AF0  wm_draw_window
blender.exe         :0x00007FF73B4E05F0  wm_draw_update
blender.exe         :0x00007FF73B4BD850  WM_main
blender.exe         :0x00007FF73B1C96E0  main
blender.exe         :0x00007FF73F621EC8  __scrt_common_main_seh
KERNEL32.DLL        :0x00007FFCCC767020  BaseThreadInitThunk
ntdll.dll           :0x00007FFCCE742630  RtlUserThreadStart

thanks for helping me

Your exception says that you don’t have access permissions to your KERNEL32.dll, you’ll need to run Blender as an administrator :slight_smile:

no it’s a EXCEPTION_ACCESS_VIOLATION, something to do with accessing forbidden memory zone or something

If I comment the culprit lines I dont get that exception

blender does not need admin right anyways, it’s not file system related

it’s not always the same internal function, but it’s always normals related

  • either my bmesh code s@#ks
  • either I cannot mod the mesh right after it has been sculpted

I have tried one hack on top of the other, at least now I do not get a crash.

If you start modifying the source code and test extensively to see what is needed and is not needed.

import bpy
import math
import bmesh
import hashlib
from bpy.app.handlers import persistent


# * temporary solution for detecting changes
# ------------------------------------------

update_count = 0
prev_hash = 0

def vertices_checksum(vertices) -> int:
    result = ''
    for v in vertices:
        result += str(v.co.x)
        result += str(v.co.y)
        result += str(v.co.z)
    return hash(result)

@persistent
def get_post_update_mesh(dummy):
    global update_count, prev_hash
    
    this_hash = vertices_checksum(bpy.context.object.data.vertices.values())
    
    if prev_hash != this_hash:
        prev_hash = this_hash
        update_count += 1

        # * the bmesh operator is bounded within an operator context
        # * it is invoked with a new context copy (extra safety measure)
        # --------------------------------------------
        ctx = bpy.context.copy()
        bpy.ops.wm.nice_operator(ctx)
        # --------------------------------------------
        
        print('updated mesh vertices:', update_count)
        
# ------------------------------------------

class NiceOperator(bpy.types.Operator):
    bl_idname = "wm.nice_operator"
    bl_label = "Noice"

    def execute(self, context):
        
        # * creating a dummy bmesh copy
        # ------------------------------------------
        bm = bmesh.new()
        bm.from_mesh(bpy.context.active_object.data)
        print('created mesh: ', len(bm.verts))
        del(bm)            
        # ------------------------------------------

        return {'FINISHED'}
    
try:
    bpy.utils.unregister_class(ModalTimerOperator)
    bpy.app.handlers.depsgraph_update_post.clear()
except:
    pass

bpy.app.handlers.depsgraph_update_post.append(get_post_update_mesh)
bpy.utils.register_class(NiceOperator)


dude !!!
thanks…I’ll have a try