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.