3D View, Curved Section and Browser Round-Trip

Yet another RevitLookup update, full roundtrip interaction between your own instance of the built-in Revit CefSharp Chromium browser and your Revit API add-in external command, different ways to locate a BIM element, pure structural 3D view and curved section view creation, and more:

RevitLookup 2024.0.10

RevitLookup 2024.0.10 is now available with the following enhancements:

Calling Revit Command from Chromium Browser

Last week, Andrej Licanin of Bimexperts shared a nice solution demonstrating how to use the Revit built-in CefSharp browser in WPF.

This week he expanded on that in his contribution on calling Revit command from Chromium browser:

This is another guide on Chromium browser using CefSharp, a continuation of the simple WPF with a Chromium browser guide. Hope someone finds it useful.

Basically, what I wanted was for a button in the browser (on a webpage) to trigger a command in Revit. This works by "binding" a JavaScript method to a C# object and its method. In the Javascript we await for the object and call its function.

So, let's make a dummy object for binding and a method in it. In order to call a Revit method it will need a reference to an external event handler and its event:


   public class BoundObject
   {
     public int Add(int a, int b)
     {
       ExtApp.handler.a = a;
       ExtApp.handler.b = b;
       ExtApp.testEvent.Raise();

       return a+b;
     }
   }

The event and its handler are saved in the external app as static for ease of access:


  internal class ExtApp : IExternalApplication
  {
    public static IExternalApplication MyApp;
    public static ChromiumWebBrowser browser;
    public static ExternalEvent testEvent;
    public static MyEvent handler;
    public Result OnShutdown(UIControlledApplication application)
    {
      // Cef.Shutdown();
      return Result.Succeeded;
    }

    public Result OnStartup(UIControlledApplication application)
    {
      MyApp = this;
      //code for making a button

      handler = new MyEvent();
      testEvent= ExternalEvent.Create(handler);

      return Result.Succeeded;
    }
  }

In the WPF control, the browser is embedded like this:


  <Window x:Class="RevitTestProject.TestWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:RevitTestProject"
    xmlns:cef="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
    mc:Ignorable="d"
    Width="1000" Height="500">
    <Grid Background="PapayaWhip">
      <cef:ChromiumWebBrowser Name="ChromiumBrowser" Address="http://www.google.com" Width="900" Height="450"  />
    </Grid>
  </Window>

Here is the code behind the window:


    public TestWindow()
    {
      InitializeComponent();
      ChromiumBrowser.Address = "https://www.google.com";
      ChromiumBrowser.Address = "C:\\Users\\XXX\\Desktop\\index.html";
      BoundObject bo = new BoundObject();
      ChromiumBrowser.JavascriptObjectRepository.Register("boundAsync", bo, true, BindingOptions.DefaultBinder);
    }

    public void Dispose()
    {
      this.Dispose();
    }

So, to use it, make an index.html and submit the path to it in the browser address.

The Test webpage look like this:


<html>
<head>
  <title>Bridge Test</title>
  <!-- <script src="script.js"></script> -->
  <script type="text/javascript">
    async function callCSharpAction() {
      await CefSharp.BindObjectAsync("boundAsync");
      boundAsync.add(16, 2);
    }
  </script>
</head>
<body>
  <button id="action1" onclick="callCSharpAction()">Action 1</button>
  <button id="action2" onclick="alert('Button is working')">Action 2</button>
  <button id="action3">Action 3</button>
</body>
</html>

The handler code:


  internal class MyEvent : IExternalEventHandler
  {
    public int a;
    public int b;
    public void Execute(UIApplication app)
    {
      TaskDialog.Show( "yoyoy",
        "data is " + a.ToString()
        + " and " + b.ToString() + ".");
    }

    public string GetName()
    {
      return "YOYOOY";
    }
  }

Chromium Browser Js Round Trip Callback

Next step: round-trip callback: To make a callback from C# function to the browser, you just need an instance of the browser, and a function in the javascript code that will be called. Here is an edited index.html with such a function to call:


<html>
<head>
  <title>Bridge Test</title>
  <!-- <script src="script.js"></script> -->
  <script type="text/javascript">
    async function callCSharpAction() {
      await CefSharp.BindObjectAsync("boundAsync");
      boundAsync.add(16, 2);
    }

    function showAlert(arg1) {
      // Your JavaScript logic here
      alert("Function called with arguments: " + arg1);
      return;
    }
  </script>
</head>
<body>
  <button id="action1" onclick="callCSharpAction()">Action 1</button>
  <button id="action2" onclick="alert('Button is working')">Action 2</button>
  <button id="action3">Action 3</button>
</body>
</html>

In our bound class, we save a instance to the browser so we can use it on command:


  public class BoundObject
  {
    public int aS;
    public int bS;
    internal ChromiumWebBrowser browser;

    public void CallCSharpMethod()
    {
      MessageBox.Show("C# method called!");
      // Add more code here as needed
    }
    public int Add(int a, int b)
    {
      ExtApp.handler.a = a;
      ExtApp.handler.b = b;
      ExtApp.testEvent.Raise();

      return a+b;
    }

    public int SendSomeDataFromLocal(int a)
    {
      browser.ExecuteScriptAsync("showAlert("+a.ToString()+")");
      return a;
    }
  }

Pass it in when creating the browser in the window codebehind:


  public TestWindow()
  {
    InitializeComponent();
    ChromiumBrowser.Address = "https://www.google.com";
    ChromiumBrowser.Address = "C:\\Users\\XXX\\Desktop\\index.html";
    BoundObject bo = new BoundObject();
    //ExtApp.boundObj = bo;
    bo.browser = ChromiumBrowser;
    ChromiumBrowser.JavascriptObjectRepository.Register(
      "boundAsync", bo, true, BindingOptions.DefaultBinder);
  }

Finally, now, you can call it from Revit:


  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements)
  {
    ExtApp.boundObj.SendSomeDataFromLocal(999);
    return Result.Succeeded;
  }

This concludes a round trip from the browser and back. I hope anyone reading this finds it useful.

Determine Element Location

We put together a nice little overview on various methods to determine the location of a BIM element discussing how can the coordinates for a Revit fabrication part be obtained with the Revit API?

Question: I need to obtain the coordinates for Revit MEP FabricationParts. All of the elements I get have a Location property, but not all of them have either a LocationPoint or a LocationCurve. More specifically, I am only able to get XYZ values through the LocationCurve for Pipe elements. Elements such as Threadolet, Elbow, Weld and Fishmouth don't have either a LocationPoint or a LocationCurve.

Answer: Three options that can be used on almost all BIM elements are:

However, for these types of FabricationParts specifically, egeer and bootsch suggest using the element's connector locations instead:

For OLets and ThreadOLets, you can use the connector that connects to the main pipe as its insertion point, since that is technically where the element was inserted:


    Connector insertionPointConnector = OLet.ConnectorManager
        .Connectors
        .OfType<Connector>()
        .FirstOrDefault(x => x.ConnectorType == ConnectorType.Curve);

    XYZ insertionPoint = insertionPointConnector?.Origin;

Since their connectors are atypical in that they do not connect to another connector, but instead a curve, you need to get the one that is ConnectorType.Curve.

For welds, elbows and other inline elements, you can similarly use the connectors and get their origins. If you want the center of the element, you can use vector math to calculate that using the connector's direction and location. The direction that the connector points is the BasisZ property of the Connector's CoordinateSystem.


    XYZ connectorDirection = insertionPointConnector?.CoordinateSystem.BasisZ;

The solution I end up with is a bit different from the answer given by egeer above: I ended up getting a Connector for each element (the ones without a LocationCurve or LocationPoint). Here's the code in VB:


    Dim insertionPointConnector As Connector = CType(e, FabricationPart).ConnectorManager.Connectors.OfType(Of Connector).FirstOrDefault()
    Dim elementOrigin as XYZ = Connector.insertionPointConnector.Origin

e is of type Element.

Many thanks to egeer and bootsch for jumping in with these good solutions!

Create a Structural-Only 3D View

Harry Mattison continues his AU solution spree presenting a nice code sample demonstrating how to create a 3D view showing only Revit wall structural layers which is discussed in further depth in the Revit API discussion forum thread on how to create new View3D that just displays wall layers of "Structure" function. Harry's sample code performs the following steps:

Many thanks to Harry for addressing this need!

Creating a Curved Section in Dynamo

I have heard several requests for a curved section view, e.g., Alex Vila in 2019: Create curved sections!

Finally, the cavalry comes to the rescue in the shape of Anna Baranova, presenting a 22-minute video tutorial on Dynamo: Curved Sections By Line (Part 1):

Many thanks to Anna for this nice piece of work!

Carbon Footprint of AI Image Generation

Researchers quantify the carbon footprint of generating AI images: creating a photograph using artificial intelligence is like charging your phone:

AI image generation carbon footprint

Sending Data by Pigeon

Talking about carbon footprint and the cost and efficiency of digital data transmission, there is obviously a point at which transmission of large data can be speeded up by putting it on a storage device and moving that around rather physically than squeezing it through the limited bandwidth of the Internet:

Send data by pigeon

Permaculture Farm Regenerates Natural Habitat

Hope for the future from a five-minute video drone tour of permaculture farm:

In this video I narrate a drone tour of our entire 250-acre farm showcasing some of the swale, dam, dugout, aquaculture, livestock food forest, cover cropping and other permaculture systems we have on our regenerative farm.

Presented by the Coen Farm, who say:

We are literally eating ourselves and our planet to death. Our mission is to provide nutrient-dense food, feed, and permaculture education to regenerate the planet and its people.

Personally, I was very touched watching and listening to it.

The Valley of Code

Quick return to digital before I end for today. If you have friends or others wanting to quickly learn to code for the web, here is a great site to get them started:

Welcome to The Valley of Code. Your journey in Web Development starts here. In the fundamentals section you'll learn the basic building blocks of the Internet, the Web and how its fundamental protocol (HTTP) works.

Toc: