Color loss of shape caused by transformation or SetShape

Hi.

When making a sphere and coloring it, and then performing transformations (such as translation), the color will be lost.

I have written a complete and executable minimal example program to reproduce this problem:

#include <TDocStd_Document.hxx>
#include <STEPCAFControl_Writer.hxx>
#include <Interface_Static.hxx>
#include <TDocStd_Application.hxx>
#include <XCAFApp_Application.hxx>
#include <TCollection_ExtendedString.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include <XCAFDoc_ColorTool.hxx>
#include <gp_Pnt.hxx>
#include <BRepPrimAPI_MakeSphere.hxx>
#include <TopoDS_Shape.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <TDataStd_Name.hxx>
#include <Quantity_Color.hxx>

#include <filesystem> // compile using C++17 or higher standard


bool write_step_with_color(const Handle(TDocStd_Document)& doc, std::filesystem::path file_name) {
    STEPCAFControl_Writer step_writer;
    Interface_Static::SetIVal("write.stepcaf.subshapes.name", 1);

    if (!step_writer.Transfer(doc, STEPControl_AsIs)) { return false; }
    if (
        IFSelect_ReturnStatus ret = step_writer.Write(file_name.string().c_str());
        ret != IFSelect_RetDone
    ) {
        return false;
    }

    return true;
}


int main(const int argc, const char** argv) {
    Handle(TDocStd_Document) doc;
    Handle(TDocStd_Application) app = XCAFApp_Application::GetApplication();
    app->NewDocument(TCollection_ExtendedString("BinXCAF"), doc);
    Handle(XCAFDoc_ShapeTool) shape_tool = XCAFDoc_DocumentTool::ShapeTool(doc->Main());
    Handle(XCAFDoc_ColorTool) color_tool = XCAFDoc_DocumentTool::ColorTool(doc->Main());

    // shape1
    const TopoDS_Shape shape1 = BRepPrimAPI_MakeSphere(gp_Pnt(-30.0, 0.0, 0.0), 1.0);
    TDF_Label label1 = shape_tool->AddShape(shape1);
    TDataStd_Name::Set(label1, TCollection_ExtendedString("shape1"));
    color_tool->SetColor(label1, Quantity_Color(1.0, 0.5, 0.5, Quantity_TOC_RGB), XCAFDoc_ColorGen);

    // shape2
    const TopoDS_Shape shape2 = BRepPrimAPI_MakeSphere(gp_Pnt(0.0, 0.0, 0.0), 3.0);
    TDF_Label label2 = shape_tool->AddShape(shape2);
    TDataStd_Name::Set(label2, TCollection_ExtendedString("shape2"));
    color_tool->SetColor(label2, Quantity_Color(0.0, 0.5, 0.5, Quantity_TOC_RGB), XCAFDoc_ColorGen);
    gp_Trsf trsf;
    trsf.SetTranslation(gp_Vec(30.0, 0.0, 0.0));
    BRepBuilderAPI_Transform shape_transformer = BRepBuilderAPI_Transform(shape2, trsf, Standard_False);
    shape_tool->SetShape(label2, shape_transformer);

    // shape3
    {
        // shape3-1
        const TopoDS_Shape shape31 = BRepPrimAPI_MakeSphere(gp_Pnt(-20.0, 0.0, 0.0), 5.0);
        TDF_Label label31 = shape_tool->AddShape(shape31);
        TDataStd_Name::Set(label31, TCollection_ExtendedString("shape3-1"));
        color_tool->SetColor(label31, Quantity_Color(1.0, 0.5, 0.5, Quantity_TOC_RGB), XCAFDoc_ColorGen);

        // shape3-2
        const TopoDS_Shape shape32 = BRepPrimAPI_MakeSphere(gp_Pnt(0.0, 0.0, 0.0), 7.0);
        TDF_Label label32 = shape_tool->AddShape(shape32);
        TDataStd_Name::Set(label32, TCollection_ExtendedString("shape3-2"));
        color_tool->SetColor(label32, Quantity_Color(0.0, 0.5, 0.5, Quantity_TOC_RGB), XCAFDoc_ColorGen);
        // if the transformation operation and SetShape are removed, everything will be normal
        gp_Trsf trsf;
        trsf.SetTranslation(gp_Vec(20.0, 0.0, 0.0));
        BRepBuilderAPI_Transform shape_transformer = BRepBuilderAPI_Transform(shape32, trsf, Standard_False);
        shape_tool->SetShape(label32, shape_transformer);

        TopoDS_Compound shape3;
        BRep_Builder builder;
        builder.MakeCompound(shape3);
        builder.Add(shape3, shape31);
        builder.Add(shape3, shape32);
        TDF_Label label3 = shape_tool->AddShape(shape3);
        TDataStd_Name::Set(label3, TCollection_ExtendedString("shape3"));
    }

    auto step_file_name = std::filesystem::path(argv[1]);
    write_step_with_color(doc, step_file_name);

    return 0;
}

I just want to use some of the basic shapes provided by OCCT (such as spheres, rectangles) and some basic transformations (such as translation, rotation, scaling) to assemble more complex shapes. As seen in the above code, in FreeCAD, shape1 and shape2 can be displayed normally, but the name of shape3-2 in shape3 has changed to "SOLID", losing color, and worse, the translation operation has failed.

If the transformation operation and SetShape are removed, everything will be normal. So, how can I balance shape transformation, coloring, and MakeCompound.

I have carefully studied the relevant documents provided by the official, but still cannot find any errors. Please give me some guidance and express my blessings in advance.

xie yujun's picture

Oh, although I don't know why the code above went wrong, I have found another solution:

        TopoDS_Shape shape32 = BRepPrimAPI_MakeSphere(gp_Pnt(0.0, 0.0, 0.0), 7.0);
        TDF_Label label32 = shape_tool->AddShape(shape32);
        TDataStd_Name::Set(label32, TCollection_ExtendedString("shape3-2"));
        color_tool->SetColor(label32, Quantity_Color(0.0, 0.5, 0.5, Quantity_TOC_RGB), XCAFDoc_ColorGen);
        gp_Trsf trsf1;
        trsf1.SetTranslation(gp_Vec(20.0, 0.0, 0.0));
        shape32.Location(trsf1);
        gp_Trsf trsf2 = shape32.Location(); // second transformation
        trsf2.SetTranslation(gp_Vec(
            -20.0 + trsf2.TranslationPart().X(),
            trsf2.TranslationPart().Y(),
            trsf2.TranslationPart().Z()
        ));
        shape32.Location(trsf2);
Dmitrii Pasukhin's picture

The reason of modification - when you call BRepAPI you modify geometry of your Shape but not shape inside document. Need to use the XCAFDoc_Editor to modify transformations of the shape nodes or apply transformation as an attirbute.

xie yujun's picture

Thank you very much for your patient reply.

If I understand correctly, "apply transformation as an attribute" means to use Location() to modify the myLocation private member variable of TopoDS_Shape. However, I find the disadvantage of this method. Since TopoDS_Shape can only use move construction or reference, I can't copy the same geometry in a TDocStd_Document: imagine a scene where spheres are arranged in a column. They are just different in color and myLocation, and the data of the geometry is identical. If I create them one by one, it is good, but there is a lot of data redundancy. A good approach for such scenarios is to create only one set of geometric data (for TopoDS_Shape, it should refer to its private member variable Handle(TopoDS_TShape) myTShape), and then create instances with different colors and myLocation according to the actual situation, which reference the same set of geometric data.

I tried the Extract method provided by XCAFDoc_Editor and indeed copied a copy of the data, but my modification of myLocation in TopoDS_Shape doesn't seem to work. Here is some of my code:

const TopoDS_Shape shape1 = BRepPrimAPI_MakeSphere(gp_Pnt(-30.0, 0.0, 0.0), 1.0);
TDF_Label label1 = shape_tool->AddShape(shape1);
TDataStd_Name::Set(label1, TCollection_ExtendedString("shape1"));
color_tool->SetColor(label1, Quantity_Color(1.0, 0.5, 0.5, Quantity_TOC_RGB), XCAFDoc_ColorGen);

TDF_Label label_copy = shape_tool->NewShape();
XCAFDoc_Editor::Extract(label1, label_copy);
TDataStd_Name::Set(label_copy, TCollection_ExtendedString("shape_copy"));
color_tool->SetColor(label_copy, Quantity_Color(0.0, 0.5, 0.5, Quantity_TOC_RGB), XCAFDoc_ColorGen); // the setting has not taken effect
TopoDS_Shape shape_temp = shape_tool->GetShape(label_copy);
gp_Trsf trsf = shape_temp.Location();
const gp_XYZ& xyz = trsf.TranslationPart();
trsf.SetTranslation(gp_Vec(30.0 + xyz.X(), xyz.Y(), xyz.Z()));
shape_temp.Location(trsf); // the setting has not taken effect