-
Notifications
You must be signed in to change notification settings - Fork 1
Visualization GeometryTypes
Developer Workflow: Select → Extract → Render
This guide covers the complete pipeline for visualizing each geometry type. All code is copy-paste ready from actual test files and helpers.
Selection Pattern:
// Single curve via edge selection
var curveRef = UiDocument.Selection.PickObject(ObjectType.Edge, "Select Curve");
var curve = Document.GetElement(curveRef)?.GetGeometryObjectFromReference(curveRef) as Edge;
// Multiple curves
var curveRefs = UiDocument.Selection.PickObjects(ObjectType.Edge, "Select Curves");
var curves = new List<Edge>();
foreach (var curveRef in curveRefs)
{
var curve = Document.GetElement(curveRef)?.GetGeometryObjectFromReference(curveRef) as Edge;
if (curve != null) curves.Add(curve);
}Extraction Pattern:
// Extract vertices from curve (tessellation)
var tessellated = edge.Tessellate(); // IList<XYZ>
// Returns points along the curve, ready for renderingRender Pattern - Simple Line:
private void MapGeometryBuffer()
{
var buffer = new RenderingBufferStorage();
// Get curve vertices
var vertices = curve.Tessellate(); // Edge.Tessellate() returns IList<XYZ>
// Map to rendering buffer
RenderHelper.MapCurveBuffer(buffer, vertices);
// Create effect and set color
buffer.EffectInstance = new EffectInstance(buffer.FormatBits);
buffer.EffectInstance.SetColor(new Color(255, 0, 0)); // Red
_curveBuffers.Add(buffer);
}
private void RenderCurves()
{
foreach (var buffer in _curveBuffers)
{
DrawContext.FlushBuffer(
buffer.VertexBuffer,
buffer.VertexBufferCount,
buffer.IndexBuffer,
buffer.IndexBufferCount,
buffer.VertexFormat,
buffer.EffectInstance,
PrimitiveType.LineList, // Key: Line rendering
0,
buffer.PrimitiveCount);
}
}RenderHelper Method Used: MapCurveBuffer(buffer, vertices)
- Maps line connectivity between consecutive vertices
- Creates primitives for each line segment
- Buffer uses
PrimitiveType.LineListfor rendering
Render Pattern - Thick Curve (Tubular):
// For visible thick curves with settable diameter
RenderHelper.MapCurveBuffer(buffer, vertices, diameter: 0.5); // Diameter in feet
// Or smooth surface tube
RenderHelper.MapCurveSurfaceBuffer(buffer, vertices, diameter: 0.5);Selection Pattern:
// Single face
var faceRef = UiDocument.Selection.PickObject(ObjectType.Face, "Select Face");
var face = Document.GetElement(faceRef)?.GetGeometryObjectFromReference(faceRef) as Face;
// Multiple faces
var faceRefs = UiDocument.Selection.PickObjects(ObjectType.Face, "Select Faces");
var faces = new List<Face>();
foreach (var faceRef in faceRefs)
{
var face = Document.GetElement(faceRef)?.GetGeometryObjectFromReference(faceRef) as Face;
if (face != null) faces.Add(face);
}Extraction Pattern:
// Face auto-tessellates to mesh internally
var mesh = face.Triangulate(); // Returns Mesh
// Optional: Get face properties for offset calculations
var bbox = face.GetBoundingBox();
var center = (bbox.Min + bbox.Max) / 2;
var normal = face.ComputeNormal(center);
var area = face.Area;
// Compute offset based on area (for visual separation)
var offset = RenderGeometryHelper.InterpolateOffsetByArea(area);Render Pattern:
private void MapGeometryBuffer()
{
foreach (var face in visualizeGeometries)
{
// Step 1: Tessellate face to mesh
var mesh = face.Triangulate();
// Step 2: Get properties for offset
var bbox = face.GetBoundingBox();
var center = (bbox.Min + bbox.Max) / 2;
var normalLength = RenderGeometryHelper.InterpolateAxisLengthByArea(face.Area);
// Step 3: Create buffers
var surfaceBuffer = new RenderingBufferStorage();
var meshGridBuffer = new RenderingBufferStorage();
var normalBuffer = new RenderingBufferStorage();
// Step 4: Map to rendering
double extrusion = 0.1; // Configurable offset in feet
RenderHelper.MapSurfaceBuffer(surfaceBuffer, mesh, extrusion);
RenderHelper.MapMeshGridBuffer(meshGridBuffer, mesh, extrusion);
var normal = face.ComputeNormal(center);
var offset = RenderGeometryHelper.InterpolateOffsetByArea(face.Area);
RenderHelper.MapNormalVectorBuffer(normalBuffer,
face.Evaluate(center) + (normal * offset),
normal,
normalLength);
_surfaceBuffers.Add(surfaceBuffer);
_meshGridBuffers.Add(meshGridBuffer);
_normalBuffers.Add(normalBuffer);
}
}
private void UpdateEffects()
{
foreach (var buffer in _surfaceBuffers)
{
buffer.EffectInstance ??= new EffectInstance(buffer.FormatBits);
buffer.EffectInstance.SetColor(new Color(100, 150, 200)); // Light blue
buffer.EffectInstance.SetTransparency(0.3); // 30% transparency
}
}
private void RenderFaces()
{
foreach (var buffer in _surfaceBuffers)
{
DrawContext.FlushBuffer(buffer.VertexBuffer,
buffer.VertexBufferCount,
buffer.IndexBuffer,
buffer.IndexBufferCount,
buffer.VertexFormat,
buffer.EffectInstance,
PrimitiveType.TriangleList, // Key: Triangle rendering
0,
buffer.PrimitiveCount);
}
}RenderHelper Methods Used:
-
MapSurfaceBuffer(buffer, mesh, offset)- Fills buffer with mesh triangles, applies offset along normals -
MapMeshGridBuffer(buffer, mesh, offset)- Creates wireframe preview with duplicate offset layer -
MapNormalVectorBuffer(buffer, origin, normal, length)- Renders normal vector with arrowhead
Selection Pattern:
// Single solid from element
var solidRef = UiDocument.Selection.PickObject(ObjectType.Element, "Select Solid Element");
var solid = Document.GetElement(solidRef).GetSolids().FirstOrDefault();
// Multiple solids
var elementRefs = UiDocument.Selection.PickObjects(ObjectType.Element, "Select Elements");
var solids = elementRefs.SelectMany(sRef => Document.GetElement(sRef).GetSolids()).ToList();Extraction Pattern (GeometryExtensions.cs):
public static List<Solid> GetSolids(this Element element)
{
var opts = new Options
{
ComputeReferences = false,
IncludeNonVisibleObjects = true
};
var solids = new List<Solid>();
var geometry = element.get_Geometry(opts);
if (geometry == null) return solids;
foreach (var geoObj in geometry)
{
switch (geoObj)
{
case Solid solid when solid.Volume != 0:
solids.Add(solid);
break;
case GeometryInstance geoInstance:
foreach (var instGeoObj in geoInstance.GetInstanceGeometry())
{
if (instGeoObj is Solid instSolid && instSolid.Volume != 0)
solids.Add(instSolid);
}
break;
}
}
return solids;
}Render Pattern:
private void MapGeometryBuffer()
{
DisposeBuffers();
foreach (var solid in visualizeGeometries)
{
if (solid.Volume == 0) continue;
// Optional: Scale solid for selection emphasis
var scaledSolid = RenderGeometryHelper.ScaleSolid(solid, _scale);
// Render each face as triangles
foreach (Face face in scaledSolid.Faces)
{
var buffer = new RenderingBufferStorage();
var mesh = face.Triangulate();
RenderHelper.MapSurfaceBuffer(buffer, mesh, 0); // No offset
_faceBuffers.Add(buffer);
}
// Render each edge as lines
foreach (Edge edge in scaledSolid.Edges)
{
var buffer = new RenderingBufferStorage();
var edgeVertices = edge.Tessellate();
RenderHelper.MapCurveBuffer(buffer, edgeVertices);
_edgeBuffers.Add(buffer);
}
}
}
private void UpdateEffects()
{
// Color all faces
foreach (var buffer in _faceBuffers)
{
buffer.EffectInstance ??= new EffectInstance(buffer.FormatBits);
buffer.EffectInstance.SetColor(new Color(200, 200, 200)); // Gray
buffer.EffectInstance.SetTransparency(0.2);
}
// Color all edges
foreach (var buffer in _edgeBuffers)
{
buffer.EffectInstance ??= new EffectInstance(buffer.FormatBits);
buffer.EffectInstance.SetColor(new Color(0, 0, 0)); // Black
}
}
private void RenderSolidFaces()
{
if (!_drawFace || _faceBuffers.Count == 0) return;
foreach (var buffer in _faceBuffers)
{
DrawContext.FlushBuffer(buffer.VertexBuffer,
buffer.VertexBufferCount,
buffer.IndexBuffer,
buffer.IndexBufferCount,
buffer.VertexFormat,
buffer.EffectInstance,
PrimitiveType.TriangleList,
0,
buffer.PrimitiveCount);
}
}
private void RenderSolidEdges()
{
if (!_drawEdge || _edgeBuffers.Count == 0) return;
foreach (var buffer in _edgeBuffers)
{
DrawContext.FlushBuffer(buffer.VertexBuffer,
buffer.VertexBufferCount,
buffer.IndexBuffer,
buffer.IndexBufferCount,
buffer.VertexFormat,
buffer.EffectInstance,
PrimitiveType.LineList,
0,
buffer.PrimitiveCount);
}
}Key Pattern: Iterate all faces → tessellate → render as triangles, iterate all edges → tessellate → render as lines
Selection Pattern:
// Single mesh from element
var meshRef = UiDocument.Selection.PickObject(ObjectType.Element, "Select Mesh Element");
var mesh = Document.GetElement(meshRef).GetMeshes().First();
// Multiple meshes
var meshRefs = UiDocument.Selection.PickObjects(ObjectType.Element, "Select Mesh Elements");
var meshes = meshRefs.SelectMany(mRef => Document.GetElement(mRef).GetMeshes()).ToList();Extraction Pattern (GeometryExtensions.cs):
public static List<Mesh> GetMeshes(this Element element)
{
var opts = new Options
{
ComputeReferences = false,
IncludeNonVisibleObjects = true
};
var meshes = new List<Mesh>();
var geometry = element.get_Geometry(opts);
if (geometry == null) return meshes;
foreach (var geoObj in geometry)
{
switch (geoObj)
{
case Mesh mesh:
meshes.Add(mesh);
break;
case GeometryInstance geoInstance:
foreach (var instGeoObj in geoInstance.GetInstanceGeometry())
{
if (instGeoObj is Mesh instMesh)
meshes.Add(instMesh);
}
break;
}
}
return meshes;
}Render Pattern:
private void MapGeometryBuffer()
{
DisposeBuffers();
try
{
foreach (var visualizeGeometry in visualizeGeometries)
{
// Step 1: Create surface buffer (already tessellated)
var surfaceBuffer = new RenderingBufferStorage();
double extrusion = 0.05; // Subtle offset in feet
RenderHelper.MapSurfaceBuffer(surfaceBuffer, visualizeGeometry, extrusion);
_surfaceBuffers.Add(surfaceBuffer);
// Step 2: Create wireframe/grid buffer (visualization of mesh structure)
var meshGridBuffer = new RenderingBufferStorage();
RenderHelper.MapMeshGridBuffer(meshGridBuffer, visualizeGeometry, extrusion);
_meshGridBuffers.Add(meshGridBuffer);
}
// Step 3: Map normal vectors for each vertex
MapNormalsBuffer();
}
catch (Exception ex)
{
Trace.TraceError($"Error mapping geometry buffer in MeshVisualizationServer: {ex}");
}
}
private void MapNormalsBuffer()
{
_normalBuffers.Clear();
foreach (var mesh in visualizeGeometries)
{
// Compute mesh properties
var area = RenderGeometryHelper.ComputeMeshSurfaceArea(mesh);
var offset = RenderGeometryHelper.InterpolateOffsetByArea(area);
var normalLength = RenderGeometryHelper.InterpolateAxisLengthByArea(area);
// Create one buffer per vertex (showing normals)
var normals = Enumerable.Range(0, mesh.Vertices.Count)
.Select(_ => new RenderingBufferStorage())
.ToArray();
for (var i = 0; i < mesh.Vertices.Count; i++)
{
var vertex = mesh.Vertices[i];
var buffer = normals[i];
var normal = RenderGeometryHelper.GetMeshVertexNormal(mesh, i, mesh.DistributionOfNormals);
RenderHelper.MapNormalVectorBuffer(buffer,
vertex + (normal * (offset + extrusion)),
normal,
normalLength);
}
_normalBuffers.Add(normals);
}
}
private void UpdateEffects()
{
foreach (var buffer in _surfaceBuffers)
{
buffer.EffectInstance ??= new EffectInstance(buffer.FormatBits);
buffer.EffectInstance.SetColor(new Color(100, 200, 100)); // Green
buffer.EffectInstance.SetTransparency(0.1);
}
foreach (var meshGridBuffer in _meshGridBuffers)
{
meshGridBuffer.EffectInstance ??= new EffectInstance(meshGridBuffer.FormatBits);
meshGridBuffer.EffectInstance.SetColor(new Color(0, 0, 0));
}
foreach (var normalBuffer in _normalBuffers)
{
foreach (var buffer in normalBuffer)
{
buffer.EffectInstance ??= new EffectInstance(buffer.FormatBits);
buffer.EffectInstance.SetColor(new Color(255, 0, 0));
}
}
}
private void RenderMeshSurfaces()
{
if (!_drawSurface || _surfaceBuffers.Count == 0) return;
if (!ShouldRenderTransparentPass(_transparency)) return;
foreach (var buffer in _surfaceBuffers)
{
DrawContext.FlushBuffer(buffer.VertexBuffer,
buffer.VertexBufferCount,
buffer.IndexBuffer,
buffer.IndexBufferCount,
buffer.VertexFormat,
buffer.EffectInstance,
PrimitiveType.TriangleList,
0,
buffer.PrimitiveCount);
}
}RenderHelper Methods Used:
-
MapSurfaceBuffer(buffer, mesh, offset)- Renders mesh as triangles -
MapMeshGridBuffer(buffer, mesh, offset)- Wireframe showing all edges -
GetMeshVertexNormal(mesh, index, normalDistribution)- Retrieves per-vertex normal
Selection Pattern:
// Single point
var xyz = UiDocument.Selection.PickObject(ObjectType.PointOnElement);
var xyzPoint = xyz.GlobalPoint;
// Multiple points
var xyzRefs = UiDocument.Selection.PickObjects(ObjectType.PointOnElement);
var xyzs = xyzRefs.Select(x => x.GlobalPoint).ToList();Render Pattern - Point Axes (Planes + Axes):
private void MapGeometryBuffer()
{
DisposeBuffers();
var normalExtendLength = _axisLength > 1 ? 0.8 : _axisLength * 0.8;
foreach (var visualizeGeometry in visualizeGeometries)
{
// Create 3 axes (X, Y, Z) for each point
var axisBuffers = Enumerable.Range(0, 3)
.Select(axisIndex => MapAxisBuffer(visualizeGeometry, axisIndex, normalExtendLength))
.ToArray();
// Create 3 planes (XY, YZ, ZX) for each point
var planeBuffers = Enumerable.Range(0, 3)
.Select(axisIndex => MapPlaneBuffer(visualizeGeometry, axisIndex))
.ToArray();
_axisBufferArrays.Add(axisBuffers);
_planeBufferArrays.Add(planeBuffers);
}
}
private RenderingBufferStorage MapAxisBuffer(XYZ origin, int axisIndex, double length)
{
var buffer = new RenderingBufferStorage();
// Each axis is a line from origin in the axis direction
var axis = _normals[axisIndex]; // X=BasisX, Y=BasisY, Z=BasisZ
var endPoint = origin + (axis * length);
var vertices = new List<XYZ> { origin, endPoint };
RenderHelper.MapCurveBuffer(buffer, vertices);
return buffer;
}
private RenderingBufferStorage MapPlaneBuffer(XYZ origin, int axisIndex)
{
var buffer = new RenderingBufferStorage();
// Create a square plane perpendicular to axis
var axis = _normals[axisIndex];
var sideLength = _axisLength;
// Get perpendicular vectors
var perpendicular = axis.CrossProduct(XYZ.BasisZ).Normalize();
if (perpendicular.IsZeroLength())
perpendicular = axis.CrossProduct(XYZ.BasisX).Normalize();
var binormal = axis.CrossProduct(perpendicular).Normalize();
var p1 = origin + (perpendicular * sideLength) + (binormal * sideLength);
var p2 = origin + (perpendicular * sideLength) - (binormal * sideLength);
var p3 = origin - (perpendicular * sideLength) - (binormal * sideLength);
var p4 = origin - (perpendicular * sideLength) + (binormal * sideLength);
var vertices = new List<XYZ> { p1, p2, p3, p4 };
// Manually build buffer for square plane
buffer.VertexBufferCount = 4;
buffer.PrimitiveCount = 2;
var vertexBufferSize = VertexPosition.GetSizeInFloats() * 4;
buffer.FormatBits = VertexFormatBits.Position;
buffer.VertexBuffer = new VertexBuffer(vertexBufferSize);
buffer.VertexBuffer.Map(vertexBufferSize);
var stream = buffer.VertexBuffer.GetVertexStreamPosition();
foreach (var v in vertices) stream.AddVertex(new VertexPosition(v));
buffer.VertexBuffer.Unmap();
buffer.IndexBufferCount = 6 * IndexTriangle.GetSizeInShortInts();
buffer.IndexBuffer = new IndexBuffer(buffer.IndexBufferCount);
buffer.IndexBuffer.Map(buffer.IndexBufferCount);
var indexStream = buffer.IndexBuffer.GetIndexStreamTriangle();
indexStream.AddTriangle(new IndexTriangle(0, 1, 2));
indexStream.AddTriangle(new IndexTriangle(2, 3, 0));
buffer.IndexBuffer.Unmap();
buffer.VertexFormat = new VertexFormat(buffer.FormatBits);
return buffer;
}
private void RenderAxisBuffers()
{
foreach (var axisBufferArray in _axisBufferArrays)
{
foreach (var buffer in axisBufferArray)
{
DrawContext.FlushBuffer(buffer.VertexBuffer,
buffer.VertexBufferCount,
buffer.IndexBuffer,
buffer.IndexBufferCount,
buffer.VertexFormat,
buffer.EffectInstance,
PrimitiveType.LineList,
0,
buffer.PrimitiveCount);
}
}
}
private void UpdateEffects()
{
var colors = new[] { _xColor, _yColor, _zColor };
for (int i = 0; i < _axisBufferArrays.Count; i++)
{
for (int j = 0; j < 3; j++)
{
var buffer = _axisBufferArrays[i][j];
buffer.EffectInstance ??= new EffectInstance(buffer.FormatBits);
buffer.EffectInstance.SetColor(colors[j]);
}
for (int j = 0; j < 3; j++)
{
var buffer = _planeBufferArrays[i][j];
buffer.EffectInstance ??= new EffectInstance(buffer.FormatBits);
buffer.EffectInstance.SetColor(colors[j]);
buffer.EffectInstance.SetTransparency(_transparency);
}
}
}Selection Pattern:
// Single bounding box
var elementRef = UiDocument.Selection.PickObject(ObjectType.Element, "Select Element");
var element = Document.GetElement(elementRef);
var bbox = element.get_BoundingBox(ActiveView);
// Multiple bounding boxes
var elementRefs = UiDocument.Selection.PickObjects(ObjectType.Element, "Select Elements");
var boxes = new List<BoundingBoxXYZ>();
foreach (var elementRef in elementRefs)
{
var element = Document.GetElement(elementRef);
var bbox = element.get_BoundingBox(ActiveView);
if (bbox != null) boxes.Add(bbox);
}Render Pattern:
private void MapGeometryBuffer()
{
DisposeBuffers();
foreach (var box in visualizeGeometries)
{
// Optional: Scale box for visual emphasis
var scaledBox = _scale != 1
? RenderGeometryHelper.GetScaledBoundingBox(box, _scale)
: box;
// Create surface buffer (transparent box faces)
var surfaceBuffer = new RenderingBufferStorage();
RenderHelper.MapBoundingBoxSurfaceBuffer(surfaceBuffer, scaledBox);
_surfaceBuffers.Add(surfaceBuffer);
// Create edge buffer (wireframe outline)
var edgeBuffer = new RenderingBufferStorage();
RenderHelper.MapBoundingBoxEdgeBuffer(edgeBuffer, scaledBox);
_edgeBuffers.Add(edgeBuffer);
}
}
private void RenderSurfaceBuffers()
{
if (!_drawSurface || _surfaceBuffers.Count == 0) return;
if (!ShouldRenderTransparentPass(_transparency)) return;
foreach (var buffer in _surfaceBuffers)
{
DrawContext.FlushBuffer(buffer.VertexBuffer,
buffer.VertexBufferCount,
buffer.IndexBuffer,
buffer.IndexBufferCount,
buffer.VertexFormat,
buffer.EffectInstance,
PrimitiveType.TriangleList,
0,
buffer.PrimitiveCount);
}
}
private void RenderEdgeBuffers()
{
if (!_drawEdge || _edgeBuffers.Count == 0) return;
foreach (var buffer in _edgeBuffers)
{
DrawContext.FlushBuffer(buffer.VertexBuffer,
buffer.VertexBufferCount,
buffer.IndexBuffer,
buffer.IndexBufferCount,
buffer.VertexFormat,
buffer.EffectInstance,
PrimitiveType.LineList,
0,
buffer.PrimitiveCount);
}
}RenderHelper Methods Used:
-
MapBoundingBoxSurfaceBuffer(buffer, box)- 8 corners → 12 triangles (6 faces) -
MapBoundingBoxEdgeBuffer(buffer, box)- 8 corners → 12 edges (wireframe) -
GetScaledBoundingBox(box, scale)- Scales around center for emphasis
Key Implementation Detail: Box corners are in LOCAL coordinates then transformed by box.Transform.OfPoint(corner) to world space, handling rotated bounding boxes correctly.
All geometry types use these core RenderHelper methods:
| Geometry Type | RenderHelper Method | Buffer Type | Primitive Type |
|---|---|---|---|
| Curves | MapCurveBuffer(buffer, vertices) |
Line vertices + indices | LineList |
| Faces/Solids | MapSurfaceBuffer(buffer, mesh, offset) |
Mesh vertices + triangles | TriangleList |
| Face Grid | MapMeshGridBuffer(buffer, mesh, offset) |
Duplicate vertices + edges | LineList |
| Normal Vectors | MapNormalVectorBuffer(buffer, origin, vector, length) |
4 vertices (line+arrow) | LineList |
| BoundingBox Faces | MapBoundingBoxSurfaceBuffer(buffer, box) |
8 corners + 12 triangles | TriangleList |
| BoundingBox Edges | MapBoundingBoxEdgeBuffer(buffer, box) |
8 corners + 12 edges | LineList |
All follow this pattern:
- Create
RenderingBufferStorage - Set
VertexBufferCount,PrimitiveCount,FormatBits - Create and map
VertexBuffer(add vertices) - Unmap vertex buffer
- Create and map
IndexBuffer(add indices/triangles/lines) - Unmap index buffer
- Set
VertexFormatfrom format bits - Create
EffectInstancelater and set color/transparency
For smooth scaling based on geometry size:
// By mesh area
var offset = RenderGeometryHelper.InterpolateOffsetByArea(face.Area);
// Returns 0.01 for tiny faces, 0.1 for large faces
// By tube diameter
var offset = RenderGeometryHelper.InterpolateOffsetByDiameter(diameter);
// Axis length from bounding box
var axisLength = RenderGeometryHelper.InterpolateAxisLengthByPoints(min, max);
// Mesh scaling
var scaledSolid = RenderGeometryHelper.ScaleSolid(solid, scale: 1.1);These automatically scale visualization elements proportionally to geometry size.
- Run Code Overview
- Modern Python Scripting
- Python Debugging
- Python Ecosystems
- RevitDevTool And pyRevit
- Python Stub Generation
- Run .NET Add-ins
- Scripting Runtimes