
Mon, 09/22/2025 - 15:45
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
Attachments:
Wed, 09/24/2025 - 08:08
The used method
XCAFApp_Application::Open()
has peculiar definition to pay attention:Although might think that the method will fill in the document passed in parameter
theDoc
, it is not (see also@param[out]
, it is not@param[in,out]
). Instead, the function will create a new document and return it to the passed variable reference. So thatdoc = new TDocStd_Document("XmlXCAF")
line before has no effect and you may pass an emptyHandle(TDocStd_Document)
.This syntax has dramatic consequences when automatically wrapped (via SWIG in this case) into another languages like Python, which follows different paradigm of working with pointers and references compared to C++. Moreover,
XCAFApp_Application::Open()
has several overloads, which only adds complexity to automatic wrapping.I have tried your code in pythonOCC 7.9.0 and have struggled that even first assertion doesn't pass:
In fact,
status
wasn't of typePCDM_ReaderStatus
at all, and it looked like function actually returnedTDocStd_Document
instance.Probing this document confirmed my assumption that this was indeed expected opened document having 1 free root shape (while dummy document passed via arguments remained empty).
I have looked at the C++ code generated by SWIG for this method, and it is become excessively confusing considering multiple overloads it tries to combine, and indeed in this overload scenario it returns newly created
TDocStd_Document
instead as return value, which contradicts to automatically generated documentation for the method by pythonOCC building routines.I cannot say that the behavior is exactly the same on older version of pythonOCC / SWIG, but I suppose that anyway SWIG struggles with wrapping this method and will need some human help here.
I suggest asking this question on pythonOCC site, so maybe they already has some alternative Pythonic methods for opening XCAF documents, and/or will consider improving documentation / wrapping of this API.