From 2978939242342a25827e413d6472be293ba34126 Mon Sep 17 00:00:00 2001 From: Victor LEUNG Date: Wed, 24 Apr 2024 23:36:03 +0800 Subject: [PATCH 1/9] Fixes #896 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa45abc0ae5..3fba43cb87f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ 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 `compas.geometry.Vector.reverse` and `compas.geometry.Vector.reversed`. * Added an `compas.geometry.Frame.interpolate_frame(s)` method * Added `compas.colors.Color.contrast`. * Added `compas.geometry.Brep.from_plane`. From 5ba85438b101eecc99f7a26adf03d0c2299d70d2 Mon Sep 17 00:00:00 2001 From: Victor LEUNG Date: Thu, 25 Apr 2024 00:52:40 +0800 Subject: [PATCH 2/9] New methods and tests. Fixes #896 --- CHANGELOG.md | 1 - src/compas/geometry/curves/line.py | 89 ++++++++++++++++++++++- tests/compas/geometry/test_curves_line.py | 71 +++++++++++++++++- 3 files changed, 155 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fba43cb87f..34f5557a191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * 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 `compas.geometry.Vector.reverse` and `compas.geometry.Vector.reversed`. * Added an `compas.geometry.Frame.interpolate_frame(s)` method * Added `compas.colors.Color.contrast`. * Added `compas.geometry.Brep.from_plane`. diff --git a/src/compas/geometry/curves/line.py b/src/compas/geometry/curves/line.py index e0aa9dba02c..f72ab83fc41 100644 --- a/src/compas/geometry/curves/line.py +++ b/src/compas/geometry/curves/line.py @@ -140,7 +140,8 @@ def frame(self): @frame.setter def frame(self, frame): - raise AttributeError("Setting the coordinate frame of a line is not supported.") + raise AttributeError( + "Setting the coordinate frame of a line is not supported.") @property def point(self): @@ -297,17 +298,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. + Number 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 -------- @@ -323,6 +326,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. @@ -349,3 +390,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) diff --git a/tests/compas/geometry/test_curves_line.py b/tests/compas/geometry/test_curves_line.py index 6fbd2209481..d81fb282c14 100644 --- a/tests/compas/geometry/test_curves_line.py +++ b/tests/compas/geometry/test_curves_line.py @@ -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( @@ -98,7 +99,8 @@ def test_line_create_from_point_direction_length(point, direction, length): line = Line.from_point_direction_length(point, direction, length) assert line.start == point - assert line.end == add_vectors(point, scale_vector(normalize_vector(direction), length)) + assert line.end == add_vectors( + point, scale_vector(normalize_vector(direction), length)) # ============================================================================= @@ -216,3 +218,70 @@ 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]), + ], +) +@pytest.mark.parametrize( + "distance", [0, 1, 4, -9, 3.3, 0.00001, -0.00001], +) +def test_line_point_from_start(p1, p2, distance): + 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]), + ], +) +@pytest.mark.parametrize( + "distance", [0, 1, 4, -9, 3.3, 0.00001, -0.00001], +) +def test_line_point_from_end(p1, p2, distance): + 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)) From 60ca15eb17c1094960b9cd95de3b726f574feee8 Mon Sep 17 00:00:00 2001 From: Victor LEUNG Date: Thu, 25 Apr 2024 00:55:36 +0800 Subject: [PATCH 3/9] black :heart --- src/compas/geometry/curves/line.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compas/geometry/curves/line.py b/src/compas/geometry/curves/line.py index f72ab83fc41..dc3bd9f1eae 100644 --- a/src/compas/geometry/curves/line.py +++ b/src/compas/geometry/curves/line.py @@ -140,8 +140,7 @@ def frame(self): @frame.setter def frame(self, frame): - raise AttributeError( - "Setting the coordinate frame of a line is not supported.") + raise AttributeError("Setting the coordinate frame of a line is not supported.") @property def point(self): From 3b63c5eb50610555329c052ce9f4e801c2d7c99c Mon Sep 17 00:00:00 2001 From: Victor LEUNG Date: Thu, 25 Apr 2024 00:56:19 +0800 Subject: [PATCH 4/9] more lint --- tests/compas/geometry/test_curves_line.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/compas/geometry/test_curves_line.py b/tests/compas/geometry/test_curves_line.py index d81fb282c14..8bc40fb8af7 100644 --- a/tests/compas/geometry/test_curves_line.py +++ b/tests/compas/geometry/test_curves_line.py @@ -99,8 +99,7 @@ def test_line_create_from_point_direction_length(point, direction, length): line = Line.from_point_direction_length(point, direction, length) assert line.start == point - assert line.end == add_vectors( - point, scale_vector(normalize_vector(direction), length)) + assert line.end == add_vectors(point, scale_vector(normalize_vector(direction), length)) # ============================================================================= @@ -230,7 +229,8 @@ def test_line_accessors(p1, p2): ], ) @pytest.mark.parametrize( - "distance", [0, 1, 4, -9, 3.3, 0.00001, -0.00001], + "distance", + [0, 1, 4, -9, 3.3, 0.00001, -0.00001], ) def test_line_point_from_start(p1, p2, distance): line = Line(p1, p2) @@ -254,7 +254,8 @@ def test_line_point_from_start(p1, p2, distance): ], ) @pytest.mark.parametrize( - "distance", [0, 1, 4, -9, 3.3, 0.00001, -0.00001], + "distance", + [0, 1, 4, -9, 3.3, 0.00001, -0.00001], ) def test_line_point_from_end(p1, p2, distance): line = Line(p1, p2) From b95ac8096979b92664a100643cb485a06638d67e Mon Sep 17 00:00:00 2001 From: Victor LEUNG Date: Fri, 26 Apr 2024 11:32:09 +0800 Subject: [PATCH 5/9] modify test for ipy --- tests/compas/geometry/test_curves_line.py | 29 +++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/compas/geometry/test_curves_line.py b/tests/compas/geometry/test_curves_line.py index 8bc40fb8af7..3674cf68b29 100644 --- a/tests/compas/geometry/test_curves_line.py +++ b/tests/compas/geometry/test_curves_line.py @@ -99,7 +99,8 @@ def test_line_create_from_point_direction_length(point, direction, length): line = Line.from_point_direction_length(point, direction, length) assert line.start == point - assert line.end == add_vectors(point, scale_vector(normalize_vector(direction), length)) + assert line.end == add_vectors( + point, scale_vector(normalize_vector(direction), length)) # ============================================================================= @@ -228,20 +229,18 @@ def test_line_accessors(p1, p2): ([-11.1, 22.2, 33.3], [1.1, -2.2, -3.3]), ], ) -@pytest.mark.parametrize( - "distance", - [0, 1, 4, -9, 3.3, 0.00001, -0.00001], -) -def test_line_point_from_start(p1, p2, distance): - 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 +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( From 9e1945d03a8eb709243c3930545668bf8c739875 Mon Sep 17 00:00:00 2001 From: Victor LEUNG Date: Fri, 26 Apr 2024 13:16:55 +0800 Subject: [PATCH 6/9] black --- tests/compas/geometry/test_curves_line.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/compas/geometry/test_curves_line.py b/tests/compas/geometry/test_curves_line.py index 3674cf68b29..9d2ec670b3e 100644 --- a/tests/compas/geometry/test_curves_line.py +++ b/tests/compas/geometry/test_curves_line.py @@ -99,8 +99,7 @@ def test_line_create_from_point_direction_length(point, direction, length): line = Line.from_point_direction_length(point, direction, length) assert line.start == point - assert line.end == add_vectors( - point, scale_vector(normalize_vector(direction), length)) + assert line.end == add_vectors(point, scale_vector(normalize_vector(direction), length)) # ============================================================================= From 409faad85aff2732365c3395a6b22c43d3a82628 Mon Sep 17 00:00:00 2001 From: Victor LEUNG Date: Fri, 26 Apr 2024 13:32:18 +0800 Subject: [PATCH 7/9] ipy fix --- tests/compas/geometry/test_curves_line.py | 24 +++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tests/compas/geometry/test_curves_line.py b/tests/compas/geometry/test_curves_line.py index 9d2ec670b3e..357f282388f 100644 --- a/tests/compas/geometry/test_curves_line.py +++ b/tests/compas/geometry/test_curves_line.py @@ -251,20 +251,18 @@ def test_line_point_from_start(p1, p2): ([-11.1, 22.2, 33.3], [1.1, -2.2, -3.3]), ], ) -@pytest.mark.parametrize( - "distance", - [0, 1, 4, -9, 3.3, 0.00001, -0.00001], -) def test_line_point_from_end(p1, p2, distance): - 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 + 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( From 80b21c2ecb7ee0eb08afb469d919db0c8a0fa323 Mon Sep 17 00:00:00 2001 From: Victor LEUNG Date: Fri, 26 Apr 2024 13:48:31 +0800 Subject: [PATCH 8/9] arrrrrgh --- tests/compas/geometry/test_curves_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/compas/geometry/test_curves_line.py b/tests/compas/geometry/test_curves_line.py index 357f282388f..d049ec091a7 100644 --- a/tests/compas/geometry/test_curves_line.py +++ b/tests/compas/geometry/test_curves_line.py @@ -251,7 +251,7 @@ def test_line_point_from_start(p1, p2): ([-11.1, 22.2, 33.3], [1.1, -2.2, -3.3]), ], ) -def test_line_point_from_end(p1, p2, distance): +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) From 458aa256404abae5553a2b1707fbeff072b6919e Mon Sep 17 00:00:00 2001 From: Victor LEUNG Date: Sat, 27 Apr 2024 02:47:10 +0200 Subject: [PATCH 9/9] Update src/compas/geometry/curves/line.py Co-authored-by: Gonzalo Casas --- src/compas/geometry/curves/line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas/geometry/curves/line.py b/src/compas/geometry/curves/line.py index dc3bd9f1eae..427d82511a3 100644 --- a/src/compas/geometry/curves/line.py +++ b/src/compas/geometry/curves/line.py @@ -304,7 +304,7 @@ def point_at(self, t): t : float 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. - Number outside of this range are also valid and correspond to points beyond the start and end point. + Numbers outside of this range are also valid and correspond to points beyond the start and end point. Returns -------