All - I am new to Revit, and to the Revit API, but I'm trying to get a jump on Revit use in our office by instituting some standards before we get into serious use on projects. My question is this:
If you want to detect changes to elements in the database, you implement IUpdater and register it with appropriate triggers. Then your Execute method returns an UpdaterData object, whose GetDeletedElementIds() returns a collection of all the IDs of the deleted elements. So far, so good. But, since these are the IDs of deleted elements, document.Element(ElementId) always returns null. Meaning that, unless you are very lucky, you probably can't do anything with all these ElementIds. Hmmmn... What am I missing? My IUpdater uses UniqueIds instead of ElementIds since it deals with objects that persist from session to session. Specifically, I am using the IUpdater to make changes to a group of other elements in the database in response to changes to one element, and storing the UniqueIds of these daughter elements in an ExtensibleStorage.Entity in the primary element. When I delete the primary element, all my extended data goes up in smoke. Don't laugh.
Is there a way to get access to the "ghost" of an element after it is deleted? Or a way to recover its UniqueId from its ElementId? Or is there a way to establish a relationship between an element in the database and view-dependent other elements in the same database that will delete them in a cascade when the primary is deleted? Any ideas?
As you guesses, there is no way to get to elements once they are deleted. You only get their Ids, and if you do not know what they belonged to before their deletion, you cannot get that info anymore. I agree it is a set-back of sort and we plan on improving that. (At the very least we have a tasks to provide UniqueIDs as well). However, in most cases it is possible to work around this “inconvenience”.
I must admit I do not understand your scenario. From how you described I assume that there is a group of elements but I am not sure if it an actual group in Revit sense. But I assume it is not, for if it were, you would not have the problem, correct? So I assume it is just a collection of elements that you call “a group” because they are somehow logically “grouped” from your application standpoint. Then you designate one of the elements in the “group” as the primary element even though the only significance of this element is that it has external data attached while the other elements in you group don’t; correct?
If my assumptions are all correct, I am afraid that there is probably only one solution: You need to collect the elements if interested every time a document opens. Then you need to set up you updater so it triggers when any of those elements get deleted. When you get called with that trigger, you examine what was deleted and delete other elements from that same group. I admit it is not the most elegant solution, perhaps, but depending on what kind of elements yours are, the process (of looking them up) should be relatively fast.
By the way, when you say “the database” you mean the Revit Model, right?
Autodesk Revit R&D
Yes, by "the database" I mean the Revit Model.
When you say that you have tasks to provide UniqueIDs as well, do you mean that you will expose UpdaterData methods to return collections of UniqueIds? Do you have any estimated timeline for this?
To give you a clearer picture of what I am doing, my primary elements are walls, floors and roofs and my secondary elements are DetailCurves which exist on particular views, and which are updated as the primary elements change, and have locations corresponding to the wall, floor or roof centerline and linestyles corresponding to their "Fire Rating" parameter. I think it is better to symbolize this property as a bold line on the plan, where the mechanical engineer, code official, interior designer, etc., can see it even if he is not looking, than to bury it in the wall tag or other such place. Also, I have written my code so I can reuse it later to draw centerlines representing other things, like insulation, boundaries of regulatory or contractual regimes, etc.
My work-around for the "deleted walls" problem is to create a UniqueId back-pointer to the parent object (wall, etc.) in each centerline and then, on detecting a "delete" change, to clear out of the affected views everything that has an orphaned pointer to its parent. I don't know how this will work out in a big model in terms of CPU time yet.
Do you have any plans to address centerlines of things (for this and other purposes)? I seem to remember that a certain product called Trifor... (or something) had a built-in set of centerline symbolization options. Let me know...
I do not have any estimated time-line. I remember it being requested previously and I know there is a task for it which was prioritized with all the other incoming requests. Unfortunately, I am not a liberty to reveal any future additions or fixing of features.
Anyhow, I think your workaround is sound, though maybe a bit slow in big models, as you expect it to be. I think there are few ideas that may improve your implementation.
First of all, I do not think you need to store unique Ids. If you store regular Ids in your extensible storage (as Element Ids, that is, not integers), the Ids will be automatically remapped should your local document get synchronized with the central file (in work-sharing environment). Because of this neat behavior, your Element Ids stored inside extensible storage are as good as unique Ids. With that knowledge you should be able to find you “orphaned” lines faster – when you get notified about a wall master element deleted, you go quickly through your lines to see if any one points to that element.
Another possible solution would be storing your information not on the “primary” elements, but on some independent element that does not get deleted. You can create your own DataStorage element and keep your data there. (You would have to think a bit about how to synchronize it in work-sharing environment, but it is definitely doable). You could even store your data on some standard Revit elements that does get deleted, or not easily. Since your lines are view-specific (if I understand it correctly), you can attach your storage to each view in which you have these lines. If the view dies, so does the data, but you do not care, for the lines will get deleted too.
I admit this task is not exactly trivial, but there is a reasonable number of possible solutions, where some of them I would not even call workarounds :-)
Autodesk Revit R&D
Success! I took your advice and attached the ExtensibleStorage Entity to the View. It took me a while to work out how to store a list of lists - the documentation in the SDK is not strong - but it seems to work fine now. I also tried using ElementIds instead of UniqueIds and the ElementIds seem to be able to function through a Quit Revit and restart. This feature is indeed very useful and seems to be at variance with AutoCAD API practice. The ElementIds were successfully maintained even though they are nested several layers deep in the Entity holding them.
Thanks for your help.
I am really glad my suggestion worked for you. Like I said earlier, yours is not one of trivial cases, but there are a few reasonably sound solutions that are not hard to implement.
As you found out, regular Element Ids work just fine when stored as data in External Storage structures. However, please keep in mind that it works so only because the External Storage framework makes it work. Should you want to store the Ids somewhere else, like in an external database, you would indeed have to use unique Ids to be sure elements can always mapped correctly, even in a work-shared environment.
Autodesk Revit R&D