Debugging and Maintaining the Image Relationship

The Revit database contains a number of

undocumented relationships
,
and it can be challenging and useful to discover them.

Christian Tonn of kubit presents a powerful method to determine and maintain such a relationship using an officially supported approach instead:

Question: Is it possible to get the ImageType object from an image you inserted in the Revit document with Document.Import?

Answer: Not directly!

I am using the Import method:


  bool Import(
    string file,
    ImageImportOptions options,
    View view,
    out Element element);

It only returns an Element object, which has no parameters attached – I used RevitLookup to check – nor can it be cast to anything useful, e.g. ImageType.

However, I can query Revit for all ImageType classes, and it really is possible to read the right Bitmap object out of the appropriate instance.

But how to connect the element returned by the Import method with the appropriate ImageType instance?

The Element holds the image position in the view and the ImageType holds the Bitmap without position.

I need both.

Luckily, both items have the same Element.Name in the database, the image filename.

I implemented a solution to determine and maintain the missing relationship like this:

  1. Start a TransactionGroup.
  2. Start a first transaction.
  3. Import the image with a unique filename:
  4. 
    String tmpFilename = System.IO.Path.GetTempPath()
      + "kubit_" + Guid.NewGuid().ToString() + ".png";
     
    mDoc.Import( tmpFilename, options, newView,
      out element );
    
  5. Attach an extensible storage Entity to the returned image element:
  6. 
    Entity additionalData = newEntity(
      MyImageSchema.GetSchema() );
     
    element.SetEntity( additionalData );
    
  7. Commit the first transaction.
  8. Search the Revit database for all ImageType objects.
  9. Use them to populate a dictionary mapping the Name property value to the ImageType instance: Dictionary<string, ImageType> nameImageTypeMap.
  10. Retrieve all Element objects returned by the Import method in step 3 using an ExtensibleStorageFilter.
  11. Start a second transaction.
  12. Loop through all the elements and add the ImageType information to them using the dictionary:
  13. 
    Entity additionalData = elem.GetEntity(
      MyImageSchema.GetSchema() );
     
    if( additionalData.IsValid() )
    {
      // Find correspondence between ImageType and 
      // Element (first has image, second has additionalData)
     
      if( !nameImageTypeMap.ContainsKey( elem.Name ) )
        continue;
     
      ImageType image = nameImageTypeMap[elem.Name];
      additionalData.Set<ElementId>( "ImageTypeId", image.Id );
      elem.SetEntity( additionalData );
    }
    
  14. Commit the second transaction.
  15. Commit the TransactionGroup.

After this you can easily retrieve the image elements using an ExtensibleStorageFilter and also determine their associated ImageType instanced using the element id stored in ImageTypeId.

I am so happy I could figure this out.

I really hope this will be improved in some future release of the Revit API.

Element Lister Description and Questions

Very many thanks to Christian for his research and solution!

An easy way to discover certain element relationships between related objects that are added to Revit by certain operations is provided by the element lister:

  1. List all elements to a file A.
  2. Perform an operation in Revit.
  3. List all elements to a file B.
  4. Determine the difference between A and B.

That gives me the newly added or deleted elements and their ids.

I used it any number of times in the past to explore:

Possibly something similar could be implemented using a more API oriented approach by subscribing to the DocumentChanged event, which returns the same information in the added and deleted element id collections.

The element lister and other basic tools were used to establish certain

undocumented element id relationships
.

Use of these element id relationships is

totally unsupported
,
of course, and they can obviously change at any time.

Your approach sounds much more robust.

How does yours compare with these?

Do you see any similarity at all?

Are the element ids of the related elements also related?

Confirmation

Yes, the ElementId of the ImageType is one less than the ElementId of the returned image Element from doc.Import, so they should obviously belong together.

I was not sure about this, however, and chose the Element.Name approach instead.

It is kind of similar, and I agree that it ought to be more robust.

Conclusion

The relationship discussed here between an image and the associated image type exists analogously for other pairs of Revit elements as well, so the approach described above is probably also useful for other similar situations.

Welcome Back, Jaime!

Six weeks ago, we

welcomed our new colleague Jaime
and
enjoyed his first blog post.
Immediately afterwards, he went off for a long holiday and begin a much longer marriage.
Now he returned and immediately answered a

Revit API forum issue
with
a new blog post on

reading a value from an app.config file
and
the good times he had while away.

Welcome back, Jaime!


Comments

8 responses to “Debugging and Maintaining the Image Relationship”

  1. Scott Wilson Avatar
    Scott Wilson

    There’s a few of these undocumented relationships that are quite useful. One that I discovered a while back was for obtaining the crop box of a view for use in rotation operations. There was some forum discussion on this and Jeremy also made a blog post about it to present a work-around: http://thebuildingcoder.typepad.com/blog/2013/09/rotating-a-plan-view.html
    Jeremy’s method for this was to hide the cropbox, get the collection of visible elements in the view, show it again then get the element collection one more time to discover the additional element. This was a little too messy for me so I looked further.
    I found that for any view with a crop box (visibility doesn’t matter), there will be exactly 2 elements in the database that share the same name and type id. One of them will be the view itself and the other will be the crop box.
    To determine which is the crop box you can simply test for the one with the id that doesn’t match the parent view’s id.

  2. Dear Scott,
    Wow, thank you for that useful hint!
    Do you have a snippet of sample code to demonstrate?
    Preferably a minimal reproducible case?
    I could put together a proper blog post to highlight it and ensure it is easily found.
    Thank you!
    Cheers, Jeremy.

  3. Scott Wilson Avatar
    Scott Wilson

    Yeah no problems.
    public static Element GetViewUiCropBox(View view)
    {
    Element cropBox = null;
    if(view != null)
    {
    String viewName = view.Name;
    ElementId viewTypeId = view.GetTypeId();
    List elemList = new FilteredElementCollector(view.Document).WherePasses(new ElementIsElementTypeFilter(true)).Where(x => x.IsValidType(viewTypeId)).Where(x => x.GetTypeId() == viewTypeId).ToList();
    if(elemList != null)
    {
    foreach(Element elem in elemList)
    {
    if(elem.Name == viewName && elem.Id != view.Id)
    {
    cropBox = elem;
    }
    }
    }
    }
    return cropBox;
    }

  4. Mille grazie.

  5. Jason Isherwood Avatar
    Jason Isherwood

    Hi Jeremy
    How can I progmatically set the value of an image parameter (or the default type image field) in a family in the family editor ?
    I can use imagetype.create to load my image into the family but how do you then associate it with the relevant image parameter as the familymanager.set or familymanager.SetForumla expect string inputs ?
    I’ve looked everywhere and this page is the only bit I can find with any example code of imagetype in use.
    Please help !

  6. Dear Jason,
    I can imagine that the only way to set the preview image is by setting the current view and then saving and closing the family RFA file.
    Cheers, Jeremy.

  7. Jason Isherwood Avatar
    Jason Isherwood

    Thanks Jeremy.
    That wasn’t quite what I was after I meant how do I progmatically set the value of the Type Image field.
    I have since worked out the solution:
    it = ImageType.Create(doc, jpgname);
    ElementId idIt = it.Id;
    FamilyParameter paramFamilyTypeImage= manager.get_Parameter(“Type Image”);
    manager.Set(paramFamilyTypeImage, idIt);
    What I need now is to work out how to clear to remove loaded images from the family so that I can load different ones on. Any ideas ?

  8. Dear Jason,
    Wow, that is an impressive solution.
    Congratulations on figuring that one out!
    Regarding clearing out existing images, how about
    manager.Set(
    paramFamilyTypeImage,
    ElementId.InvalidElementId );
    Cheers, Jeremy.

Leave a Reply to Jason IsherwoodCancel reply

Discover more from Autodesk Developer Blog

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

Continue reading