Lock the Model, e.g. Prevent Deletion

I recently discussed two common and interesting issues with developers which ended up being easily resolved:

  • Locking the state of certain programmatically generated parts of the model to prevent the user from messing them up.
  • Simpler still, prevent deletion of certain elements.

Both of these issues can be efficiently addressed using the

Failure API
.
It allows us to

  • Define our own failure conditions.
  • Post pre-defined or custom failures.
  • Handle failures, e.g.

    suppress unwanted messages
    or
    completely replace the standard failure processing.

The Revit SDK includes the ErrorHandling sample to demonstrate all of these features,
and we later returned to this topic to discuss various

failure API aspects in more depth
.

So here is the first query that came up:

Lock Down Part of the Model

Question: I am creating certain AssemblyInstance elements in the model programmatically and would like to prevent the user from being able to modify these.

How can I use the API to lock these elements?

It should be impossible to disassemble them and edit or remove elements.
How can I achieve this?

Answer: This can be achieved using the Failure API.
Section 26.1.1 ‘Defining and registering a failure’ in the developer guide shows how you can define a new failure.
If you maintain a cache keeping track of the original state of all your assembly instances, you can define a failure that is triggered when anything is changed in any assembly instance.

The failure API is linked with the Dynamic Model Update framework DMU.

The simpler DocumentChanged event is not cancellable, unfortunately, or you could simply use that.

Below, I demonstrate the detailed implementation steps for the simpler task of simply preventing deletion of certain elements, which does not require keeping track of their previous state.

Prevent Deletion of Elements

Question: How do I prevent the deletion of an element?
I tried to use DocumentChanged and IUpdater, but both of those seem to be “after the fact” type interfaces.

Answer: As said, DocumentChanged is indeed after the fact.

The IUpdater is on the right track.
It is part of the Dynamic Model Update framework DMU and ties in with the failure API, which enables you to easily achieve what you need.

To prove it, I implemented a small sample application PreventDeletion.
It implements three things:

  • An external command allowing the user to specify which elements to protect from deletion.
  • A class DeletionUpdater derived from IUpdater which prevents deletion of selected elements.
  • An external application to register the updater and its trigger.

All three components are extremely simple.
Let’s start with the external command.
It maintains a list of protected element ids and implements an externally accessible IsProtected method:


  static List<ElementId> _protectedIds
    = new List<ElementId>();
 
  static public bool IsProtected( ElementId id )
  {
    return _protectedIds.Contains( id );
  }

The command itself asks the user to select elements to protect and adds their ids to the list:


public Result Execute(
  ExternalCommandData commandData,
  ref string message,
  ElementSet elements )
{
  UIApplication uiapp = commandData.Application;
  UIDocument uidoc = uiapp.ActiveUIDocument;
 
  IList<Reference> refs = null;
 
  try
  {
    Selection sel = uidoc.Selection;
 
    refs = sel.PickObjects( ObjectType.Element,
      "Please pick elements to prevent from deletion. " );
  }
  catch( OperationCanceledException )
  {
    return Result.Cancelled;
  }
 
  if( null != refs && 0 < refs.Count )
  {
    foreach( Reference r in refs )
    {
      ElementId id = r.ElementId;
 
      if( !_protectedIds.Contains( id ) )
      {
        _protectedIds.Add( id );
      }
    }
    int n = refs.Count;
 
    TaskDialog.Show( Caption, string.Format(
      "{0} new element{1} selected and protected "
      + " from deletion, {2} in total.",
      n, ( 1 == n ? "" : "s" ),
      _protectedIds.Count ) );
  }
  return Result.Succeeded;
}

The deletion updater is just as simple.
The only tricky thing about it is that it defines a custom failure definition in its constructor.
The severity of the failure is specified as error, so the user really cannot ignore it, which will prevent the elements from being deleted.
Whatever action that caused the deletion will have to be undone:


public DeletionUpdater( AddInId addInId )
{
  _appId = addInId;
 
  _updaterId = new UpdaterId( _appId, new Guid(
    "6f453eba-4b9a-40df-b637-eb72a9ebf008" ) );
 
  _failureId = new FailureDefinitionId(
    new Guid( "33ba8315-e031-493f-af92-4f417b6ccf70" ) );
 
  FailureDefinition failureDefinition
    = FailureDefinition.CreateFailureDefinition(
      _failureId, FailureSeverity.Error,
      "PreventDeletion: sorry, this element cannot be deleted." );
}

The updater Execute method simply checks whether any of the deleted element ids are protected from deletion and posts the failure if that is the case:


public void Execute( UpdaterData data )
{
  Document doc = data.GetDocument();
  Application app = doc.Application;
  foreach( ElementId id in data.GetDeletedElementIds() )
  {
    if( Command.IsProtected( id ) )
    {
      FailureMessage failureMessage
        = new FailureMessage( _failureId );
 
      failureMessage.SetFailingElement(id);
      doc.PostFailure(failureMessage);
    }
  }
}

In the external application, all we have to do is register the updater and its trigger in the start-up method:


public Result OnStartup( UIControlledApplication a )
{
  DeletionUpdater deletionUpdater
    = new DeletionUpdater( a.ActiveAddInId );
 
  UpdaterRegistry.RegisterUpdater(
    deletionUpdater );
 
  ElementClassFilter filter
    = new ElementClassFilter(
      typeof( Wall ) );
 
  UpdaterRegistry.AddTrigger(
    deletionUpdater.GetUpdaterId(), filter,
    Element.GetChangeTypeElementDeletion() );
 
  return Result.Succeeded;
}

If I specify that an element is protected from deletion using the command above and then try to delete it anyway, the standard Revit error message is displayed:

Prevent deletion error message

If this message should not be seen by the user, you can easily use the failure handling functionality mentioned above to suppress it.

Here is
PreventDeletion.zip including
the complete source code and Visual Studio solution of this command.

Finally, my first personal use of the failure API :-)

Entry Points to Point Clouds in Revit

Here is a short note on a non-API-related issue, an overview of some introductory material on point clouds in Revit from a user point of view:

Question: I am interested in retrofit projects and would like to understand better what I can do in Revit using point clouds.
Where should I start, please?

Answer: Here are a couple of starting points for you:


Comments

17 responses to “Lock the Model, e.g. Prevent Deletion”

  1. I’ve successfully implemented a command that prevents non-electrical users from deleting circuited mechanical equipment. It’s admittedly heavy-handed, but does ensure a higher level of coordination and collaboration, and prevents any suprises at print time.
    Thanks!
    Jason

  2. Dear Jason,
    Thank you for your update and appreciation, and for raising this sweet little issue.
    I am very glad it helped!
    Cheers, Jeremy.

  3. Hello, Jeremy.
    I wrote my own Updater which delete elements from external database when user delete it from model. Our designers use worksets and use shared file. When somebody try open this file in computer without my Updater they see the following warning:
    Caption: Missing Third Party Updater
    Text: The file …. was modified by the third-party updater which is not currently installed.
    If you continue to edit the file, data maintained by will not be updated properly. This may create problems when is later opened with
    And 4 commands:
    Continue working with the file
    Do not warn about this updater again and continue working with the file
    Close without saving
    Save the file under a different name and continue working
    Did you ever see this warning?
    I tell them, “choose continue…”. Designers do it, but when they try synchronize file Revit close with error.
    How can I avoid this situation? As I understand information about updater saved in revit project file. And when there are no this updater warning is showing. But in my test project I cannot repeat this situation: test file opened without some warning.
    I’m confused.
    Thanks! Victor

  4. Dear Victor,
    Of course I have seen that warning, and discussed it in some depth as well:
    http://thebuildingcoder.typepad.com/blog/2010/12/vsta-to-stay-and-updater-to-go.html#2
    I hope that helps clarify.
    Cheers, Jeremy.

  5. Andreas Ricke Avatar
    Andreas Ricke

    Hi Jeremy,
    I have your Deleted Element Protection routine working but I’m trying to work out how to store the IDs in the Revit file so that when I save and later reopen the file, the protected elements are still protected.
    I looked at extensible storage, and worked out you can assign information to elements, but couldn’t work out if you can store the list of IDs using extensible storage or some other technique.
    Any suggestions?
    Thanks for a great blog. I’m referencing your stuff almost every day.

  6. Dear Andreas,
    Thank you for your appreciation, and I am very glad you find it all useful!
    Yes, sure, extensible storage is probably exactly the right way to go for this, because it will automatically update stored element ids should they change for some reason.
    Here are some posts on it:
    http://thebuildingcoder.typepad.com/blog/2011/04/extensible-storage.html
    http://thebuildingcoder.typepad.com/blog/2011/05/extensible-storage-of-a-map.html
    http://thebuildingcoder.typepad.com/blog/2011/06/extensible-storage-features.html
    You would presumably not use a map, but a list, i.e.
    FieldBuilder fieldBuilder
    = schemaBuilder.AddArrayField( “Ids”,
    typeof( ElementId ) );
    to define it and
    List[ElementId] ids
    = new ListElementId;

    entity.Set[IList[ElementId]](
    field, ids );
    to populate it.
    You can use the singleton project info element to store that data.
    Cheers, Jeremy.

  7. Hi, Jeremy.
    I have one more question about DMU.
    Maybe you still remember in my project I use my own Updater that delete elements from external database when I delete elements from model (I asked you some questions about it months ago).
    Now I have next problem: I must temporary disable my updater and then enable it again.
    Of course you’ll ask me ‘Why?’ :)
    The answer is – Collaborate.
    I have following situation: two or more users work with local files. One of them delete one or more elements. At this time IUpdater.Execute() method called and I check are there deleting elements in external db. If they exists I delete them from model an external db.
    Next, user synchronize your local copy with central file.
    Second user also synchronize your local copy. In his copy those elements which first user deleted also will be deleted on sync. That means that updater will execute. But I don’t need it because it can take a lot of time to check elements in external db.
    My idea: disable updater then user start sync with central and enable it again then sync will finished. Fortunately we have to Events for it: DocumentSynchronizingWithCentral – then start the sync and DocumentSynchronizedWithCentral then finish sync.
    The problem:
    In OnApplicationStart I register updater and trigger.
    I Unregister updater in DocumentSynchronizingWithCentral event handle and register it again in DocumentSynchronizedWithCentral.
    But It is not enough to register updater again. Also I must register trigger.
    Of course I can register as updater as trigger but I think it will be better to disable and enable updater like IUpdater.Enable() and IUpdater.Disable() or UpdaterRegistry.DisableUpdater(UpdaterId) and UpdaterRegistry.Enable(UpdaterId). Futhermore I think better way deny execute updater then you sync with central because in many cases it is not nessesary. Somthing like this UpdaterRegistry.RegisterUpdater(IUpdater updater, bool doNotExecuteOnSync)
    Is it possible? If not – it is my wish)
    P.S. In API help I see some static methods in UpdaterRegistry: GetIsUpdaterOptional() and SetIsUpdaterOptional. Also in UpdaterRegistry.RegisterUpdater I can see parameter isOptional. But that does this ‘Optional’ mean? In Help I just see strange description of this parameter: ‘Kind of the updater ‘. Do you know that does this parameter means?
    Thanks. Regards, Victor.

  8. Dear Victor,
    Thank you for these very interesting and useful suggestions.
    Could you please submit an ADN DevHelp Online case for these? That would make it much easier to manage. Thank you!
    Regarding the SetIsUpdaterOptional functionality, that was implemented to avoid the warning popping up if an updater that was once registered in a model is later missing:
    http://thebuildingcoder.typepad.com/blog/2010/12/vsta-to-stay-and-updater-to-go.html#2
    Cheers, Jeremy.

  9. Hi Jeremy.
    Yeah of course I’ll submit my suggestion to ADN DevHelp Online.
    And you can congratulate me. Today i’ve got an access to ADN. And now I’m ADN member.:)
    Best regards
    Victor.

  10. Dear Victor,
    Great, fantastic, congratulations!
    Looking forward to seeing your case.
    Cheers, Jeremy.

  11. Hi, Jeremy. As I promised I open the case.
    Case #06907739.
    Best Regards, Victor

  12. Dear Victor,
    Thank you for submitting it, I see it and picked it up as case 06907739 [Dynamic Model Update API improvement].
    Cheers, Jeremy.

  13. Jeremy,
    I’m not sure if this is possible, but what I’d like to do is lock a few items within our company template. We have a standards view, and then some standard text types, etc. Would it be possible to lock them with a list of their element IDs?
    I’m fairly new to .net programming as well as the Revit SDK. I played around a bit with your code but am thinking it might not be as simple as I had hoped.
    Thanks,
    Casey

  14. Dear Casey,
    Nope, sorry, there is no official support for this afaik.
    Cheers, Jeremy.

  15. Too bad, maybe some day.
    Thanks for the quick reply. I guess we’ll just have to push out knowledge instead of forcing the standards upon the masses.
    Thanks again!
    Casey

  16. Hi Jeremy,
    I’m new to all this but I was able to get your Deleted Prevention routine working, but how do I make sure that the protected elements I’ve selected in the model will be protected from other users?
    Thanks in advance,
    AJ

  17. Kailas Dhage Avatar
    Kailas Dhage

    Hi Jeremy,
    I want to update our product external database on the deletion of elements having custom data attached. We do not store revit element Ids.
    When I delete element I get the list of deleted ElementIds but when I try to get Element from ElementId I get the null reference.
    Is there way to get the deleting element? Following my code for reference.
    public void Execute(UpdaterData data)
    {
    var document = data.GetDocument();
    var elementIds = data.GetDeletedElementIds();
    foreach(ElementId id in elementIds)
    {
    var ent = document.GetElement(id); //The ent is always NULL … …, Need Help
    }
    }

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading