Dear Naveen,
Thank you for your guidance so far. I would like to provide further context regarding the extraction of clash data in Navisworks, which might help in clarifying my requirements.
I am exporting IFC models or NWC model for by examples both architectural and structural elements into Navisworks, and I then run the Clash Detective to identify any conflicts. Once the clashes are detected, I utilize the Navisworks Coordination Issues Add-in to group these clashes effectively. It is at this point that I aim to extract as much relevant data as possible for each ClashResultGroup.
The data I wish to extract includes the IFC Class or the revit category of each element in a conflict group, the material, discipline, clash distance, intrusion volume, angle between elements, type of conflict, clash status, and any additional parameters that can help classify the conflict further. My ultimate goal is to capture this information in a structured format such as JSON or CSV, which I can then use for machine learning algorithms to differentiate between real conflicts and false positives.
I know that some of this information is available in the Navisworks UI, specifically from the HTML Tabular report I export (screenshot attached). I am trying to automate this extraction process as much as possible and capture all relevant details programmatically, including the ones shown in that HTML report.
This is my first experience using C#, and I am utilizing LLM (Large Language Models) to help structure the plugin code. Below is the code I have so far, which attempts to extract this data, but I am encountering challenges in retrieving certain details, particularly for grouped clashes. I would appreciate your assistance in refining this extraction process, ensuring that the necessary data is captured correctly.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Clash;
using Autodesk.Navisworks.Api.Plugins;
using Newtonsoft.Json;
namespace NavisworksConflictExtractor
{
[Plugin("ConflictExtractor", "SNC", DisplayName = "Extract Conflict Groups")]
public class ConflictExtractor : AddInPlugin
{
// Define the output directory for saving the extracted data.
private const string OutputPath = "C:\\temp\\ClashData\\";
// Entry point of the plugin that gets triggered during execution
public override int Execute(params string[] parameters)
{
try
{
// Get the active document in Navisworks
Document doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
// Check if the document or the Clash tests are available, if not show an error
if (doc == null || doc.GetClash() == null || doc.GetClash().TestsData == null)
{
MessageBox.Show(" No tests found in Clash Detective!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return 1;
}
// Get all available Clash tests and check if there are any
List<ClashTest> availableTests = doc.GetClash().TestsData.Tests.Cast<ClashTest>().ToList();
if (availableTests.Count == 0)
{
MessageBox.Show(" No Clash Detective tests available!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return 1;
}
// Show a dialog to select a specific test
string selectedTestName = ShowTestSelectionDialog(availableTests);
if (string.IsNullOrEmpty(selectedTestName))
{
MessageBox.Show(" No test selected. Cancellation.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return 1;
}
// Retrieve the selected test from the list of available tests
ClashTest selectedTest = availableTests.FirstOrDefault(t => t.DisplayName == selectedTestName);
if (selectedTest == null)
{
MessageBox.Show(" The selected test was not found.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return 1;
}
// Extract the grouped conflict data for the selected test
List<MLClashGroup> groupedConflicts = ExtractClashGroups(selectedTest);
// Ensure the output directory exists, if not, create it
if (!Directory.Exists(OutputPath)) Directory.CreateDirectory(OutputPath);
// Define the file path for saving the output as a JSON file
string filePath = Path.Combine(OutputPath, $"ML_ClashData_{selectedTestName}.json");
// Serialize the grouped conflicts into JSON format
string jsonOutput = JsonConvert.SerializeObject(groupedConflicts, Formatting.Indented);
// Write the JSON output to a file
File.WriteAllText(filePath, jsonOutput);
// Show a success message with the file path
MessageBox.Show($" Conflict groups exported to JSON at:\n{filePath}", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
// If any exception occurs, show the error message
MessageBox.Show($" Error: {ex.Message}\n{ex.StackTrace}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return 0;
}
// Method to extract the grouped conflicts from the selected ClashTest
private List<MLClashGroup> ExtractClashGroups(ClashTest test)
{
// List to store the grouped conflicts
List<MLClashGroup> groupedConflicts = new List<MLClashGroup>();
// Iterate over the children of the ClashTest (which includes groups of conflicts)
foreach (SavedItem item in test.Children)
{
// Check if the item is a ClashResultGroup
if (item is ClashResultGroup group)
{
// Get the individual conflicts in the group
var conflicts = group.Children.OfType<ClashResult>().ToList();
// If there are no conflicts, continue to the next group
if (conflicts.Count == 0)
continue;
// Create a new MLClashGroup object to hold the group information
MLClashGroup clashGroup = new MLClashGroup
{
GroupName = group.DisplayName, // Name of the clash group
ConflictCount = conflicts.Count, // Count of conflicts in the group
Element1_IFC = GetIFCType(conflicts.First().Selection1), // Get the IFC type of the first element
Element2_IFC = GetIFCType(conflicts.First().Selection2), // Get the IFC type of the second element
Level = GetClashLevel(group), // Get the level of the conflict group
GridLocation = GetClashGrid(conflicts.First()), // Get the grid location of the conflict
AverageDistance = conflicts.Average(c => c.Distance), // Calculate the average distance of conflicts
AverageClashPoint = new ClashPoint(
conflicts.Average(c => c.Center.X), // Average X coordinate of the conflict points
conflicts.Average(c => c.Center.Y), // Average Y coordinate of the conflict points
conflicts.Average(c => c.Center.Z) // Average Z coordinate of the conflict points
)
};
// Add the grouped conflict to the list
groupedConflicts.Add(clashGroup);
}
}
return groupedConflicts;
}
// Method to get the IFC type from the first element in a ClashSelection
private string GetIFCType(ModelItemCollection selection)
{
if (selection != null && selection.Count > 0)
{
ModelItem element = selection.First();
return GetProperty(element, "IFC Type");
}
return "Unknown Type";
}
// Helper method to extract a specific property from a ModelItem
private string GetProperty(ModelItem item, string propertyName)
{
try
{
// Attempt to find and return the property value
return item.PropertyCategories
.SelectMany(pc => pc.Properties)
.FirstOrDefault(p => p.DisplayName.Contains(propertyName))?.Value.ToString() ?? "Undefined";
}
catch
{
// Return "Undefined" if the property cannot be found or an error occurs
return "Undefined";
}
}
// Method to get the level of the conflict group (in this case, it uses the group display name)
private string GetClashLevel(ClashResultGroup group)
{
return group.DisplayName; // The level is often represented in the group name
}
// Method to get the grid location of the conflict (using the clash result display name)
private string GetClashGrid(ClashResult clash)
{
return clash.DisplayName; // The grid intersection is often represented in the conflict display name
}
/// <summary>
/// Method to show a dialog for selecting a Clash Detective test.
/// </summary>
private string ShowTestSelectionDialog(List<ClashTest> availableTests)
{
using (Form form = new Form())
{
// Set up the form for test selection
form.Text = "Select a Clash Detective Test";
form.Width = 400;
form.Height = 250;
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.StartPosition = FormStartPosition.CenterScreen;
Label label = new Label() { Left = 20, Top = 20, Text = "Select a test:", Width = 350 };
ComboBox comboBox = new ComboBox() { Left = 20, Top = 50, Width = 350 };
Button okButton = new Button() { Text = "OK", Left = 250, Width = 100, Top = 100, DialogResult = DialogResult.OK };
Button cancelButton = new Button() { Text = "Cancel", Left = 140, Width = 100, Top = 100, DialogResult = DialogResult.Cancel };
// Add available tests to the ComboBox
foreach (var test in availableTests)
{
comboBox.Items.Add(test.DisplayName);
}
comboBox.SelectedIndex = 0;
// Add controls to the form
form.Controls.Add(label);
form.Controls.Add(comboBox);
form.Controls.Add(okButton);
form.Controls.Add(cancelButton);
// Show the form and return the selected test name
form.AcceptButton = okButton;
form.CancelButton = cancelButton;
return form.ShowDialog() == DialogResult.OK ? comboBox.SelectedItem.ToString() : null;
}
}
}
// Class to store information about each conflict group
public class MLClashGroup
{
public string GroupName { get; set; }
public int ConflictCount { get; set; }
public string Element1_IFC { get; set; }
public string Element2_IFC { get; set; }
public string Level { get; set; }
public string GridLocation { get; set; }
public double AverageDistance { get; set; }
public ClashPoint AverageClashPoint { get; set; }
}
// Class to store the coordinates of a conflict point
public class ClashPoint
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
public ClashPoint(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
}
}
As I am working with Visual Studio 2022 and Navisworks 2025, the Navisworks API should support these functionalities, but some additional data may require further processing or accessing specific properties within the Navisworks model or ClashDetective. I would appreciate your insight into any missing pieces or adjustments I can make to properly capture all the data possible to enable a good classification.
Please let me know if you need any further clarifications.
Thank you and best regards,
Rayane A