Skip to content

Commit c2516e9

Browse files
authored
Merge pull request #1338 from compas-dev/feature-some-geometry-methods
Add some convenience geometry methods
2 parents f28cdb8 + 458aa25 commit c2516e9

File tree

3 files changed

+151
-3
lines changed

3 files changed

+151
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
* Added `compas.geometry.Line.point_from_start` and `compas.geometry.Line.point_from_end`.
13+
* Added `compas.geometry.Line.flip` and `compas.geometry.Line.flipped`.
1214
* Added an `compas.geometry.Frame.interpolate_frame(s)` method
1315
* Added `compas.colors.Color.contrast`.
1416
* Added `compas.geometry.Brep.from_plane`.

src/compas/geometry/curves/line.py

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,17 +297,19 @@ def transform(self, T):
297297
# ==========================================================================
298298

299299
def point_at(self, t):
300-
"""Construct a point at a specific location along the line.
300+
"""Construct a point along the line at a fractional position.
301301
302302
Parameters
303303
----------
304304
t : float
305-
The location along the line.
305+
The relative position along the line as a fraction of the length of the line.
306+
0.0 corresponds to the start point and 1.0 corresponds to the end point.
307+
Numbers outside of this range are also valid and correspond to points beyond the start and end point.
306308
307309
Returns
308310
-------
309311
:class:`compas.geometry.Point`
310-
The point at the specified location.
312+
The point at the specified position.
311313
312314
See Also
313315
--------
@@ -323,6 +325,44 @@ def point_at(self, t):
323325
point = self.point + self.vector * t
324326
return point
325327

328+
def point_from_start(self, distance):
329+
"""Construct a point along the line at a distance from the start point.
330+
331+
Parameters
332+
----------
333+
distance : float
334+
The distance along the line from the start point towards the end point.
335+
If the distance is negative, the point is constructed in the opposite direction of the end point.
336+
If the distance is larger than the length of the line, the point is constructed beyond the end point.
337+
338+
Returns
339+
-------
340+
:class:`compas.geometry.Point`
341+
The point at the specified distance.
342+
343+
"""
344+
point = self.point + self.direction * distance
345+
return point
346+
347+
def point_from_end(self, distance):
348+
"""Construct a point along the line at a distance from the end point.
349+
350+
Parameters
351+
----------
352+
distance : float
353+
The distance along the line from the end point towards the start point.
354+
If the distance is negative, the point is constructed in the opposite direction of the start point.
355+
If the distance is larger than the length of the line, the point is constructed beyond the start point.
356+
357+
Returns
358+
-------
359+
:class:`compas.geometry.Point`
360+
The point at the specified distance.
361+
362+
"""
363+
point = self.end - self.direction * distance
364+
return point
365+
326366
def closest_point(self, point, return_parameter=False):
327367
"""Compute the closest point on the line to a given point.
328368
@@ -349,3 +389,43 @@ def closest_point(self, point, return_parameter=False):
349389
if return_parameter:
350390
return closest, t
351391
return closest
392+
393+
def flip(self):
394+
"""Flip the direction of the line.
395+
396+
Returns
397+
-------
398+
None
399+
400+
Examples
401+
--------
402+
>>> line = Line([0, 0, 0], [1, 2, 3])
403+
>>> line
404+
Line(Point(x=0.0, y=0.0, z=0.0), Point(x=1.0, y=2.0, z=3.0))
405+
>>> line.flip()
406+
>>> line
407+
Line(Point(x=1.0, y=2.0, z=3.0), Point(x=0.0, y=0.0, z=0.0))
408+
409+
"""
410+
new_vector = self.vector.inverted()
411+
self.start = self.end
412+
self.vector = new_vector
413+
414+
def flipped(self):
415+
"""Return a new line with the direction flipped.
416+
417+
Returns
418+
-------
419+
:class:`Line`
420+
A new line.
421+
422+
Examples
423+
--------
424+
>>> line = Line([0, 0, 0], [1, 2, 3])
425+
>>> line
426+
Line(Point(x=0.0, y=0.0, z=0.0), Point(x=1.0, y=2.0, z=3.0))
427+
>>> line.flipped()
428+
Line(Point(x=1.0, y=2.0, z=3.0), Point(x=0.0, y=0.0, z=0.0))
429+
430+
"""
431+
return Line(self.end, self.start)

tests/compas/geometry/test_curves_line.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from compas.geometry import Vector
1212
from compas.geometry import Frame
1313
from compas.geometry import Line
14+
from compas.tolerance import TOL
1415

1516

1617
@pytest.mark.parametrize(
@@ -216,3 +217,68 @@ def test_line_accessors(p1, p2):
216217
# =============================================================================
217218
# Other Methods
218219
# =============================================================================
220+
221+
222+
@pytest.mark.parametrize(
223+
"p1,p2",
224+
[
225+
([0, 0, 0], [1, 0, 0]),
226+
([0, 0, 0], [1, 2, 3]),
227+
([1, 2, 3], [-1, -2, -3]),
228+
([-11.1, 22.2, 33.3], [1.1, -2.2, -3.3]),
229+
],
230+
)
231+
def test_line_point_from_start(p1, p2):
232+
distances = [0, 1, 4, -9, 3.3, 0.00001, -0.00001]
233+
for distance in distances:
234+
line = Line(p1, p2)
235+
point = line.point_from_start(distance)
236+
distance_to_start = distance_point_point(point, p1)
237+
distance_to_end = distance_point_point(point, p2)
238+
# Check that the distance is correct
239+
assert TOL.is_close(distance_to_start, abs(distance))
240+
# Check that negative distance gives a point far away from end
241+
if distance < 0:
242+
assert distance_to_end > line.length
243+
244+
245+
@pytest.mark.parametrize(
246+
"p1,p2",
247+
[
248+
([0, 0, 0], [1, 0, 0]),
249+
([0, 0, 0], [1, 2, 3]),
250+
([1, 2, 3], [-1, -2, -3]),
251+
([-11.1, 22.2, 33.3], [1.1, -2.2, -3.3]),
252+
],
253+
)
254+
def test_line_point_from_end(p1, p2):
255+
distances = [0, 1, 4, -9, 3.3, 0.00001, -0.00001]
256+
for distance in distances:
257+
line = Line(p1, p2)
258+
point = line.point_from_end(distance)
259+
distance_to_start = distance_point_point(point, p1)
260+
distance_to_end = distance_point_point(point, p2)
261+
# Check that the distance is correct
262+
assert TOL.is_close(distance_to_end, abs(distance))
263+
# Check that negative distance gives a point far away from start
264+
if distance < 0:
265+
assert distance_to_start > line.length
266+
267+
268+
@pytest.mark.parametrize(
269+
"p1,p2",
270+
[
271+
([0, 0, 0], [1, 0, 0]),
272+
([0, 0, 0], [1, 2, 3]),
273+
([1, 2, 3], [-1, -2, -3]),
274+
([-11.1, 22.2, 33.3], [1.1, -2.2, -3.3]),
275+
],
276+
)
277+
def test_line_flip(p1, p2):
278+
line = Line(p1, p2)
279+
line.flip()
280+
assert TOL.is_zero(distance_point_point(line.start, p2))
281+
assert TOL.is_zero(distance_point_point(line.end, p1))
282+
flipped_line = Line(p1, p2).flipped()
283+
assert TOL.is_zero(distance_point_point(flipped_line.start, p2))
284+
assert TOL.is_zero(distance_point_point(flipped_line.end, p1))

0 commit comments

Comments
 (0)