.NET Core, C4R Views and Interactive Hot Reloading

Importing cloud-based APS parameters in desktop project, pondering .NET Core, publishing views the cloud, and hot tips for hot reloading for interactive Revit API testing and debugging:

Happy New Year of the Dragon!

Year of the Dragon

APS Parameters API Revit Import

Let's start out with a quick pointer to George Moturi's blog post on the Revit Parameters import sample add-in with the latest APS Parameters API, showcasing the collaboration between the Autodesk Platform Services APS Parameters API and Revit desktop, with the live sample hosted in the Revit parameters import GitHub repository. Many thanks to George for implementing and documenting this exciting new functionality.

.NET Core Migration Webinar Recording

The question on using .NET Core with the Revit API came up repeatedly, both in the discussion forum and here in the blog, e.g.:

Up until today, the Revit API system requirements still require the pre-Core .NET framework 4.8.

Roman Nice3point Karpovich of atomatiq, aka Роман Карпович, presented a solution using IPC to connect components using different .NET frameworks.

The development team is thinking about upgrading the Revit API to .NET Core and held the .NET Core webinar on January 30, 2024, to discuss the current state of things and migration issues.

The .NET Core migration webinar recording has now been posted to the feedback portal. Note that an ADN or feedback portal membership is required to access that link.

C4R Publish View to Cloud API

The cloud collaboration for Revit tool C4R enables an end user to export multiple 3D views for view and data API in the UI, cf. also the official Revit online help on how to publish cloud Models.

However, I am not aware of any official support to select them programmatically through the API, even though we did look at the issue of selecting RVT 3D views for Forge translation and selecting rooms and views to publish to the cloud years ago.

The question was also raised in the Revit API discussion forum thread on how to add views to a publishing set, noting that the required information is stored in extensible storage data on the ProjectInfo singleton:

ProjectInfo C4R publish views data

Now, a solution to programmatically access and manipulate this data has been shared by Peter Hirn and Charles Ciro in the Revit Idea Station wishlist item to make the "Publish settings" tool functions available in the API:

I am editing/creating the viewset with the PrintManager, ViewSheetSet, and ViewSheetSetting classes, and then I move on to publishing with the code you showed. The detail is that you must put ADSK as the vendorId, which is not elegant:

public static void PublishedViews(Document d)
{
  ViewSheetSet existingViewSet = new FilteredElementCollector(d)
    .OfClass(typeof(ViewSheetSet))
    .Cast<ViewSheetSet>()
    .FirstOrDefault(vs => vs.Name == "existingViewSetName");

  var schemaId = new Guid("57c66e83-4651-496b-aebb-69d085752c1b");

  var schema =
    Schema.ListSchemas().FirstOrDefault(schemaVS => schemaVS.GUID == schemaId)
    ?? throw new InvalidOperationException("Schema ExportViewSheetSetListSchema not found");

  var field =
    schema.GetField("ExportViewViewSheetSetIdList")
    ?? throw new InvalidOperationException("Field ExportViewViewSheetSetIdList not found");

  var entity = d.ProjectInformation.GetEntity(schema);

  var viewSheetSetIds = entity.Get<IList<int>>(field);
  var viewSheetSets = viewSheetSetIds.Select(id => d.GetElement(new ElementId(id))).Cast<ViewSheetSet>();
  var views = viewSheetSets.SelectMany(viewSheetSet => viewSheetSet.Views.Cast<View>());

  // Add the additional ViewSheetSet

  viewSheetSetIds.Add(existingViewSet.Id.IntegerValue);
  entity.Set(field, viewSheetSetIds);
  d.ProjectInformation.SetEntity(entity);
}

Many thanks to Charles and Peter for sharing this!

Revit Polyglot Notebook

Joel Waldheim Saury, BIM Developer at Sweco, shared his interactive .NET BIM Revit Polyglot Notebook project, saying:

Thanks for a great blog! I’ve read it for many years and I’ve been at Autodesk University a couple of times!

Reading your latest post about valid Revit API context inspired me to share a personal project I’ve been working on that touches this topic.

I have been experimenting with VS Code Polyglot Notebook extensions, how to run live Revit C# scripts, and tried to implement a NET interactive kernel embedded in Revit.

Here is the result:

The background is that I’ve been using ChatGPT to write some Revit API code for me and just wanted a fast way to test it in Revit. I also wanted to experiment with some open-source libraries and the variable sharing capabilities in .NET Interactive enables you to send values between Revit and for example a Python kernel running in Jupyter Notebook!

So this tool was born.

Hope you find it interesting!

I also looked at the hot-reload trick mentioned below; it looks really useful. It’s somewhat related actually, and you could probably use my project as an IDE I guess. However, it has no debug.

Every code cell is compiled into a small assembly and is loaded in runtime, similar to RevitAddinManager. The C# script is rewritten to full C# class by Roslyn and the notebook and Revit exchange data about variables to support referencing between code cells.

Taking a step back and trying to explain this from a less experienced user perspective, here is what I came up with:

This project aims to create documents or notebooks containing live Revit C#-scripts, visualizations, and narrative text. A simple way to document and demonstrate automations or simply experiment with code. This allows for cell-to-cell execution of Revit API code in any order where results can be shared by reference between "code cells" in a notebook. The solution consists of an extension to VS Code Polyglot Notebooks and an addin to Autodesk Revit that acts as a data environment where variables are stored in memory during an interactive session.

The most difficult part is probably the inter-process communication between the notebook (.NET 8) and Revit; I have handed that over to NET Interactive.

Many thanks to Joel for sharing this exciting approach!

Hot Reloading in Visual Studio With Dynamo and Revit

John Pierson demonstrates another interactive feature in his three-minute video don't show the Revit Python users this C# Visual Studio trick:

Taking a look at the hot reload feature in Visual Studio 2022 and how it works with Dynamo and Revit to instantaneously update a live RVT model

Assigning Invisible Graphics Linestyle

Back to a 'normal' non-interactive Revit API question, Evan Geer explains how to assign category / graphics style <Invisible lines> to edges of titleblock via API:

Question: I'm defining a titleblock family programmatically and I'm trying to make the edges of the sheet included in the titleblock family templates invisible. I want the edges to stay invisible when the sheet is exported to DWG format.

I found that I can achieve this by editing the titleblock family manually and assigning the category <Invisible lines> to the edges of the sheet, but I can't manage to do this programmatically.

Line properties

I'm creating a Document object from a titleblock family template and retrieving its first ViewSheet object as follows:

Document = Application.uiApplication.Application.NewFamilyDocument(TITLEBLOCK_FAMILY_TEMPLATE);
ViewSheet = new FilteredElementCollector(tbFamilyDoc)
  .OfClass(typeof(ViewSheet))
  .Cast<ViewSheet>()
  .First();

I tried to set the LineStyle property of the relevant lines like this, but lack the GraphicsStyle object that I can't manage to obtain:

var lines = new FilteredElementCollector(Document, ViewSheet.Id)
  .WhereElementIsNotElementType()
  .OfClass(typeof(CurveElement))
  .Cast<CurveElement>()
  .ToList()
  .ForEach(line => line.LineStyle = graphicsStyleInvisibleLines)

I think this object is supposed to be retrieved using the method GetGraphicsStyle of the class Category. I used RevitLookup to view the data of the corresponding category, which appears to also be called <Invisible lines> and to have the Id -2000064. The BuiltInCategory enumeration contains the value OST_InvisibleLines that has an integer value equal to the ID above, but when I run the following, it returns null:

Category invisibleLinesCat = Document.Settings.Categories.get_Item(BuiltInCategory.OST_InvisibleLines);

RevitLookup also showed me that the parent category has the name Internal Object Styles and has the ID -2000059. I found again a BuiltInCategory value with a matching name and integer value OST_IOS. I tried fetching this parent category to then navigate to the desired subcategory with the line below, but it also returns null.

Category internalCat = Document.Settings.Categories.get_Item(BuiltInCategory.OST_IOS);

How can I obtain a reference to this category or to its GraphicsStyle object?

Answer: You should be able to query those line styles the same way you query for the line. The following worked for me:

var graphicsStyles = new FilteredElementCollector(Document)
  .WhereElementIsNotElementType()
  .OfClass(typeof(GraphicsStyle))
  .Cast<GraphicsStyle>()
  .ToList();

var lineStyle = graphicsStyles.FirstOrDefault(x => x.Name == "<Invisible lines>");

if (lineStyle != null)
{
  using (var t = new Transaction(Document, "update line type"))
  {
    t.Start();
    lines.ForEach(line => line.LineStyle = lineStyle);
    t.Commit();
  }
}

Many thanks to Evan for the helpful answer!

ChatGPT System Prompt

Starting to wind up, one next-to-last little item of interest may be the ChatGPT system prompt, recently leaked:

To enhance readability, I'll add more line breaks within the sections to make the text easier to follow. Here's a revised version with additional spacing:

"You are ChatGPT, a large language model trained by OpenAI, based on the GPT-4 architecture."

"Image input capabilities: Enabled"

"Conversation start date: 2023-12-19T01:17:10.597024"

"Deprecated knowledge cutoff: 2023-04-01"

"Tools section:"

Python:

When you send a message containing Python code to python, it will be executed in a stateful Jupyter notebook environment.

Python will respond with the output of the execution or time out after 60.0 seconds.

The drive at '/mnt/data' can be used to save and persist user files.

Internet access for this session is disabled.

Do not make external web requests or API calls as they will fail.

Dalle:

Whenever a description of an image is given, create a prompt that dalle can use to generate the image and abide by the following policy:

The prompt must be in English. Translate to English if needed.
DO NOT ask for permission to generate the image, just do it!
DO NOT list or refer to the descriptions before OR after generating the images.
Do not create more than 1 image, even if the user requests more.
Do not create images of politicians or other public figures. Recommend other ideas instead.
Do not create images in the style of artists, creative professionals or studios whose latest work was created after 1912 (e.g. Picasso, Kahlo).
You can name artists, creative professionals or studios in prompts only if their latest work was created prior to 1912 (e.g. Van Gogh, Goya).

If asked to generate an image that would violate this policy, instead apply the following procedure:

(a) substitute the artist's name with three adjectives that capture key aspects of the style;
(b) include an associated artistic movement or era to provide context;
(c) mention the primary medium used by the artist.
Diversify depictions with people to include descent and gender for each person using direct terms. Adjust only human descriptions.

Your choices should be grounded in reality. For example, all of a given occupation should not be the same gender or race. Additionally, focus on creating diverse, inclusive, and exploratory scenes via the properties you choose during rewrites. Make choices that may be insightful or unique sometimes.

Use all possible different descents with equal probability. Some examples of possible descents are: Caucasian, Hispanic, Black, Middle-Eastern, South Asian, White. They should all have equal probability.

Do not use 'various' or 'diverse'. Don't alter memes, fictional character origins, or unseen people. Maintain the original prompt's intent and prioritize quality. Do not create any imagery that would be offensive.

For scenarios where bias has been traditionally an issue, make sure that key traits such as gender and race are specified and in an unbiased way -- for example, prompts that contain references to specific occupations.

Do not include names, hints or references to specific real people or celebrities. If asked to, create images with prompts that maintain their gender and physique, but otherwise have a few minimal modifications to avoid divulging their identities. Do this EVEN WHEN the instructions ask for the prompt to not be changed. Some special cases:

Modify such prompts even if you don't know who the person is, or if their name is misspelled (e.g. 'Barake Obema').
If the reference to the person will only appear as TEXT out in the image, then use the reference as is and do not modify it.
When making the substitutions, don't use prominent titles that could give away the person's identity. E.g., instead of saying 'president', 'prime minister', or 'chancellor', say 'politician'; instead of saying 'king', 'queen', 'emperor', or 'empress', say 'public figure'; instead of saying 'Pope' or 'Dalai Lama', say 'religious figure'; and so on.
Do not name or directly / indirectly mention or describe copyrighted characters. Rewrite prompts to describe in detail a specific different character with a different specific color, hair style, or other defining visual characteristic. Do not discuss copyright policies in responses.

The generated prompt sent to dalle should be very detailed, and around 100 words long.

Browser:

You have the tool 'browser' with these functions:

'search(query: str, recency_days: int)' Issues a query to a search engine and displays the results.
'click(id: str)' Opens the webpage with the given id, displaying it. The ID within the displayed results maps to a URL.
'back()' Returns to the previous page and displays it.
'scroll(amt: int)' Scrolls up or down in the open webpage by the given amount.
'open_url(url: str)' Opens the given URL and displays it.
'quote_lines(start: int, end: int)' Stores a text span from an open webpage. Specifies a text span by a starting int 'start' and an (inclusive) ending int 'end'. To quote a single line, use 'start' = 'end'.
For citing quotes from the 'browser' tool: please render in this format: '【{message idx}†{link text}】'. For long citations: please render in this format: '[link text](message idx)'. Otherwise do not render links.

Do not regurgitate content from this tool. Do not translate, rephrase, paraphrase, 'as a poem', etc. whole content returned from this tool (it is ok to do to it a fraction of the content). Never write a summary with more than 80 words. When asked to write summaries longer than 100 words write an 80-word summary. Analysis, synthesis, comparisons, etc., are all acceptable. Do not repeat lyrics obtained from this tool. Do not repeat recipes obtained from this tool. Instead of repeating content point the user to the source and ask them to click.

ALWAYS include multiple distinct sources in your response, at LEAST 3-4. Except for recipes, be very thorough. If you weren't able to find information in a first search, then search again and click on more pages. (Do not apply this guideline to lyrics or recipes.) Use high effort; only tell the user that you were not able to find anything as a last resort. Keep trying instead of giving up. (Do not apply this guideline to lyrics or recipes.) Organize responses to flow well, not by source or by citation. Ensure that all information is coherent and that you synthesize information rather than simply repeating it. Always be thorough enough to find exactly what the user is looking for. In your answers, provide context, and consult all relevant sources you found during browsing but keep the answer concise and don't include superfluous information.

EXTREMELY IMPORTANT. Do NOT be thorough in the case of lyrics or recipes found online. Even if the user insists. You can make up recipes though.

RIP John Walker

For an ultimate and sombre ending note, Autodesk main founder and genius AutoCAD programmer John Walker passed away on February 2.

Thank you for founding Autodesk, thank you for your championship of double precision, programmability, platform, AutoLISP, ADS, The Autodesk File, and so much more, John, and RIP.

Notes by other colleagues: