Skip to content

Commit

Permalink
Added geometry intersections Issue #327 #328
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Jun 23, 2024
1 parent 29ad538 commit ce72ee2
Show file tree
Hide file tree
Showing 6 changed files with 495 additions and 191 deletions.
244 changes: 214 additions & 30 deletions src/build123d/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,38 @@
RAD2DEG = 180 / pi


def _parse_intersect_args(*args, **kwargs):
axis, plane, vector, location, shape = (None,) * 5

if args:
if isinstance(args[0], Axis):
axis = args[0]
elif isinstance(args[0], Plane):
plane = args[0]
elif isinstance(args[0], Location):
location = args[0]
elif isinstance(args[0], (Vector, tuple)):
vector = Vector(args[0])
elif hasattr(args[0], "wrapped"):
shape = args[0]
else:
raise ValueError(f"Unexpected argument type {type(args[0])}")

unknown_args = ", ".join(
set(kwargs.keys()).difference(["axis", "plane", "location", "vector", "shape"])
)
if unknown_args:
raise ValueError(f"Unexpected argument(s) {unknown_args}")

axis = kwargs.get("axis", axis)
plane = kwargs.get("plane", plane)
vector = kwargs.get("vector", vector)
location = kwargs.get("location", location)
shape = kwargs.get("shape", shape)

return axis, plane, vector, location, shape


class Vector:
"""Create a 3-dimensional vector
Expand Down Expand Up @@ -397,6 +429,10 @@ def __abs__(self) -> float:
"""Vector length operator abs()"""
return self.length

def __and__(self: Plane, other: Union[Axis, Location, Plane, VectorLike, "Shape"]):
"""intersect vector with other &"""
return self.intersect(other)

def __repr__(self) -> str:
"""Display vector"""
return "Vector: " + str((self.X, self.Y, self.Z))
Expand Down Expand Up @@ -451,6 +487,40 @@ def rotate(self, axis: Axis, angle: float) -> Vector:
"""
return Vector(self.wrapped.Rotated(axis.wrapped, pi * angle / 180))

@overload
def intersect(self, vector: VectorLike) -> Union[Vector, None]:
"""Find intersection of vector and vector"""

@overload
def intersect(self, location: Location) -> Union[Vector, None]:
"""Find intersection of location and vector"""

@overload
def intersect(self, axis: Axis) -> Union[Vector, None]:
"""Find intersection of axis and vector"""

@overload
def intersect(self, plane: Plane) -> Union[Vector, None]:
"""Find intersection of plane and vector"""

def intersect(self, *args, **kwargs):
axis, plane, vector, location, shape = _parse_intersect_args(*args, **kwargs)

if axis is not None:
return axis.intersect(self)

elif plane is not None:
return plane.intersect(self)

elif vector is not None and self == vector:
return vector

elif location is not None:
return location.intersect(self)

elif shape is not None:
return shape.intersect(self)


#:TypeVar("VectorLike"): Tuple of float or Vector defining a position in space
VectorLike = Union[
Expand Down Expand Up @@ -686,6 +756,66 @@ def __neg__(self) -> Axis:
"""Flip direction operator -"""
return self.reverse()

def __and__(self: Plane, other: Union[Axis, Location, Plane, VectorLike, "Shape"]):
"""intersect vector with other &"""
return self.intersect(other)

@overload
def intersect(self, vector: VectorLike) -> Union[Vector, None]:
"""Find intersection of vector and axis"""

@overload
def intersect(self, location: Location) -> Union[Location, None]:
"""Find intersection of location and axis"""

@overload
def intersect(self, axis: Axis) -> Union[Axis, None]:
"""Find intersection of axis and axis"""

@overload
def intersect(self, plane: Plane) -> Union[Axis, None]:
"""Find intersection of plane and axis"""

def intersect(self, *args, **kwargs):
axis, plane, vector, location, shape = _parse_intersect_args(*args, **kwargs)

if axis is not None:
if self.is_coaxial(axis):
return self
else:
pnt = self.as_infinite_edge().intersect(axis.as_infinite_edge())
if pnt is not None:
return Vector(pnt)

elif plane is not None:
return plane.intersect(self)

elif vector is not None:
# Create a vector from the origin to the point
vec_to_point: Vector = vector - self.position

# Project the vector onto the direction of the axis
projected_length = vec_to_point.dot(self.direction)
projected_vec = self.direction * projected_length + self.position

# Calculate the difference between the original vector and the projected vector
if vector == projected_vec:
return vector

elif location is not None:
# Find the "direction" of the location
location_dir = Plane(location).z_dir

# Is the location on the axis with the same direction?
if (
self.intersect(location.position) is not None
and location_dir == self.direction
):
return location

elif shape is not None:
return shape.intersect(self)


class BoundBox:
"""A BoundingBox for a Shape"""
Expand Down Expand Up @@ -1322,6 +1452,10 @@ def __neg__(self) -> Location:
"""Flip the orientation without changing the position operator -"""
return Location(-Plane(self))

def __and__(self: Plane, other: Union[Axis, Location, Plane, VectorLike, "Shape"]):
"""intersect axis with other &"""
return self.intersect(other)

def to_axis(self) -> Axis:
"""Convert the location into an Axis"""
return Axis.Z.located(self)
Expand Down Expand Up @@ -1364,6 +1498,40 @@ def __str__(self):
orientation_str = ", ".join((f"{v:.2f}" for v in self.to_tuple()[1]))
return f"Location: (position=({position_str}), orientation=({orientation_str}))"

@overload
def intersect(self, vector: VectorLike) -> Union[Vector, None]:
"""Find intersection of vector and location"""

@overload
def intersect(self, location: Location) -> Union[Location, None]:
"""Find intersection of location and location"""

@overload
def intersect(self, axis: Axis) -> Union[Location, None]:
"""Find intersection of axis and location"""

@overload
def intersect(self, plane: Plane) -> Union[Location, None]:
"""Find intersection of plane and location"""

def intersect(self, *args, **kwargs):
axis, plane, vector, location, shape = _parse_intersect_args(*args, **kwargs)

if axis is not None:
return axis.intersect(self)

elif plane is not None:
return plane.intersect(self)

elif vector is not None and self.position == vector:
return vector

elif location is not None and self == location:
return self

elif shape is not None:
return shape.intersect(self)


class LocationEncoder(json.JSONEncoder):
"""Custom JSON Encoder for Location values
Expand Down Expand Up @@ -1983,6 +2151,10 @@ def __mul__(
)
return result

def __and__(self: Plane, other: Union[Axis, Location, Plane, VectorLike, "Shape"]):
"""intersect plane with other &"""
return self.intersect(other)

def __repr__(self):
"""To String
Expand Down Expand Up @@ -2042,7 +2214,7 @@ def shift_origin(self, locator: Union[Axis, VectorLike, "Vertex"]) -> Plane:
if not self.contains(locator):
raise ValueError(f"{locator} is not located within plane")
elif isinstance(locator, Axis):
new_origin = self.find_intersection(locator)
new_origin = self.intersect(locator)
if new_origin is None:
raise ValueError(f"{locator} doesn't intersect the plane")
else:
Expand Down Expand Up @@ -2245,47 +2417,48 @@ def contains(
return return_value

@overload
def find_intersection(self, axis: Axis) -> Union[Vector, None]:
def intersect(self, vector: VectorLike) -> Union[Vector, None]:
"""Find intersection of vector and plane"""

@overload
def intersect(self, location: Location) -> Union[Location, None]:
"""Find intersection of location and plane"""

@overload
def intersect(self, axis: Axis) -> Union[Axis, Vector, None]:
"""Find intersection of axis and plane"""

@overload
def find_intersection(self, plane: Plane) -> Union[Axis, None]:
def intersect(self, plane: Plane) -> Union[Axis, None]:
"""Find intersection of plane and plane"""

def find_intersection(self, *args, **kwargs):
axis, plane = None, None

if args:
if isinstance(args[0], Axis):
axis = args[0]
elif isinstance(args[0], Plane):
plane = args[0]
else:
raise ValueError(f"Unexpected argument type {type(args[0])}")
@overload
def intersect(self, shape: "Shape") -> Union["Shape", None]:
"""Find intersection of plane and shape"""

unknown_args = ", ".join(set(kwargs.keys()).difference(["axis", "plane"]))
if unknown_args:
raise ValueError(f"Unexpected argument(s) {unknown_args}")
def intersect(self, *args, **kwargs):

axis: Axis = kwargs.get("axis", axis)
plane: Plane = kwargs.get("plane", plane)
axis, plane, vector, location, shape = _parse_intersect_args(*args, **kwargs)

if axis is not None:
geom_line = Geom_Line(axis.wrapped)
geom_plane = Geom_Plane(self.local_coord_system)
if self.contains(axis):
return axis
else:
geom_line = Geom_Line(axis.wrapped)
geom_plane = Geom_Plane(self.local_coord_system)

intersection_calculator = GeomAPI_IntCS(geom_line, geom_plane)
intersection_calculator = GeomAPI_IntCS(geom_line, geom_plane)

if (
intersection_calculator.IsDone()
and intersection_calculator.NbPoints() == 1
):
# Get the intersection point
intersection_point = Vector(intersection_calculator.Point(1))
else:
intersection_point = None
if (
intersection_calculator.IsDone()
and intersection_calculator.NbPoints() == 1
):
# Get the intersection point
intersection_point = Vector(intersection_calculator.Point(1))
else:
intersection_point = None

return intersection_point
return intersection_point

elif plane is not None:
surface1 = Geom_Plane(self.wrapped)
Expand All @@ -2297,3 +2470,14 @@ def find_intersection(self, *args, **kwargs):
# Extract the axis from the intersection line
axis = intersection_line.Position()
return Axis(axis)

elif vector is not None and self.contains(vector):
return vector

elif location is not None:
pln = Plane(location)
if pln.origin == self.origin and pln.z_dir == self.z_dir:
return location

elif shape is not None:
return shape.intersect(self)
2 changes: 1 addition & 1 deletion src/build123d/objects_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ def __init__(
axis = Axis(start, direction)

intersection_pnts = [
i for edge in other.edges() for i in edge.intersections(axis)
i for edge in other.edges() for i in edge.find_intersection_points(axis)
]
if not intersection_pnts:
raise ValueError("No intersections found")
Expand Down
4 changes: 1 addition & 3 deletions src/build123d/operations_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,9 +794,7 @@ def project(
projection_axis = -Axis(pnt, workplane.z_dir * projection_flip)
else:
projection_axis = Axis(pnt, workplane.z_dir * projection_flip)
projection = workplane.to_local_coords(
workplane.find_intersection(projection_axis)
)
projection = workplane.to_local_coords(workplane.intersect(projection_axis))
if projection is not None:
projected_points.append(projection)

Expand Down
2 changes: 1 addition & 1 deletion src/build123d/operations_part.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ def project_workplane(
# Project a point off the origin to find the projected x direction
screen = Face.make_rect(1e9, 1e9, plane=workplane)
x_dir_point_axis = Axis(origin + x_dir, projection_dir)
projection = screen.find_intersection(x_dir_point_axis)
projection = screen.find_intersection_points(x_dir_point_axis)
if not projection:
raise ValueError("x_dir perpendicular to projection_dir")

Expand Down
Loading

0 comments on commit ce72ee2

Please sign in to comment.