Revit API: Understanding Cross-Document Material Handling in DirectShape.SetShape()

If you’ve ever tried to update a DirectShape element’s geometry across two Revit documents and watched the material silently revert to something unexpected, you’re not alone. This is a subtle behavior of the Revit API that catches many developers off guard. Understanding why it happens is the key to working around it correctly — and reliably.

The Use Case: Model Comparison and Geometry Sync

Imagine you’re building a Revit Model Compare Tool that works across two versions of a model:

  • Base.rvt — the original model
  • Modified.rvt — a revised version containing geometry and material changes

The goal is to match elements between the two models and then update the Base model’s geometry with the changes from the Modified model. Because ElementId values can differ across documents for the same physical element, matching is done on the IFCGuid, which remains stable.

Here’s a concrete example of a matched pair of elements:

{
"elements": [
{
"model": "Base Model",
"ifcGuid": "1E6SPXMa59Kv50QxvDC3k7",
"elementId": 10587430,
"materialId": 10563223,
"materialName": "STEEL/S235JR"
},
{
"model": "Modified Model",
"ifcGuid": "1E6SPXMa59Kv50QxvDC3k7",
"elementId": 10570628,
"materialId": 10563281,
"materialName": "STEEL/S355JR"
}
]
}

On the surface, the workflow looks straightforward:

  1. Match elements by IFCGuid.
  2. Extract the geometry from the DirectShape in Modified.rvt.
  3. Clone the native Solid geometry.
  4. Update the Base model element via DirectShape.SetShape().
  5. Apply the material from Modified.rvt (STEEL/S355JR).

The geometry update works. The ElementId is preserved. But the material? That’s where things break.

What Actually Happens

After calling SetShape(), the updated DirectShape in Base.rvt does not display STEEL/S355JR. Instead, it shows a completely different material — in this case "8.8 50" (MaterialId 10563197) — inherited from the associated DirectShapeType.

What makes this especially surprising is that STEEL/S355JR already exists in the Base model — it’s simply stored under a different MaterialId (10563841) than in the Modified model. A reasonable expectation is that Revit would recognize the material by name and reuse the existing Base model definition. It doesn’t. The material carried by the source geometry is not matched against the target document at all; it’s discarded.

The geometry is correct. The material is not. And nothing in the call raised an error.

The Root Cause

When you extract geometry from a Revit document, the GeometryObject instances carry material and style IDs that are local to the source document. When you pass that geometry to SetShape() in a different document, Revit validates whether those IDs exist in the target document. If they don’t — and across documents they generally won’t — the IDs are silently reset to InvalidElementId.

There is no automatic material matching or remapping — not even by name. Even when an equivalent material exists in the target document (as STEEL/S355JR does in the Base model, under MaterialId 10563841), Revit makes no attempt to reconcile the two. The geometry arrives stripped of its intended material, and Revit falls back to the type-level material assignment inherited from the DirectShapeType.

This isn’t a bug — it’s a fundamental aspect of how Revit handles geometry object IDs across document boundaries.

What About Externally Tagged Geometry?

A natural question is whether externally tagged geometry would behave differently. It does offer general advantages over SetShape(IList<GeometryObject>), but when it comes to material handling across documents, the behavior is identical: the same ID invalidation applies. It is not a workaround for this particular problem.

The Recommended Workaround: Copy/Paste Across Documents

The supported way to carry materials across documents is the Revit copy/paste API. Unlike SetShape(), the copy/paste mechanism:

  • Copies associated materials and styles along with the element.
  • Attempts to find a compatible existing material in the target document and remaps IDs accordingly.
  • Produces geometry with correctly remapped material and style IDs.

In other words, copy/paste does the ID remapping work that SetShape() deliberately does not.

The Workaround Workflow

The core idea is simple: let copy/paste do the ID remapping, then hand the already-remapped geometry to SetShape(). Instead of extracting geometry directly from one document and pushing it into another, you first route the element through the copy/paste API so that Revit resolves the materials for you.

Here’s the practical sequence, using the matched pair from the example above:

  1. Copy/paste the DirectShape from Modified.rvt (ElementId 10570628) into Base.rvt using the Revit copy/paste API. During this step, Revit copies the associated material or finds a compatible one already present in Base.rvt, automatically remapping the material and style IDs into the target document.
  2. Retrieve the geometry from the pasted DirectShape, not the original. The pasted element is geometrically identical to the DirectShape in Modified.rvt, but its embedded material and style IDs have been remapped so that they reference valid definitions in Base.rvt. The geometry itself is unchanged—the only difference is the document-local IDs it carries.
  3. Call SetShape() on the target DirectShape (ElementId 10587430) in Base.rvt using the geometry extracted from the pasted element. Because the material and style IDs are now valid in the target document, they are preserved instead of being reset to InvalidElementId.
  4. Delete the temporary pasted element. It has served its purpose as a vehicle for remapping the document-local IDs and is no longer needed.

The key insight is that the temporary copy is not created to change the geometry—it exists solely to let Revit translate the embedded material and style IDs into the target document. The copied DirectShape looks identical to the original, but the geometry extracted from it carries document-valid IDs, making it suitable for use with SetShape().


Comments

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading