Skip to content

Commit

Permalink
fix: takeoff operator now attempts to select a start frame that does …
Browse files Browse the repository at this point in the history
…not violate constraints
  • Loading branch information
ntamas committed Sep 18, 2024
1 parent 8c07c31 commit 0ad7e5f
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 32 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- When selecting a start frame for the takeoff operation, the operator chooses
in a way that it always falls between the first and the second formation.

## [3.5.0] - 2024-09-17

### Added
Expand Down
86 changes: 54 additions & 32 deletions src/modules/sbstudio/plugin/operators/takeoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from bpy.props import BoolProperty, FloatProperty, IntProperty
from bpy.types import Context
from math import ceil
from math import ceil, inf

from sbstudio.math.nearest_neighbors import find_nearest_neighbors
from sbstudio.plugin.api import get_api
Expand All @@ -19,7 +19,7 @@

class TakeoffOperator(StoryboardOperator):
"""Blender operator that adds a takeoff transition to the show, starting at
the current frame.
a given frame.
"""

bl_idname = "skybrush.takeoff"
Expand Down Expand Up @@ -79,21 +79,27 @@ class TakeoffOperator(StoryboardOperator):
)

@classmethod
def poll(cls, context):
def poll(cls, context: Context):
if not super().poll(context):
return False

drones = Collections.find_drones(create=False)
return drones is not None and len(drones.objects) > 0

def invoke(self, context, event):
def invoke(self, context: Context, event):
self.start_frame = context.scene.frame_current

# The start frame cannot be earlier than the start time of the first
# formation and must be earlier than the start time of the second
# formation. Constrain it to the valid range.
start, end = self._get_valid_range_for_start_frame(context)
self.start_frame = int(max(min(self.start_frame, end), start))
return context.window_manager.invoke_props_dialog(self)

def execute_on_storyboard(self, storyboard, entries, context):
def execute_on_storyboard(self, storyboard, entries, context: Context):
return {"FINISHED"} if self._run(storyboard, context=context) else {"CANCELLED"}

def _run(self, storyboard: Storyboard, *, context) -> bool:
def _run(self, storyboard: Storyboard, *, context: Context) -> bool:
bpy.ops.skybrush.prepare()

if not self._validate_start_frame(context):
Expand Down Expand Up @@ -171,34 +177,50 @@ def _run(self, storyboard: Storyboard, *, context) -> bool:

return True

def _validate_start_frame(self, context: Context) -> bool:
"""Returns whether the takeoff time chosen by the user is valid."""
storyboard = get_storyboard(context=context)
def _get_valid_range_for_start_frame(self, context: Context) -> tuple[float, float]:
"""Returns the interval that must contain the start frame of the takeoff
operation.
The returned range is closed from the left and open from the right.
"""
# Note: we assume here that the first entry is the takeoff grid on ground
if len(storyboard.entries) > 0:
storyboard = get_storyboard(context=context)
if len(storyboard.entries) <= 0:
# Storyboard is empty
return -inf, inf
elif len(storyboard.entries) == 1:
# Storyboard has a takeoff grid only
assert storyboard.first_entry is not None
frame = storyboard.first_entry.frame_end
if self.start_frame < frame:
self.report(
{"ERROR"},
(
f"Takeoff maneuver must start after the first (takeoff "
f"grid) entry of the storyboard (frame {frame})"
),
)
return False
if len(storyboard.entries) > 1:
assert storyboard.second_entry is not None
frame = storyboard.second_entry.frame_start
if frame is not None and self.start_frame >= frame:
self.report(
{"ERROR"},
(
f"Takeoff maneuver must start before the second "
f"entry of the storyboard (frame {frame})"
),
)
return False
return storyboard.first_entry.frame_end, inf
else:
# Storyboard has both a takeoff grid and an existing takeoff
# formation
assert storyboard.first_entry is not None
assert storyboard.second_entry is not None
return storyboard.first_entry.frame_end, storyboard.second_entry.frame_start

def _validate_start_frame(self, context: Context) -> bool:
"""Returns whether the takeoff time chosen by the user is valid."""
start, end = self._get_valid_range_for_start_frame(context)
if self.start_frame < start:
self.report(
{"ERROR"},
(
f"Takeoff maneuver must start after the first (takeoff "
f"grid) entry of the storyboard (frame {start})"
),
)
return False

if self.start_frame >= end:
self.report(
{"ERROR"},
(
f"Takeoff maneuver must start before the second "
f"entry of the storyboard (frame {end})"
),
)
return False

return True

Expand Down

0 comments on commit 0ad7e5f

Please sign in to comment.