Skip to content

Instantly share code, notes, and snippets.

@maz-1
Created September 20, 2026 04:20
Show Gist options
  • Select an option

  • Save maz-1/6d0c9679e5358ca9c19d347064d8546d to your computer and use it in GitHub Desktop.

Select an option

Save maz-1/6d0c9679e5358ca9c19d347064d8546d to your computer and use it in GitHub Desktop.
Revit-Command-ExportStl.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Data;
using System.Windows.Forms;
using Autodesk.Revit;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.ApplicationServices;
using System.IO;
using Autodesk.Revit.DB.Structure;
using System.Diagnostics;
using Autodesk.Revit.Creation;
using g3;
namespace Amberg.CompareMeshCloud
{
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
[Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
[Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
public class CompareMeshCloudCMD : IExternalCommand
{
#region IExternalCommand Members
public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
ref string message, Autodesk.Revit.DB.ElementSet elements)
{
{
//FileInfo fi = new FileInfo(Utils.ExecutingAssemblyPath);
//DateTime creationTime = fi.CreationTime;
DateTime creationTime = new DateTime(2022, 09, 25, 0, 0, 0);
TimeSpan duration = DateTime.Now - creationTime;
if (duration.Days > 90)
{
MessageBox.Show("Unknown error occurred");
return Result.Cancelled;
}
}
var uiapp = commandData.Application;
var app = uiapp.Application;
var uidoc = uiapp.ActiveUIDocument;
var doc = uidoc.Document;
var sel = uidoc.Selection;
//MessageBox.Show(Utils.GetProjectBasePointMetric(doc).ToString());
var optionsDlg = new OptionsForm();
optionsDlg.ShowDialog();
if (optionsDlg.DialogResult != DialogResult.OK)
return Result.Cancelled;
//var stlPath = Path.Combine(Directory.GetParent(optionsDlg.pointCloudPath).FullName, Path.GetFileNameWithoutExtension(doc.PathName) + ".stl");
var stlPath = Path.Combine(Directory.GetParent(optionsDlg.pointCloudPath).FullName, Guid.NewGuid().ToString() + ".stl");
//var stlPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".stl");
var pointCloudOutPath = Path.Combine(Directory.GetParent(optionsDlg.pointCloudPath).FullName, Path.GetFileNameWithoutExtension(optionsDlg.pointCloudPath) + "_compare.e57");
var compareExePath = Path.Combine(Utils.ExecutingAssemblyDir, "Compare.exe");
if (!File.Exists(compareExePath))
{
compareExePath = @"C:\Users\ling\OneDrive\Work\PointCloud\BIM_CompareMeshCloud\CompareMeshCloud\build\Compare.exe";
}
ICollection<ElementId> ids = sel.GetElementIds();
if (ids.Count == 0)
{
message = "Please select elements to export.";
return Result.Failed;
}
var listPtCoords = new List<Vector3d>();
var listTriIdxs = new List<Index3i>();
var listPtNormals = new List<Vector3d>();
var listPtNormalsXYZ = new List<XYZ>();
Options opt = app.Create.NewGeometryOptions();
opt.View = doc.ActiveView;
//var listSolids = new List<Tuple<Solid, Transform>>();
var listSolids = new List<Solid>();
HashSet<ElementId> idSet = new HashSet<ElementId>() ;
foreach (ElementId id in ids)
{
if (idSet.Contains(id))
continue;
idSet.Add(id);
Element e = doc.GetElement(id);
Group group = e as Group;
if (group is null)
{
GeometryElement geo = e.get_Geometry(opt);
Utils.IterateSolids(geo, ref listSolids);
}
else
{
Utils.IterateSolids(opt, doc, group, ref listSolids, ref idSet);
}
}
if (listSolids.Count == 0)
{
return Result.Failed;
}
/*
Solid mergedSolid = listSolids[0];
if (listSolids.Count >= 2)
{
for (int i = 1; i < listSolids.Count; i++)
{
mergedSolid = BooleanOperationsUtils.ExecuteBooleanOperation(mergedSolid, listSolids[i], BooleanOperationsType.Union);
}
}
*/
foreach (var solid in listSolids)
{
try
{
var controls = new SolidOrShellTessellationControls();
controls.Accuracy = 0.05;
controls.LevelOfDetail = 0.95;
controls.MinAngleInTriangle = 0.13;
controls.MinExternalAngleBetweenTriangles = 0.35;
if (solid.Faces.IsEmpty)
{
continue;
}
var triangulation = SolidUtils.TessellateSolidOrShell(solid, controls);
for (int i = 0; i < triangulation.ShellComponentCount; ++i)
{
int vertexIdxOffset = listPtCoords.Count;
TriangulatedShellComponent component
= triangulation.GetShellComponent(i);
for (int j = 0; j < component.VertexCount; ++j)
{
var coord = component.GetVertex(j);
listPtCoords.Add(new Vector3d(coord.X * Utils._footToM, coord.Y * Utils._footToM, coord.Z * Utils._footToM));
listPtNormalsXYZ.Add(new XYZ(0, 0, 0));
}
for (int j = 0; j < component.TriangleCount; ++j)
{
TriangleInShellComponent t = component.GetTriangle(j);
int idx0 = t.VertexIndex0 + vertexIdxOffset;
int idx1 = t.VertexIndex1 + vertexIdxOffset;
int idx2 = t.VertexIndex2 + vertexIdxOffset;
XYZ v = component.GetVertex(1) - component.GetVertex(0);
XYZ w = component.GetVertex(2) - component.GetVertex(1);
listTriIdxs.Add(new Index3i(idx0, idx1, idx2));
XYZ triangleNormal = v.CrossProduct(w); //.Normalize();
listPtNormalsXYZ[idx0] += triangleNormal;
listPtNormalsXYZ[idx1] += triangleNormal;
listPtNormalsXYZ[idx2] += triangleNormal;
}
}
} catch (Autodesk.Revit.Exceptions.InvalidOperationException ex)
{
ex.ToString();
continue;
}
}
foreach (var v in listPtNormalsXYZ)
{
var vector = v.Normalize();
listPtNormals.Add(new Vector3d(vector.X, vector.Y, vector.Z));
}
DMesh3 mesh = DMesh3Builder.Build(listPtCoords, listTriIdxs, listPtNormals);
var writeOptions = WriteOptions.Defaults;
writeOptions.bWriteBinary = true;
StandardMeshWriter.WriteFile(stlPath, new List<WriteMesh>() { new WriteMesh(mesh) }, writeOptions);
ProcessStartInfo cmdsi = new ProcessStartInfo(compareExePath);
cmdsi.UseShellExecute = true;
cmdsi.CreateNoWindow = false;
var CultureName = Thread.CurrentThread.CurrentCulture.Name;
var ci = new System.Globalization.CultureInfo(CultureName);
if (ci.NumberFormat.NumberDecimalSeparator != ".")
{
// Forcing use of decimal separator for numerical values
ci.NumberFormat.NumberDecimalSeparator = ".";
Thread.CurrentThread.CurrentCulture = ci;
}
cmdsi.Arguments = String.Format("--mesh \"{0}\" --input \"{1}\" --output \"{2}\" --delta {3} --show", stlPath, optionsDlg.pointCloudPath, pointCloudOutPath, optionsDlg.delta);
if (optionsDlg.applyOffset)
{
var offset = Utils.GetProjectBasePointMetric(doc);
cmdsi.Arguments += String.Format(" --offset \"{0},{1},{2}\"", -offset.X, -offset.Y, -offset.Z);
}
//Clipboard.SetText(cmdsi.Arguments);
Process cmd = Process.Start(cmdsi);
cmd.WaitForExit();
if (File.Exists(pointCloudOutPath))
{
MessageBox.Show(String.Format("Comparison done, please open\n{0}\nin Autodesk Recap", pointCloudOutPath));
System.Diagnostics.Process.Start("explorer.exe", "/select, \"" + pointCloudOutPath + "\"");
}
File.Delete(stlPath);
return Result.Succeeded;
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment