Question: Free shapes missing when reading XCAF XML in PythonOCC

Hi everyone,

I’m new to the OCCT community, so this is probably a dumb question, but I’m running into a puzzling issue.

We have a C++ script that generates an XML/XCAF document from a STEP file. When we read back this XML in C++, everything works fine — the document has 1 free shape as expected.

However, when I try to read the same XML file in PythonOCC, I get 0 free shapes. Here’s a minimal example of what I’m doing in Python:

from OCC.Core.TDocStd import TDocStd_Document,TDocStd_Application
from OCC.Core.XCAFApp import XCAFApp_Application
from OCC.Core.XCAFDoc import XCAFDoc_DocumentTool
from OCC.Core.XmlXCAFDrivers import xmlxcafdrivers
from OCC.Core.PCDM import PCDM_ReaderStatus
from OCC.Core.TDF import TDF_LabelSequence, TDF_Label

# ----------- Input data (XML file w/metadata) --------------
INP_FILE = "C:/Users/vnoailles/Downloads/test.xml"
# ------------------------------------------------------------

# Create XDE application and register drivers
app = XCAFApp_Application.GetApplication()
xmlxcafdrivers.DefineFormat(app)

# Initialize document and open file
doc = TDocStd_Document("XmlXCAF") 
status = app.Open(INP_FILE,doc)

# Report status
print("Open status:", status, "OK is", PCDM_ReaderStatus.PCDM_RS_OK)

if status != PCDM_ReaderStatus.PCDM_RS_OK:
    raise RuntimeError(f"Failed to open XML document, status = {status}")

# Access XDE tools
shape_tool = XCAFDoc_DocumentTool.ShapeTool(doc.Main())

# Get root labels 
labels = TDF_LabelSequence()     #Initialize TDF labels
shape_tool.GetFreeShapes(labels) #Populate labels with FreeShapes
print(f"OCAF Document - Free shapes found: {labels.Length()}")
if labels.Length() == 0:
    raise RuntimeError("No free shapes found in STEP file")

And here is the code I have in C++:

// corrected_xcaf_io.cpp
#include <STEPCAFControl_Reader.hxx>
#include <TDocStd_Application.hxx>
#include <TDocStd_Document.hxx>
#include <XCAFApp_Application.hxx>
#include <XmlXCAFDrivers.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include <XCAFDoc_ColorTool.hxx>
#include <PCDM.hxx>
#include <TDF_LabelSequence.hxx>
#include <TDataStd_Name.hxx>
#include <TCollection_ExtendedString.hxx>
#include <Quantity_Color.hxx>
#include <IFSelect_ReturnStatus.hxx>
#include <TCollection_AsciiString.hxx>

#include <iostream>
#include <stdexcept>

#define STEP_PATH "C:/Users/vnoailles/Downloads/testStep/base_wall_mount_vc.stp"
#define XML_PATH  "C:/Users/vnoailles/Downloads/test.xml"

void writeXML()
{
    // init app and XML driver
    Handle(XCAFApp_Application) app = XCAFApp_Application::GetApplication();
    XmlXCAFDrivers::DefineFormat(app);

    // create new document (XmlXCAF)
    Handle(TDocStd_Document) doc;
    app->NewDocument("XmlXCAF", doc);

    // read STEP
    STEPCAFControl_Reader reader;
    IFSelect_ReturnStatus rf = reader.ReadFile(STEP_PATH);
    if (rf != IFSelect_RetDone) {
        throw std::runtime_error("STEP read failed (IFSelect_ReturnStatus != IFSelect_RetDone)");
    }

    // transfer STEP into OCAF/XCAF document
    if (!reader.Transfer(doc)) {
        throw std::runtime_error("STEPCAFControl_Reader::Transfer() failed");
    }
    std::cout << "Imported STEP into XCAF document.\n";

    // tools
    TDF_Label mainLabel = doc->Main();
    Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(mainLabel);
    Handle(XCAFDoc_ColorTool) colorTool = XCAFDoc_DocumentTool::ColorTool(mainLabel);

    // get free shapes
    TDF_LabelSequence freeShapes;
    shapeTool->GetFreeShapes(freeShapes);
    std::cout << "Free shapes found: " << freeShapes.Length() << "\n";

    if (freeShapes.Length() > 0) {
        TDF_Label shapeLabel = freeShapes.Value(1);

        // set a color (example)
        Quantity_Color red(1.0, 0.0, 0.0, Quantity_TOC_RGB);
        colorTool->SetColor(shapeLabel, red, XCAFDoc_ColorGen);

        // set a name using the TDataStd_Name attribute (correct approach)
        TCollection_ExtendedString nameExt("PrismShape");
        TDataStd_Name::Set(shapeLabel, nameExt);
        std::cout << "Set name 'PrismShape' and color on first free shape.\n";
    }

    // save document as XML
    PCDM_StoreStatus s = app->SaveAs(doc, TCollection_ExtendedString(XML_PATH));
    if (s != PCDM_StoreStatus::PCDM_SS_OK) {
        throw std::runtime_error("Failed to save XCAF document to XML (SaveAs returned non-OK).");
    }
    std::cout << "Saved XCAF document to: " << XML_PATH << "\n";

    app->Close(doc);
}

void readXML()
{
    Handle(XCAFApp_Application) app = XCAFApp_Application::GetApplication();
    XmlXCAFDrivers::DefineFormat(app);

    // create and open doc
    Handle(TDocStd_Document) doc = new TDocStd_Document("XmlXCAF");
    PCDM_ReaderStatus rstatus = app->Open(XML_PATH, doc);
    if (rstatus != PCDM_ReaderStatus::PCDM_RS_OK) {
        throw std::runtime_error(std::string("Error: cannot open document. Status = ") + std::to_string((int)rstatus));
    }
    std::cout << "OCAF document loaded successfully.\n";

    TDF_Label mainLabel = doc->Main();
    Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(mainLabel);
    Handle(XCAFDoc_ColorTool) colorTool = XCAFDoc_DocumentTool::ColorTool(mainLabel);

    TDF_LabelSequence freeShapes;
    shapeTool->GetFreeShapes(freeShapes);
    std::cout << "OCAF Document - Free shapes found: " << freeShapes.Length() << "\n";

    if (freeShapes.Length() > 0) {
        TDF_Label shapeLabel = freeShapes.Value(1);

        // read the name attribute (if present)
        Handle(TDataStd_Name) nameAttr;
        if (shapeLabel.FindAttribute(TDataStd_Name::GetID(), nameAttr)) {
            TCollection_ExtendedString ext = nameAttr->Get();
            // convert to ASCII for printing (simple approach)
            TCollection_AsciiString ascii(ext);
            std::cout << "Shape name: " << ascii.ToCString() << "\n";
        }
        else {
            std::cout << "No TDataStd_Name attribute attached to the label.\n";
        }
    }

    app->Close(doc);

}

int main()
{
    try {
        writeXML();
        readXML();
    }
    catch (const std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
        return 1;
    }
    return 0;
}

Also here is the C++ output :

Imported STEP into XCAF document.
Free shapes found: 1
Set name 'PrismShape' and color on first free shape.
Saved XCAF document to: C:/Users/vnoailles/Downloads/prism_wBCs.xml
OCAF document loaded successfully.
OCAF Document - Free shapes found: 1
Shape name: PrismShape

And here is the output on python side

Open status: 0 OK is PCDM_ReaderStatus.PCDM_RS_OK
OCAF Document - Free shapes found: 0
Traceback (most recent call last):
  File "C:\Users\vnoailles\Desktop\CAD-Project-Test-Archi\FastAPI\main.py", line 34, in <module>
    raise RuntimeError("No free shapes found in STEP file")
RuntimeError: No free shapes found in STEP file

You can also see the stp file used and the xml generated in attached file