How check wether block content did change at InstanceDefinitionTableEvent

Hi There,

I am looking for an efficient way to determine whether the contents of a block have actually changed, after a InstanceDefinitionTableEvent has been triggered.

It seems to doubleclick a blockinstance and close the block edit dialog, even when geometry was not changed, always triggers an event with event.EventType = Rhino.RhinoDoc.Tables.InstanceDefinitionTableEventType.Modified

Is there a method that tells me wether the actual geometry has changed, without having to check each rhino object inside OldState and NewState for equality? (see the code snippet below for the methods I tried)

I hope you can help me out.

Best regards,
Tim


def AutoUpdateEvent(sender, e):


    # only trigger at 'Modified' event type. Not at 'Added' event"
    if str(e.EventType) == 'Modified':

        old_def_geometry = e.OldState
        new_def = e.NewState

        # this method always returns "False", also when content did NOT change
        print 'equals', new_def.Equals(old_def_geometry)

        # This method always returns "True", also when content did change
        json_old = old_def_geometry.ToJSON(Rhino.FileIO.SerializationOptions())
        json_new = old_def_geometry.ToJSON(Rhino.FileIO.SerializationOptions())
        print 'equal json', json_old == json_new

        # this method always returns "False", 
        # appearently the object ids are always updated after "BeginEdit+EndEdit"
        equal = True
        objs_new = new_def.GetObjectIds()
        objs_old = old_def_geometry.GetObjectIds()
        if not set(objs_new) == set(objs_old):
            equal = False
        print 'ids equal', equal
        

No, sorry.

You might consider calculating the CRC of the instance definition objects. When a modification event, calculate it again anc compare values.

– Dale

Hi @dale,

thanks for the response. Do you know how to implement crc calculation? DataCRC expects a currentRemainder, but how do I calculate the first CRC value?

Thanks,
Tim

# pseudo code 
def pseudo_update_function(sender, e):

    for item in e.NewState.GetObjects():
        current_remainder = pseudo_get_current_remainder(item)
        if not current_remainder:
            # what to do if crc was not calculated before?
            current_remainder = unkown_initial_value
            
        new_remainder = item.DataCRC(current_remainder)
        psuedo_update_current_remainder(item, new_remainder)

        if current_remainder == new_remainder:
            print 'objects are equal'
import Rhino
import scriptcontext as sc

def test_geometry_datacrc():
    idef = sc.doc.InstanceDefinitions.Find('Block 01')
    if idef:
        current_remainder = 0
        for obj in idef.GetObjects():
            current_remainder = obj.Geometry.DataCRC(current_remainder)
        print current_remainder

if __name__ == '__main__':
    test_geometry_datacrc()

– Dale

Hi @Dale,

thanks for you reply. I must be doing something wrong.

step1
I open my testfile.3dm (363.9 KB), run the script below and the handler is added to the event. I double click to open the blockeditor and close again, the event is triggered and the handler is called. remainders are equal as expected. So far so good.

step2
now, if I open blockeditor, move 1 object and close again to trigger the event, the remainders are stil equal. I expected the remainders to change, but suppose the remainder is only calculated on the geometry and not the transformation, maybe this is normal.

step3
I open the blockeditor, add 1 rectangle to the block and close. The remainders are different as expected

step 4, 5, 6, etc.
I open and close the block editor without any change, the remainders different each time. I did not expect this

I hope you can help me out.

Thanks,
Tim

N.B.: e.NewState is of type InstanceDefinition, e.OldState is of type InstanceDefinitionGeometry. InstanceDefinitionGeometry has no method GetObjects(). Therefore I use GetObjectIds() instead

also, I have other methods of comparing objects (by guid), but unfortunately e.OldState.GetObjectIds() and e.NewState.GetObjectIds() return two completely different sets, making it very difficult or expensive to compare

def AutoUpdateEvent(sender, e):
        # only change at 'Modified' event. Not at 'Added' event,
        if e.EventType == Rhino.DocObjects.Tables.InstanceDefinitionTableEventType.Modified:
  
            # calc remainder for oldstate objects
            old_remainder = 0
            for id in e.OldState.GetObjectIds():
                rh_obj = rs.coercerhinoobject(id)
                old_remainder = rh_obj.Geometry.DataCRC(old_remainder)

            # calc remainder for newstate objects
            new_remainder = 0
            for id in e.NewState.GetObjectIds():
                rh_obj = rs.coercerhinoobject(id)
                new_remainder = rh_obj.Geometry.DataCRC(new_remainder)
  
            print 'remainders', old_remainder, new_remainder
            if old_remainder != new_remainder:
                print 'remainders are different'

if __name__ == '__main__':
    Rhino.RhinoDoc.InstanceDefinitionTableEvent += AutoUpdateEvent

Hi @dale,

I’ve chosen to rewrite the has_changed() function to check for each object in both OldState and NewState if an equal object exists after the modified event.

best regards,
Tim

import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
import traceback as tb

def group_by_type(items):
    grouped = {}
    for item in items:
        type = rs.ObjectType(item)
        if not type in grouped:
            grouped[type] = []
        grouped[type].append(item)
    return grouped


def find_equal(item, type, grouped_old):
    # go over old items and try to find a duplicate
    for itemB in list(grouped_old[type]):
        if rs.CompareGeometry(item, itemB):
            # remove item from old items to check, no need to check it again
            grouped_old[type].remove(itemB)
            return itemB

def has_changed(old, new):
    
    if len(old) == 0:
        print 'block loaded'
        return False
    
    if len(old) != len(new):
        print 'number of block objects has changed'
        return True
    
    # for each old object, check if an equal object exist in 'new' and vise versa
    grouped_old = group_by_type(old)
    
    new_types = {}
    for item in new:
        new_types[item] = rs.ObjectType(item)
    
    new_items_not_in_old = []
    old_with_equal = []
    for item, type in new_types.items():
        equal = find_equal(item, type, grouped_old)
        if equal:
            old_with_equal.append(equal)
            continue
                
        new_items_not_in_old.append(item)
        
    old_items_not_in_new = set(old) - set(old_with_equal)
    
    if len(new_items_not_in_old)>0 or len(old_items_not_in_new)>0:
        print 'block objects are different. %s in old but not in new, %s in new but not in old' % (len(new_items_not_in_old), len(old_items_not_in_new))
        return True
    
    
    return False
        

def AutoUpdateEvent(sender, e):
    try:
        # only change at 'Modified' event. Not at 'Added' event,
        if e.EventType == Rhino.DocObjects.Tables.InstanceDefinitionTableEventType.Modified:
            
            old = e.OldState.GetObjectIds()
            new = e.NewState.GetObjectIds()
            
            print has_changed(old, new)
                
    except Exception as exc:
        print 'error', exc
        print tb.format_exc()

if __name__ == '__main__':
    
    if sc.sticky.has_key('handler'):
        AutoUpdateEvent = sc.sticky['handler']
        Rhino.RhinoDoc.InstanceDefinitionTableEvent -= AutoUpdateEvent
        sc.sticky.Remove('handler')
        print 'handler removed'
    else:
        
        Rhino.RhinoDoc.InstanceDefinitionTableEvent += AutoUpdateEvent
        sc.sticky['handler'] = AutoUpdateEvent
        print 'handler added'

Hi @Dale,

sorry to re-open this topic again, but i’d really like to solve this challenge.

I am trying to calculate the CRC of a blockDefinition with 1 or more nested instances, some of which may be linked reference blocks. I make changes to a blockInstance and later apply the inverse change, to end up with an identical geometries before and after. I expected the value for current_remainder to be similar, but it is not.

I have added my test script based on your snippet and two test files below.

Do you know a way to get equal values for ‘current_remainder’ for a set of geometries that have identical geometrical properties?

Thanks!
Tim

  1. select the block and run the script to calculate the CRC
  2. double-click the instance to start the block-editor, and move the nested instance of the linked definition to 50 in X direction. Value for “current_remainder” changes as expected. The printed values for “sub_obj_crc” show that the crc for the geom in layer ‘sub_layer_1’ remains unchanged (as expected because it did not move) and the value for an object in layer default (the instance of the nested block) did change. So far so good.
  3. redo action in step 2, but now move the instance 50 in Negative x direction. The situation should be identical to as it was in step 1, hence I expected the values for “current_remainder” and “sub_obj_crc” to be equal, but they are not.

main.3dm (161.7 KB)
test_ref.3dm (404.8 KB)

from __future__ import print_function

import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs

#get all (linked) nested blocks inside a block definition
def traverseBlockWithXform(blockInstanceName, func, result=None, parentXform=rs.XformIdentity()):
    
    if result == None:
        result = []
    
    content = rs.BlockObjects(blockInstanceName)

    for obj in content:
        if rs.IsBlockInstance(obj):
            blockInstanceXform = rs.BlockInstanceXform(obj)
            if parentXform:
                blockInstanceXform = rs.XformMultiply( parentXform, blockInstanceXform)

            traverseBlockWithXform(rs.BlockInstanceName(obj), func, result, blockInstanceXform)

        func(obj, result, parentXform)

    return result

def add_obj_to_result(obj, result, parentXform):
    result.append(obj)
    

def test_geometry_datacrc(block_name):
    
    # get all nested instances and geometry within this definition
    result = traverseBlockWithXform(block_name, add_obj_to_result)

    
    # calculate the remainder
    current_remainder = 0
    for obj_uuid in result:
        obj = sc.doc.Objects.FindId(obj_uuid)
        current_remainder = obj.Geometry.DataCRC(current_remainder)
        print( 'sub_obj_crc: ', rs.ObjectLayer(obj_uuid), obj.Geometry.DataCRC(0) )
        
    print( current_remainder )
    
    rs.DeleteObjects(result)

def main():

    block_name = 'B_testblock'
    test_geometry_datacrc(block_name)


if __name__ == '__main__':
    main()
    
#results
#step1
sub_obj_crc:  main_layer_1::sub_layer_1 3620413159
sub_obj_crc:  Default 0
sub_obj_crc:  Default 3990858749
2744759847

#step2
sub_obj_crc:  main_layer_1::sub_layer_1 3620413159
sub_obj_crc:  Default 0
sub_obj_crc:  Default 4139732063
3103347077

#step3
sub_obj_crc:  main_layer_1::sub_layer_1 3620413159
sub_obj_crc:  Default 0
sub_obj_crc:  Default 1549039236
303197022

Hi @timcastelijn ,
I’m not sure about CRC, however I believe the problem might have a simpler solution.
How about we approach this differently?

  • Custom identifier = Pointcloud of that specific geometry
  • Create a table containing each Instance and its custom identifier.
  • Every X event of your choice check compute the block identifier, if It’s different than geometry was changed

-Farouk