Skip to content

Commit

Permalink
Improve Edge.intersect & coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Jan 15, 2025
1 parent f9afe1f commit cd69e6e
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 35 deletions.
62 changes: 27 additions & 35 deletions src/build123d/topology/one_d.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@
import copy
import itertools
import warnings
from collections.abc import Iterable
from itertools import combinations
from math import radians, inf, pi, cos, copysign, ceil, floor
from typing import Tuple, Union, overload, TYPE_CHECKING

from collections.abc import Iterable
from typing import Literal, overload, TYPE_CHECKING
from typing_extensions import Self
from numpy import ndarray
from scipy.optimize import minimize
from scipy.spatial import ConvexHull

import OCP.TopAbs as ta
from OCP.BRep import BRep_Tool
Expand Down Expand Up @@ -163,12 +166,6 @@
VectorLike,
logger,
)
from numpy import ndarray
from scipy.optimize import minimize
from scipy.spatial import ConvexHull
from typing_extensions import Self

from typing import Literal

from .shape_core import (
Shape,
Expand Down Expand Up @@ -370,10 +367,10 @@ def common_plane(self, *lines: Edge | Wire | None) -> None | Plane:
of the infinite number of valid planes.
Args:
lines (sequence of Union[Edge,Wire]): edges in common with self
lines (sequence of Edge | Wire): edges in common with self
Returns:
Union[None, Plane]: Either the common plane or None
None | Plane: Either the common plane or None
"""
# pylint: disable=too-many-locals
# Note: BRepLib_FindSurface is not helpful as it requires the
Expand Down Expand Up @@ -912,7 +909,7 @@ def split(self, tool: TrimmingTool, keep: Keep = Keep.TOP):
Split this shape by the provided plane or face.
Args:
surface (Union[Plane,Face]): surface to segment shape
surface (Plane | Face): surface to segment shape
keep (Keep, optional): which object(s) to save. Defaults to Keep.TOP.
Returns:
Expand Down Expand Up @@ -1044,7 +1041,7 @@ def tangent_at(
is either a float (or int) parameter or a point that lies on the shape.
Args:
position (Union[float, VectorLike]): distance, parameter value, or
position (float | VectorLike): distance, parameter value, or
point on shape. Defaults to 0.5.
position_mode (PositionMode, optional): position calculation mode.
Defaults to PositionMode.PARAMETER.
Expand Down Expand Up @@ -1650,7 +1647,7 @@ def distribute_locations(
Distribute locations along edge or wire.
Args:
self: Union[Wire:Edge]:
self: Wire:Edge:
count(int): Number of locations to generate
start(float): position along Edge|Wire to start. Defaults to 0.0.
stop(float): position along Edge|Wire to end. Defaults to 1.0.
Expand Down Expand Up @@ -1824,10 +1821,10 @@ def intersect(
"""intersect Edge with Edge or Axis
Args:
other (Union[Edge, Axis]): other object
other (Edge | Axis): other object
Returns:
Union[Shape, None]: Compound of vertices and/or edges
Shape | None: Compound of vertices and/or edges
"""
edges: list[Edge] = []
planes: list[Plane] = []
Expand All @@ -1846,10 +1843,16 @@ def intersect(

# Find any edge / edge intersection points
points_sets: list[set[Vector]] = []
# Find crossing points
for edge_pair in combinations([self] + edges, 2):
intersection_points = edge_pair[0].find_intersection_points(edge_pair[1])
points_sets.append(set(intersection_points))

# Find common end points
self_end_points = set(Vector(v) for v in self.vertices())
edge_end_points = set(Vector(v) for edge in edges for v in edge.vertices())
common_end_points = set.intersection(self_end_points, edge_end_points)

# Find any edge / plane intersection points & edges
for edge, plane in itertools.product([self] + edges, planes):
# Find point intersections
Expand All @@ -1867,15 +1870,18 @@ def intersect(
points_sets.append(set(plane_intersection_points))

# Find edge intersections
if (edge_plane := edge.common_plane()) is not None: # is a 2D edge
if plane.z_dir in (edge_plane.z_dir, -edge_plane.z_dir):
edges_common_to_planes.append(edge)
if all(
plane.contains(v) for v in edge.positions(i / 7 for i in range(8))
): # is a 2D edge
edges_common_to_planes.append(edge)

edges.extend(edges_common_to_planes)

# Find the intersection of all sets
common_points = set.intersection(*points_sets)
common_vertices = [Vertex(*pnt) for pnt in common_points]
common_vertices = [
Vertex(pnt) for pnt in common_points.union(common_end_points)
]

# Find Edge/Edge overlaps
common_edges: list[Edge] = []
Expand Down Expand Up @@ -2054,20 +2060,6 @@ def trim_to_length(self, start: float, length: float) -> Edge:
new_edge = BRepBuilderAPI_MakeEdge(trimmed_curve).Edge()
return Edge(new_edge)

def _intersect_with_edge(self, edge: Edge) -> tuple[list[Vertex], list[Edge]]:
"""find intersection vertices and edges"""

# Find any intersection points
vertex_intersections = [
Vertex(pnt) for pnt in self.find_intersection_points(edge)
]

# Find Edge/Edge overlaps
intersect_op = BRepAlgoAPI_Common()
edge_intersections = self._bool_op((self,), (edge,), intersect_op).edges()

return vertex_intersections, edge_intersections


class Wire(Mixin1D, Shape[TopoDS_Wire]):
"""A Wire in build123d is a topological entity representing a connected sequence
Expand Down Expand Up @@ -2313,7 +2305,7 @@ def combine(
Combine a list of wires and edges into a list of Wires.
Args:
wires (Iterable[Union[Wire, Edge]]): unsorted
wires (Iterable[Wire | Edge]): unsorted
tol (float, optional): tolerance. Defaults to 1e-9.
Returns:
Expand Down
27 changes: 27 additions & 0 deletions tests/test_direct_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2027,6 +2027,33 @@ def test_intersection(self):

self.assertIsNone(b.intersect(b.moved(Pos(X=10))))

# Look for common vertices
e1 = Edge.make_line((0, 0), (1, 0))
e2 = Edge.make_line((1, 0), (1, 1))
e3 = Edge.make_line((1, 0), (2, 0))
i = e1.intersect(e2)
self.assertEqual(len(i.vertices()), 1)
self.assertEqual(tuple(i.vertex()), (1, 0, 0))
i = e1.intersect(e3)
self.assertEqual(len(i.vertices()), 1)
self.assertEqual(tuple(i.vertex()), (1, 0, 0))

# Intersect with plane
e1 = Edge.make_line((0, 0), (2, 0))
p1 = Plane.YZ.offset(1)
i = e1.intersect(p1)
self.assertEqual(len(i.vertices()), 1)
self.assertEqual(tuple(i.vertex()), (1, 0, 0))

e2 = Edge.make_line(p1.origin, p1.origin + 2 * p1.x_dir)
i = e2.intersect(p1)
self.assertEqual(len(i.vertices()), 2)
self.assertEqual(len(i.edges()), 1)
self.assertAlmostEqual(i.edge().length, 2, 5)

with self.assertRaises(ValueError):
e1.intersect("line")

def test_pos(self):
with self.assertRaises(TypeError):
Pos(0, "foo")
Expand Down

0 comments on commit cd69e6e

Please sign in to comment.