-
Notifications
You must be signed in to change notification settings - Fork 109
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
new functions for compas geometry #569
base: main
Are you sure you want to change the base?
Changes from 11 commits
5f9b52b
6408ef7
0e34204
401a7e2
2df9dcf
77549aa
829696c
ad06707
f29a933
4eafb73
faf7bc8
e9209c8
8b75011
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
from __future__ import print_function | ||
from __future__ import absolute_import | ||
from __future__ import division | ||
|
||
from compas.geometry import Point | ||
from compas.geometry import length_vector | ||
from compas.geometry import subtract_vectors | ||
from compas.geometry import intersection_line_triangle | ||
from compas.geometry import intersection_segment_plane | ||
|
||
__all__ = [ | ||
'intersection_mesh_line', | ||
'intersection_mesh_plane', | ||
'mesh_vertices_to_points', | ||
] | ||
|
||
|
||
def intersection_mesh_line(mesh, line): | ||
"""Compute intersection between mesh faces and line. After one single intersection, stops searching for more. | ||
|
||
Parameters | ||
---------- | ||
mesh : compas.datastructures.Mesh | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make the documentation reference-able, this should read
and similarly for all references in this PR. |
||
line : compas.geometry.Line | ||
|
||
Returns | ||
------- | ||
Point : compas.geometry.Point | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
""" | ||
for fkey in list(mesh.faces()): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I understand it
|
||
vertex_keys = mesh.face_vertices(fkey) | ||
if not vertex_keys: | ||
continue | ||
vertices = [mesh.vertex_attributes(vkey, 'xyz') for vkey in vertex_keys] | ||
if len(vertex_keys) not in (3, 4): | ||
continue | ||
Comment on lines
+32
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be simplified to
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well or a condition that fails early (the if not vertex_keys or len(vertex_keys) not in (3, 4):
continue |
||
|
||
triangle = [vertices[0], vertices[1], vertices[2]] | ||
intersection = intersection_line_triangle(line, triangle) | ||
if intersection: | ||
return Point(intersection[0], intersection[1], intersection[2]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would this work?
|
||
|
||
if len(vertex_keys) == 4: | ||
triangle_2 = [vertices[2], vertices[3], vertices[0]] | ||
intersection_2 = intersection_line_triangle(line, triangle_2) | ||
if intersection_2: | ||
return Point(intersection_2[0], intersection_2[1], intersection_2[2]) | ||
else: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indent |
||
return None | ||
|
||
|
||
def intersection_mesh_plane(mesh, plane, tol=0.0001): | ||
"""Calculate the keys of the points of the intersection of a mesh with a plane | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a full stop at the end of the sentence. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function not only returns the intersection points, but modifies the mesh structure, splitting any edge that cross the plane. I think that should be noted in the docstring. |
||
|
||
Parameters | ||
---------- | ||
mesh : compas.datastructures.Mesh | ||
plane : compas.geometry.Plane | ||
|
||
Returns | ||
------- | ||
intersections: list of points as keys from mesh | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
""" | ||
intersections = [] | ||
for u, v in list(mesh.edges()): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, for efficiency |
||
a = mesh.vertex_attributes(u, 'xyz') | ||
b = mesh.vertex_attributes(v, 'xyz') | ||
intersection = intersection_segment_plane((a, b), plane) | ||
if not intersection: | ||
continue | ||
len_a_inters = length_vector(subtract_vectors(intersection, a)) | ||
len_a_b = length_vector(subtract_vectors(b, a)) | ||
t = len_a_inters / len_a_b | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure that |
||
if t >= 1.0: | ||
t = 1 - tol | ||
elif t <= 0.0: | ||
t = tol | ||
intersection_key = mesh.split_edge(u, v, t=t, allow_boundary=True) | ||
intersections.append(intersection_key) | ||
return intersections | ||
|
||
|
||
def mesh_vertices_to_points(mesh, v_keys): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function doesn't have much functionality, and I don't see much point of adding it to the library. |
||
"""Compute compas points from vertex keys from specific mesh | ||
Returns list of compas points from a list of indexes of the vertexes of a mesh | ||
|
||
Parameters | ||
---------- | ||
mesh : compas.datastructures.Mesh | ||
v_keys : list of vertex indexes of a mesh | ||
|
||
Returns | ||
------- | ||
list of compas.geometry.Point | ||
""" | ||
return [Point(*mesh.vertex_attributes(v_key, 'xyz')) for v_key in v_keys] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ | |
from compas.geometry._primitives import Primitive | ||
from compas.geometry._primitives import Point | ||
|
||
|
||
__all__ = ['Line'] | ||
|
||
|
||
|
@@ -337,11 +336,37 @@ def transformed(self, T): | |
line.transform(T) | ||
return line | ||
|
||
def divide_by_count(self, number=10, include_ends=False): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find it a little confusing that the name of the function is |
||
"""Return list of points from dividing the line by specific number of divisions | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps |
||
|
||
Parameters | ||
---------- | ||
number : integer | ||
number of divisions | ||
includeEnds : boolean | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be
|
||
True if including start and end point in division points | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Double ` around True and False would make the documentation look a little more consistent, that is:
|
||
False if not including start and end point in division points | ||
|
||
Returns | ||
------- | ||
list of :class:`compas.geometry.Point` | ||
Point as sequence of values xyz | ||
|
||
Example | ||
-------- | ||
>>> line = Line([0.0, 0.0, 0.0], [5.0 ,0.0, 0.0]) | ||
>>> line.divide_by_count(5, True) | ||
[Point(0.000, 0.000, 0.000), Point(1.000, 0.000, 0.000), Point(2.000, 0.000, 0.000), Point(3.000, 0.000, 0.000), Point(4.000, 0.000, 0.000), Point(5.000, 0.000, 0.000)] | ||
""" | ||
if include_ends: | ||
return [self.point(i * float(1 / number)) for i in range(int(number)+1)] | ||
else: | ||
return [self.point(i * float(1.0 / number)) for i in range(int(number) + 1) if i != 0 or i != number] | ||
# ============================================================================== | ||
# Main | ||
# ============================================================================== | ||
|
||
|
||
if __name__ == '__main__': | ||
|
||
import doctest | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -319,11 +319,112 @@ def transformed(self, T): | |
polyline.transform(T) | ||
return polyline | ||
|
||
def shorten(self, start_distance=0, end_distance=0): | ||
"""Return a new polyline which is shorter than the original in one end side, other or both by a given distance. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about something like "Return a new polyline truncated by the given distances on either side."? |
||
|
||
Parameters | ||
---------- | ||
start_distance : float. | ||
distance to shorten from the starting point of the polyline | ||
Comment on lines
+327
to
+328
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
and similarly below |
||
end_distance : float. | ||
distance to shorten from the ending point of the polyline | ||
|
||
Returns | ||
------- | ||
:class:`compas.geometry.Polyline` | ||
The transformed copy. | ||
""" | ||
if start_distance != 0 or end_distance != 0: | ||
points = [] | ||
acum_length = 0 | ||
switch = True | ||
for i, line in enumerate(self.lines): | ||
acum_length += line.length | ||
if acum_length < start_distance: | ||
continue | ||
elif acum_length > start_distance and switch: | ||
if start_distance == 0: | ||
points.append(line.start) | ||
else: | ||
points.append(self.point(start_distance/self.length)) | ||
switch = False | ||
else: | ||
points.append(line.start) | ||
if end_distance == 0: | ||
if i == len(self.lines)-1: | ||
points.append(line.end) | ||
else: | ||
if acum_length >= (self.length - end_distance): | ||
points.append(self.point(1-(end_distance/self.length))) | ||
break | ||
Comment on lines
+338
to
+359
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little worried about edge cases. Let's say you take
|
||
return points | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a list, not a polyline as indicated in the docstring. So either the docstring should be changed or something along the lines of (maybe there's a better way):
|
||
return self | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a copy as might be indicated by the docstring. So either, the docstring should be changed or
|
||
|
||
def rebuild(self, number=20): | ||
"""Reconstruct a polyline with evenly spaced points based on a number of interpolations | ||
Returns new rebuilt polyline | ||
|
||
Parameters | ||
---------- | ||
number : integer. | ||
number of points for the amount of definition of the polyline | ||
|
||
Returns | ||
------- | ||
:class: 'compas.geometry.Polyline' | ||
the rebuilt copy | ||
""" | ||
rebuilt_polyline = self.copy() | ||
points = [self.point(i * float(1.0 / number)) for i in range(number)] | ||
points.append(self.point(1)) | ||
Comment on lines
+378
to
+379
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
rebuilt_polyline.points = [Point(x, y, z) for x, y, z in points] | ||
return rebuilt_polyline | ||
|
||
def divide_by_count(self, number=10, include_ends=False): | ||
"""Divide a polyline by count. Returns list of Points from the division | ||
Comment on lines
+383
to
+384
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I find the count/number discrepancy confusing. Whatever you choose to do with the |
||
|
||
Parameters | ||
---------- | ||
number : integer. | ||
number of divisions | ||
Comment on lines
+388
to
+389
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
includeEnds : boolean | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
True if including start and ending points. | ||
False if not including start and ending points. | ||
|
||
Returns | ||
------- | ||
list of :class: 'compas.geometry.Point' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
""" | ||
points = [self.point(i * float(1.0 / number)) for i in range(number)] | ||
if include_ends: | ||
points.append(self.point(1)) | ||
else: | ||
points.pop(0) | ||
return points | ||
Comment on lines
+398
to
+403
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
def tween(self, polyline_two, number=50): | ||
"""Create an average polyline between two polylines interpolating their points | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean to create a polyline or a list of points? |
||
|
||
Parameters | ||
---------- | ||
polyline_two : compas.geometry.Polyline | ||
polyline to create the tween polyline | ||
number : number of points of the tween polyline | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
Returns | ||
------- | ||
list of :class: 'compas.geometry.Point' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
""" | ||
rebuilt_polyline_one = self.rebuild(number) | ||
rebuilt_polyline_two = polyline_two.rebuild(number) | ||
lines = [Line(point_one, point_two) for point_one, point_two in zip(rebuilt_polyline_one, rebuilt_polyline_two)] | ||
return [line.midpoint for line in lines] | ||
|
||
# ============================================================================== | ||
# Main | ||
# ============================================================================== | ||
|
||
|
||
if __name__ == '__main__': | ||
|
||
import doctest | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
from __future__ import print_function | ||
from __future__ import absolute_import | ||
from __future__ import division | ||
|
||
from compas.geometry import Translation | ||
from compas.geometry import Polyline | ||
from compas.geometry import Vector | ||
|
||
__all__ = [ | ||
'extend_line', | ||
'extend_polyline', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have two comments about these two functions. First, |
||
] | ||
|
||
|
||
def extend_line(line, start_extension=0, end_extension=0): | ||
"""Extend the given line from one end or the other, or both, depending on the given values | ||
|
||
Parameters | ||
---------- | ||
line : tuple | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tuple or Line or both? |
||
Two points defining the line. | ||
start_extension : float | ||
The extension distance at the start of the line as float. | ||
end_extension : float | ||
The extension distance at the end of the line as float. | ||
|
||
Returns | ||
------- | ||
extended line : tuple | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tuple or Line? |
||
Two points defining the extended line. | ||
|
||
Examples | ||
-------- | ||
>>> line = Line([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]) | ||
>>> extended_line = extend_line(line, 1, 1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be simply |
||
Line([-1.0, 0.0, 0.0], [2.0, 0.0, 0.0]) | ||
""" | ||
def calculate_translation(line, distance): | ||
vector = line.direction.copy() | ||
vector.scale(distance) | ||
return Translation(vector) | ||
|
||
if start_extension != 0: | ||
translation = calculate_translation(line, -start_extension) | ||
line.start.transform(translation) | ||
if end_extension != 0: | ||
translation = calculate_translation(line, end_extension) | ||
line.end.transform(translation) | ||
|
||
return line | ||
|
||
|
||
def extend_polyline(polyline, start_extension=0, end_extension=0): | ||
"""Extend a polyline by line from the vectors on segments at extreme sides | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe mention that this function returns a new polyline with 2 more points than was in the original polyline |
||
|
||
Parameters | ||
---------- | ||
polyline : list | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. list or Polyline or both? |
||
list of points defining the polyline. | ||
start_extension : float | ||
The extension distance at the start of the polyline as float. | ||
end_extension : float | ||
The extension distance at the end of the polyline as float. | ||
|
||
Returns | ||
------- | ||
extended polyline : compas.geometry.Polyline(points) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
the documentation doesn't need to know what you called the variable internally. |
||
|
||
Examples | ||
-------- | ||
>>> polyline = Polyline([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 1.0, 0.0], [3.0, 1.0, 0.0], [4.0, 0.0, 0.0], [5.0, 0.0, 0.0]) | ||
>>> extended_polyline = extend_polyline(polyline, 1, 1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as in example above. |
||
Polyline([-1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 1.0, 0.0], [3.0, 1.0, 0.0], [4.0, 0.0, 0.0], [5.0, 0.0, 0.0], [6.0, 0.0, 0.0]) | ||
""" | ||
def calculate_translation_vector(vector, distance): | ||
vector.unitize() | ||
vector.scale(distance) | ||
return Translation(vector) | ||
|
||
points = polyline.points | ||
if start_extension != 0: | ||
point_start = polyline.points[0] | ||
vec = Vector.from_start_end(polyline.points[1], point_start) | ||
translation = calculate_translation_vector(vec, start_extension) | ||
new_point_start = point_start.transformed(translation) | ||
points.insert(0, new_point_start) | ||
|
||
if end_extension != 0: | ||
point_end = polyline.points[-1] | ||
vec_end = Vector.from_start_end(polyline.points[-2], point_end) | ||
translation = calculate_translation_vector(vec_end, end_extension) | ||
new_point_end = point_end.transformed(translation) | ||
points.append(new_point_end) | ||
|
||
return Polyline(points) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from __future__ import print_function | ||
from __future__ import absolute_import | ||
from __future__ import division | ||
|
||
__all__ = [name for name in dir() if not name.startswith('_')] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are very clear in the docstrings about the behavior of
intersection_mesh_line
andintersection_mesh_plane
, but I don't like that the one fails early (returning the first point of intersection with a line) and the other gives all points of intersection. I think the difference should be reflected in the names.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename both to
intersections_mesh_xxx
? with returned intersections sorted based on distance in the case ofline
?