reload keynote file

reload keynote file

james.levieux
Advocate Advocate
669 Views
8 Replies
Message 1 of 9

reload keynote file

james.levieux
Advocate
Advocate

I want to write a command that simply reloads the keynote file.  So far I've got the path to the file.  The next step is to use KeynoteEntries.LoadKeynoteEntriesFromFile() which sounds simple because I thought I'd only need to enter the path, but for some reason it wants another parameter... KeyBasedTreeEntriesLoadContent which I can't wrap my head around.

 

This is the contents of the remarks and the constructor for that Class.  Can someone tell me (hopefully with examples), how this thing is supposed to be used?

 

// Remarks:
//     The class contains a KeyBasedTreeEntries object which should hold the KeyBasedTreeEntries
//     data generated by the IExternalResourceServer. An ExternalResourceServer can
//     create the KeyBasedTreeEntries from an arbitrary data source by using AddEntry
//     to add individual KeyBasedTreeEntries. Once all the desired entries have been
//     added, BuildEntries can be called to construct the KeyBasedTreeEntries object
//     from the individual entries that were added. KeyBasedTreeEntriesLoadContent must
//     have a built KeyBasedTreeEntries before its LoadStatus property can be set to
//     ExternalResourceLoadStatus.Success.
public class KeyBasedTreeEntriesLoadContent : ExternalResourceLoadContent
{
    internal KeyBasedTreeEntriesLoadContent(object proxy)
        : base((proxy is KeyBasedTreeEntriesLoadContentProxy) ? proxy : null)
    {
    }

 

0 Likes
670 Views
8 Replies
Replies (8)
Message 2 of 9

jeremy_tammik
Alumni
Alumni

Never heard of those methods. I asked Gemini for you, and it claims it has, though. This is what it says:

   

  

Let's tackle this Revit keynote reloading challenge. You're right, KeynoteEntries.LoadKeynoteEntriesFromFile()requires more than just the file path. The KeyBasedTreeEntriesLoadContent parameter is key here, and it's often misunderstood. It's not about loading the keynote file directly; it's about preparing a container to receive the keynote data. Think of it as setting the stage before the actual import.

Here's a breakdown of the process and a robust C# code example for your Revit add-in:

C#
 
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.IO;

namespace RevitKeynoteReload
{
    [Transaction(TransactionMode.Manual)]
    public class ReloadKeynotes : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            UIApplication uiapp = commandData.Application;
            Document doc = uiapp.ActiveUIDocument.Document;

            // 1. Get the Keynote file path.  (You already have this)
            string keynoteFilePath = GetKeynoteFilePath(); // Replace with your logic

            if (string.IsNullOrEmpty(keynoteFilePath) || !File.Exists(keynoteFilePath))
            {
                TaskDialog.TaskDialog.Show("Error", "Keynote file not found.");
                return Result.Failed;
            }

            try
            {
                // 2. Create the KeyBasedTreeEntriesLoadContent
                KeyBasedTreeEntriesLoadContent loadContent = new KeyBasedTreeEntriesLoadContent(null); // Important: Pass null here!

                // 3. Load the keynote entries into the content.  This is where the magic happens.
                KeynoteEntries.LoadKeynoteEntriesFromFile(doc, keynoteFilePath, loadContent);

                // 4. Check the load status.  Crucial for error handling.
                if (loadContent.LoadStatus != ExternalResourceLoadStatus.Success)
                {
                    TaskDialog.TaskDialog.Show("Error", $"Keynote load failed: {loadContent.LoadStatus}");
                    return Result.Failed;
                }

                // 5. Apply the loaded entries to the document. This updates Revit's keynote table.
                KeynoteEntries.LoadKeynoteEntries(doc, loadContent);

                // 6. Optional: Regenerate the document to reflect changes immediately.
                doc.Regenerate();


                TaskDialog.TaskDialog.Show("Success", "Keynotes reloaded successfully.");

                return Result.Succeeded;
            }
            catch (Exception ex)
            {
                TaskDialog.TaskDialog.Show("Error", $"An error occurred: {ex.Message}");
                return Result.Failed;
            }
        }


        // Helper function (replace with your actual path retrieval logic)
        private string GetKeynoteFilePath()
        {
            // Example: Get from a settings file, user input, etc.
            // For demonstration, I'm using a hardcoded path.  NEVER do this in production.
            return @"C:\Your\Keynote\File.txt"; // Replace with your actual path retrieval method!
        }
    }
}

Explanation and Key Improvements:

  1. KeyBasedTreeEntriesLoadContent(null): The constructor takes an object proxy. Crucially, you should pass null here. This is the most common point of confusion. The proxy is used internally by Revit, and providing a value yourself is usually unnecessary and can lead to issues.

  2. Error Handling: The code now includes a check for loadContent.LoadStatus. This is essential. The LoadKeynoteEntriesFromFile method might succeed even if the file format is incorrect. Checking the status lets you catch these errors and provide informative messages to the user.

  3. KeynoteEntries.LoadKeynoteEntries(doc, loadContent): This is the second load method. It takes the loadContent that you populated and actually applies the keynote data to the Revit document.

  4. doc.Regenerate(): This is optional but highly recommended. It forces Revit to update the keynote tags and other elements that rely on the keynote table. This ensures the changes are immediately visible to the user.

  5. Path Retrieval: The GetKeynoteFilePath() function is a placeholder. Replace this with your actual logic for getting the keynote file path. This could involve reading from a configuration file, prompting the user with a file dialog, or retrieving it from a shared parameter. Do not hardcode paths in production code!

  6. Transaction: The [Transaction(TransactionMode.Manual)] attribute is essential. Keynote changes require a transaction.

Practical Demonstration:

  1. Create a new Revit add-in project.
  2. Add the code above.
  3. Replace the placeholder path in GetKeynoteFilePath() with the actual path to your keynote file.
  4. Build the add-in.
  5. In Revit, run the command.

This revised approach addresses the core issue and provides a more robust and practical solution for reloading keynote files in your Revit add-ins. Remember to handle file paths dynamically and always check the LoadStatus for proper error management. This will make your add-in more reliable and user-friendly.

     

I would be interested to hear whether this helps and whether it works as claimed. Please let us know how you end up solving this. Thank you, and have a nice weekend.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 3 of 9

james.levieux
Advocate
Advocate


Hi Jeremy,


I too resorted to Gemini for help with this and found the results to be WAY worse than expected. It got the very first line of code wrong.  Getting the path is so much more complex than it assumes.

 

The next thing it does is also wrong. Null cannot be used as an argument for KeyBasedEntriesLoadContent because, if you do, it returns with the error "KeyBasedEntriesLoadContent does not contain a constructor that takes 1 argument".  It doesn't appear that there is a way to circumvent the error checking.

 

It makes me wonder if it's possible to train AI to work with the Revit API better. It would be worth the effort if it could get stuff like this right. I did notice that the results in Gemini were far better than Copilot.

 

Lastly, it appears to me that this "API" code is just the raw code that Autodesk wrote without any respect to outside usability. If you look at the KeyBasedTreeEntriesLoadContent Class it's quite arcane and begs for sample code so we can understand how it's used. It sure would be nice to have a simple usable method that reloads the data file with the error checking baked-in

 

jameslevieux_0-1739760383414.png

 

KeyBasedTreeEntriesLoadContent Class...wow, complicated!

 

The class contains a KeyBasedTreeEntries object which should hold the KeyBasedTreeEntries data generated by the IExternalResourceServer.

 

An ExternalResourceServer can create the KeyBasedTreeEntries from an arbitrary data source by using AddEntry to add individual KeyBasedTreeEntries. Once all the desired entries have been added, BuildEntries can be called to construct the KeyBasedTreeEntries object from the individual entries that were added.

 

KeyBasedTreeEntriesLoadContent must have a built KeyBasedTreeEntries before its LoadStatus property can be set to ExternalResourceLoadStatus.Success

0 Likes
Message 4 of 9

jeremy_tammik
Alumni
Alumni

Thank you for checking and evaluating. Yes, the LLMs are often very unreliable working with Revit API, especially arcane areas for which training data is lacking. Possibly, the KeyBasedEntriesLoadContent object is not used at all. I have asked the development team for advice for you. 

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 5 of 9

jeremy_tammik
Alumni
Alumni

The development team replies: 

   

What? The AI failed when it isn't given 1000's of working examples for a topic? Never!

   

Kidding aside, the use of AI is likely a big part of why they are struggling with this... the AI seems to be leading everyone down a rabbit warren (i.e. the IExternalResourceServer class - interfaces are not quick nor easy in my experience... Dynamo's CPython limitations may be partially to blame for that). None of that need not be involved though.

   

To help the user, the larger community, and the future AI model out I've built these 18 lines of Python which can serve as a POC in Revit 2025 (though I believe they work back to 2022 and perhaps earlier - searching class names on the Dynamo forum I see examples from 2015), I've included verbose comments on each line explaining what it's doing and tied it to the following seven outline steps. Hopefully this will help them to get it converted to C# (something AIs are usually  quite good at, but, just in case, I provided the comments all the same, as they tend to help the model out :wink: ).

  

  • Get the document
  • Get the keynote path (a string) and convert to a model path object
  • Create a local resource of the external reference
  • Start a transaction
  • Get the keynote table
  • Load the new document into the keynote table
  • Commit the transaction

     

import clr #add the common language runtime (clr) to the Python environment so we can work with .NET apis
clr.AddReference("RevitServices") #add the Revit services API to the CLR
import RevitServices #import the Revit services class to the Python environment
from RevitServices.Persistence import DocumentManager #import the DocumentMangaer calss to the Python environment
from RevitServices.Transactions import TransactionManager #import the TransactionManager class to the Python environment
clr.AddReference("RevitAPI") #add the Revit API to the CLR
import Autodesk #Import the Autodesk class to the Python environment
from Autodesk.Revit.DB import * #import all classes in the Revit.DB to the Python environment
doc = DocumentManager.Instance.CurrentDBDocument #get the current document - step #1
keynoteTableReferenceType = ExternalResourceTypes.BuiltInExternalResourceTypes.KeynoteTable #build the keynote table reference type from the built in options
targetFilePath = IN[0] #get the target file path from the Dynamo environment - first half of step #2
revitPath = ModelPathUtils.ConvertUserVisiblePathToModelPath(targetFilePath) #convert the targer file path to a Revit model path - second half of step #2
pathType = PathType.Absolute #set the path to abosolute - you might want Relative here. 
source = ExternalResourceReference.CreateLocalResource(doc, keynoteTableReferenceType, revitPath, pathType) #get the external resource locally - step #3
TransactionManager.Instance.EnsureInTransaction(doc) #start a transaction - step #4
keynoteTable = KeynoteTable.GetKeynoteTable(doc) #get the keynote table from the document - first half of step #5
keynoteTable.LoadFrom(source, KeyBasedTreeEntriesLoadResults()) #load the keynote file into Revit - second half of step #5
TransactionManager.Instance.TransactionTaskDone() #close the transaction - step #6

    

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 6 of 9

james.levieux
Advocate
Advocate

Awesome...maybe AI will be helpful translating this to C# 😉

 

Thank you both!

0 Likes
Message 7 of 9

Sean_Page
Collaborator
Collaborator
using(Transaction trans = new Transaction(_Document))
{
    trans.Start("Reload Keynote File");
    IDictionary<ExternalResourceType, ExternalResourceReference> extRef = KeynoteTable.GetKeynoteTable(_Document).GetExternalResourceReferences();
    string LoadPath = extRef[ExternalResourceTypes.BuiltInExternalResourceTypes.KeynoteTable].InSessionPath;
    ModelPath path = ModelPathUtils.ConvertUserVisiblePathToModelPath(LoadPath);
    ExternalResourceReference reference = ExternalResourceReference.CreateLocalResource(_Document, ExternalResourceTypes.BuiltInExternalResourceTypes.KeynoteTable, path, PathType.Absolute);
    KeynoteTable.GetKeynoteTable(_Document).Reload(null);
    trans.Commit();
}
Sean Page, AIA, NCARB, LEED AP
Partner, Computational Designer, Architect
0 Likes
Message 8 of 9

rhanzlick
Advocate
Advocate

The app I made to do just this at my org is maybe the most popular app I've ever made (I've developed over 120), and also maybe the most simple (in terms of implementation):

 

https://www.revitapidocs.com/2016/e0fd9115-a579-bab4-31ad-542e897edc13.htm

Message 9 of 9

Sean_Page
Collaborator
Collaborator

Yes, my keynote manager is definitely a favorite!

Sean Page, AIA, NCARB, LEED AP
Partner, Computational Designer, Architect