Skip to content
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

Option to offset trajectory playback #608

Merged
merged 3 commits into from
Sep 16, 2024
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
50 changes: 31 additions & 19 deletions molecularnodes/entities/trajectory/trajectory.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ def subframes(self, value: int):
return None
obj.mn.subframes = value

@property
def offset(self) -> int:
try:
return self.object.mn.offset
except AttributeError:
return None

@offset.setter
def offset(self, value: int):
self.object.mn.offset = value

@property
def interpolate(self) -> bool:
obj = self.object
Expand Down Expand Up @@ -516,15 +527,17 @@ def _update_positions(self, frame):
raise ObjectMissingError(
"Object is deleted and unable to establish a connection with a new Blender Object."
)
try:
subframes = obj.mn.subframes
interpolate = obj.mn.interpolate
except ReferenceError as e:
print(e)
return None

if frame < 0:
return None
subframes: int = obj.mn.subframes
interpolate: bool = obj.mn.interpolate
offset: int = obj.mn.offset

# we subtraect the offset, a negative offset value ensures that the trajectory starts
# playback that many frames before 0 and a positive value ensures we start the
# playback after 0
frame -= offset
# for actually getting frames from the trajectory we need to clamp it to a lower
# bound of 0 which will be the start frame for the trajectory
frame = max(frame, 0)

if frame_mapping:
# add the subframes to the frame mapping
Expand Down Expand Up @@ -553,27 +566,26 @@ def _update_positions(self, frame):
fraction = frame % (subframes + 1) / (subframes + 1)

# get the positions for the next frame
locations_a = self.positions
positions_a = self.positions

if frame_b < universe.trajectory.n_frames:
self.frame = frame_b
locations_b = self.positions
positions_b = self.positions

if obj.mn.correct_periodic and self.is_orthorhombic:
locations_b = correct_periodic_positions(
locations_a,
locations_b,
positions_b = correct_periodic_positions(
positions_a,
positions_b,
dimensions=universe.dimensions[:3] * self.world_scale,
)

# interpolate between the two sets of positions
locations = lerp(locations_a, locations_b, t=fraction)
positions = lerp(positions_a, positions_b, t=fraction)
else:
locations = self.positions
positions = self.positions

# update the positions of the underlying vertices and record which frame was used
# for setting these positions
self.set_position(locations)
# update the positions of the underlying vertices
self.set_position(positions)

def __repr__(self):
return f"<Trajectory, `universe`: {self.universe}, `object`: {self.object}"
8 changes: 7 additions & 1 deletion molecularnodes/props.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ class MolecularNodesObjectProperties(bpy.types.PropertyGroup):
default=0,
update=_update_trajectories,
)
offset: IntProperty( # type: ignore
name="Offset",
description="Offset the starting playback for the trajectory on the timeine. Positive starts the playback later than frame 0, negative starts it earlier than frame 0",
default=0,
update=_update_trajectories,
)
interpolate: BoolProperty( # type: ignore
name="Interpolate",
description="Whether to interpolate when using subframes",
Expand All @@ -84,7 +90,7 @@ class MolecularNodesObjectProperties(bpy.types.PropertyGroup):
)
correct_periodic: BoolProperty( # type: ignore
name="Correct",
description="Correct for periodic boundary crossing when using interpolation. Assumes cubic dimensions.",
description="Correct for periodic boundary crossing when using interpolation. Assumes cubic dimensions",
default=True,
update=_update_trajectories,
)
1 change: 1 addition & 0 deletions molecularnodes/ui/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ def panel_md_properties(layout, context):

layout.label(text="Trajectory Playback", icon="OPTIONS")
row = layout.row()
row.prop(obj.mn, "offset")
row.prop(obj.mn, "subframes")
row.prop(obj.mn, "interpolate")

Expand Down
23 changes: 23 additions & 0 deletions tests/test_trajectory.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,29 @@ def test_trajectory_update(self, snapshot_custom, Trajectory):

assert not np.allclose(pos_a, pos_b)

@pytest.mark.parametrize("offset", [-2, 2])
def test_trajectory_offset(self, Trajectory, offset):
traj = Trajectory
traj.offset = 0
bpy.context.scene.frame_set(0)
pos_0 = traj.named_attribute("position")

# if the offset is negative, the positions of the starting frame 0 will change.
# if the offset is positive, then all of the frames up till the offset frame
# will remain the same
traj.offset = offset
if offset < 0:
assert not np.allclose(pos_0, traj.named_attribute("position"))
else:
assert np.allclose(pos_0, traj.named_attribute("position"))
bpy.context.scene.frame_set(offset - 1)
assert np.allclose(pos_0, traj.named_attribute("position"))

# after resetting the offset to 0, it should be the same as the initial positions
bpy.context.scene.frame_set(0)
traj.offset = 0
assert np.allclose(pos_0, traj.named_attribute("position"))

@pytest.mark.parametrize("interpolate", [True, False])
def test_subframes(self, Trajectory, interpolate):
traj = Trajectory
Expand Down
Loading