Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an is_interior property to the Edge class #816

Open
gumyr opened this issue Dec 15, 2024 · 4 comments
Open

Add an is_interior property to the Edge class #816

gumyr opened this issue Dec 15, 2024 · 4 comments
Assignees
Labels
enhancement New feature or request

Comments

@gumyr
Copy link
Owner

gumyr commented Dec 15, 2024

from build123d import *
from build123d import Shape, TOLERANCE
from ocp_vscode import show
from OCP.TopTools import TopTools_IndexedDataMapOfShapeListOfShape
import OCP.TopAbs as ta
from OCP.TopExp import TopExp
from OCP.TopoDS import TopoDS
from OCP.BRepOffset import BRepOffset_MakeOffset
from OCP.BRepAlgoAPI import BRepAlgoAPI_Section
from OCP.TopExp import TopExp, TopExp_Explorer
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Shape


def topo_explore_connected_faces(edge: Edge, parent: Shape = None) -> list[TopoDS_Face]:
    """Given an edge extracted from a Shape, return the topods_faces connected to it"""

    parent = parent if parent is not None else edge.topo_parent

    # make a edge --> faces mapping
    edge_face_map = TopTools_IndexedDataMapOfShapeListOfShape()
    TopExp.MapShapesAndAncestors_s(
        parent.wrapped, ta.TopAbs_EDGE, ta.TopAbs_FACE, edge_face_map
    )

    # Query the map
    faces = []
    if edge_face_map.Contains(edge.wrapped):
        face_list = edge_face_map.FindFromKey(edge.wrapped)
        for face in face_list:
            faces.append(TopoDS.Face_s(face))

    if len(faces) != 2:
        raise RuntimeError("Invalid # of faces connected to this edge")

    return faces


def offset_topods_face(face: TopoDS_Face, amount: float) -> TopoDS_Shape:
    """Offset a topods_face"""
    offsetor = BRepOffset_MakeOffset()
    offsetor.Initialize(face, Offset=amount, Tol=TOLERANCE)
    offsetor.MakeOffsetShape()

    return offsetor.Shape()


@property
def is_interior(self) -> bool:
    """
    Check if the edge is an interior edge.

    An interior edge lies between surfaces that are part of the body (internal
    to the geometry) and does not form part of the exterior boundary.

    Returns:
        bool: True if the edge is an interior edge, False otherwise.
    """
    # Find the faces connected to this edge and offset them
    topods_face_pair = topo_explore_connected_faces(self)
    offset_face_pair = [
        offset_topods_face(f, self.length / 100) for f in topods_face_pair
    ]

    # Intersect the offset faces
    sectionor = BRepAlgoAPI_Section(
        offset_face_pair[0], offset_face_pair[1], PerformNow=False
    )
    sectionor.Build()
    face_intersection_result = sectionor.Shape()

    # If an edge was created the faces intersect and the edge is interior
    explorer = TopExp_Explorer(face_intersection_result, ta.TopAbs_EDGE)
    return explorer.More()


Edge.is_interior = is_interior

# target = Box(2, 2, 1) - Box(1, 1, 1, align=(Align.MIN, Align.MIN, Align.CENTER))

path = RegularPolygon(5, 5).face().outer_wire()
profile = path.location_at(0) * (Circle(0.6) & Rectangle(2, 1))
target = sweep(profile, path, transition=Transition.RIGHT)

target_edges = target.edges()
inside_edges = [e for e in target_edges if e.is_interior]
outside_edges = target_edges - inside_edges
insides = Compound(inside_edges)
insides.color = Color("White")
outsides = Compound(outside_edges)
outsides.color = Color("Black")

show(target, insides, outsides)

image

@gumyr gumyr added the enhancement New feature or request label Dec 15, 2024
@gumyr gumyr added this to the Not Gating Release 1.0.0 milestone Dec 15, 2024
@gumyr gumyr self-assigned this Dec 15, 2024
@MatthiasJ1
Copy link
Contributor

Why not simply compare the face normals at the edge? Your implementation would also break for this case:

Screenshot

@gumyr
Copy link
Owner Author

gumyr commented Dec 17, 2024

Why not simply compare the face normals at the edge? Your implementation would also break for this case:
Screenshot

Would you share the code for this shape, I'd like to try it out. I spent a lot of time comparing face normals along the edge (even integrating them for non-planar surfaces) but I found it impossible to get a consistent edge direction in order to differentiate crossing normals from diverging normals. I think this implementation is actually quite robust - but we'll see.

@MatthiasJ1
Copy link
Contributor

def s(x, z): return Spline((0*x,0,0*z), (10*x,3,10*z), (20*x,0,20*z), (30*x,-3,10*z), (40*x,0,0*z))
p = [Pos(-5, 3) * s(1.25 1), Pos(5, 3) * s(0.75, 0.6)]
f = Face.make_surface_from_curves(*p)
p = extrude(f, -10, Axis.Y.direction)
p += mirror(p, Plane.XY)

@gumyr
Copy link
Owner Author

gumyr commented Dec 18, 2024

Thanks. It almost gets it right:
image

Here a single face has both interior and exterior edges and the algorithm sees the intersection of both ends and makes the wrong conclusion. A partial solution is to trim the face to just around the edge being tested to avoid this. However, the current algorithm is going to work on the vast majority of shapes so this enhancement could be done in a follow on change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants