Missing Delete Object Event After Block Modification

I encountered the potential bug when testing a custom UserData object with blocks. Using the SampleCsEventWatcher project from the development samples, I was able to determine that there appears to be a missing DeleteRhinoObject event invocation in the block editing process.
Steps to Reproduce:

  1. Create a box BRep
  2. Add create a block from the box
  3. Open the block for editing
  4. Close block editing

The Associated Event Log Looks Like

  1. Create a box BRep
    a. AddRhinoObject (Box Guid 1) ← The original box
  2. Add create a block from the box
    a. AddRhinoObject (Box Guid 2) ← Copy of Box to be used as reference geometry)
    b. InstanceDefinitionTableEvent (Add) ← Referenced geometry lists Box Guid 2
    c. DeleteRhinoObject (Box Guid 1) ← The original box is removed
    d. AddRhinoObject (InstanceReference Object)
  3. Open the block for editing
    a. AddRhinoObject (Box Guid 3) ← New Copy of Box available for editing while block is open
    b. ModifyObjectAttributes (InstanceReference Object)
  4. Close block editing
    a. DeleteRhinoObject (Box Guid 3) ← Version created for editing is removed
    b. ModifyObjectAttributes (InstanceReference Object)
    c. AddRhinoObject (Box Guid 3) ← Version created for editing is added back to persist as the new reference geometry
    d. InstanceDefinitionTableEvent (Modify) ← Referenced geometry now lists Box Guid 3
    e. PurgeObject (Box Guid 3) ← Version creating for editing is purged, not the one in the object table, just the one(s) in the undo record for block editing. You get one PurgeObject per AddRhinoObject that occurred during block editing

You will note that the we were never notified that Box Guid 2 was removed from the object table. In fact, if you open up grasshopper and use the GUID to get the object from the document, it’s still accessible. If you save, close, and reopen the document the that object is gone, like we’d expect it to be.

I haven’t delved into the c++ API to see if this problem persists at that level. But from what I can tell, the C# object table is keeping copies of the objects alive, but they’ve been removed from the C++ object table since they don’t get rendered, are not saved, and are not removed if you clear the undo record.

Hi @cullen.sarles,

I’ve cleaned up the output generated by SampleCsEventWatcher and truncated the Guids so as to be more readable. Then I followed your steps:

> BeginCommand - Box
> AddRhinoObject:ModelObject - 89f187d4:Extrusion
> EndCommand - Box

> BeginCommand - Block
> AddRhinoObject:InstanceDefinitionObject - 01bfd544:Extrusion
> InstanceDefinitionTableEvent:Type - Added
> DeleteRhinoObject:ModelObject - 89f187d4:Extrusion
> AddRhinoObject:ModelObject - 19ebffc8:InstanceReference
> EndCommand - Block

> BeginCommand - BlockEdit
> AddRhinoObject:ModelObject - c93416fc:Extrusion
> ModifyObjectAttributes:ModelObject - 19ebffc8:InstanceReference
> EndCommand - BlockEdit

> BeginCommand - BlockEditApplyInPlaceEditItemChanges
> DeleteRhinoObject:ModelObject - c93416fc:Extrusion
> ModifyObjectAttributes:ModelObject - 19ebffc8:InstanceReference
> AddRhinoObject:InstanceDefinitionObject - c93416fc:Extrusion
> InstanceDefinitionTableEvent:Type - Modified
> PurgeRhinoObject:ModelObject - c93416fc:Extrusion
> EndCommand - BlockEditApplyInPlaceEditItemChanges

Here is brief summary of what happened:

  1. Box: creates an extrusion object and adds it to the document as a model object.

  2. Block: copies the selected extrusion object and adds it to the document as an instance definition object (a non-visible objects referenced by an instance definition). The selected extrusion is then deleted, and new instance reference object is added to the document.

  3. BlockEdit: with the selected instance reference, adds copies of the instance definition’s geometry to the document as model objects. The selected instance definition is then hidden.

  4. BlockEditApplyInPlaceEditItemChanges: undoes the actions performed by the prior command. That is, it deletes the extrusion object and unhides the instance reference. The extrusion is then added to the document as an instance definition object. Notice how the GUID is retained? This slight of hand is so any custom data added to the object by the user during editing is retained. The instance definition is then modified to use the newly added instance definition object. Finally, the prior deleted model object is purged, so as not to have two objects in the object table with the same GUID.

Give all this, what is the problem you are trying to solve?

Thanks,

– Dale

Dale, thanks for the quick reply,
I’ve included some context about my current project below, but what I’m running into appears in your output as well:

The question at hand is: When does the object added in this line get deleted?:

The instance definition is being retargeted to point at the new c93416fc:Extrusion, but the 01bfd544:Extrusion never gets a DeleteRhinoObject event. In my testing, it’s still in the Document Object table, and can be retrieved via it’s GUID. So the GUID slight of hand works beautifully except that the object that’s being replaced seems to have been not cleaned up properly. Are instance definition objects supposed to remain in the document object list once they’re no longer being referenced by any instance definitions? If so, I can account for that.

The larger context of this is:
The plugin I’m working on needs to keep track of what userdata is currently objects that are in the document object list. I’ve got global properties that, when changed, need to be able to effect a subset of the objects in the document. I could comb through the object list every time one of those objects was changed, but that doesn’t seem idea to me, especially in large models.

The approach I am currently using is to leverage events by subscribing a custom userdata object on the effected objects to the relevant global events. This has the downside of needing to disconnect that userdata from the global events as the objects they’re attached to are removed from the object table. If you don’t do this then the objects in the undo & redo queues still get affected. The other approach I’ve considered is keeping track of the relevant object id’s at the document level and leveraging the events to add and remove object ids from that list as necessary, but that would run into the same problem I’m currently encountering.

Which is, because the InstanceDefinitionObject that get’s replaced in the instance definition table doesn’t seem to be deleted from the document object table, I’d need to keep track of which instance definition objects needed to be detached from the global events when the InstanceDefinitionTableEvent myself. One option would be to to build a mirror of the instance definition tables so that when InstanceDefinitionTableEvent is fired off I know which InstanceDefinitionObjects used to be in that table. I can build that, it just didn’t seem like the way things were supposed to work. Especially since the object in question is still in the document object table, but doesn’t persist after a save and reload of the document.

Thanks again!
-Cullen

Hi @cullen.sarles,

I have not studied the code. But I believe this is true.

– Dale

@dale

Great, I’ll account for that.

As an observation, this means that workflows that rely heavily on modifying blocks will cause the object table to clutter up over time with unused objects. This will feel like a memory leak to normal rhino users since you can’t clear them out by clearing the undo history like you can the copies of objects contained there. Your only recourse for freeing that memory is to reload the document. For users of grasshopper or the API, it requires that you treat the instance definition objects differently than the rest of rhino objects since you can’t rely upon the objects referenced by blocks to notify you if they’re “deleted”.

If you or someone else on the team has time to look deeper into the behavior, I’d really appreciate it.

Thanks again for your help, came in time for me to make my plans for the week!

-Cullen