In my extension, upon the user placing a component instance, my extension will place a Text object instance onto the active model via an EntitiesObserver subclass (1 to 1 relationship between ComponentInstance and Text instance). However, I have a bug whereby if the user deletes the component instance (which will also delete the text label via an observer), then hits undo, the old label re-appears which is good, but the EntitiesObserver will fire again upon the undo and add a second unwanted text label.
Iv’e looked into disabling the observer by inspecting the call stack and disabling if called from an undo related location, however the call stack appears to be the same when the observer is called via the normal way of the user placing something on the model (being from ‘commit_operation’) so I am unable to distinguish between the use cases. Any suggestions?
Also ‘onTransactionUndo’ is called after the bug has occurred so I can’t really make use of it.
You might “mark” the instance with your extension’s uniquely named attribute dictionary, which has an attribute holding the text callout’s persistent id. Your code that adds the text should check for the existence of the attribute dictionary and the attribute for a text object, if it’s there skip adding the text.
The callback may also need to update the id after an undo or redo.
The problem you encounter is one reason why the API docs say not to modify the model within an EntitiesObserver callback. (See Overview at top of the class doc.)
Thanks @DanRathbun… of course! I didn’t think to check if the entity has pre-defined attributes in the observer or not (as I am using the attribute dictionaries already). So obvious now you’ve pointed that out! I can now distinguish between elements added via the user, and elements added from an undo operation.
Yes I’ve seen the warnings however i’m pressing on with notion of modifying the model via observers. Is modifying the model in some way in response to user action literally the crux of all extensions? I see that developers are being told not to do it, but like is that not the whole point of having this API?
Well the warning in the docs is an old one. Crashes caused by modifying the model dB within observer callbacks were so common that it was a major impetus for the v2016 observer overhaul.
So definitely download and read the PDF doc on that overhaul.
But think about generally, an EntitiesObserver that gets fired when changes are made, which fire the observer because itself makes changes, which fire the observer … etc., … etc. (The “vicious circle” syndrome.)
Just a note to say that although the API doc examples show the subclassing paradigm for observers, in reality all the observer class (since the 2016 overhaul) are abstract. (This means they have no functionality to pass down to subclasses. Ie, all the previously empty callback methods were removed so that the SketchUp API engine can poll observer objects to ask if they respond to each callback. If not, the engine does not call that method for that particular observer. Since method calls eat time, this speeds up the processing of observer calls.)
Also, the various add_observer attachment methods do not do any type checking to ask if the argument object is a subclass of any particular class. All the object’s callback methods need be is publicly accessible. So this means the observer object can be of any class, or it could be a module with public callback module methods, or even some instance object with dynamically defined singleton callback methods.
I myself have found that the combo-observer module paradigm to be the most useful and easiest to implement. One submodule with most of the observer callback methods all together is easier because you need not pass information back and forth so much. Many times my observer module is the extension submodule itself. (Data can be kept in local objects accessible to many callbacks that are published as being in different observer classes.)
If you will support MacOS where multiple models might be open, then you’ll need to take care to store data in a hash whose keys are model references. On Windows it’s simpler as only 1 model can be open at a time.
There still are times when instantiating an observer object for each open model makes sense. Extension code needs to track these and dispose of their references when the model is no longer open.
Iv’e got another question if you’re keen to give it a ponder… how can I distinguish between a ComponentInstance added to the model via an undo action and a ComponentInstance added to the model via the duplicate tool? As in both cases, the attribute dictionary i’m using will be populated on the ComponentInstance (whereas with the original problem I was able to distinguish between ComponentInstance added via undo or added fresh through checking if the attribute dictionary existed or not). Thanks
The “safe observers” was to workaround issues pre-SU202016. Prior to that versions observers would trigger in the middle in operations and if any model changes was made in the observer event it would corrupt the state of the model.