Skip to content
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 @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Fixed `NotImplementedErorr` when calling `BrepLoop.vertices`.
* Fixed `python -m compas` to detect extensions based on `importlib` rather than `pkg_resources`.
* `compas_rhino.uninstall` will try to remove compas packages from all possible install locations.
* Changed `angle_vectors_projected` to raise `ValueError` when an input vector is parallel to projection normal.
* Changed `angle_vectors` to raise `ValueError` when one of the input vectors is a zero-length vector instead of returning 0.

### Removed

Expand Down
47 changes: 23 additions & 24 deletions src/compas/geometry/_core/angles.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ def angle_vectors(u, v, deg=False, tol=None):
The tolerance for comparing values to zero.
Default is :attr:`TOL.absolute`.

Raises
------
ValueError
If one of the input vectors is a zero-length vector.

Returns
-------
float
Expand All @@ -44,32 +49,16 @@ def angle_vectors(u, v, deg=False, tol=None):
1.57079

"""
L = length_vector(u) * length_vector(v)
if TOL.is_zero(L, tol):
return 0
a = dot_vectors(u, v) / L
len_u = length_vector(u)
len_v = length_vector(v)

if TOL.is_zero(len_u, tol) or TOL.is_zero(len_v, tol):
raise ValueError("Cannot compute the angle between one or more zero-length vectors.")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps the length check should be done on the individual vectors. otherwise two small vectors will also trigger the error, even when both vectors individually would qualify for a valid angle calculation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like

a = length_vector(u)
b = length_vector(v)

if TOL.is_zero(a, tol) or TOL.is_zero(b, tol):
    raise ValueError(...)

cosine = dot_vectors(u, v) / (a * b)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, this is more or less what you do in the projected version


a = dot_vectors(u, v) / (len_u * len_v)
a = max(min(a, 1), -1)
angle = acos(a)

# a = length_vector(u)
# b = length_vector(v)
# if a < tol or b < tol:
# return 0
# c = length_vector(subtract_vectors(u, v))
# if c < tol:
# return 0
# if b >= c and c >= 0:
# mu = c - (a - b)
# elif c > b and b >= 0:
# mu = b - (a - c)
# else:
# raise Exception("Invalid input vectors.")
# angle = 2 * atan(sqrt(((a - b) + c) * mu / ((a + (b + c)) * ((a - c) + b))))

# a = normalize_vector(u)
# b = normalize_vector(v)
# angle = 2 * atan2(length_vector(subtract_vectors(a, b)), length_vector(add_vectors(a, b)))

if deg:
return degrees(angle)
return angle
Expand Down Expand Up @@ -140,6 +129,11 @@ def angle_vectors_projected(u, v, normal, deg=False, tol=None):
The tolerance for comparing values to zero.
Default is :attr:`TOL.absolute`.

Raises
------
ValueError
If one of the input vectors is parallel to the normal vector.

Returns
-------
float
Expand All @@ -155,7 +149,7 @@ def angle_vectors_projected(u, v, normal, deg=False, tol=None):
u_cross = cross_vectors(u, normal)
v_cross = cross_vectors(v, normal)

if TOL.is_allclose(u_cross, [0.0, 0.0, 0.0]) or TOL.is_allclose(v_cross, [0.0, 0.0, 0.0]):
if TOL.is_zero(length_vector(u_cross), tol) or TOL.is_zero(length_vector(v_cross), tol):
raise ValueError("Cannot compute angle between vectors projected onto a plane defined by the normal vector. One of the vectors is parallel to the normal vector.")

return angle_vectors_signed(u_cross, v_cross, normal, deg, tol)
Expand Down Expand Up @@ -283,6 +277,11 @@ def angles_vectors(u, v, deg=False):
deg : bool, optional
If True, returns the angle in degrees.

Raises
------
ValueError
If one of the input vectors is a zero-length vector.

Returns
-------
float
Expand Down
60 changes: 38 additions & 22 deletions tests/compas/geometry/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,28 @@ def test_angle_vectors(u, v, angle):
assert TOL.is_close(angle_vectors(u, v), angle)


# @pytest.mark.parametrize(
# ("u", "v"),
# [
# ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
# ([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]),
# ([1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
# ],
# )
# def test_angle_vectors_fails_when_input_is_zero(u, v):
# with pytest.raises(ZeroDivisionError):
# angle_vectors(u, v)
@pytest.mark.parametrize(
("u", "v"),
[
([1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]),
([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
],
)
def test_angle_vectors_fails_when_input_is_zero(u, v):
with pytest.raises(ValueError):
angle_vectors(u, v)


def test_angle_vectors_fails_when_input_is_zero_within_tolerance():
# length_vector(u) == 1e-4, which is zero within tol=1e-3
with pytest.raises(ValueError):
angle_vectors([1e-4, 0.0, 0.0], [0.0, 1e-2, 0.0], tol=1e-3)


def test_angle_vectors_when_input_is_not_zero_within_tolerance():
# length_vector(u) * length_vector(v) == 1e-4, which is not zero within tol=1e-6
assert TOL.is_close(angle_vectors([1e-2, 0.0, 0.0], [0.0, 1e-2, 0.0], tol=1e-6), pi / 2)


@pytest.mark.parametrize(
Expand All @@ -93,17 +104,17 @@ def test_angles_vectors(u, v, angles):
assert TOL.is_allclose(angles_vectors(u, v), (a, b))


# @pytest.mark.parametrize(
# ("u", "v"),
# [
# ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
# ([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]),
# ([1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
# ],
# )
# def test_angles_vectors_fails_when_input_is_zero(u, v):
# with pytest.raises(ZeroDivisionError):
# angles_vectors(u, v)
@pytest.mark.parametrize(
("u", "v"),
[
([1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]),
([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
],
)
def test_angles_vectors_fails_when_input_is_zero(u, v):
with pytest.raises(ValueError):
angles_vectors(u, v)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -149,6 +160,11 @@ def test_angle_vectors_projected(u, v, normal, result):
assert TOL.is_close(angle_vectors_projected(u, v, normal), result)


def test_angle_vectors_projected_fails_when_input_is_parallel_to_normal():
with pytest.raises(ValueError):
angle_vectors_projected([1, 0, 0], [0, 1, 0], [1, 0, 0])


# ==============================================================================
# average
# ==============================================================================
Expand Down
Loading