how to get triangle vertices that match what is visible in Navisworks?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
I’m writing a Navisworks .NET add-in and need triangle vertices in world coordinates for a selected ModelItem (wall/floor/duct/pipe). The goal is to run my own triangle intersection and host-face normal clustering, but my extracted triangles don’t always match the actual visible surface in Navisworks (triangles appear offset/tilted when reconstructed in Dynamo), which causes wrong intersection results.
I’m extracting triangles using COM
ComApiBridge.ToInwOaPath(ModelItem)
InwOaPath3.Fragments()
InwOaFragment3.GenerateSimplePrimitives(...)
callback InwSimplePrimitivesCB.Triangle(v1,v2,v3) and reading v.coord
applying frag.GetLocalToWorldMatrix() (InwLTransform3f3.Matrix) to get world coords
Is GenerateSimplePrimitives expected to return triangles that exactly match the visible geometry / clash geometry? If not, what is the recommended API/approach to obtain reliable triangle vertices that match Navisworks display (or the clash engine) for intersection tests?
Below is my code.
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.ComApi;
using System;
using System.Collections.Generic;
using COMApi = Autodesk.Navisworks.Api.Interop.ComApi;
namespace Navisworks90degApprover.Utilities
{
public static class ComTriangleExtractor
{
public static bool TryGetWorldTrianglesMm(ModelItem item, out List<TriangleIntersection.Tri> triangles, out string error)
{
triangles = new List<TriangleIntersection.Tri>();
error = null;
if (item == null) { error = "item=null"; return false; }
if (!item.HasGeometry) { error = "item.HasGeometry=false"; return false; }
try
{
object pathAny = ComApiBridge.ToInwOaPath(item);
COMApi.InwOaPath3 path = pathAny as COMApi.InwOaPath3;
if (path == null)
{
error = "ComApiBridge.ToInwOaPath did not return InwOaPath3. Actual type: " +
(pathAny == null ? "(null)" : pathAny.GetType().FullName);
return false;
}
var frags = path.Fragments();
int fragCount = frags.Count; // 1-based COM collection
// First pass: sample some triangles to infer whether vertex units are meters or mm
var sample = new List<TriF>(64);
for (int i = 1; i <= fragCount && sample.Count < 32; i++)
{
var frag = frags[i] as COMApi.InwOaFragment3;
if (frag == null) continue;
var mtx = (COMApi.InwLTransform3f3)(object)frag.GetLocalToWorldMatrix();
var arr = (Array)(object)mtx.Matrix;
float[] m = new float[16];
for (int k = 0; k < 16; k++)
m[k] = Convert.ToSingle(arr.GetValue(k + 1));
Mat4 mat = Mat4.FromNavisworksMatrixRaw(m);
var cbSample = new SimplePrimCb { Matrix = mat, ScaleToMm = 1.0f };
cbSample.Clear();
frag.GenerateSimplePrimitives(COMApi.nwEVertexProperty.eNORMAL, cbSample);
for (int t = 0; t < cbSample.Triangles.Count && sample.Count < 32; t++)
sample.Add(cbSample.Triangles[t]);
}
if (sample.Count == 0)
{
error = "No sample triangles extracted (0).";
return false;
}
float scaleToMm = InferScaleToMm(sample);
// Second pass: full extraction with detected scaling
SimplePrimCb cb = new SimplePrimCb { ScaleToMm = scaleToMm };
for (int i = 1; i <= fragCount; i++)
{
var frag = frags[i] as COMApi.InwOaFragment3;
if (frag == null) continue;
var mtx = (COMApi.InwLTransform3f3)(object)frag.GetLocalToWorldMatrix();
var arr = (Array)(object)mtx.Matrix;
float[] m = new float[16];
for (int k = 0; k < 16; k++)
m[k] = Convert.ToSingle(arr.GetValue(k + 1));
Mat4 mat = Mat4.FromNavisworksMatrixRaw(m);
cb.Matrix = mat;
cb.Clear();
frag.GenerateSimplePrimitives(COMApi.nwEVertexProperty.eNORMAL, cb);
foreach (var tri in cb.Triangles)
{
triangles.Add(new TriangleIntersection.Tri(
new TriangleIntersection.V3(tri.A.X, tri.A.Y, tri.A.Z),
new TriangleIntersection.V3(tri.B.X, tri.B.Y, tri.B.Z),
new TriangleIntersection.V3(tri.C.X, tri.C.Y, tri.C.Z)
));
}
}
if (triangles.Count == 0)
{
error = "No triangles extracted (0).";
return false;
}
return true;
}
catch (Exception ex)
{
error = ex.ToString();
return false;
}
}
private static float InferScaleToMm(List<TriF> tris)
{
var edges = new List<float>(tris.Count * 3);
foreach (var t in tris)
{
edges.Add(Dist(t.A, t.B));
edges.Add(Dist(t.B, t.C));
edges.Add(Dist(t.C, t.A));
}
edges.RemoveAll(e => float.IsNaN(e) || float.IsInfinity(e) || e <= 1e-9f);
edges.Sort();
float median = edges.Count == 0 ? 1.0f : edges[edges.Count / 2];
// Heuristic: if edges look like meters (0.001..50), scale to mm (x1000).
if (median > 0.0001f && median < 50.0f)
return 1000.0f;
return 1.0f;
}
private static float Dist(V3f a, V3f b)
{
float dx = a.X - b.X;
float dy = a.Y - b.Y;
float dz = a.Z - b.Z;
return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
private struct V3f
{
public float X, Y, Z;
public V3f(float x, float y, float z) { X = x; Y = y; Z = z; }
}
private struct Mat4
{
public float M11, M12, M13, M14;
public float M21, M22, M23, M24;
public float M31, M32, M33, M34;
public float M41, M42, M43, M44;
// Raw matrix values from InwLTransform3f3.Matrix (SAFEARRAY, 1..16)
public static Mat4 FromNavisworksMatrixRaw(float[] m16)
{
Mat4 r = new Mat4();
r.M11 = m16[0]; r.M12 = m16[1]; r.M13 = m16[2]; r.M14 = m16[3];
r.M21 = m16[4]; r.M22 = m16[5]; r.M23 = m16[6]; r.M24 = m16[7];
r.M31 = m16[8]; r.M32 = m16[9]; r.M33 = m16[10]; r.M34 = m16[11];
r.M41 = m16[12]; r.M42 = m16[13]; r.M43 = m16[14]; r.M44 = m16[15];
return r;
}
// Transform vertices using the convention that seems to match in our environment.
// (The key problem: unclear whether this convention is correct for all models.)
public V3f Transform(V3f v, float scaleToMm)
{
float x = (v.X * M11 + v.Y * M21 + v.Z * M31 + M41) * scaleToMm;
float y = (v.X * M12 + v.Y * M22 + v.Z * M32 + M42) * scaleToMm;
float z = (v.X * M13 + v.Y * M23 + v.Z * M33 + M43) * scaleToMm;
return new V3f(x, y, z);
}
}
private sealed class SimplePrimCb : COMApi.InwSimplePrimitivesCB
{
public Mat4 Matrix;
public float ScaleToMm = 1000.0f;
public List<TriF> Triangles = new List<TriF>();
public void Clear() { Triangles.Clear(); }
public void Triangle(COMApi.InwSimpleVertex v1, COMApi.InwSimpleVertex v2, COMApi.InwSimpleVertex v3)
{
V3f a = ReadVertexRaw(v1);
V3f b = ReadVertexRaw(v2);
V3f c = ReadVertexRaw(v3);
a = Matrix.Transform(a, ScaleToMm);
b = Matrix.Transform(b, ScaleToMm);
c = Matrix.Transform(c, ScaleToMm);
Triangles.Add(new TriF(a, b, c));
}
public void Line(COMApi.InwSimpleVertex v1, COMApi.InwSimpleVertex v2) { }
public void Point(COMApi.InwSimpleVertex v1) { }
public void SnapPoint(COMApi.InwSimpleVertex v1) { }
private static V3f ReadVertexRaw(COMApi.InwSimpleVertex v)
{
Array arr = (Array)(object)v.coord;
float x = Convert.ToSingle(arr.GetValue(1));
float y = Convert.ToSingle(arr.GetValue(2));
float z = Convert.ToSingle(arr.GetValue(3));
return new V3f(x, y, z);
}
}
private struct TriF
{
public V3f A, B, C;
public TriF(V3f a, V3f b, V3f c) { A = a; B = b; C = c; }
}
}
// Your project’s triangle types; included here for completeness if forum readers ask:
public static class TriangleIntersection
{
public struct V3
{
public double X, Y, Z;
public V3(double x, double y, double z) { X = x; Y = y; Z = z; }
}
public struct Tri
{
public V3 A, B, C;
public Tri(V3 a, V3 b, V3 c) { A = a; B = b; C = c; }
}
}
}vishalghuge2500_0-1767708235485.png
vishalghuge2500_1-1767708254927.png