"""
design3d utils for calculating curves intersections.
"""
import math
import design3d
from design3d.utils.common_operations import (
get_abscissa_discretization,
get_plane_equation_coefficients,
get_point_distance_to_edge,
)
[docs]
def get_planar_circle3d_line_intersections(circle_3d, line, abs_tol: float = 1e-7):
"""
Calculates the intersections between a coplanar Circle3D and Line3D.
:param circle_3d: Circle3D or Arc3D
:param line: Line3D to verify intersections
:param abs_tol: Tolerance.
:return: list of points intersecting Circle
"""
if line.point1.is_close(circle_3d.center):
point1 = line.point2
vec = (line.point1 - line.point2).unit_vector()
else:
point1 = line.point1
vec = (line.point2 - line.point1).unit_vector()
quadratic_equation_a = vec.dot(vec)
quadratic_equation_b = 2 * vec.dot(point1 - circle_3d.center)
quadratic_equation_c = (
point1.dot(point1) + circle_3d.center.dot(circle_3d.center)
- 2 * point1.dot(circle_3d.center)
- circle_3d.radius ** 2
)
delta = quadratic_equation_b ** 2 - 4 * quadratic_equation_a * quadratic_equation_c
if delta < 0: # No real solutions, no intersection
return []
if math.isclose(delta, 0, abs_tol=abs_tol): # One real solution, tangent intersection
t_param = -quadratic_equation_b / (2 * quadratic_equation_a)
return [point1 + t_param * vec]
sqrt_delta = math.sqrt(delta)
t_param = (-quadratic_equation_b + sqrt_delta) / (2 * quadratic_equation_a)
s_param = (-quadratic_equation_b - sqrt_delta) / (2 * quadratic_equation_a)
return [point1 + t_param * vec, point1 + s_param * vec]
[docs]
def circle_3d_line_intersections(circle_3d, line, abs_tol: float = 1e-6):
"""
Calculates the intersections between a Circle3D and a Line3D.
:param circle_3d: Circle3D or Arc3D
:param line: Line3D to verify intersections
:param abs_tol: Tolerance.
:return: list of points intersecting Circle
"""
intersections = []
if not math.isclose(abs(circle_3d.frame.w.dot(design3d.Z3D)), 1, abs_tol=abs_tol):
frame_mapped_circle = circle_3d.frame_mapping(circle_3d.frame, "new")
frame_mapped_line = line.frame_mapping(circle_3d.frame, "new")
circle_linseg_intersections = circle_3d_line_intersections(frame_mapped_circle, frame_mapped_line)
for inter in circle_linseg_intersections:
intersections.append(circle_3d.frame.local_to_global_coordinates(inter))
return intersections
distance_center_lineseg = line.point_distance(circle_3d.frame.origin)
if distance_center_lineseg > circle_3d.radius:
return []
direction_vector = line.direction_vector()
if math.isclose(line.point1.z, line.point2.z, abs_tol=1e-6) and math.isclose(
line.point2.z, circle_3d.frame.origin.z, abs_tol=abs_tol
):
return get_planar_circle3d_line_intersections(circle_3d, line)
z_constant = circle_3d.frame.origin.z
constant = (z_constant - line.point1.z) / direction_vector.z
x_coordinate = constant * direction_vector.x + line.point1.x
y_coordinate = constant * direction_vector.y + line.point1.y
if math.isclose(
(x_coordinate - circle_3d.frame.origin.x) ** 2 + (y_coordinate - circle_3d.frame.origin.y) ** 2,
circle_3d.radius ** 2,
abs_tol=1e-6,
):
intersections = [design3d.Point3D(x_coordinate, y_coordinate, z_constant)]
return intersections
[docs]
def conic3d_line_intersections(conic3d, line3d, abs_tol: float = 1e-6):
"""
Calculates the intersections between an Ellipse3D and a Line3D.
:param conic3d: The Hyperbola 3D.
:param line3d: The Line 3D.
:param abs_tol: Tolerance.
:return: list of points intersecting the Hyperbola 3D.
"""
intersections = []
if not math.isclose(abs(conic3d.frame.w.dot(design3d.Z3D)), 1, abs_tol=abs_tol):
frame_mapped_conic3d = conic3d.frame_mapping(conic3d.frame, "new")
frame_mapped_line = line3d.frame_mapping(conic3d.frame, "new")
circle_linseg_intersections = conic3d_line_intersections(frame_mapped_conic3d, frame_mapped_line, abs_tol)
for inter in circle_linseg_intersections:
intersections.append(conic3d.frame.local_to_global_coordinates(inter))
return intersections
if abs(line3d.point1.z - line3d.point2.z) <= abs_tol and abs(line3d.point1.z - conic3d.frame.origin.z) <= abs_tol:
conic2d = conic3d.self_2d
line2d = line3d.to_2d(conic3d.frame.origin, conic3d.frame.u, conic3d.frame.v)
intersections_2d = conic2d.line_intersections(line2d)
for intersection in intersections_2d:
intersections.append(design3d.Point3D(intersection[0], intersection[1], conic3d.frame.origin.z))
return intersections
plane_lineseg_intersections = get_plane_line_intersections(conic3d.frame, line3d)
if plane_lineseg_intersections and conic3d.point_belongs(plane_lineseg_intersections[0], abs_tol):
return plane_lineseg_intersections
return []
def _get_ellipse2d_vertical_line_intersectioons(ellipse2d, line2d):
"""
Calculates the intersections between a vertical line and an ellipse.
:param ellipse2d: Ellipse to calculate intersections
:param line2d: vertical line to calculate intersections
:return: list of points intersections, if there are any
"""
x1 = line2d.point1.x
x2 = x1
y1 = ellipse2d.minor_axis * math.sqrt((1 - x1 ** 2 / ellipse2d.major_axis ** 2))
y2 = -y1
point1 = design3d.Point2D(x1, y1)
point2 = design3d.Point2D(x2, y2)
intersections = [point1, point2]
if point1.is_close(point2):
intersections = [point1]
return intersections
def _get_local_ellise2d_line_intersections(ellipse2d, line2d, abs_tol: float = 1e-6):
"""
Calculates the intersections between a line and an ellipse locally, with the ellipse at the origin.
:param ellipse2d: Ellipse to calculate intersections
:param line2d: line to calculate intersections
:param abs_tol: tolerance.
:return: list of points intersections, if there are any
"""
if math.isclose(line2d.point2.x, line2d.point1.x, abs_tol=abs_tol):
return _get_ellipse2d_vertical_line_intersectioons(ellipse2d, line2d)
line_slope = line2d.get_slope()
line_y_intersection = line2d.get_y_intersection()
a_param = 1 / ellipse2d.major_axis ** 2 + line_slope ** 2 / ellipse2d.minor_axis ** 2
b_param = 2 * line_slope * line_y_intersection / ellipse2d.minor_axis ** 2
c_param = line_y_intersection ** 2 / ellipse2d.minor_axis ** 2 - 1
if b_param ** 2 > 4 * a_param * c_param:
x1 = (-b_param + math.sqrt(b_param ** 2 - 4 * a_param * c_param)) / (2 * a_param)
x2 = (-b_param - math.sqrt(b_param ** 2 - 4 * a_param * c_param)) / (2 * a_param)
y1 = line_slope * x1 + line_y_intersection
y2 = line_slope * x2 + line_y_intersection
point1 = design3d.Point2D(x1, y1)
point2 = design3d.Point2D(x2, y2)
intersections = [point1, point2]
if point1.is_close(point2, abs_tol):
intersections = [point1]
return intersections
return []
[docs]
def ellipse2d_line_intersections(ellipse2d, line2d, abs_tol: float = 1e-6):
"""
Calculates the intersections between a line and an ellipse.
:param ellipse2d: Ellipse to calculate intersections
:param line2d: line to calculate intersections
:param abs_tol: tolerance.
:return: list of points intersections, if there are any
"""
if line2d.point_distance(ellipse2d.center) > ellipse2d.major_axis + abs_tol:
return []
theta = design3d.geometry.clockwise_angle(ellipse2d.major_dir, design3d.X2D)
if (
not math.isclose(theta, 0.0, abs_tol=1e-6) and not math.isclose(theta, 2 * math.pi, abs_tol=1e-6)
) or not ellipse2d.center.is_close(design3d.O2D):
frame = design3d.Frame2D(ellipse2d.center, ellipse2d.major_dir, ellipse2d.minor_dir)
frame_mapped_ellipse = ellipse2d.frame_mapping(frame, "new")
line_inters = _get_local_ellise2d_line_intersections(frame_mapped_ellipse, line2d.frame_mapping(frame, "new"))
line_intersections = [frame.local_to_global_coordinates(point) for point in line_inters]
return line_intersections
return _get_local_ellise2d_line_intersections(ellipse2d, line2d)
[docs]
def get_circle_intersections(circle1, circle2):
"""
Calculates the intersections between two circle 2d.
:param circle1: circle 1 verify intersection with bspline
:param circle2: circle 2 to search for intersections.
:return: a list with all intersections between the two circles.
"""
if circle1.center.is_close(circle2.center):
return []
x0, y0 = circle1.center
x1, y1 = circle2.center
d_param = math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
# non-intersecting
if d_param > circle1.radius + circle2.radius:
return []
# One circle within other
if d_param < abs(circle1.radius - circle2.radius):
return []
# coincident circles
if d_param == 0 and circle1.radius == circle2.radius:
return []
a = (circle1.radius ** 2 - circle2.radius ** 2 + d_param ** 2) / (2 * d_param)
if abs(circle1.radius - a) < 1e-6:
h_param = 0.0
else:
h_param = math.sqrt(circle1.radius ** 2 - a ** 2)
x2 = x0 + a * (x1 - x0) / d_param
y2 = y0 + a * (y1 - y0) / d_param
x3 = x2 + h_param * (y1 - y0) / d_param
y3 = y2 - h_param * (x1 - x0) / d_param
x4 = x2 - h_param * (y1 - y0) / d_param
y4 = y2 + h_param * (x1 - x0) / d_param
return [design3d.Point2D(x3, y3), design3d.Point2D(x4, y4)]
[docs]
def bspline_intersections_initial_conditions(primitive, bsplinecurve, resolution: float = 100, recursion_iteration=0):
"""
Gets the initial conditions to calculate intersections between a bspline curve 2d and another edge 2d.
:param primitive: primitive to verify intersection with bspline
:param bsplinecurve: bsplinecurve to search for intersections.
:param resolution: bspline discretization resolution, to search for initial intersection conditions.
:param recursion_iteration: parameter to count recursions.
:return: a list with all initial sections where there may exist an intersection.
"""
line_seg_class_ = getattr(design3d.edges, "LineSegment" + bsplinecurve.__class__.__name__[-2:])
abscissa1 = 0
abscissa2 = bsplinecurve.length()
if bsplinecurve.__class__.__name__ in ("BSplineCurve2D", "BSplineCurve3D"):
bspline_discretized_points, points_abscissas = bsplinecurve.get_abscissa_discretization(
abscissa1, abscissa2, number_points=resolution, return_abscissas=True)
else:
bspline_discretized_points, points_abscissas = get_abscissa_discretization(
bsplinecurve, abscissa1, abscissa2, max_number_points=resolution)
if bsplinecurve.periodic:
bspline_discretized_points += [bspline_discretized_points[0]]
if points_abscissas[0] == 0.0:
points_abscissas += [bsplinecurve.length()]
else:
points_abscissas += [points_abscissas[0]]
param_intersections = []
for point1, point2, abscissa1, abscissa2 in zip(
bspline_discretized_points[:-1],
bspline_discretized_points[1:],
points_abscissas[:-1],
points_abscissas[1:],
):
if point1 == point2:
continue
line_seg = line_seg_class_(point1, point2)
intersection = primitive.linesegment_intersections(line_seg)
if intersection:
param_intersections.append((abscissa1, abscissa2))
if not param_intersections and recursion_iteration < 1:
return bspline_intersections_initial_conditions(primitive, bsplinecurve, 100,
recursion_iteration+1)
return param_intersections
[docs]
def get_bsplinecurve_intersections(primitive, bsplinecurve, abs_tol: float = 1e-6):
"""
Calculates the intersections between a primitive and a BSpline Curve.
This method calculates the intersections between a given primitive and a BSpline Curve.
The primitive can be any edge type or a plane3D. It first determines the initial intersection
conditions using the `bspline_intersections_initial_conditions` function. Then, it iteratively
checks for intersections by discretizing the BSpline Curve and checking for intersections with
line segments. It maintains a list of intersections and validates each intersection against
the specified tolerance. The method continues until there are no more intersection conditions
to process. The resulting intersections are returned as a list.
:param primitive: The primitive object to verify intersection with the BSpline Curve.
It can be any edge type or a plane3D.
:param bsplinecurve: The BSpline Curve object to search for intersections.
:param abs_tol: The tolerance to be considered while validating an intersection.
:return: A list with all intersections between the edge and BSpline Curve.
:rtype: [design3d.Point3D].
"""
param_intersections = bspline_intersections_initial_conditions(primitive, bsplinecurve, 10)
line_seg_class_ = getattr(design3d.edges, "LineSegment" + bsplinecurve.__class__.__name__[-2:])
intersections = []
if not param_intersections:
return []
while True:
if not param_intersections:
break
abscissa1, abscissa2 = param_intersections[0]
if bsplinecurve.__class__.__name__ in ("BSplineCurve2D", "BSplineCurve3D"):
(
discretized_points_between_1_2, points_abscissas
) = bsplinecurve.get_abscissa_discretization(abscissa1, abscissa2, number_points=10, return_abscissas=True)
else:
(
discretized_points_between_1_2, points_abscissas,
) = get_abscissa_discretization(bsplinecurve, abscissa1, abscissa2, max_number_points=10)
for point1, point2, abscissa_point1, abscissa_point2 in zip(
discretized_points_between_1_2[:-1],
discretized_points_between_1_2[1:],
points_abscissas[:-1],
points_abscissas[1:],
):
line_seg = line_seg_class_(point1, point2)
intersection = primitive.linesegment_intersections(line_seg, abs_tol)
if not intersection:
continue
if get_point_distance_to_edge(bsplinecurve, intersection[0], point1, point2) > 1e-7:
if not (abscissa_point1 == abscissa1 and abscissa_point2 == abscissa2):
param_intersections.insert(0, (abscissa_point1, abscissa_point2))
elif not intersection[0].in_list(intersections):
intersections.append(intersection[0])
param_intersections.remove((abscissa1, abscissa2))
return intersections
[docs]
def conic_intersections(conic1, conic2, abs_tol: float = 1e-6):
"""
Gets intersections between a two conic curves 3D.
:param conic1: First conic curve 3D.
:param conic2: Other conic curve 3D.
:param abs_tol: tolerance.
:return: A list of points, containing all intersections between the Line 3D and the Parabola3D.
"""
intersections = []
if conic1.frame.w.is_colinear_to(conic2.frame.w) and math.isclose(
conic1.frame.w.dot(conic2.frame.origin - conic1.frame.origin),
0,
abs_tol=abs_tol,
):
frame_mapped_conic1 = conic1.frame_mapping(conic1.frame, "new")
frame_mapped_conic2 = conic2.frame_mapping(conic1.frame, "new")
conic1_2d = frame_mapped_conic1.self_2d
conic2_2d = frame_mapped_conic2.to_2d(
frame_mapped_conic1.frame.origin,
frame_mapped_conic1.frame.u,
frame_mapped_conic1.frame.v,
)
intersections_2d = conic1_2d.intersections(conic2_2d, abs_tol)
if not intersections_2d:
return []
local_intersections = []
for intersection in intersections_2d:
local_intersections.append(design3d.Point3D(intersection[0], intersection[1], 0.0))
# circle_linseg_intersections = conic3d_line_intersections(frame_mapped_conic1, frame_mapped_conic2, abs_tol)
for inter in local_intersections:
intersections.append(conic1.frame.local_to_global_coordinates(inter))
return intersections
plane_intersections = get_two_planes_intersections(conic1.frame, conic2.frame)
if not plane_intersections:
return []
plane_intersections = design3d.curves.Line3D(plane_intersections[0], plane_intersections[1])
self_ellipse3d_line_intersections = conic3d_line_intersections(conic1, plane_intersections)
ellipse3d_line_intersections = conic3d_line_intersections(conic2, plane_intersections)
for intersection in self_ellipse3d_line_intersections + ellipse3d_line_intersections:
if intersection.in_list(intersections):
continue
if conic1.point_belongs(intersection, abs_tol) and conic2.point_belongs(intersection, abs_tol):
intersections.append(intersection)
return intersections
[docs]
def get_plane_linesegment_intersections(plane_frame, linesegment, abs_tol: float = 1e-6):
"""
Gets the intersections of a plane a line segment 3d.
:param plane_frame: the plane's frame.
:param linesegment: other line segment.
:param abs_tol: tolerance allowed.
:return: a list with the intersecting point.
"""
u_vector = linesegment.end - linesegment.start
w_vector = linesegment.start - plane_frame.origin
normaldotu = plane_frame.w.dot(u_vector)
if normaldotu == 0.0 or math.isclose(
plane_frame.w.unit_vector().dot(u_vector.unit_vector()), 0.0, abs_tol=abs_tol
):
return []
intersection_abscissea = -plane_frame.w.dot(w_vector) / normaldotu
if intersection_abscissea < 0 or intersection_abscissea > 1:
if math.isclose(abs(intersection_abscissea), 0, abs_tol=abs_tol):
return [linesegment.start]
if math.isclose(intersection_abscissea, 1, abs_tol=abs_tol):
return [linesegment.end]
return []
return [linesegment.start + intersection_abscissea * u_vector]
[docs]
def get_plane_line_intersections(plane_frame, line, abs_tol: float = 1e-6):
"""
Find the intersection with a line.
:param plane_frame: the plane's frame.
:param line: Line to evaluate the intersection
:type line: :class:`edges.Line`.
:param abs_tol: tolerance.
:type abs_tol: float.
:return: ADD DESCRIPTION
:rtype: List[design3d.Point3D]
"""
u_vector = line.point2 - line.point1
w_vector = line.point1 - plane_frame.origin
if math.isclose(plane_frame.w.dot(u_vector), 0, abs_tol=abs_tol):
return []
intersection_abscissea = -plane_frame.w.dot(w_vector) / plane_frame.w.dot(u_vector)
return [line.point1 + intersection_abscissea * u_vector]
def _helper_two_plane_intersections(plane1_frame, plane2_frame):
"""
Helper function to get point 1 on two plane intersections.
"""
a1, b1, c1, d1 = get_plane_equation_coefficients(plane1_frame)
a2, b2, c2, d2 = get_plane_equation_coefficients(plane2_frame)
tol = 1e-10
if abs(a1 * b2 - a2 * b1) > tol:
x0 = (b1 * d2 - b2 * d1) / (a1 * b2 - a2 * b1)
y0 = (a2 * d1 - a1 * d2) / (a1 * b2 - a2 * b1)
point1 = design3d.Point3D(x0, y0, 0)
elif abs(a2 * c1 - a1 * c2) > tol:
x0 = (c2 * d1 - c1 * d2) / (a2 * c1 - a1 * c2)
z0 = (a1 * d2 - a2 * d1) / (a2 * c1 - a1 * c2)
point1 = design3d.Point3D(x0, 0, z0)
elif abs(c1 * b2 - b1 * c2) > tol:
y0 = (- c2 * d1 + c1 * d2) / (b1 * c2 - c1 * b2)
z0 = (- b1 * d2 + b2 * d1) / (b1 * c2 - c1 * b2)
point1 = design3d.Point3D(0, y0, z0)
else:
raise NotImplementedError
return point1
[docs]
def get_two_planes_intersections(plane1_frame, plane2_frame, abs_tol=1e-8):
"""
Calculates the intersections between two planes, given their frames.
:param plane1_frame: Plane's 1 frame.
:param plane2_frame: Plane's 2 frame.
:param abs_tol: tolerance.
:return: A list containing two points that define an infinite line if there is any intersections,
or an empty list if the planes are parallel.
"""
if plane1_frame.w.is_colinear_to(plane2_frame.w, abs_tol):
return []
line_direction = plane1_frame.w.cross(plane2_frame.w)
if line_direction.norm() < abs_tol:
return None
point1 = _helper_two_plane_intersections(plane1_frame, plane2_frame)
return [point1, point1 + line_direction]