How to define PBR materials for GLTF export

Hi!

I have spent a lot of time, trying to figure this one out - with no success...

I'm using OpenCascade.js, so the code is in JavaScript. But it works more or less the same way as in C++.

First, I create a polygon shape.

const makePolygon = () => {
    const path = [[-50, 0, 0], [50, 0, 0], [50, 100, 0]].map(([x, y, z]) => new oc.gp_Pnt_3(x, y, z));
    const wire = new oc.BRepBuilderAPI_MakePolygon_3(path[0], path[1], path[2], true).Wire();
    return new oc.BRepBuilderAPI_MakeFace_15(wire, false).Shape();
}

Next, I want to export my shape into a GLB file.

const exportShape = (s: TopoDS_Shape) => {
    // create doc, add shape label...
    const doc = new oc.TDocStd_Document(new oc.TCollection_ExtendedString_1());
    const theChild = doc.Main().NewChild();
    const st = oc.XCAFDoc_ShapeTool.Set(theChild).get();
    const shapeLabel = st.NewShape();
    st.SetShape(shapeLabel, s);

    // trying to define visualization material...
    const vmtool = oc.XCAFDoc_VisMaterialTool.Set(shapeLabel);
    const m = new oc.XCAFDoc_VisMaterial();
    const pbrm = new oc.XCAFDoc_VisMaterialPBR();
    pbrm.BaseColor.SetValues(1, 0, 0, 1);
    m.SetPbrMaterial(pbrm);
    const matLabel = vmtool.get().AddMaterial_1(new oc.Handle_XCAFDoc_VisMaterial_2(m), new oc.TCollection_AsciiString_2("myMatName"));
    vmtool.get().SetShapeMaterial_1(shapeLabel, matLabel);

    // triangulate and export
    new oc.BRepMesh_IncrementalMesh_2(s, 0.1, false, 0.1, false);
    const cafWriter = new oc.RWGltf_CafWriter(new oc.TCollection_AsciiString_2("./file.glb"), true);
    cafWriter.Perform_2(new oc.Handle_TDocStd_Document_2(doc), new oc.TColStd_IndexedDataMapOfStringString_1(), new oc.Message_ProgressRange_1());
}
// ...
exportShape(makePolygon());

The export works without errors. But the material definition doesn't get picked up in the GLTF export. And my polygon shows up always in white.

What can I do to have my material exported correctly?

Any help would be greatly appreciated!

Attachments: 
Kirill Gavrilov's picture

Make sure to set XCAFDoc_VisMaterialPBR::IsDefined flag to TRUE, as it is FALSE by default. Maybe it wasn't a good idea setting it to FALSE by default...

Sebastian Alff's picture

Thanks for your help!

However, adding the line

    pbrm.IsDefined = true;

doesn't seem to make a difference. Polygon is still white.

Kirill Gavrilov's picture

It might be also helpful dumping an XBF file (TDocStd_Application::SaveAs) to be able to traverse it's structure and see if materials are displayed before glTF export.

Sebastian Alff's picture

...I'm also still on version 7.5.2. Can that be part of the problem? I will make the update and post my results here.

Kirill Gavrilov's picture
    const theChild = doc.Main().NewChild();
    const st = oc.XCAFDoc_ShapeTool.Set(theChild).get();
...
    const vmtool = oc.XCAFDoc_VisMaterialTool.Set(shapeLabel);

Where have you found such a weird pattern for working with Tools? XCAFDoc Tools are not expected to be placed randomly in the document - they should lie on specific labels in the document created by XCAFDoc_DocumentTool and returned by static methods XCAFDoc_DocumentTool::VisMaterialTool() / XCAFDoc_DocumentTool::ShapeTool() and others.
I guess XCAFDoc_DocumentTool::Set() and similar methods are declared public by mistake.

Don't how this should look correctly in JavaScript:

> Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(aDoc->Main());
> Handle(XCAFDoc_VisMaterialTool) aMatTool = XCAFDoc_DocumentTool::VisMaterialTool (aDoc->Main());
Sebastian Alff's picture

Where have you found such a weird pattern for working with Tools

I guess, I was just trying different things. Thanks for clarifying how to use those classes.

I updated my code with your recommendations, which took a while, since I was missing some bindings. The code now looks like this:

const visualizeShape = (s: TopoDS_Shape) => {
    // create app
    const app = new oc.TDocStd_Application();
    oc.BinXCAFDrivers.DefineFormat(new oc.Handle_TDocStd_Application_2(app));

    // create doc, add shape label...
    const docHandle = new oc.Handle_TDocStd_Document_1();
    app.NewDocument(new oc.TCollection_ExtendedString_2("BinXCAF", true), docHandle);
    const doc = docHandle.get();
    const st = oc.XCAFDoc_DocumentTool.ShapeTool(doc.Main()).get();
    const shapeLabel = st.NewShape();
    st.SetShape(shapeLabel, s);

    // trying to define visualization material...
    const vmtool = oc.XCAFDoc_DocumentTool.VisMaterialTool(doc.Main());
    const pbrm = new oc.XCAFDoc_VisMaterialPBR();
    (pbrm as any).IsDefined = true;
    (pbrm as any).BaseColor.SetValues(1, 0, 0, 1);
    const m = new oc.XCAFDoc_VisMaterial();
    m.SetPbrMaterial(pbrm);
    const matLabel = vmtool.get().AddMaterial_1(new oc.Handle_XCAFDoc_VisMaterial_2(m), new oc.TCollection_AsciiString_2("myMatName"));
    vmtool.get().SetShapeMaterial_1(shapeLabel, matLabel);

    // triangulate and export
    new oc.BRepMesh_IncrementalMesh_2(s, 0.1, false, 0.1, false);
    const cafWriter = new oc.RWGltf_CafWriter(new oc.TCollection_AsciiString_2("./file.glb"), true);
    cafWriter.Perform_2(docHandle, new oc.TColStd_IndexedDataMapOfStringString_1(), new oc.Message_ProgressRange_1())

    const file = oc.FS.readFile("./file.glb");
    app.SaveAs_1(docHandle, new oc.TCollection_ExtendedString_2("./export.xbf", false), new oc.Message_ProgressRange_1());
}

The resulting glb file seems to be unchanged. My polygon is still plain white. The exported xbf file is attached - it shows that no material definition is attached. This is still done with version 7.5.2 - a build for 7.5.3. will finish soon.

I'm pretty sure that I'm probably just attaching / defining the material in an incorrect way. Do you (or anyone else) know a good reference with an example on how to do this correctly?

Thanks a lot for your help!

Sebastian Alff's picture

My previous comment was not quite accurate: The material name actually shows up in CAD Assistant. The fact that the color is not exported correctly seems more like a OpenCascade.js - related problem (not one of OpenCascade)...

Kirill Gavrilov's picture

Indeed, your XBF file contains myMatName material with PBR enabled, but it defines a WHITE color. So probably XCAFDoc_VisMaterialPBR modification code doesn't work as expected in JavaScript wrapper. Maybe you may try something like this:

    let pbrm = new oc.XCAFDoc_VisMaterialPBR();
    //pbrm.BaseColor.SetValues(1, 0, 0, 1);
    pbrm.BaseColor = oc.Quantity_ColorRGBA (1.0f, 0.0f, 0.0f, 1.0f);

Sebastian Alff's picture

Thanks a lot for your help, Kirill.

Your suggested JavaScript code seems to solve the issue.

Having another look at this line...

(pbrm as any).BaseColor.SetValues(1, 0, 0, 1);

...I think what happens is that the part (pbrm as any).BaseColor actually calls the get handler of an Emscripten-generated JavaScript proxy, which calls C++ code that creates a copy of that property. The call to SetValues then operates on that copy. I think this situation should only happen for properties (for which Emscripten auto-generates get and set handlers) - not for regular function calls.

It's definitely a bit unintuitive (at least for me), but since it's Emscripten's default behavior, I think I will keep the functionality as it is, for the moment.

For completeness (and if someone else has a similar problem in the future), here is my code, which now works.

const makePolygon = () => {
  const path = [[-50, 0, 0], [50, 0, 0], [50, 100, 0]].map(([x, y, z]) => new oc.gp_Pnt_3(x, y, z));
  const wire = new oc.BRepBuilderAPI_MakePolygon_3(path[0], path[1], path[2], true).Wire();
  return new oc.BRepBuilderAPI_MakeFace_15(wire, false).Shape();
}

const exportShape = (s: TopoDS_Shape) => {
    // create app
    const app = new oc.TDocStd_Application();
    oc.BinXCAFDrivers.DefineFormat(new oc.Handle_TDocStd_Application_2(app));

    // create doc, add shape label...
    const docHandle = new oc.Handle_TDocStd_Document_1();
    app.NewDocument(new oc.TCollection_ExtendedString_2("BinXCAF", true), docHandle);
    const doc = docHandle.get();
    const st = oc.XCAFDoc_DocumentTool.ShapeTool(doc.Main()).get();
    const shapeLabel = st.NewShape();
    st.SetShape(shapeLabel, s);

    // trying to define visualization material...
    const vmtool = oc.XCAFDoc_DocumentTool.VisMaterialTool(doc.Main());
    const visMat = new oc.XCAFDoc_VisMaterial();
    const matLabel = vmtool.get().AddMaterial_1(new oc.Handle_XCAFDoc_VisMaterial_2(visMat), new oc.TCollection_AsciiString_2("myMatName"));
    vmtool.get().SetShapeMaterial_1(shapeLabel, matLabel);
    const visMatPbr = new oc.XCAFDoc_VisMaterialPBR();
    (visMatPbr as any).BaseColor = new oc.Quantity_ColorRGBA_5(.9, .1, .1, 1);
    (visMatPbr as any).IsDefined = true;
    visMat.SetPbrMaterial(visMatPbr);

    // triangulate and export
    new oc.BRepMesh_IncrementalMesh_2(s, 0.1, false, 0.1, false);
    const cafWriter = new oc.RWGltf_CafWriter(new oc.TCollection_AsciiString_2("./file.glb"), true);
    cafWriter.Perform_2(docHandle, new oc.TColStd_IndexedDataMapOfStringString_1(), new oc.Message_ProgressRange_1())

    const file = oc.FS.readFile("./file.glb");
    app.SaveAs_1(docHandle, new oc.TCollection_ExtendedString_2("./export.xbf", false), new oc.Message_ProgressRange_1());
};
exportShape(makePolygon());

Thanks again!

Kirill Gavrilov's picture

I think this situation should only happen for properties (for which Emscripten auto-generates get and set handlers) - not for regular function calls.

This kind of issue happens from time to time with language wrapping layers (to C#, Java too), as it is tricky to work with structures with mixed by reference / by copy meaning - something natural to C++, but not to (some) other languages. Just need to know where them to expect within particular binding to avoid writing a broken code.