tpaviot / pythonocc-core

Python package for 3D geometry CAD/BIM/CAM
GNU Lesser General Public License v3.0
1.33k stars 374 forks source link

Combining all edges of an outer wire into a edge fails to compute PCurve #970

Closed rohan-kekatpure closed 3 years ago

rohan-kekatpure commented 3 years ago

I'm having difficulty getting PythonOCC to compute PCurve of a newly created face. Specifically the command BRep_Tool().CurveOnSurface(newedge, newface) fails to return the PCurve. It just returns the tuple (0.0, 0.0). As far as I can tell, I have set up the topology correctly. The constituent wires and edges all have the expected structure. It is just that the CurveOnSurface command fails to compute the PCurve.

I know that newedge lies on newface. How? newface is just a face created from the BSpline of the original face and newedge is an edge created by combining splines of the edges on the original face. So I know that the projection must compute.

Here is the relevant code (apologies for the wall of code, but the setup steps are long):

    import sys
    from typing import Any, NewType

    from OCC.Core.BRep import BRep_Tool, BRep_Tool_Curve
    from OCC.Core.BRepBuilderAPI import (BRepBuilderAPI_NurbsConvert,
                                         BRepBuilderAPI_MakeEdge,
                                         BRepBuilderAPI_MakeFace,
                                         BRepBuilderAPI_MakeWire)
    from OCC.Core.BRepTools import breptools_OuterWire
    from OCC.Core.GeomConvert import GeomConvert_CompCurveToBSplineCurve, geomconvert_CurveToBSplineCurve, \
        geomconvert_SurfaceToBSplineSurface
    from OCC.Core.IFSelect import IFSelect_RetDone, IFSelect_ItemsByEntity
    from OCC.Core.STEPControl import STEPControl_Reader
    from OCC.Core.TopoDS import topods_Face, TopoDS_Wire, topods_Edge, topods_Wire, TopoDS_Face
    from OCC.Extend.TopologyUtils import TopologyExplorer, WireExplorer

    NURBSObject = NewType('NURBSObject', Any)

    def _bspline_curve_from_wire(wire: NURBSObject) -> NURBSObject:
        if not isinstance(wire, TopoDS_Wire):
            raise TypeError("wire must be a TopoDS_Wire")

        composite_curve_builder = GeomConvert_CompCurveToBSplineCurve()
        edge_explorer = WireExplorer(wire)
        edges = list(edge_explorer.ordered_edges())
        for edge in edges:
            if BRep_Tool.Degenerated(edge):
                continue

            # convert to Nurbs edge
            nurbs_converter = BRepBuilderAPI_NurbsConvert(edge)
            nurbs_converter.Perform(edge)
            nurbs_edge = topods_Edge(nurbs_converter.Shape())
            nurbs_curve = BRep_Tool_Curve(nurbs_edge)[0]
            bspline_curve = geomconvert_CurveToBSplineCurve(nurbs_curve)
            tolerance = 1e-4
            composite_curve_builder.Add(bspline_curve, tolerance)

        comp_curve = composite_curve_builder.BSplineCurve()
        return comp_curve

    def _bspline_surface_from_face(face: NURBSObject) -> NURBSObject:
        if not isinstance(face, TopoDS_Face):
            raise TypeError("face must be a TopoDS_Face")

        nurbs_face = topods_Face(BRepBuilderAPI_NurbsConvert(face).Shape())
        surface = BRep_Tool.Surface(nurbs_face)
        return geomconvert_SurfaceToBSplineSurface(surface)

    def _new_wire_by_combining_edges(wire: NURBSObject) -> NURBSObject:
        wire = topods_Wire(wire)
        composite_curve = _bspline_curve_from_wire(wire)
        composite_edge = BRepBuilderAPI_MakeEdge(composite_curve).Edge()
        wire_builder = BRepBuilderAPI_MakeWire()
        wire_builder.Add(composite_edge)
        modified_wire = wire_builder.Wire()
        return modified_wire

    def main() -> None:
        step_reader = STEPControl_Reader()
        status = step_reader.ReadFile(sys.argv[1])

        if status != IFSelect_RetDone:
            raise ValueError('Error parsing STEP file')

        failsonly = False
        step_reader.PrintCheckLoad(failsonly, IFSelect_ItemsByEntity)
        step_reader.PrintCheckTransfer(failsonly, IFSelect_ItemsByEntity)
        step_reader.TransferRoot()
        shape = step_reader.Shape()
        topo_explorer = TopologyExplorer(shape)
        shape_faces = list(topo_explorer.faces())

        for i, face in enumerate(shape_faces):
            # Only compute Face 22 for illustration purposes
            if i != 22:
                continue

            surface_spline = _bspline_surface_from_face(face)
            face_explorer = TopologyExplorer(face)
            wires = list(face_explorer.wires())
            newwire = None
            for wire in wires:
                if wire == breptools_OuterWire(face):
                    newwire = _new_wire_by_combining_edges(wire)
                    break

            if newwire is None:
                return  # nothing to do

            # Make new face with new wire
            face_maker = BRepBuilderAPI_MakeFace(surface_spline, newwire)
            newface = face_maker.Face()

            # Now we get the PCurve of this wire
            newface_explorer = TopologyExplorer(newface)
            newface_wires = list(newface_explorer.wires())
            assert len(newface_wires) == 1

            newface_wire_explorer = WireExplorer(newface_wires[0])
            newface_edges = list(newface_wire_explorer.ordered_edges())
            assert len(newface_edges) == 1

            pcurve_object = BRep_Tool().CurveOnSurface(newface_edges[0], newface)
            # ^^^ This object is just a trivial (0.0, 0.0). It does not return pcurve

    if __name__ == '__main__':
        main()
rohan-kekatpure commented 3 years ago

I discovered that I am not able to create a PCurve at all. So I need to dig a bit more and may open another issue with a more focused issue.

tpaviot commented 3 years ago

The BRep_Tool().CurveOnSurface function has 4 different signatures, see https://dev.opencascade.org/doc/refman/html/class_b_rep___tool.html#afec881ea1e3d42d9de0999d8aa5f7544 Two of them return a Geom2d_Curve, and other two return nothing in c++ (void type), but will return a tuple (First, Last).

rohan-kekatpure commented 3 years ago

Thanks @tpaviot.

My real issue is the following. I'm constructing a brand new TopoDS_Face (with new wires, edges etc). The PCurve of the newly created edges on the newly created face does not yet exist. So BRep_Tool.CurveOnSurface returns a null. The PCurve first has to be constructed and attached to the new edge (using UpdateEdge) before BRep_Tool.CurveOnSurface can return it thereafter. However, I'm unable to create/compute this curve through Python because the PCurve construction routines (ShapeConstruct_ProjectCurveOnSurface().Perform()) requires an empty handle to a Geom2d_Curve. I dont know how to create empty Geom2d_Curve. I have looked through the docs and also scanned all functions that return a Geom2d_Curve. This difficulty was the subject of Issue # 971.

I ended up migrating to C++, and I could successfully compute the PCurve of the new edge on the new face using ShapeConstruct_ProjectCurveOnSurface(). So theoretically I'm not blocked and can continue my development in C++. But I would really like to use PythonOCC since my development and iterations are so much faster. Anyway, if you could point me to how to create empty Geom2d_Curve handle for passing to ShapeConstruct_ProjectCurveOnSurface().Perform() or show how ShapeConstruct_ProjectCurveOnSurface().Perform() should be used, that would be a great help. Thanks.