diff --git a/src/addons/ui_skybrush_studio.py b/src/addons/ui_skybrush_studio.py index ad58acaa..95c8b8fe 100644 --- a/src/addons/ui_skybrush_studio.py +++ b/src/addons/ui_skybrush_studio.py @@ -55,7 +55,6 @@ LEDControlPanelProperties, LightEffect, LightEffectCollection, - MappingType, SafetyCheckProperties, ScheduleOverride, StoryboardEntry, @@ -147,7 +146,6 @@ FormationsPanelProperties, LightEffect, LightEffectCollection, - MappingType, ScheduleOverride, StoryboardEntry, Storyboard, diff --git a/src/modules/sbstudio/plugin/model/__init__.py b/src/modules/sbstudio/plugin/model/__init__.py index 90d07e56..b0f6ffa3 100644 --- a/src/modules/sbstudio/plugin/model/__init__.py +++ b/src/modules/sbstudio/plugin/model/__init__.py @@ -6,7 +6,7 @@ from .safety_check import SafetyCheckProperties from .settings import DroneShowAddonFileSpecificSettings from .show import DroneShowAddonProperties -from .storyboard import MappingType, ScheduleOverride, StoryboardEntry, Storyboard +from .storyboard import ScheduleOverride, StoryboardEntry, Storyboard __all__ = ( "DroneShowAddonFileSpecificSettings", @@ -17,7 +17,6 @@ "LEDControlPanelProperties", "LightEffect", "LightEffectCollection", - "MappingType", "SafetyCheckProperties", "ScheduleOverride", "StoryboardEntry", diff --git a/src/modules/sbstudio/plugin/model/storyboard.py b/src/modules/sbstudio/plugin/model/storyboard.py index 08aab6a7..dd66d167 100644 --- a/src/modules/sbstudio/plugin/model/storyboard.py +++ b/src/modules/sbstudio/plugin/model/storyboard.py @@ -1,4 +1,5 @@ import bpy +import json from bpy.props import ( BoolProperty, @@ -25,7 +26,7 @@ from .formation import count_markers_in_formation from .mixins import ListMixin -__all__ = ("MappingType", "ScheduleOverride", "StoryboardEntry", "Storyboard") +__all__ = ("ScheduleOverride", "StoryboardEntry", "Storyboard") class ScheduleOverride(PropertyGroup): @@ -84,17 +85,11 @@ def _handle_formation_change(operator, context): operator.name = operator.formation.name if operator.formation else "" -class MappingType(bpy.types.PropertyGroup): - target = bpy.props.IntProperty() - - class StoryboardEntry(PropertyGroup): """Blender property group representing a single entry in the storyboard of the drone show. """ - mapping = CollectionProperty(type=MappingType) - maybe_uuid_do_not_use = StringProperty( name="Identifier", description=( @@ -201,6 +196,20 @@ class StoryboardEntry(PropertyGroup): ), ) + # mapping is stored as a string so we don't need to maintain a separate + # Blender collection as it would not be efficient + + _mapping = StringProperty( + name="Mapping", + description=( + "Mapping where the i-th element is the index of the drone that " + "marker i was matched to in the storyboard entry, or -1 if the " + "marker is unmatched." + ), + default="", + options={"HIDDEN"}, + ) + #: Sorting key for storyboard entries sort_key = attrgetter("frame_start", "frame_end") @@ -295,6 +304,21 @@ def get_enabled_schedule_override_map(self) -> Dict[int, ScheduleOverride]: return result + def get_mapping(self) -> Optional[List[int]]: + """Returns the mapping of the markers in the storyboard entry to drone + indices, or ``None`` if there is no mapping yet. + """ + encoded_mapping = self._mapping.strip() + if ( + not encoded_mapping + or len(encoded_mapping) < 2 + or encoded_mapping[0] != "[" + or encoded_mapping[-1] != "]" + ): + return None + else: + return json.loads(encoded_mapping) + def remove_active_schedule_override_entry(self) -> None: """Removes the active schedule override entry from the collection and adjusts the active entry index as needed. @@ -309,6 +333,20 @@ def remove_active_schedule_override_entry(self) -> None: max(0, index), len(self.schedule_overrides) ) + def update_mapping(self, mapping: Optional[List[int]]) -> None: + """Updates the mapping of the markers in the storyboard entry to drone + indices. + + Arguments: + mapping: mapping where the i-th item contains the index of the drone + that the i-th marker was mapped to, or -1 if the marker is + unmapped. You can also pass ``None`` to clear the entire mapping. + """ + if mapping is None: + self._mapping = "" + else: + self._mapping = json.dumps(mapping) + class Storyboard(PropertyGroup, ListMixin): """Blender property group representing the entire storyboard of the @@ -558,11 +596,11 @@ def get_mapping_at_frame(self, frame: int): """ index = self.get_index_of_entry_containing_frame(frame) if index >= 0: - return self.entries[index].mapping + return self.entries[index].get_mapping() index = self.get_index_of_entry_before_frame(frame) if index >= 0: - return self.entries[index].mapping + return self.entries[index].get_mapping() return None diff --git a/src/modules/sbstudio/plugin/operators/recalculate_transitions.py b/src/modules/sbstudio/plugin/operators/recalculate_transitions.py index efcf84ab..6a56f4d5 100644 --- a/src/modules/sbstudio/plugin/operators/recalculate_transitions.py +++ b/src/modules/sbstudio/plugin/operators/recalculate_transitions.py @@ -468,15 +468,19 @@ def update_transition_for_storyboard_entry( num_targets=num_markers, ) - # store mapping in Blender-compatible format for later use - entry.mapping.clear() - for i in range(len(mapping)): - try: - j = mapping.index(i) - entry.mapping.add() - entry.mapping[-1].target = j - except ValueError: - break + # Store mapping in Blender-compatible format for later use + # + # 'mapping' is a list where the i-th element contains the index of the + # target point that the i-th drone was matched to, or ``None`` if the drone + # was left unmatched; we need to invert that so we get a vector where the + # i-th element is the index of the drone that marker i was matched to or + # -1 if the marker is unmatched + inv_mapping: List[int] = [] + for drone_index, marker_index in enumerate(inv_mapping): + if len(inv_mapping) <= marker_index: + inv_mapping.extend([-1] * (marker_index - len(inv_mapping) + 1)) + inv_mapping[marker_index] = drone_index + entry.update_mapping(inv_mapping) # Calculate how many drones will participate in the transition num_drones_transitioning = sum(