Brep construction from polygonal mesh

Hi everyone,

I have a volume as a polygonal mesh constructed using CGAL, which I would like to send to Rhino as Brep via Speckle.
I have gotten so far that the Brep that I get in Rhino is a closed Brep. But I think there is still something wrong (probably with my surfaces), but I am just not sure where to start debugging.

Specifically, the representation of the Brep bows inwards in places, and if I explode the Brep, some of the edges seem short. But if I construct the edges from the start and end vertex index using the raw data all edges are fine. So, I am not quite sure where the error occurs.

1 Like

Try baking the output geometry and inspecting the vertexes & edges to see if they correspond to your original mesh.

Sometimes grasshopper object display can appear distorted.

Hi @baileydw,
It’s the same when I bake them. At least I don’t get any closer to where the issue is.

Below is an extract from my code which shows the method I used to get the NURBS values.
Those are then passed to Speckle and their Brep class (python code example), which on import, passes the values to Rhino’s Brep class like so: link

This is my example as GH file. It uses some Speckle components but I have also internalised just the Brep.
Brep Example.gh (127.1 KB)

C++ Methods to get the Nurbs values for the Brep.
It’s still in development, and currently, get_Surfaces needs to be called before get_Curves2D.

    Brep::Curves2D Brep::get_Curves2D() const {

        auto face_origin = this->mesh.property_map<Surface_mesh::Face_index, Kernel::Point_2>("f:origin").value();

        Curves2D curves = Curves2D();
        curves.resize(mesh.number_of_halfedges());

        for (auto h : mesh.halfedges()){

            Brep::Curve2D curve = std::vector<tg::pos2>(2);


            auto v1 = mesh.source(h);
            auto v2 = mesh.target(h);

            auto plane = compute_plane(mesh, mesh.face(h));

            // Alight geometry to surace
            auto o = face_origin[mesh.face(h)];
            Kernel::Vector_2 Ov = Kernel::Vector_2(o.x(), o.y());
   
            auto pt1 = plane.to_2d(mesh.point(v1)) - Ov;
            auto pt2 = plane.to_2d(mesh.point(v2)) - Ov;

            curve[1] = tg::pos2(CGAL::to_double(pt1.x()), CGAL::to_double(pt1.y()));
            curve[0] = tg::pos2(CGAL::to_double(pt2.x()), CGAL::to_double(pt2.y()));

            curves[h.idx()] = curve;
            
        }
        return curves;
    }

    Brep::Curves3D Brep::get_Curves3D() const {
        Curves3D curves = Curves3D();

        curves.resize(mesh.number_of_edges());
        for (auto e : mesh.edges())
        {
            auto curve = std::vector<tg::pos3>(2);
            auto h = mesh.halfedge(e, 0);

            auto p1 = mesh.point(mesh.source(h));
            auto p2 = mesh.point(mesh.target(h));
            
            curve[0] = tg::pos3(CGAL::to_double(p1.x()), CGAL::to_double(p1.y()), CGAL::to_double(p1.z()));
            curve[1] = tg::pos3(CGAL::to_double(p2.x()), CGAL::to_double(p2.y()), CGAL::to_double(p2.z()));

            curves[e.idx()] = curve;
        }
        return curves;
    }

    Brep::Edges Brep::get_Edges() const { 
        Edges edges = Edges();
        edges.resize(mesh.num_edges());
        for (auto e : mesh.edges())
        {
            auto edge = Brep::Edge();
            edge.Curve3dIndex = e.idx();

            auto h = mesh.halfedge(e);
            edge.TrimIndices.resize(2);
            edge.TrimIndices[0] = h.idx();
            edge.TrimIndices[1] = mesh.opposite(h).idx(); 

            auto v1 = mesh.vertex(e, 1);
            auto v2 = mesh.vertex(e, 0);
            auto p1 = mesh.point(v1);
            auto p2 = mesh.point(v2);

            edge.StartIndex = v1.idx();
            edge.EndIndex = v2.idx();
            edge.ProxyCurveIsReversed = false;

            edge.Domain = Interval(0, tg::distance(tg::pos3(CGAL::to_double(p1.x()), CGAL::to_double(p1.y()), CGAL::to_double(p1.z())),tg::pos3(CGAL::to_double(p2.x()), CGAL::to_double(p2.y()), CGAL::to_double(p2.z()))));

            edge.Curve3dIndex = e.idx();
            edges[e.idx()] = edge;
        }
        return edges;
    }
    
    Brep::Faces Brep::get_Faces() const { 
        Faces faces = Faces();
        faces.resize(mesh.number_of_faces());
        for (auto f : mesh.faces())
        {
            auto face = Brep::Face();

            face.SurfaceIndex = f.idx();
            face.OuterLoopIndex =  f.idx();
            face.LoopIndices = f.idx();
            faces[f.idx()] = face;
        }

        return faces;
    }

    Brep::Vertices Brep::get_Vertices() const {
        Vertices vertices = Vertices();
        vertices.resize(mesh.number_of_vertices());
        for (auto v : mesh.vertices()){   
            auto p = mesh.point(v);
            vertices[v.idx()] = tg::pos3(CGAL::to_double(p.x()), CGAL::to_double(p.y()), CGAL::to_double(p.z()));
        }
        return vertices;
    }

    Brep::Surfaces Brep::get_Surfaces() const { 

        auto surface_origin = this->mesh.property_map<Surface_mesh::Face_index, Kernel::Point_2>("f:origin").value();

        Surfaces surfaces = Surfaces();
        surfaces.resize(mesh.number_of_faces());
        for (auto f : mesh.faces())
        {
            auto surface = Brep::Surface();
            surface.degreeU = 1;
            surface.degreeV = 1;
            surface.rational = false;
            surface.countU = 2;
            surface.countV = 2;
            surface.closedU = false;
            surface.closedV = false;
            surface.pointData.reserve(4/*Points*/ * 4/*Fields*/);


            // Get all the points of the face
            std::vector<Kernel::Point_3> points;
            for (auto v : mesh.vertices_around_face(mesh.halfedge(f)))
                points.push_back(mesh.point(v));

            auto normal = PMP::compute_face_normal(f, mesh);
            CGAL::Plane_3 plane = Kernel::Plane_3(points[0], normal);

            std::vector<Kernel::Point_2> points_2d;
            for (auto p: points)
                points_2d.push_back(plane.to_2d(p));


            auto bbox = CGAL::bounding_box(points_2d.begin(), points_2d.end());


            // Offset bbox
            // TODO: Find a way to calculate the correct offset.
            // Some surfaces seem to require an offset
            static const double offset = 0.3;
            bbox = CGAL::Bbox_2(
                CGAL::to_double(bbox.xmin()-offset), 
                CGAL::to_double(bbox.ymin()-offset), 
                CGAL::to_double(bbox.xmax()+offset), 
                CGAL::to_double(bbox.ymax()+offset));

            points.clear();
            points.resize(4);
            points[0] = plane.to_3d(Kernel::Point_2(bbox.xmin(), bbox.ymin()));
            points[1] = plane.to_3d(Kernel::Point_2(bbox.xmin(), bbox.ymax()));
            points[2] = plane.to_3d(Kernel::Point_2(bbox.xmax(), bbox.ymin()));
            points[3] = plane.to_3d(Kernel::Point_2(bbox.xmax(), bbox.ymax()));


            for (auto & p: points)
            {
                surface.pointData.push_back(CGAL::to_double(p.x()));
                surface.pointData.push_back(CGAL::to_double(p.y()));
                surface.pointData.push_back(CGAL::to_double(p.z()));
                surface.pointData.push_back(1.0);
            }

            surface_origin[f] = Kernel::Point_2(bbox.xmin(), bbox.ymin());
     
            float distU = bbox.xmax()-bbox.xmin();
            float distV = bbox.ymax()-bbox.ymin();
            surface.domainU = Interval(0, distU);
            surface.domainV = Interval(0, distV);
            surface.knotsU = std::vector<float>{0, distU}; 
            surface.knotsV = std::vector<float>{0, distV};

            surfaces[f.idx()] = surface;
        }
        return surfaces;
    }

    Brep::Loops Brep::get_Loops() const { 
        Loops loops = Loops();
        loops.resize(mesh.number_of_faces());
        for (auto f : mesh.faces()){
            auto loop = Brep::BrepLoop();
            loop.FaceIndex = f.idx();
            loop.Type = Brep::BrepLoop::BrepLoopType::Outer;
            loop.TrimIndices = std::vector<int>();


            for (auto h: mesh.halfedges_around_face(mesh.halfedge(f)))
                loop.TrimIndices.push_back(h.idx());
            std::reverse(loop.TrimIndices.begin(), loop.TrimIndices.end());

            
            loops[f.idx()] = loop;
        }
        return loops;
    }

    Brep::Trims Brep::get_Trims() const { 
        Trims trims = Trims();
        trims.resize(mesh.num_halfedges());
        for (auto h : mesh.halfedges())
        {
            auto trim = Brep::BrepTrim();

            // 3D curve index
            trim.EdgeIndex = mesh.edge(h).idx();


            trim.StartIndex = mesh.source(h).idx();
            trim.EndIndex = mesh.target(h).idx();


            trim.FaceIndex = mesh.face(h).idx();
            trim.LoopIndex = mesh.face(h).idx();

            // 2D curve index
            trim.CurveIndex = h.idx();// mesh.edge(h).idx();
 
            trim.IsoStatus = 0; //What is this? --> https://developer.rhino3d.com/api/rhinocommon/rhino.geometry.isostatus
            trim.TrimType = Brep::BrepTrim::BrepTrimType::Mated;
            
            // This value needs to be set
            auto e = mesh.edge(h);
            trim.IsReversed =  ( mesh.vertex(e,0) == mesh.source(h) )? true : false;

            auto p1 = mesh.point(mesh.source(h));
            auto p2 = mesh.point(mesh.target(h));
            auto dist = tg::distance(
                tg::pos3(
                    CGAL::to_double(p1.x()), 
                    CGAL::to_double(p1.y()), 
                    CGAL::to_double(p1.z())),
                tg::pos3(
                    CGAL::to_double(p2.x()), 
                    CGAL::to_double(p2.y()), 
                    CGAL::to_double(p2.z())));
            trim.Domain = Interval(0, dist);

            trims[h.idx()] = trim;
        }   

        return trims;
    }

Python code that handles the actual creation of the Speckle object.

def BrepToBase(brep: Brep ) -> Base:

    sp_brep = Speckle_Brep()
    sp_brep.provenance = "LinkML"
    sp_brep.units = Units.m

    sp_brep.bbox = AABBToBase(brep.bbox())
    sp_brep.area = brep.area()
    sp_brep.volume = brep.volume()
    sp_brep.IsClosed = brep.is_closed()
    sp_brep.Orientation = brep.Orientation()

    sp_brep.displayValue = [MeshToBase(brep.get_mesh())]

    sp_brep.Curve3D = [Line(
            # Swap y and z because of the point cloud is in a Y-up coordinate system
            start = Point(x = points[0].x, y = points[0].z, z = points[0].y),
            end = Point(x = points[-1].x, y= points[-1].z, z = points[-1].y),
            domain = Interval(start = 0, end = 1),
            # bbox: Optional[Box] = None
            # length: Optional[float] = None
        ) for points in brep.Curves3D()]

    # Swap y and z because of the point cloud is in a Y-up coordinate system
    sp_brep.Vertices = [ Point(x=p.x, y=p.z,z=p.y) for p in brep.Vertices() ] 

    # Surfaces be generated before the 2D curves
    sp_brep.Surfaces = [
        Surface(
            degreeU = s.degreeU,
            degreeV = s.degreeV,
            rational = s.rational,
            # area = s.area,
            # Swap y and z because of the point cloud is in a Y-up coordinate system
            countU = s.countU,
            countV = s.countV,
            # bbox = AABBToBase(s.bbox),
            closedU = s.closedU,
            closedV = s.closedV,
            domainU = Interval(start = s.domainU[0], end = s.domainU[1]),
            domainV = Interval(start = s.domainV[0], end = s.domainV[1]),
            pointData = [ v for p in chunks(s.pointData,4) for v in [p[0], p[2], p[1], p[3]]],
            knotsU = s.knotsU,
            knotsV = s.knotsV
        ) for s in brep.Surfaces()]
    
    # Be sure to fist construct the surfaces before the 2D curves
    sp_brep.Curve2D = [ Line(
            start = Point(x = points[0].x, y = points[0].y, z = 0),
            end = Point(x = points[-1].x, y= points[-1].y, z = 0),
            domain = Interval(start = 0, end = 1),
            # bbox: Optional[Box] = None
            # length: Optional[float] = None
        ) for points in brep.Curves2D()]
    
    sp_brep.Edges = [BrepEdge(
        Curve3dIndex = e.Curve3dIndex,
        TrimIndices = e.TrimIndices,
        StartIndex = e.StartIndex,
        EndIndex = e.EndIndex,
        ProxyCurveIsReversed = e.ProxyCurveIsReversed,
        Domain = Interval(
            start = e.Domain[0],
            end = e.Domain[1])
    ) for e in brep.Edges()]

    sp_brep.Loops = [
            BrepLoop(
            FaceIndex = l.FaceIndex,
            TrimIndices = l.TrimIndices,
            Type = BrepLoopType.Unknown ,#l.Type    
        ) for l in brep.Loops()]

    sp_brep.Faces = [
        BrepFace(
            SurfaceIndex = f.SurfaceIndex,
            OuterLoopIndex = f.OuterLoopIndex,
            OrientationReversed = f.OrientationReversed,
            LoopIndices = [f.LoopIndices]
        ) for f in brep.Faces()]
    
    sp_brep.Trims = [
        BrepTrim(
            EdgeIndex = t.EdgeIndex,
            StartIndex = t.StartIndex,
            EndIndex = t.EndIndex,
            FaceIndex = t.FaceIndex,
            LoopIndex = t.LoopIndex,
            CurveIndex = t.CurveIndex,
            IsoStatus = t.IsoStatus,
            TrimType = BrepTrimType.Unknown,# t.TrimType,
            IsReversed = t.IsReversed
            # Domain = Interval( start = t.Domain[0], end = t.Domain[0])
        ) for t in brep.Trims()]

    return sp_brep

@wim, I can see you moved the thread. I know I am using Grasshopper as an example. But I believe the issue is not with Grasshopper but rather how I construct the NURBS values. This is why I put it under Rhino.

Hi -

The general “Rhino” category is for issues with out-of-the-box Rhino. Since you are using both C++ and Python, I’m happy to move this thread to “Rhino Developer”…
-wim

1 Like

Hi @design12,

An easy way to convert a mesh to a Brep, with openNURBS, use to use either ON_BrepFromMesh or ON_BrepFromMeshWithNgons.

– Dale

Hi @dale,
Thanks, I’ll give OpenNurbs a try.