Skip to content

Commit

Permalink
EDITOR-NTUEELightDance#529 [BLENDER] LED effect editor (NTUEELightDan…
Browse files Browse the repository at this point in the history
…ce#532)

* enabled LED effect 'edit'

* enabled LED effect add/delete

* enabled (partial) subscription

* changed delete effect sub format in backend, clean unused import
  • Loading branch information
Chalkman071 authored and ppg0921 committed Feb 19, 2024
1 parent 32aa6ef commit e4a84b7
Show file tree
Hide file tree
Showing 17 changed files with 443 additions and 68 deletions.
40 changes: 39 additions & 1 deletion editor-blender/api/led_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@
from ..core.utils.convert import led_map_query_to_state
from ..graphqls.mutations import (
ADD_LED_EFFECT,
CANCEL_EDIT_LED_EFFECT_BY_ID,
DELETE_LED_EFFECT,
EDIT_LED_EFFECT,
REQUEST_EDIT_LED_EFFECT_BY_ID,
MutAddLEDEffectInput,
MutAddLEDEffectResponse,
MutCancelEditLEDEffectResponse,
MutDeleteLEDEffectResponse,
MutEditLEDEffectInput,
MutEditLEDEffectResponse,
MutLEDEffectFramePayload,
MutRequestEditLEDEffectResponse,
)
from ..graphqls.queries import GET_LED_MAP, QueryLEDMapData

Expand Down Expand Up @@ -69,7 +73,7 @@ async def add_led_effect(

return None

async def edit_led_effect(
async def save_led_effect(
self, id: ID, name: str, leds: List[Tuple[ColorID, int]]
) -> Optional[MutEditLEDEffectResponse]:
try:
Expand Down Expand Up @@ -97,6 +101,40 @@ async def edit_led_effect(

return None

async def request_edit(self, id: LEDEffectID) -> Optional[bool]:
try:
response = await client.execute(
MutRequestEditLEDEffectResponse,
REQUEST_EDIT_LED_EFFECT_BY_ID,
{"ledEffectId": id},
)
return response["RequestEditLEDEffect"].ok

except asyncio.CancelledError:
pass

except Exception as e:
print(e)

return None

async def cancel_edit(self, id: LEDEffectID) -> Optional[bool]:
try:
response = await client.execute(
MutCancelEditLEDEffectResponse,
CANCEL_EDIT_LED_EFFECT_BY_ID,
{"ledEffectId": id},
)
return response["CancelEditLEDEffect"].ok

except asyncio.CancelledError:
pass

except Exception as e:
print(e)

return None

async def delete_led_effect(
self, id: LEDEffectID
) -> Optional[MutDeleteLEDEffectResponse]:
Expand Down
47 changes: 46 additions & 1 deletion editor-blender/client/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@
set_control_record,
update_control,
)
from ..core.actions.state.led_map import (
add_led_effect,
delete_led_effect,
edit_led_effect,
)
from ..core.actions.state.pos_map import add_pos, delete_pos, set_pos_record, update_pos
from ..core.models import ID
from ..core.models import ID, LEDMap
from ..core.utils.convert import (
color_query_to_state,
control_frame_query_to_state,
control_frame_sub_to_query,
effect_list_data_sub_to_query,
led_record_sub_to_state_item,
pos_frame_query_to_state,
pos_frame_sub_to_query,
)
Expand All @@ -34,6 +40,7 @@
SUB_CONTROL_MAP,
SUB_CONTROL_RECORD,
SUB_EFFECT_LIST,
SUB_LED_RECORD,
SUB_POS_MAP,
SUB_POS_RECORD,
SubColorData,
Expand All @@ -42,6 +49,7 @@
SubControlRecordData,
SubEffectListData,
SubEffectListMutation,
SubLEDRecordData,
SubPositionMapData,
SubPositionRecordData,
)
Expand Down Expand Up @@ -243,6 +251,42 @@ async def modifier(effectList: Optional[QueryEffectListData]):
await client.cache.modify(Modifiers(fields={"effectList": modifier}))


async def sub_led_record(client: Clients):
async for data in client.subscribe(SubLEDRecordData, SUB_LED_RECORD):
print("SubEffectRecord:", data)

async def modifier(LedMap: Optional[LEDMap]):
subscriptionData = data["ledRecordSubscription"]

newLedMap: LEDMap = {}
if LedMap is not None:
newLedMap = LedMap

for item in subscriptionData.createEffects:
effect_item = led_record_sub_to_state_item(item)
# newLedMap[item.model_name][item.part_name][item.name] = effect_item
add_led_effect(item.model_name, item.part_name, item.name, effect_item)

for item in subscriptionData.deleteEffects:
# delete_name = next(name for name, effect in newLedMap[item.model_name][item.part_name].items() if effect.id == item.id)
# del newLedMap[item.model_name][item.part_name][delete_name]
delete_led_effect(item.model_name, item.part_name, item.id)

for item in subscriptionData.updateEffects:
# edited_name = next(name for name, effect in newLedMap[item.model_name][item.part_name].items() if effect.id == item.id)
effect_item = led_record_sub_to_state_item(item)
# if item.name == edited_name:
# newLedMap[item.model_name][item.part_name][edited_name] = effect_item
# else:
# del newLedMap[item.model_name][item.part_name][edited_name]
# newLedMap[item.model_name][item.part_name][item.name] = effect_item
edit_led_effect(item.model_name, item.part_name, item.name, effect_item)

return newLedMap

await client.cache.modify(Modifiers(fields={"LEDMap": modifier}))


# TODO: Implement lazy update
async def sub_color_map(client: Clients):
async for data in client.subscribe(SubColorData, SUB_COLOR_MAP):
Expand Down Expand Up @@ -306,6 +350,7 @@ async def subscribe():
# asyncio.create_task(sub_control_record(client)),
asyncio.create_task(sub_control_map(client)),
# asyncio.create_task(sub_effect_list(client)),
asyncio.create_task(sub_led_record(client)),
asyncio.create_task(sub_color_map(client)),
]

Expand Down
5 changes: 5 additions & 0 deletions editor-blender/core/actions/property/led_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ def update_edit_part(self: bpy.types.PropertyGroup, context: bpy.types.Context):
bpy.context.view_layer.objects.active = part_obj


def update_edit_effect(self: bpy.types.PropertyGroup, context: bpy.types.Context):
effect_index = self["edit_effect"]
state.current_led_index = effect_index


def update_multi_select_color(
self: bpy.types.PropertyGroup, context: bpy.types.Context
):
Expand Down
2 changes: 1 addition & 1 deletion editor-blender/core/actions/state/control_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ async def request_edit_control():

redraw_area({"VIEW_3D", "DOPESHEET_EDITOR"})
else:
notify("WARNING", "Cannot cancel edit")
notify("WARNING", "Edit request rejected")


async def cancel_edit_control():
Expand Down
135 changes: 125 additions & 10 deletions editor-blender/core/actions/state/led_editor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import Optional
from typing import List, Tuple

import bpy

from ....core.models import EditMode
from ....api.led_agent import led_agent
from ....core.models import ColorID, EditMode, LEDEffect
from ....core.states import state
from ....core.utils.operator import execute_operator
from ....core.utils.ui import (
Expand All @@ -15,24 +16,138 @@
unset_outliner_hide_mode_column,
)
from ....properties.ui.types import LEDEditorEditModeType, LEDEditorStatusType
from ...utils.notification import notify
from .app_state import set_requesting


async def add_led_effect():
ld_ui_led_editor: LEDEditorStatusType = getattr(
bpy.context.window_manager, "ld_ui_led_editor"
)
state.edit_state = EditMode.EDITING
ld_ui_led_editor.edit_mode = LEDEditorEditModeType.NEW.value
ld_ui_led_editor.new_effect = "New effect"


async def request_edit_led_effect():
# TODO: Send request
enter_editing_led_effect()
led_index = state.current_led_index
set_requesting(True)
ok = await led_agent.request_edit(led_index)
set_requesting(False)
if ok is not None and ok:
enter_editing_led_effect()
else:
notify("WARNING", "Edit request rejected")


async def cancel_edit_led_effect():
# TODO: Send request
exit_editing_led_effect()
ld_ui_led_editor: LEDEditorStatusType = getattr(
bpy.context.window_manager, "ld_ui_led_editor"
)
led_index = state.current_led_index
edit_mode = ld_ui_led_editor.edit_mode
match edit_mode:
case LEDEditorEditModeType.EDIT.value:
set_requesting(True)
ok = await led_agent.cancel_edit(led_index)
set_requesting(False)
if ok is not None and ok:
exit_editing_led_effect()
notify("INFO", "Edit cancelled")
else:
notify("WARNING", "Cannot cancel edit")
case LEDEditorEditModeType.NEW.value:
state.edit_state = EditMode.IDLE
ld_ui_led_editor.edit_mode = LEDEditorEditModeType.IDLE.value


async def save_led_effect():
pass
ld_ui_led_editor: LEDEditorStatusType = getattr(
bpy.context.window_manager, "ld_ui_led_editor"
)
edit_mode = ld_ui_led_editor.edit_mode
match edit_mode:
case LEDEditorEditModeType.EDIT.value:
led_index = state.current_led_index
led_effect = state.led_effect_id_table[led_index]
effect_name = led_effect.name
edit_dancer = ld_ui_led_editor.edit_dancer
edit_part = ld_ui_led_editor.edit_part
dancer_index = state.dancer_names.index(edit_dancer)
part_obj_name = f"{dancer_index}_" + edit_part
part_obj: bpy.types.Object = bpy.data.objects.get(part_obj_name) # type: ignore
part_child_objs = part_obj.children
new_effect: List[Tuple[ColorID, int]] = [(-1, 0)] * len(part_child_objs)
for i, obj in enumerate(part_child_objs):
if obj:
ld_color: ColorID = obj.get("ld_color") # type: ignore # must use get
ld_alpha: int = getattr(obj, "ld_alpha") # type: ignore # must use getattr
new_effect[i] = (ld_color, ld_alpha)
else:
raise Exception(f"LED bulb object missing in {part_obj_name}")
try:
set_requesting(True)
await led_agent.save_led_effect(led_index, effect_name, new_effect)
notify("INFO", "Saved LED Effect")

# Imediately apply changes produced by editing
# set_ctrl_keyframes_from_state(effect_only=True)

# Cancel editing
ok = await led_agent.cancel_edit(led_index)
set_requesting(False)
if ok is not None and ok:
exit_editing_led_effect()
else:
notify("WARNING", "Cannot exit editing")
except:
notify("WARNING", "Cannot save LED effect")
case LEDEditorEditModeType.NEW.value:
new_effect_name = ld_ui_led_editor.new_effect
edit_model = ld_ui_led_editor.edit_model
edit_dancer = ld_ui_led_editor.edit_dancer
edit_part = ld_ui_led_editor.edit_part
part_effect: LEDEffect = next(
effect for _, effect in state.led_map[edit_model][edit_part].items()
)
led_default = [
(bulb_data.color_id, bulb_data.alpha)
for bulb_data in part_effect.effect
]

try:
set_requesting(True)
res = await led_agent.add_led_effect(
new_effect_name, edit_model, edit_part, led_default
)
set_requesting(False)
if res and res.ok:
notify("INFO", f"Added LED Effect: {new_effect_name}")
state.edit_state = EditMode.IDLE
setattr(
ld_ui_led_editor, "edit_mode", LEDEditorEditModeType.IDLE.value
)
else:
notify("WARNING", "Cannot add LED effect")
except:
notify("WARNING", "Cannot add LED effect")


async def delete_led_effect():
pass
led_index = state.current_led_index
if led_index == -1:
notify("WARNING", "No LED effect is selected!")
return
try:
set_requesting(True)
res = await led_agent.delete_led_effect(led_index)
set_requesting(False)
if res and res.ok:
notify("INFO", f"Deleted LED effect: {led_index}")
else:
notify("WARNING", "Cannot delete LED effect")
except:
notify("WARNING", "Cannot delete LED effect")


def enter_editing_led_effect():
Expand Down Expand Up @@ -66,7 +181,7 @@ def enter_editing_led_effect():
dancer_obj.animation_data.action.fcurves.find("location", index=i).mute = True

for bulb_obj in part_obj.children:
for i in range(4):
for i in range(3):
bulb_obj.animation_data.action.fcurves.find("color", index=i).mute = True

# Only select human and bulbs for local view
Expand Down Expand Up @@ -111,7 +226,7 @@ def exit_editing_led_effect():
dancer_obj.animation_data.action.fcurves.find("location", index=i).mute = False

for bulb_obj in part_obj.children:
for i in range(4):
for i in range(3):
bulb_obj.animation_data.action.fcurves.find("color", index=i).mute = False

# Reset pos and color of dancer and LED bulbs
Expand Down
Loading

0 comments on commit e4a84b7

Please sign in to comment.