Skip to content

Commit

Permalink
Added Axis.is_skew and tested for is_skew in Axis.intersect
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Jan 31, 2025
1 parent 79a9625 commit b8dcad3
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 29 deletions.
87 changes: 58 additions & 29 deletions src/build123d/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,43 @@ def is_parallel(self, other: Axis, angular_tolerance: float = 1e-5) -> bool:
"""
return self.wrapped.IsParallel(other.wrapped, angular_tolerance * (pi / 180))

def is_skew(self, other: Axis, tolerance: float = 1e-5) -> bool:
"""are axes skew
Returns True if this axis and another axis are skew, meaning they are neither
parallel nor coplanar. Two axes are skew if they do not lie in the same plane
and never intersect.
Mathematically, this means:
- The axes are **not parallel** (the cross product of their direction vectors
is nonzero).
- The axes are **not coplanar** (the vector between their positions is not
aligned with the plane spanned by their directions).
If either condition is false (i.e., the axes are parallel or coplanar), they are
not skew.
Args:
other (Axis): axis to compare to
tolerance (float, optional): max deviation. Defaults to 1e-5.
Returns:
bool: axes are skew
"""
if self.is_parallel(other, tolerance):
# If parallel, check if they are coincident
parallel_offset = (self.position - other.position).cross(self.direction)
# True if distinct, False if coincident
return parallel_offset.length > tolerance

# Compute the determinant
coplanarity = (self.position - other.position).dot(
self.direction.cross(other.direction)
)

# If determinant is near zero, they are coplanar; otherwise, they are skew
return abs(coplanarity) > tolerance

def angle_between(self, other: Axis) -> float:
"""calculate angle between axes
Expand Down Expand Up @@ -830,37 +867,29 @@ def intersect(self, *args, **kwargs):
if axis is not None:
if self.is_coaxial(axis):
return self
else:
# Extract points and directions to numpy arrays
p1 = np.array([*self.position])
d1 = np.array([*self.direction])
p2 = np.array([*axis.position])
d2 = np.array([*axis.direction])

# Compute the cross product of directions
cross_d1_d2 = np.cross(d1, d2)
cross_d1_d2_norm = np.linalg.norm(cross_d1_d2)

if cross_d1_d2_norm < TOLERANCE:
# The directions are parallel
return None

# Solve the system of equations to find the intersection
system_of_equations = np.array([d1, -d2, cross_d1_d2]).T
origin_diff = p2 - p1
try:
t1, t2, _ = np.linalg.solve(system_of_equations, origin_diff)
except np.linalg.LinAlgError:
return None # The lines do not intersect

# Calculate the intersection point
intersection_point = p1 + t1 * d1
return Vector(*intersection_point)
if self.is_skew(axis):
return None

# Extract points and directions to numpy arrays
p1 = np.array([*self.position])
d1 = np.array([*self.direction])
p2 = np.array([*axis.position])
d2 = np.array([*axis.direction])

elif plane is not None:
# Solve the system of equations to find the intersection
system_of_equations = np.array([d1, -d2, np.cross(d1, d2)]).T
origin_diff = p2 - p1
t1, t2, _ = np.linalg.lstsq(system_of_equations, origin_diff, rcond=None)[0]

# Calculate the intersection point
intersection_point = p1 + t1 * d1
return Vector(*intersection_point)

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

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

Expand All @@ -872,7 +901,7 @@ def intersect(self, *args, **kwargs):
if vector == projected_vec:
return vector

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

Expand All @@ -883,7 +912,7 @@ def intersect(self, *args, **kwargs):
):
return location

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


Expand Down
23 changes: 23 additions & 0 deletions tests/test_direct_api/test_axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,26 @@ def test_axis_is_parallel(self):
self.assertTrue(Axis.X.is_parallel(Axis((1, 1, 1), (1, 0, 0))))
self.assertFalse(Axis.X.is_parallel(Axis.Y))

def test_axis_is_skew(self):
self.assertTrue(Axis.X.is_skew(Axis((0, 1, 1), (0, 0, 1))))
self.assertFalse(Axis.X.is_skew(Axis.Y))

def test_axis_is_skew(self):
# Skew Axes
self.assertTrue(Axis.X.is_skew(Axis((0, 1, 1), (0, 0, 1))))

# Perpendicular but intersecting
self.assertFalse(Axis.X.is_skew(Axis.Y))

# Parallel coincident axes
self.assertFalse(Axis.X.is_skew(Axis.X))

# Parallel but distinct axes
self.assertTrue(Axis.X.is_skew(Axis((0, 1, 0), (1, 0, 0))))

# Coplanar but not intersecting
self.assertTrue(Axis((0, 0, 0), (1, 1, 0)).is_skew(Axis((0, 1, 0), (1, 1, 0))))

def test_axis_angle_between(self):
self.assertAlmostEqual(Axis.X.angle_between(Axis.Y), 90, 5)
self.assertAlmostEqual(
Expand Down Expand Up @@ -155,6 +175,9 @@ def test_axis_intersect(self):
i = Axis.X & Axis((1, 0, 0), (1, 0, 0))
self.assertEqual(i, Axis.X)

# Skew case
self.assertIsNone(Axis.X.intersect(Axis((0, 1, 1), (0, 0, 1))))

intersection = Axis((1, 2, 3), (0, 0, 1)) & Plane.XY
self.assertAlmostEqual(intersection.to_tuple(), (1, 2, 0), 5)

Expand Down

0 comments on commit b8dcad3

Please sign in to comment.