Closed sgenoud closed 3 years ago
Hi,
unfortunately, it is currently not possible to accept "return values" from an OpenCascade function that is using references (or pointers) to built-in datatypes.
I outlined some ideas on how to implement this in this post. However, I'm currently not sure how the actual C++ syntax might look like to make this work in the context of class methods. The generated C++ binding code (in Emscripten's Embind convention) for this class looks like this:
class_<BRepTools>("BRepTools")
.constructor<>()
.class_function("UVBounds_1", reinterpret_cast<void (*) (const TopoDS_Face &, Standard_Real , Standard_Real , Standard_Real , Standard_Real ) >((void (*)(const TopoDS_Face & F, Standard_Real & UMin, Standard_Real & UMax, Standard_Real & VMin, Standard_Real & VMax) ) &BRepTools::UVBounds), allow_raw_pointers())
.class_function("UVBounds_2", reinterpret_cast<void (*) (const TopoDS_Face &, const TopoDS_Wire &, Standard_Real , Standard_Real , Standard_Real , Standard_Real ) >((void (*)(const TopoDS_Face & F, const TopoDS_Wire & W, Standard_Real & UMin, Standard_Real & UMax, Standard_Real & VMin, Standard_Real & VMax) ) &BRepTools::UVBounds), allow_raw_pointers())
.class_function("UVBounds_3", reinterpret_cast<void (*) (const TopoDS_Face &, const TopoDS_Edge &, Standard_Real , Standard_Real , Standard_Real , Standard_Real ) >((void (*)(const TopoDS_Face & F, const TopoDS_Edge & E, Standard_Real & UMin, Standard_Real & UMax, Standard_Real & VMin, Standard_Real & VMax) ) &BRepTools::UVBounds), allow_raw_pointers())
.class_function("AddUVBounds_1", reinterpret_cast<void (*) (const TopoDS_Face &, const Bnd_Box2d &) >((void (*)(const TopoDS_Face & F, Bnd_Box2d & B) ) &BRepTools::AddUVBounds), allow_raw_pointers())
.class_function("AddUVBounds_2", reinterpret_cast<void (*) (const TopoDS_Face &, const TopoDS_Wire &, const Bnd_Box2d &) >((void (*)(const TopoDS_Face & F, const TopoDS_Wire & W, Bnd_Box2d & B) ) &BRepTools::AddUVBounds), allow_raw_pointers())
.class_function("AddUVBounds_3", reinterpret_cast<void (*) (const TopoDS_Face &, const TopoDS_Edge &, const Bnd_Box2d &) >((void (*)(const TopoDS_Face & F, const TopoDS_Edge & E, Bnd_Box2d & B) ) &BRepTools::AddUVBounds), allow_raw_pointers())
.class_function("Update_1", static_cast<void (*) (const TopoDS_Vertex &) >((void (*)(const TopoDS_Vertex & V) ) &BRepTools::Update), allow_raw_pointers())
.class_function("Update_2", static_cast<void (*) (const TopoDS_Edge &) >((void (*)(const TopoDS_Edge & E) ) &BRepTools::Update), allow_raw_pointers())
.class_function("Update_3", static_cast<void (*) (const TopoDS_Wire &) >((void (*)(const TopoDS_Wire & W) ) &BRepTools::Update), allow_raw_pointers())
.class_function("Update_4", static_cast<void (*) (const TopoDS_Face &) >((void (*)(const TopoDS_Face & F) ) &BRepTools::Update), allow_raw_pointers())
.class_function("Update_5", static_cast<void (*) (const TopoDS_Shell &) >((void (*)(const TopoDS_Shell & S) ) &BRepTools::Update), allow_raw_pointers())
.class_function("Update_6", static_cast<void (*) (const TopoDS_Solid &) >((void (*)(const TopoDS_Solid & S) ) &BRepTools::Update), allow_raw_pointers())
.class_function("Update_7", static_cast<void (*) (const TopoDS_CompSolid &) >((void (*)(const TopoDS_CompSolid & C) ) &BRepTools::Update), allow_raw_pointers())
.class_function("Update_8", static_cast<void (*) (const TopoDS_Compound &) >((void (*)(const TopoDS_Compound & C) ) &BRepTools::Update), allow_raw_pointers())
.class_function("Update_9", static_cast<void (*) (const TopoDS_Shape &) >((void (*)(const TopoDS_Shape & S) ) &BRepTools::Update), allow_raw_pointers())
.class_function("UpdateFaceUVPoints", static_cast<void (*) (const TopoDS_Face &) >(&BRepTools::UpdateFaceUVPoints), allow_raw_pointers())
.class_function("Clean", static_cast<void (*) (const TopoDS_Shape &) >(&BRepTools::Clean), allow_raw_pointers())
.class_function("CleanGeometry", static_cast<void (*) (const TopoDS_Shape &) >(&BRepTools::CleanGeometry), allow_raw_pointers())
.class_function("RemoveUnusedPCurves", static_cast<void (*) (const TopoDS_Shape &) >(&BRepTools::RemoveUnusedPCurves), allow_raw_pointers())
.class_function("Triangulation", static_cast<Standard_Boolean (*) (const TopoDS_Shape &, const Standard_Real, const Standard_Boolean) >(&BRepTools::Triangulation), allow_raw_pointers())
.class_function("Compare_1", static_cast<Standard_Boolean (*) (const TopoDS_Vertex &, const TopoDS_Vertex &) >((Standard_Boolean (*)(const TopoDS_Vertex & V1, const TopoDS_Vertex & V2) ) &BRepTools::Compare), allow_raw_pointers())
.class_function("Compare_2", static_cast<Standard_Boolean (*) (const TopoDS_Edge &, const TopoDS_Edge &) >((Standard_Boolean (*)(const TopoDS_Edge & E1, const TopoDS_Edge & E2) ) &BRepTools::Compare), allow_raw_pointers())
.class_function("OuterWire", static_cast<TopoDS_Wire (*) (const TopoDS_Face &) >(&BRepTools::OuterWire), allow_raw_pointers())
.class_function("Map3DEdges", reinterpret_cast<void (*) (const TopoDS_Shape &, const TopTools_IndexedMapOfShape &) >(&BRepTools::Map3DEdges), allow_raw_pointers())
.class_function("IsReallyClosed", static_cast<Standard_Boolean (*) (const TopoDS_Edge &, const TopoDS_Face &) >(&BRepTools::IsReallyClosed), allow_raw_pointers())
.class_function("DetectClosedness", reinterpret_cast<void (*) (const TopoDS_Face &, Standard_Boolean , Standard_Boolean ) >(&BRepTools::DetectClosedness), allow_raw_pointers())
.class_function("Dump", reinterpret_cast<void (*) (const TopoDS_Shape &, const Standard_OStream &) >(&BRepTools::Dump), allow_raw_pointers())
.class_function("Write_1", reinterpret_cast<void (*) (const TopoDS_Shape &, const Standard_OStream &, const Message_ProgressRange &) >((void (*)(const TopoDS_Shape & Sh, Standard_OStream & S, const Message_ProgressRange & theProgress) ) &BRepTools::Write), allow_raw_pointers())
.class_function("Read_1", reinterpret_cast<void (*) (const TopoDS_Shape &, const Standard_IStream &, const BRep_Builder &, const Message_ProgressRange &) >((void (*)(TopoDS_Shape & Sh, Standard_IStream & S, const BRep_Builder & B, const Message_ProgressRange & theProgress) ) &BRepTools::Read), allow_raw_pointers())
.class_function("Write_2", reinterpret_cast<Standard_Boolean (*) (const TopoDS_Shape &, std::string, const Message_ProgressRange &) >((Standard_Boolean (*)(const TopoDS_Shape & Sh, const Standard_CString File, const Message_ProgressRange & theProgress) ) &BRepTools::Write), allow_raw_pointers())
.class_function("Read_2", reinterpret_cast<Standard_Boolean (*) (const TopoDS_Shape &, std::string, const BRep_Builder &, const Message_ProgressRange &) >((Standard_Boolean (*)(TopoDS_Shape & Sh, const Standard_CString File, const BRep_Builder & B, const Message_ProgressRange & theProgress) ) &BRepTools::Read), allow_raw_pointers())
.class_function("EvalAndUpdateTol", static_cast<Standard_Real (*) (const TopoDS_Edge &, const opencascade::handle<Geom_Curve> &, const opencascade::handle<Geom2d_Curve>, const opencascade::handle<Geom_Surface> &, const Standard_Real, const Standard_Real) >(&BRepTools::EvalAndUpdateTol), allow_raw_pointers())
.class_function("OriEdgeInFace", static_cast<TopAbs_Orientation (*) (const TopoDS_Edge &, const TopoDS_Face &) >(&BRepTools::OriEdgeInFace), allow_raw_pointers())
.class_function("RemoveInternals", reinterpret_cast<void (*) (const TopoDS_Shape &, const Standard_Boolean) >(&BRepTools::RemoveInternals), allow_raw_pointers())
;
If you happen to have an idea how the syntax for a modified UVBounds_1
binding definition might look like, please let me know.
However, the runtime error that you mentioned is unexpected... I was able to reproduce it and got:
Uncaught (in promise) RuntimeError: function signature mismatch
at _ZN10emscripten8internal7InvokerIvJRK19BRepAdaptor_SurfaceddddEE6invokeEPFvS4_ddddEPS2_dddd (<anonymous>:wasm-function[2460]:0xa69d36)
at new BRepTools$UVBounds_1 (eval at new_ (webpack://opencascade.js-examples/./node_modules/opencascade.js/dist/opencascade.js?), <anonymous>:13:1)
at makePolygon (webpack://opencascade.js-examples/./src/demos/polygon/library.js?:17:3)
at eval (webpack://opencascade.js-examples/./src/demos/polygon/index.js?:22:140)
_ZN10emscripten8internal7InvokerIvJRK19BRepAdaptor_SurfaceddddEE6invokeEPFvS4_ddddEPS2_dddd @ 04410a92:0xa69d36
BRepTools$UVBounds_1 @ VM126223:13
makePolygon @ library.js:17
eval @ index.js:22
The demangled error is
emscripten::internal::Invoker<void, BRepAdaptor_Surface const&, double, double, double, double>::invoke(void (*)(BRepAdaptor_Surface const&, double, double, double, double), BRepAdaptor_Surface*, double, double, double, double)
The presence of BRepAdaptor_Surface
in the error message is confusing... I need to investigate this further.
Sorry I missed the part on passing by reference in your doc!
I would not be surprised if the BRepAdaptor_Surface
was actually used by the BRepTool API
. I have looked into different ways to access the UV info, but so far all seem to use this type of interface 😢.
So if I understand it well, you would need to manually define all the interfaces that work with the passing by reference. For this API in particular, I would rewrite the signature, such that it returns an array with the four values. It would not follow exactly the documentation - but be a bit easier to use in JS / TS land.
So if I understand it well, you would need to manually define all the interfaces that work with the passing by reference. For this API in particular, I would rewrite the signature, such that it returns an array with the four values. It would not follow exactly the documentation - but be a bit easier to use in JS / TS land.
Hmm, so this project is automatically generating the C++ code for the bindings. Therefore, I would like to find a way of handling these references to built-in's in a way that works across all (or most) of OpenCascade's methods. Rewriting the signature to return the references to built-in datatypes might work, but my instinct is that "wrapping" the references to built-ins like this
const intRef = {
current: 123,
};
new oc.Something(intRef); // intRef is passed as a shallow copy, therefore intRef.current can be mutated by this method.
might be easier and more consistent, as it keeps the structure of the function signatures very similar to the original ones. Here is another issue over at the Emscripten repo with a few more examples of the idea... This approach is pretty straightforward to do with "normal" functions. With class methods, I'm currently not sure how to implement that in C++, i.e.:
object
i.e. object->someClassMethod()
? Maybe there is a way to achieve this using templates? Capturing lambdas? Clever casting? Or probably a combination of those :slightly_smiling_face:....if you want a quick workaround, you might want to look into creating a custom build (using the beta
version), like this (untested):
MyBRepTools.wasm:
inputs: []
bindings:
- symbol: MyBRepTools
additionalCppCode: |
#include <emscripten/val.h>
class MyBRepTools: public BRepTools {
public:
static emscripten::val MyUVBounds(const TopoDS_Face &f) {
Standard_Real uMin;
Standard_Real uMax;
Standard_Real vMin;
Standard_Real vMax;
BRepTools::UVBounds(f, uMin, uMax, vMin, vMax);
emscripten::val ret(emscripten::val::object());
ret.set("uMin", emscripten::val(uMin));
ret.set("uMax", emscripten::val(uMax));
ret.set("vMin", emscripten::val(vMin));
ret.set("vMax", emscripten::val(vMax));
return ret;
}
};
emccFlags:
- -sSIDE_MODULE=1
- -O3
Have a look at the docs for Emscripten::val. You can then load the library like this:
import MyBRepTools from "MyBRepTools.wasm";
import {
initOpenCascade,
ocCore,
ocModelingAlgorithms,
ocVisualApplication,
ocDataExchangeBase,
ocDataExchangeExtra,
} from 'opencascade.js/dist';
initOpenCascade({
libs: [
ocCore,
ocModelingAlgorithms,
ocVisualApplication,
ocDataExchangeBase,
ocDataExchangeExtra,
MyBRepTools,
]
}).then(oc => {
const path = [[-50, 0, 0], [50, 0, 0], [50, 100, 0]].map(([x, y, z]) => new oc.gp_Pnt_3(x, y, z));
const makePolygon = new oc.BRepBuilderAPI_MakePolygon_3(path[0], path[1], path[2], true);
const wire = makePolygon.Wire();
const makeFace = new oc.BRepBuilderAPI_MakeFace_15(wire, false);
const uvs = oc.MyBRepTools.MyUVBounds(makeFace.Face());
console.log(uvs);
});
Output:
{
uMax: 35.35533905932738,
uMin: -64.64466094067264,
vMax: 64.64466094067262,
vMin: -35.35533905932739
}
Thanks, I will look into this. I will keep you posted - but I will not have time in the next couple of days!
I updated the code in my previous post with a tested working example.
Keep in mind though that the API of the beta
builds is not yet stable and might change...
Thanks a lot, it works! It also makes clearer to be how awsome your docker based build system is!
Nice! Happy to hear that it works for you!
There is now "native" support for references to built-in types, as described here.
If you experience any issues with that, please let me know. Cheers.
I was trying to retries the UV bounds of a face using this API. From what I understand in C++ you need to pass a reference to a real, and the function update the value pointed.
Is there a way to do this with opencascade.js? I have tried something like this:
but it fails miserably with a
RuntimeError: function signature mismatch
.Am I missing a way to retrieve the values?