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 some convenience geometry methods #1338

Merged
merged 9 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Added `compas.geometry.Line.point_from_start` and `compas.geometry.Line.point_from_end`.
* Added `compas.geometry.Line.flip` and `compas.geometry.Line.flipped`.
* Added an `compas.geometry.Frame.interpolate_frame(s)` method
* Added `compas.colors.Color.contrast`.
* Added `compas.geometry.Brep.from_plane`.
Expand Down
86 changes: 83 additions & 3 deletions src/compas/geometry/curves/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,17 +297,19 @@ def transform(self, T):
# ==========================================================================

def point_at(self, t):
"""Construct a point at a specific location along the line.
"""Construct a point along the line at a fractional position.

Parameters
----------
t : float
The location along the line.
The relative position along the line as a fraction of the length of the line.
0.0 corresponds to the start point and 1.0 corresponds to the end point.
Numbers outside of this range are also valid and correspond to points beyond the start and end point.

Returns
-------
:class:`compas.geometry.Point`
The point at the specified location.
The point at the specified position.

See Also
--------
Expand All @@ -323,6 +325,44 @@ def point_at(self, t):
point = self.point + self.vector * t
return point

def point_from_start(self, distance):
"""Construct a point along the line at a distance from the start point.

Parameters
----------
distance : float
The distance along the line from the start point towards the end point.
If the distance is negative, the point is constructed in the opposite direction of the end point.
If the distance is larger than the length of the line, the point is constructed beyond the end point.

Returns
-------
:class:`compas.geometry.Point`
The point at the specified distance.

"""
point = self.point + self.direction * distance
return point

def point_from_end(self, distance):
"""Construct a point along the line at a distance from the end point.

Parameters
----------
distance : float
The distance along the line from the end point towards the start point.
If the distance is negative, the point is constructed in the opposite direction of the start point.
If the distance is larger than the length of the line, the point is constructed beyond the start point.

Returns
-------
:class:`compas.geometry.Point`
The point at the specified distance.

"""
point = self.end - self.direction * distance
return point

def closest_point(self, point, return_parameter=False):
"""Compute the closest point on the line to a given point.

Expand All @@ -349,3 +389,43 @@ def closest_point(self, point, return_parameter=False):
if return_parameter:
return closest, t
return closest

def flip(self):
"""Flip the direction of the line.

Returns
-------
None

Examples
--------
>>> line = Line([0, 0, 0], [1, 2, 3])
>>> line
Line(Point(x=0.0, y=0.0, z=0.0), Point(x=1.0, y=2.0, z=3.0))
>>> line.flip()
>>> line
Line(Point(x=1.0, y=2.0, z=3.0), Point(x=0.0, y=0.0, z=0.0))

"""
new_vector = self.vector.inverted()
self.start = self.end
self.vector = new_vector

def flipped(self):
"""Return a new line with the direction flipped.

Returns
-------
:class:`Line`
A new line.

Examples
--------
>>> line = Line([0, 0, 0], [1, 2, 3])
>>> line
Line(Point(x=0.0, y=0.0, z=0.0), Point(x=1.0, y=2.0, z=3.0))
>>> line.flipped()
Line(Point(x=1.0, y=2.0, z=3.0), Point(x=0.0, y=0.0, z=0.0))

"""
return Line(self.end, self.start)
66 changes: 66 additions & 0 deletions tests/compas/geometry/test_curves_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from compas.geometry import Vector
from compas.geometry import Frame
from compas.geometry import Line
from compas.tolerance import TOL


@pytest.mark.parametrize(
Expand Down Expand Up @@ -216,3 +217,68 @@ def test_line_accessors(p1, p2):
# =============================================================================
# Other Methods
# =============================================================================


@pytest.mark.parametrize(
"p1,p2",
[
([0, 0, 0], [1, 0, 0]),
([0, 0, 0], [1, 2, 3]),
([1, 2, 3], [-1, -2, -3]),
([-11.1, 22.2, 33.3], [1.1, -2.2, -3.3]),
],
)
def test_line_point_from_start(p1, p2):
distances = [0, 1, 4, -9, 3.3, 0.00001, -0.00001]
for distance in distances:
line = Line(p1, p2)
point = line.point_from_start(distance)
distance_to_start = distance_point_point(point, p1)
distance_to_end = distance_point_point(point, p2)
# Check that the distance is correct
assert TOL.is_close(distance_to_start, abs(distance))
# Check that negative distance gives a point far away from end
if distance < 0:
assert distance_to_end > line.length


@pytest.mark.parametrize(
"p1,p2",
[
([0, 0, 0], [1, 0, 0]),
([0, 0, 0], [1, 2, 3]),
([1, 2, 3], [-1, -2, -3]),
([-11.1, 22.2, 33.3], [1.1, -2.2, -3.3]),
],
)
def test_line_point_from_end(p1, p2):
distances = [0, 1, 4, -9, 3.3, 0.00001, -0.00001]
for distance in distances:
line = Line(p1, p2)
point = line.point_from_end(distance)
distance_to_start = distance_point_point(point, p1)
distance_to_end = distance_point_point(point, p2)
# Check that the distance is correct
assert TOL.is_close(distance_to_end, abs(distance))
# Check that negative distance gives a point far away from start
if distance < 0:
assert distance_to_start > line.length


@pytest.mark.parametrize(
"p1,p2",
[
([0, 0, 0], [1, 0, 0]),
([0, 0, 0], [1, 2, 3]),
([1, 2, 3], [-1, -2, -3]),
([-11.1, 22.2, 33.3], [1.1, -2.2, -3.3]),
],
)
def test_line_flip(p1, p2):
line = Line(p1, p2)
line.flip()
assert TOL.is_zero(distance_point_point(line.start, p2))
assert TOL.is_zero(distance_point_point(line.end, p1))
flipped_line = Line(p1, p2).flipped()
assert TOL.is_zero(distance_point_point(flipped_line.start, p2))
assert TOL.is_zero(distance_point_point(flipped_line.end, p1))
Loading