Skip to content

Commit

Permalink
fixes #1208
Browse files Browse the repository at this point in the history
the updated scipy dependency is due to the Slerp being added in 1.2

see: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Slerp.html
  • Loading branch information
jf--- committed Apr 6, 2024
1 parent bba3f4b commit bb2683b
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 1 deletion.
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 an `compas.geometry.Frame.interpolate_frame(s)` method

### Changed

* Changed and updated the `compas_view2` examples into `compas_viewer`.
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
jsonschema
networkx >= 3.0
numpy >= 1.15.4
scipy >= 1.1
scipy >= 1.2
typing_extensions
watchdog; sys_platform != 'emscripten'
88 changes: 88 additions & 0 deletions src/compas/geometry/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
from .point import Point
from .quaternion import Quaternion

from scipy.spatial.transform import Rotation as R
from scipy.spatial.transform import Slerp
from ..itertools import linspace


# from compas.geometry import Frame, Point, Vector
# from typing import List


class Frame(Geometry):
"""A frame is defined by a base point and two orthonormal base vectors.
Expand Down Expand Up @@ -588,6 +596,86 @@ def to_transformation(self):
"""
return Transformation.from_frame(self)


# def frame_to_rotation_matrix(frame: Frame) -> np.ndarray:
# """Convert a COMPAS frame to a rotation matrix."""
# return np.array(frame.rotation_matrix)
#
# def rotation_matrix_to_quat(matrix: np.ndarray) -> np.quaternion:
# """Convert a rotation matrix to a quaternion."""
# return R.from_matrix(matrix).as_quat()
#
# def quat_to_rotation_matrix(quat: np.quaternion) -> np.ndarray:
# """Convert a quaternion to a rotation matrix."""
# return R.from_quat(quat).as_matrix()

def interpolate_frame(self, other, t):
"""Interpolates between two frames at a given parameter t in the range [0, 1]
Parameters
----------
other : :class:`compas.geometry.Frame`
t: float
a parameter in the range [0-1]
Returns
-------
:class:`compas.geometry.Frame`
the interpolated frame
Examples
--------
>>> frame1 = Frame(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0))
>>> frame2 = Frame(Point(1, 1, 1), Vector(0, 0, 1), Vector(0, 1, 0))
>>> start_frame = frame1.interpolate_frame(frame2, 0)
>>>> allclose(start_frame.point, frame1.point) and allclose(start_frame.quaternion, frame1.quaternion)
True
"""
quat1 = Quaternion.from_frame(self)
quat2 = Quaternion.from_frame(other)
key_rotations = R.from_quat([quat1, quat2])

key_times = [0.0, 1.0]

slerp = Slerp(key_times, key_rotations)

interpolated_rot = slerp([t])[0]

# Interpolate origin
origin_interpolated = (self.point * (1.0 - t)) + other.point * t

# Convert interpolated quaternion back to a compas Quaternion
rot = Quaternion(*interpolated_rot.as_quat().tolist())

# Create a new frame with the interpolated position and orientation
interpolated_frame = Frame.from_quaternion(rot, origin_interpolated)

return interpolated_frame

def interpolate_frames(self, other, steps):
"""Generates a specified number of interpolated frames between two given frames
Parameters
----------
other: Frame
steps: int
the number of interpolated frames to return
Returns
-------
list of :class:`compas.geometry.Frame`
Examples
--------
>>> frame1 = Frame(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0))
>>> frame2 = Frame(Point(1, 1, 1), Vector(0, 0, 1), Vector(0, 1, 0))
>>> steps = 5
>>> frames = frame1.interpolate_frames(frame2, steps)
>>> assert len(frames) == steps
True
"""
return [self.interpolate_frame(other, t) for t in linspace(0, 1, steps)]

# ==========================================================================
# Methods
# ==========================================================================
Expand Down
13 changes: 13 additions & 0 deletions tests/compas/geometry/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,16 @@ def test_frame_predefined():
assert frame.point == Point(0, 0, 0)
assert frame.xaxis == Vector(0, 0, 1)
assert frame.yaxis == Vector(1, 0, 0)


def test_interpolate_frame_start_end():
frame1 = Frame(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0))
frame2 = Frame(Point(1, 1, 1), Vector(0, 0, 1), Vector(0, 1, 0))

# Test interpolation at the start
start_frame = frame1.interpolate_frame(frame2, 0)
assert start_frame.point == frame1.point and start_frame.xaxis == frame1.xaxis and start_frame.yaxis == frame1.yaxis, "Failed at t=0"

# Test interpolation at the end
end_frame = frame1.interpolate_frame(frame2, 1)
assert end_frame.point == frame2.point and end_frame.xaxis == frame2.xaxis and end_frame.yaxis == frame2.yaxis, "Failed at t=1"

0 comments on commit bb2683b

Please sign in to comment.