Table of Contents

Assemblies

XCAF (Extended CAF) documents hold what a STEP file has beyond geometry: product structure, names, colors, layers, materials, PMI. They are OCAF documents with a fixed layout, managed by tools: XCAFDoc_ShapeTool for shapes and assemblies, XCAFDoc_ColorTool for colors, XCAFDoc_LayerTool for layers.

The label tree

A table made of a plate and one post placed four times:

flowchart TD
    main["0:1 main"] --> shapes["0:1:1 shapes<br/>XCAFDoc_ShapeTool"]
    main --> colors["0:1:2 colors<br/>XCAFDoc_ColorTool"]
    main --> layers["0:1:3 layers<br/>XCAFDoc_LayerTool"]
    shapes --> plate["0:1:1:1 plate<br/><i>a part: a shape</i>"]
    shapes --> post["0:1:1:2 post<br/><i>a part</i>"]
    shapes --> table["0:1:1:3 table<br/><i>an assembly</i>"]
    table --> c1["0:1:1:3:1<br/><i>a component: plate at its place</i>"]
    table --> c2["0:1:1:3:2 ... 0:1:1:3:5<br/><i>components: post, four places</i>"]
    c1 -.->|refers to| plate
    c2 -.->|refers to| post
Label XCAFDoc_ShapeTool test Holds
part IsSimpleShape a shape, defined once
assembly IsAssembly components
component IsReference (IsComponent) a reference to a part or an assembly, and a location
free shape IsFree a part or an assembly nothing refers to: a root

Names (TDataStd_Name) and colors sit on any of them: a component's name is the instance's, and its color overrides its part's.

Writing

/// <summary>A table: a plate on four posts, one post shape placed four times.</summary>
public static void WriteTable(string path)
{
  var application = XCAFApp_Application.GetApplication();
  TDocStd_Document? document = null;
  application.NewDocument("MDTV-XCAF", ref document);
  try
  {
    var shapes = XCAFDoc_DocumentTool.ShapeTool(document!.Main());
    var colors = XCAFDoc_DocumentTool.ColorTool(document.Main());

    // the parts, each defined once
    var plate = shapes.AddShape(new BRepPrimAPI_MakeBox(100, 60, 4).Shape(), false);
    var post = shapes.AddShape(new BRepPrimAPI_MakeCylinder(3, 40).Shape(), false);
    TDataStd_Name.Set(plate, "plate");
    TDataStd_Name.Set(post, "post");
    colors.SetColor(plate, new Quantity_Color(Quantity_NameOfColor.Quantity_NOC_BURLYWOOD),
                    XCAFDoc_ColorType.XCAFDoc_ColorGen);
    colors.SetColor(post, new Quantity_Color(Quantity_NameOfColor.Quantity_NOC_GRAY40),
                    XCAFDoc_ColorType.XCAFDoc_ColorGen);

    // the assembly places them: components refer to the parts, with a location each
    var table = shapes.NewShape();
    TDataStd_Name.Set(table, "table");
    shapes.AddComponent(table, plate, new TopLoc_Location());
    foreach (var (x, y) in new[] { (8, 8), (92, 8), (8, 52), (92, 52) })
    {
      var below = new gp_Trsf();
      below.SetTranslation(new gp_Vec(x, y, -40));
      shapes.AddComponent(table, post, new TopLoc_Location(below));
    }

    shapes.UpdateAssemblies();

    var writer = new STEPCAFControl_Writer();
    writer.SetNameMode(true);
    writer.SetColorMode(true);
    if (!writer.Transfer(document, STEPControl_StepModelType.STEPControl_AsIs)
        || writer.Write(path) != IFSelect_ReturnStatus.IFSelect_RetDone)
    {
      throw new IOException($"writing {path} failed");
    }
  }
  finally
  {
    application.Close(document);
  }
}

AddShape(shape, true) (the default) turns a compound into an assembly with its children as parts; false keeps it one part.

Reading

Walk the tree from the free shapes, following references and composing locations:

/// <summary>
/// A part as the assembly places it: its name, its shape where it sits, its color.
/// </summary>
public record Part(string Name, TopoDS_Shape Shape, Quantity_Color? Color);

public static List<Part> ReadParts(string path)
{
  var application = XCAFApp_Application.GetApplication();
  TDocStd_Document? document = null;
  application.NewDocument("MDTV-XCAF", ref document);
  try
  {
    var reader = new STEPCAFControl_Reader();
    reader.SetNameMode(true);
    reader.SetColorMode(true);
    if (reader.ReadFile(path) != IFSelect_ReturnStatus.IFSelect_RetDone
        || !reader.Transfer(document))
    {
      throw new InvalidDataException($"{path} isn't a readable STEP file");
    }

    var parts = new List<Part>();
    var roots = new TDF_LabelSequence();
    XCAFDoc_DocumentTool.ShapeTool(document!.Main()).GetFreeShapes(roots);
    foreach (var root in roots)
    {
      Collect(root, new TopLoc_Location(), parts);
    }

    return parts;
  }
  finally
  {
    application.Close(document);
  }
}

private static void Collect(TDF_Label label, TopLoc_Location placement, List<Part> parts)
{
  // a component refers to the shape it places, and adds its location
  var shape = label;
  if (XCAFDoc_ShapeTool.IsReference(label))
  {
    shape = new TDF_Label();
    XCAFDoc_ShapeTool.GetReferredShape(label, shape);
    placement = placement.Multiplied(XCAFDoc_ShapeTool.GetLocation(label));
  }

  if (XCAFDoc_ShapeTool.IsAssembly(shape))
  {
    var components = new TDF_LabelSequence();
    XCAFDoc_ShapeTool.GetComponents(shape, components);
    foreach (var component in components)
    {
      Collect(component, placement, parts);
    }

    return;
  }

  var color = new Quantity_Color();
  var colored = XCAFDoc_ColorTool.GetColor(shape, XCAFDoc_ColorType.XCAFDoc_ColorGen, ref color)
                || XCAFDoc_ColorTool.GetColor(shape, XCAFDoc_ColorType.XCAFDoc_ColorSurf,
                                              ref color);
  parts.Add(new Part(NameOf(shape), XCAFDoc_ShapeTool.GetShape(shape).Moved(placement),
                     colored ? color : null));
}

private static string NameOf(TDF_Label label)
{
  TDF_Attribute? attribute = null;
  return label.FindAttribute(TDataStd_Name.GetID(), ref attribute)
    ? TDataStd_Name.DownCast(attribute)!.Get()
    : "";
}

Colors come in three kinds: XCAFDoc_ColorGen (the whole shape), XCAFDoc_ColorSurf (faces), XCAFDoc_ColorCurv (edges). Readers put a STEP file's colors where the file had them; ask for more than one kind. Faces can have their own colors: XCAFDoc_ShapeTool.GetSubShapes lists the labels of colored sub-shapes.

Showing a document

XCAFPrs_AISObject shows a label with its colors, see 3D views.

Important

XCAFApp_Application.GetApplication() is one per process. Close each document with application.Close(document): OCCT keeps documents it opened until then.