Why embed a web view inside Rhino?

If you've spent any time working with environmental simulation data in the AEC industry, you'll know the drill. You run a daylight analysis, export a CSV, open it in Excel or a Jupyter notebook, build a chart, screenshot it, and paste it into a report. Repeat for wind data. Repeat for energy data. Repeat every time the geometry changes.

The disconnect between where data is generated and where it's understood has always been one of the biggest friction points in building physics workflows. Rhino and Grasshopper are where the geometry lives, but they've never been great at rich data visualisation — heatmaps, wind roses, interactive time-series charts.

The idea was simple: what if we could bring a full web-based data visualisation layer directly into the Rhino environment? Not a clunky WinForms panel, but an actual modern browser rendering engine — D3.js, Chart.js, responsive layouts, the works — running inside a Rhino dockable panel.

The best tool is the one people actually use. If the data lives inside the design environment, it gets looked at. If it lives in a separate report, it gets ignored.

The architecture at a glance

The stack is surprisingly compact. The entire dashboard is a single HTML file — no build step, no bundler, no node_modules. It uses D3.js and Chart.js loaded from CDNs, and all the data parsing logic is written in vanilla JavaScript.

On the C# side, a Rhino plugin creates a WPF UserControl containing a WebView2 control. When the panel opens, it loads the HTML file from an embedded resource and passes EPW weather data to it via the postMessage bridge.

The key pieces:

  • Rhino plugin (.rhp) — registers the command and dockable panel
  • WPF UserControl — hosts the WebView2 browser control
  • WebView2 — renders the HTML dashboard with full Chromium capabilities
  • Single HTML file — contains all visualisation logic, styling, and interactivity
  • postMessage bridge — passes data from C# into the web layer

Setting up WPF inside a Rhino plugin

This is where things get interesting — and where I spent most of my debugging time. Rhino 7 targets .NET Framework 4.8, while Rhino 8 has moved to .NET 7. If you want your plugin to work across both, you need to multi-target, and that introduces a cascade of compatibility considerations.

The .csproj file needs to target both frameworks:

<TargetFrameworks>net48;net7.0-windows</TargetFrameworks>
<UseWPF>true</UseWPF>

The critical lesson here: WPF works differently across these targets. On .NET Framework 4.8, the WPF assemblies are part of the runtime — you don't need explicit package references. On .NET 7, you need UseWPF in your project file, and certain APIs have subtle behavioural differences.

WebView2 itself requires the Microsoft.Web.WebView2 NuGet package. Initialisation is asynchronous, and you must call EnsureCoreWebView2Async() before attempting to navigate or post messages. The common pattern:

public async Task InitializeAsync()
{
    await webView.EnsureCoreWebView2Async(null);
    webView.CoreWebView2.Settings.IsScriptEnabled = true;
    webView.CoreWebView2.NavigateToString(htmlContent);
}

A detail that caught me out: if you try to call NavigateToString before the core WebView2 environment has finished initialising, it fails silently. No exception, no error — just a blank panel. Adding a CoreWebView2InitializationCompleted event handler is essential for debugging.

The data pipeline: EPW to interactive charts

EPW (EnergyPlus Weather) files are the standard format for climate data in building simulation. They contain 8,760 rows — one for each hour of the year — with columns for dry-bulb temperature, relative humidity, wind speed, wind direction, solar radiation, and dozens more variables.

On the C# side, the EPW file is parsed into a structured JSON object. The key decision was what to send and what to compute on each side:

  • C# handles: file reading, basic validation, structuring the raw data into a clean JSON schema
  • JavaScript handles: all derived calculations (UTCI, wind rose binning, monthly aggregations) and rendering

The data is sent from C# to the web view using the postMessage API:

string json = JsonConvert.SerializeObject(weatherData);
webView.CoreWebView2.PostWebMessageAsString(json);

On the JavaScript side, the dashboard listens for this message and triggers the full rendering pipeline — parsing the JSON, computing derived metrics, and drawing each chart. The UTCI heatmap alone involves mapping 8,760 hourly comfort values onto a 365 × 24 grid using D3's colour scales.

Lessons and pitfalls

1. The WebView2 runtime isn't guaranteed

WebView2 relies on the Edge Chromium runtime being installed on the user's machine. On most modern Windows machines it is, but not always — especially on corporate-managed environments where IT controls installations. Including a fallback check (and a helpful error message) is worth the extra ten minutes.

2. WPF air-space issues are real

If you've worked with older WPF applications embedding web content via WebBrowser (the old IE-based control), you'll know about the "airspace" problem — WPF and Win32 content don't composite cleanly. WebView2 largely solves this, but there are edge cases with dockable panels in Rhino where the Z-ordering can behave unexpectedly. Testing the panel in both docked and floating states is important.

3. Single-file simplicity is worth it

Keeping the entire dashboard as a single HTML file — no build step, no dependencies beyond CDN scripts — made the development cycle remarkably fast. Edit the HTML, reload the panel, see the result. No webpack, no transpilation, no waiting. For internal tools and plugins, this approach trades scalability for speed, and it's a trade worth making.

4. Async initialisation requires patience

The WebView2 initialisation is genuinely asynchronous. If your plugin architecture doesn't naturally support async/await in the panel creation flow, you'll need to carefully manage the timing. I ended up using a simple flag-and-queue pattern: any messages sent before initialisation completes are queued and flushed once the WebView is ready.

What's next

The climate dashboard is functional and in use internally, but there's a longer roadmap. Next steps include adding bidirectional communication — so clicking on a data point in the web view can select or highlight corresponding geometry in the Rhino viewport. This opens up a whole new interaction model: click a cell in the UTCI heatmap to see which hour it represents, then visualise the solar conditions for that specific moment in 3D.

There's also the question of whether this pattern — WPF + WebView2 + single HTML — could become a reusable framework for other plugins. A lightweight "web panel SDK" for Rhino that handles the boilerplate initialisation, message passing, and panel registration, letting developers focus purely on the web content.

The AEC industry has a tooling gap. Most environmental data never reaches the design table because the software layers between simulation and design are missing or broken. Embedding modern web technologies directly into design tools is one way to start closing that gap — and the results, so far, suggest it's worth pursuing.