tpaviot / pythonocc-core

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

Extend bounded surface #445

Open MAN90 opened 7 years ago

MAN90 commented 7 years ago

I am working on removal of fillets from a solid imported as STEP file. I could determine the surfaces connected to the fillet surface and now need to extend them in order to find their intersection. Here is the relevant code snippet: for n_face in neighbour_faces : aGeomSurfHandle = BRep_Tool.Surface(n_face) aBoundSurfHandle = Handle_Geom_BoundedSurface.DownCast(aGeomSurfHandle) L = 2 * r_0 geomlib_ExtendSurfByLength(aBoundSurfHandle, L, 1, True, False)

Here, neighbour_faces is a list of the faces, r_0 is the fillet radius I get the following error: File "C:\Program Files\Anaconda3\lib\site-packages\OCC\GeomLib.py", line 617, in geomlib_ExtendSurfByLength return _GeomLib.geomlib_ExtendSurfByLength(*args) RuntimeError: Standard_NullObject GeomAdaptor_Surface::Load

Could someone please help me understand where I went wrong. I use the following software config: Anaconda 3; PythonOCC-core 0.17; Python 3.5.2

Thanks and regards Paras

jf--- commented 7 years ago

looks like aBoundSurfHandle is a null object. Can you call aBoundSurfHandle.IsNull()? I think you can get the surface from the aGeomSurfHandle's object. It would be easier to help when provided with a complete example.

MAN90 commented 7 years ago

Hi, Thanks for the response. Yes, aBoundSurfHandle is NULL. The function geomlib_ExtendSurfByLength() requires a Handle_Geom_BoundedSurface

Below is the code of the function. Here, I first find the faces connected to the cylindrical surface forming the fillet and store them into neighbour_faces. Then i want to extend these faces and find their intersection.

def remove_filet(solid, r_0) :
    r_lim = 5
    aSolidTopo = Topo(solid, True)
    possible_fillet_surfs = get_possible_fillet_surfaces(solid, r_lim)
    assert  len(possible_fillet_surfs), "This solid has no surfaces which could have fillets"
    for fillet_face in possible_fillet_surfs :
        if is_simple_fillet(fillet_face, r_lim):
            fillet_curves = get_fillet_curves(fillet_face)
            aSurfHandle = BRep_Tool.Surface(fillet_face)
            print("face is ",fillet_face)
        # TODO find the surface connected to each edge of the fillet surface and store them as list of edge,surf pairs
            neighbour_faces = list()
            neighbour_face_edges = list()
            aFaceTopo = Topo(fillet_face, True)
            for edge in aFaceTopo.edges():
                for face in aSolidTopo.faces_from_edge(edge):
                    if not(face.IsSame(fillet_face)):
                        neighbour_faces.append(face)
                        neighbour_face_edges.append(edge)
            print("this face has ", len(neighbour_faces), " faces connected to it")
            for n_face in neighbour_faces :
                aGeomSurfHandle = BRep_Tool.Surface(n_face)
                L = 2 * r_0
                aBoundSurfHandle =  Handle_Geom_BoundedSurface.DownCast(aGeomSurfHandle)
                if aBoundSurfHandle.IsNull():
                    print ("this bounded surf handle is a null handle")
                geomlib_ExtendSurfByLength(aBoundSurfHandle, L, 3, True, False)
                color = Quantity_Color(random.random(),
                                  random.random(),
                                  random.random(),
                                  Quantity_TOC_RGB)
                display.DisplayColoredShape(n_face, color)

The picture below shows the 4 faces which I want to extend and the one surface missing is the one which forms the fillet.

Thanks and regards Paras

capture

tpaviot commented 7 years ago

@MAN90 can u please copy/paste (or gist) a full code so that I can easily test your code

tpaviot commented 7 years ago

Personal note: maybe some relevant code can be found here https://github.com/tpaviot/oce/blob/master/src/ChFi3d/ChFi3d_Builder_C1.cxx

MAN90 commented 7 years ago

Hi, Here is the complete code with the test geometry. I need to find the intersection of surfaces which do not intersect currently, but would intersect when extended. Thus, I could get the original edge on which the fillet was built.

file_upload_issue_445.zip

MarAlder commented 5 years ago

Has any solution been found for this case?

numerical2017 commented 1 year ago

I tried to use the function geomlib_ExtendSurfByLength with the latest PythonOCC version 7.7.0 but I'm not able to let it work...maybe I'm doing something wrong. I just created a simple Geom_BSplineSurface and then I tried to extend it passing it directly to the function. Does anybody has a small code example that works with one of the latest versions?

Squirrel8475 commented 1 year ago

Hello, I have problems too. As no working example popped out in the last five years, I was wondering if it is about users not handling the function properly (suggested by the "howto" flag) or if it is a deeper problem?

tpaviot commented 1 year ago

No progress on that side, here is the code updated to the latest pythonocc version:

# General Python Libraries
import os
import sys
import random

# OpenCASCADE libraries
from OCC.Core.STEPControl import STEPControl_Reader
from OCC.Core.IFSelect import IFSelect_RetDone, IFSelect_ItemsByEntity
from OCC.Core.TopoDS import TopoDS_Solid, TopoDS_Face
from OCC.Core.TopAbs import TopAbs_SOLID
from OCC.Core.Quantity import Quantity_Color, Quantity_TOC_RGB
from OCC.Core.gp import gp_Circ, gp_Pnt
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
from OCC.Core.BRep import BRep_Tool
from OCC.Core.GeomLib import geomlib
from OCC.Core.Geom import Geom_BoundedSurface
from OCC.Core.PrsDim import PrsDim_RadiusDimension
from OCC.Core.ShapeUpgrade import ShapeUpgrade_ShapeConvertToBezier

# Helper functions
from OCC.Extend.TopologyUtils import TopologyExplorer

# User Interface GUI
from OCC.Display.SimpleGui import init_display

#######################################################################################################################
# fucntion to read a STEP file and returns a list of solid shapes
def read_STEP(filepath):
    step_reader = STEPControl_Reader()
    status = step_reader.ReadFile(filepath)

    if status == IFSelect_RetDone:  # check status
        failsonly = False
        step_reader.PrintCheckLoad(failsonly, IFSelect_ItemsByEntity)
        step_reader.PrintCheckTransfer(failsonly, IFSelect_ItemsByEntity)
        ok = step_reader.TransferRoot(1)
        _nbs = step_reader.NbShapes()
        aResShape = step_reader.Shape(1)
    else:
        print("Error: can't read file.")
        sys.exit(0)
    solid_list = list()
    aTopo = TopologyExplorer(aResShape)
    num_solids = aTopo.number_of_solids()
    print("number of solids imported form the STEP file : ", num_solids)
    for solid in aTopo.solids():
        solid_list.append(solid)
    return solid_list

# function to check if an edge could be circular arc of a fillet
def could_be_fillet_curve(edge, r_0):
    aRadDim = PrsDim_RadiusDimension(edge)
    curve = aRadDim.Circle()
    curve_radius = curve.Radius()
    if curve_radius < r_0:
        return curve_radius
    else:
        return False

# function which returns a list of possible fillet surfaces for a given solid
def get_possible_fillet_surfaces(solid, r_0):
    aSolidTopo = TopologyExplorer(solid, True)
    possible_surfaces = list()
    # iterating over faces of the solid
    for face in aSolidTopo.faces():
        curve_ctr = 0
        aFaceTopo = TopologyExplorer(face, True)
        # iterating over edges of a face
        for edge in aFaceTopo.edges():
            if could_be_fillet_curve(edge, r_0):
                curve_ctr += 1
        if curve_ctr >= 2:
            possible_surfaces.append(face)
    return possible_surfaces

# fucntion to check if the face forms a simple fillet
def is_simple_fillet(face, r_0):
    curve_radii = list()
    aFaceTopo = TopologyExplorer(face, True)
    for edge in aFaceTopo.edges():
        r = could_be_fillet_curve(edge, r_0)
        if r:
            curve_radii.append(r)
    if curve_radii.count(curve_radii[0]) == len(curve_radii) and len(curve_radii) == 2:
        print("This surface forms a simple fillet ")
        return True
    else:
        print("This surface does not form a simple fillet ")
        return False

# function to get the curves for  simple fillet
def get_fillet_curves(face):
    aFaceTopo = TopologyExplorer(face)
    curve_list = list()
    for edge in aFaceTopo.edges():
        aRadDim = PrsDim_RadiusDimension(edge)
        curve_list.append(aRadDim.Circle())
    return curve_list

# function to remove fillet
def remove_filet(solid, r_0):
    r_lim = 5
    aSolidTopo = TopologyExplorer(solid, True)
    possible_fillet_surfs = get_possible_fillet_surfaces(solid, r_lim)
    assert len(
        possible_fillet_surfs
    ), "This solid has no surfaces which could have fillets"
    for fillet_face in possible_fillet_surfs:
        if is_simple_fillet(fillet_face, r_lim):
            fillet_curves = get_fillet_curves(fillet_face)
            aSurfHandle = BRep_Tool.Surface(fillet_face)
            print("face is ", fillet_face)
            # TODO find the surface connected to each edge of the fillet surface and store them as list of edge,surf pairs
            neighbour_faces = list()
            neighbour_face_edges = list()
            aFaceTopo = TopologyExplorer(fillet_face, True)
            for edge in aFaceTopo.edges():
                for face in aSolidTopo.faces_from_edge(edge):
                    if not (face.IsSame(fillet_face)):
                        neighbour_faces.append(face)
                        neighbour_face_edges.append(edge)
            print("this face has ", len(neighbour_faces), " faces connected to it")
            for n_face in neighbour_faces:
                aGeomSurf = BRep_Tool.Surface(n_face)
                L = 2 * r_0
                try:
                    aBoundSurf = Geom_BoundedSurface.DownCast(aGeomSurf)
                    geomlib.ExtendSurfByLength(aBoundSurf, L, 3, True, False)
                except:
                    print("Surface extension failed")
                color = Quantity_Color(
                    random.random(), random.random(), random.random(), Quantity_TOC_RGB
                )
                display.DisplayColoredShape(n_face, color)

if __name__ == "__main__":
    # input parameters
    FILEDIR = "Test_Geoms"
    FILENAME = "BoxSingleFillet.STEP"

    # reading the file to get the compound
    filepath = FILENAME
    solids = read_STEP(filepath)

    # Initializing the GUI
    display, start_display, add_menu, add_fucntion_to_menu = init_display()
    # display.DisplayShape(solids[0])

    remove_filet(solids[0], 0.01)

    display.FitAll()
    display.View_Top()
    start_display()

The issue deals with the geomlib.ExtendSurfByLength function, which takes a Geom_BoundedSurface as a parameter, but the Donwcast fails

Squirrel8475 commented 1 year ago

Thank you for your quick reply. Indeed arriving from a Geom_Surface and trying to downcast with Geom_BoundedSurface does not work. But additionnally, starting from a a Geom_BSplineSurface which is a Geom_BoundedSurface by inheritance does seem not work either (see the message above from @numerical2017 ) and in that case no downcast is needed. So maybe two different issues?

tpaviot commented 1 year ago

I never played with Geom_BoundedSurface and geomlib.ExtendSurfByLength, we should first try to have a simple and working example.

Squirrel8475 commented 1 year ago

20231018_extendsurface

The example is basic, it is created based on the available example to generate a Bspline surface, and only one additional line of code is added for geomlib.ExtendSurfByLength. It is unfortunately a non working example because the output surface is the same than the input. . Thank you for your help.

    from OCC.Core.gp import gp_Pnt,gp_Vec
    from OCC.Core.Geom import Geom_BezierSurface,Geom_BSplineSurface
    from OCC.Core.TColGeom import TColGeom_Array2OfBezierSurface
    from OCC.Core.TColgp import TColgp_Array2OfPnt 
    from OCC.Core.GeomConvert import GeomConvert_CompBezierSurfacesToBSplineSurface
    from OCC.Display.SimpleGui import init_display
    from OCC.Core.GeomLib import geomlib

    #==============================================================================
    def create_surface():
        # Based on https://github.com/tpaviot/pythonocc-demos/blob/master/examples/core_geometry_bspline.py
        array1 = TColgp_Array2OfPnt(1, 4, 1, 4)
        array1.SetValue(1, 1, gp_Pnt(1, 1, -1))
        array1.SetValue(1, 2, gp_Pnt(2, 1, 0))
        array1.SetValue(1, 3, gp_Pnt(3, 1, -1))
        array1.SetValue(1, 4, gp_Pnt(4, 1, 0))
        array1.SetValue(2, 1, gp_Pnt(1, 2, 3))
        array1.SetValue(2, 2, gp_Pnt(2, 2, 5))
        array1.SetValue(2, 3, gp_Pnt(3, 2, 2))
        array1.SetValue(2, 4, gp_Pnt(4, 2, 3))
        array1.SetValue(3, 1, gp_Pnt(1, 3, 2))
        array1.SetValue(3, 2, gp_Pnt(2, 3, 1))
        array1.SetValue(3, 3, gp_Pnt(3, 3, 0))
        array1.SetValue(3, 4, gp_Pnt(4, 3, 1))
        array1.SetValue(4, 1, gp_Pnt(1, 4, 0))
        array1.SetValue(4, 2, gp_Pnt(2, 4, -1))
        array1.SetValue(4, 3, gp_Pnt(3, 4, 0))
        array1.SetValue(4, 4, gp_Pnt(4, 4, -1))
        a_bezier_surface = Geom_BezierSurface(array1)

        a_bezier_array = TColGeom_Array2OfBezierSurface(1, 1, 1, 1)
        a_bezier_array.SetValue(1, 1, a_bezier_surface)

        a_converted_surface = GeomConvert_CompBezierSurfacesToBSplineSurface(a_bezier_array)
        if not a_converted_surface.IsDone(): raise ValueError('GeomConvert failed')
        poles = a_converted_surface.Poles()
        uknots = a_converted_surface.UKnots()
        vknots = a_converted_surface.VKnots()
        umult = a_converted_surface.UMultiplicities()
        vmult = a_converted_surface.VMultiplicities()
        udeg = a_converted_surface.UDegree()
        vdeg = a_converted_surface.VDegree()

        a_bspline_surf = Geom_BSplineSurface( poles, uknots, vknots, umult, vmult, udeg, vdeg,False,False)
        a_bspline_surf.Translate(gp_Vec(0,0,2))
        return a_bspline_surf

    #==============================================================================
    if __name__ == '__main__':

        display, start_display, add_menu, add_function_to_menu = init_display()

        a_bspline_surf=create_surface()
        display.DisplayShape(a_bspline_surf,color='BLUE',update=False)

        Lenght=10 ; Cont=1 ; InU=True ; After=True
        geomlib.ExtendSurfByLength(a_bspline_surf,Lenght,Cont,InU,After)
        display.DisplayShape(a_bspline_surf,color='RED',update=True)

        start_display()
rainman110 commented 1 year ago

Use geomconvert to create a bspline surface, see https://dev.opencascade.org/doc/refman/html/class_geom_convert.html#aec7d0c9e937cc0bcbe97fba8b3c360bf This is safer than downcast,as it will potentially also use some algorithms , if the surface is no bspline yet

tpaviot commented 1 year ago

BRepOffset_Tool.EnlargeFace or BRepOffset_Tool.ExtendFace should also be interesting pointers https://dev.opencascade.org/doc/refman/html/class_b_rep_offset___tool.html#add9d760d50704def44d1a8bb5b70a6cb

Squirrel8475 commented 1 year ago

BRepOffset_Tool.EnlargeFace works for my application. Thank you for your help

The meaning of the options is not fully clear to me though. I did not try BRepOffset_Tool.ExtentFace and I have just seen there is also a BRepLib::ExtendFace which might be helpful to understand how things work.

I am not affected by the other issue mentionned above, the Downcast returning None.

tpaviot commented 1 year ago

@Squirrel8475 thank you for the feedback, feel free to share any code snippet to explain you use case and how the BRepLib::ExtendFace method can be used

Squirrel8475 commented 1 year ago

@tpaviot sure, I just needed a bit more time but please find below the code snippet and the obtained results.

20231025_picture

# -*- coding: utf-8 -*-
from OCC.Core.gp import gp_Pnt,gp_Vec
from OCC.Core.Geom import Geom_BezierSurface,Geom_BSplineSurface
from OCC.Core.TColGeom import TColGeom_Array2OfBezierSurface
from OCC.Core.TColgp import TColgp_Array2OfPnt 
from OCC.Core.GeomConvert import GeomConvert_CompBezierSurfacesToBSplineSurface
from OCC.Display.SimpleGui import init_display
from OCC.Core.GeomLib import geomlib
from OCC.Core.BRepOffset import BRepOffset_Tool
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace 
from OCC.Core.TopoDS import TopoDS_Face

#==============================================================================
def create_surface(event=None):
    # Reference:
    # https://github.com/tpaviot/pythonocc-demos/blob/master/examples/core_geometry_bspline.py
    array1 = TColgp_Array2OfPnt(1, 4, 1, 4)
    array1.SetValue(1, 1, gp_Pnt(1, 1, -1))
    array1.SetValue(1, 2, gp_Pnt(2, 1, 0))
    array1.SetValue(1, 3, gp_Pnt(3, 1, -1))
    array1.SetValue(1, 4, gp_Pnt(4, 1, 0))
    array1.SetValue(2, 1, gp_Pnt(1, 2, 3))
    array1.SetValue(2, 2, gp_Pnt(2, 2, 5))
    array1.SetValue(2, 3, gp_Pnt(3, 2, 2))
    array1.SetValue(2, 4, gp_Pnt(4, 2, 3))
    array1.SetValue(3, 1, gp_Pnt(1, 3, 2))
    array1.SetValue(3, 2, gp_Pnt(2, 3, 1))
    array1.SetValue(3, 3, gp_Pnt(3, 3, 0))
    array1.SetValue(3, 4, gp_Pnt(4, 3, 1))
    array1.SetValue(4, 1, gp_Pnt(1, 4, 0))
    array1.SetValue(4, 2, gp_Pnt(2, 4, -1))
    array1.SetValue(4, 3, gp_Pnt(3, 4, 0))
    array1.SetValue(4, 4, gp_Pnt(4, 4, -1))
    a_bezier_surface = Geom_BezierSurface(array1)

    a_bezier_array = TColGeom_Array2OfBezierSurface(1, 1, 1, 1)
    a_bezier_array.SetValue(1, 1, a_bezier_surface)

    a_converted_surface = GeomConvert_CompBezierSurfacesToBSplineSurface(a_bezier_array)
    if not a_converted_surface.IsDone(): raise ValueError('GeomConvert failed')
    poles = a_converted_surface.Poles()
    uknots = a_converted_surface.UKnots()
    vknots = a_converted_surface.VKnots()
    umult = a_converted_surface.UMultiplicities()
    vmult = a_converted_surface.VMultiplicities()
    udeg = a_converted_surface.UDegree()
    vdeg = a_converted_surface.VDegree()

    a_bspline_surf = Geom_BSplineSurface( poles, uknots, vknots, umult, vmult, udeg, vdeg,False,False)
    a_bspline_surf.Translate(gp_Vec(0,0,2))
    return a_bspline_surf

#==============================================================================
if __name__ == '__main__':

    # Surface extension - trials and errors
    # Warning: using multiple instances of Display is not a valid approach, just for quick & dirty purpose here

    # Create a Bspline surface and the corresponging TopoDS Face
    # A Bspline surface is chosen as it seems not possible to come 
    # from a Geom_Surface  due to the Downcast returning None as per
    # ticket #445 from @Man90. The ticket is still open for this issue.
    # https://github.com/tpaviot/pythonocc-core/issues/445
    a_bspline_surf=create_surface()
    a_face_maker=BRepBuilderAPI_MakeFace(a_bspline_surf,1e-6)
    a_face_maker.Build()
    the_original_topods_face=a_face_maker.Face()

    # First try: geomlib.ExtendSurfByLength
    #============================================
    # Should be similar to the description of @numerical2017 
    # in ticket  https://github.com/tpaviot/pythonocc-core/issues/445
    Lenght=10 ; Cont=1 ; InU=True ; After=True
    geomlib.ExtendSurfByLength(a_bspline_surf,Lenght,Cont,InU,After)
    display, start_display, add_menu, add_function_to_menu = init_display()
    display.DisplayShape(a_bspline_surf,color='red',update=False)
    display.DisplayShape(the_original_topods_face,color='cyan',update=True)
    start_display()
    # This example is not working as the surface is not modified, more work
    # needed here to clarify the usage.

    # Second try: BRepOffset_Tool.EnLargeFace
    #==============================================
    #
    # * Dev doc:
    #   https://dev.opencascade.org/doc/refman/html/class_b_rep_offset___tool.html#add9d760d50704def44d1a8bb5b70a6cb
    #

    ChangeGeom         = True
    UpDatePCurve       = True
    enlargeu           = True
    enlargeVfirst      = True
    enlargeVlast       = True
    theExtensionMode   =2
    lenght             =0.5
    theLenBeforeUfirst = lenght
    theLenAfterUlast   = lenght
    theLenBeforeVfirst = lenght
    theLenAfterVlast   = lenght
    a_new_topo_ds_face = TopoDS_Face()

    BRepOffset_Tool.EnLargeFace(the_original_topods_face,a_new_topo_ds_face,ChangeGeom,
        UpDatePCurve,enlargeu,enlargeVfirst,enlargeVlast,theExtensionMode,
        theLenBeforeUfirst, theLenAfterUlast, theLenBeforeVfirst, theLenAfterVlast)

    display, start_display, add_menu, add_function_to_menu = init_display()
    display.DisplayShape(a_new_topo_ds_face,color='red',update=False)
    display.DisplayShape(the_original_topods_face,color='cyan',update=True)
    start_display()

    # This example is producing a modified surface, but it is not simply
    # an extension: the "corners" are "joined". It might make sense that 
    # additionnal algorithms are running behing because the class
    # belonging to the "Offset" package, for instance offseting faces from
    # a dice required offset + extension + blending the corners, but
    # no further digging was done to clarify that. 
    # BRepOffset_Tool.ExtentFace also not tested.

    # Third try: breplib_ExtendFace
    #==============================================
    #
    from OCC.Core.BRepLib import breplib_ExtendFace

    theExtVal=0.5
    theExtUMin=False
    theExtUMax=False
    theExtVMin=True
    theExtVMax=True

    the_modified_topods_face=TopoDS_Face()
    breplib_ExtendFace(the_original_topods_face,theExtVal,theExtUMin,theExtUMax,
         theExtVMin, theExtVMax, the_modified_topods_face)

    display, start_display, add_menu, add_function_to_menu = init_display()
    display.DisplayShape(the_modified_topods_face,color='red',update=False)
    display.DisplayShape(the_original_topods_face,color='cyan',update=True)
    start_display()

    # Faces are extended with the behavior expected for my application

    # Other classes that might be related to same topic
    #==============================================
    # BRepOffset_Tool.ExtentFace