diff --git a/editor-blender/api/control_agent.py b/editor-blender/api/control_agent.py index dfc374ca5..276702900 100644 --- a/editor-blender/api/control_agent.py +++ b/editor-blender/api/control_agent.py @@ -129,7 +129,7 @@ async def save_frame( EDIT_CONTROL_FRAME_TIME, { "input": MutEditControlFrameTimeInput( - frameID=id, start=start + frameId=id, start=start ) }, ) diff --git a/editor-blender/api/pos_agent.py b/editor-blender/api/pos_agent.py index 617a2cd84..694dafe17 100644 --- a/editor-blender/api/pos_agent.py +++ b/editor-blender/api/pos_agent.py @@ -125,7 +125,7 @@ async def save_frame( EDIT_POS_FRAME_TIME, { "input": MutEditPositionFrameTimeInput( - frameID=id, start=start + frameId=id, start=start ) }, ) diff --git a/editor-blender/core/actions/property/animation_data/__init__.py b/editor-blender/core/actions/property/animation_data/__init__.py new file mode 100644 index 000000000..91e1c0a00 --- /dev/null +++ b/editor-blender/core/actions/property/animation_data/__init__.py @@ -0,0 +1,12 @@ +from .control import ( + add_single_ctrl_keyframe, + delete_single_ctrl_keyframe, + edit_single_ctrl_keyframe, + set_ctrl_keyframes_from_state, +) +from .position import ( + add_single_pos_keyframe, + delete_single_pos_keyframe, + edit_single_pos_keyframe, + set_pos_keyframes_from_state, +) diff --git a/editor-blender/core/actions/property/animation_data/control.py b/editor-blender/core/actions/property/animation_data/control.py new file mode 100644 index 000000000..707c86a1b --- /dev/null +++ b/editor-blender/core/actions/property/animation_data/control.py @@ -0,0 +1,612 @@ +from typing import Dict, List, Optional, Tuple, cast + +import bpy + +from .....properties.types import RevisionPropertyItemType, RevisionPropertyType +from ....models import ControlMapElement, DancerName, LEDData, MapID, PartName, PartType +from ....states import state +from ....utils.convert import rgba_to_float +from .utils import ensure_action, ensure_curve, get_keyframe_points + +""" +setups & update colormap(===setups) +""" + + +def set_ctrl_keyframes_from_state(effect_only: bool = False): + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + ctrl_map = state.control_map + ctrl_frame_number = len(ctrl_map) + + color_map = state.color_map + led_effect_table = state.led_effect_id_table + + fade_seq: List[Tuple[int, bool]] = [(0, False)] * ctrl_frame_number + + prev_effect_ids: Dict[DancerName, Dict[PartName, List[int]]] = {} + + sorted_ctrl_map = sorted(ctrl_map.items(), key=lambda item: item[1].start) + + for i, (id, ctrl_map_element) in enumerate(sorted_ctrl_map): + frame_start = ctrl_map_element.start + ctrl_status = ctrl_map_element.status + + fade = ctrl_map_element.fade + fade_seq[i] = frame_start, fade + + for dancer_name, ctrl in ctrl_status.items(): + dancer_index = state.dancer_part_index_map[dancer_name].index + + for part_name, part_data in ctrl.items(): + part_obj_name = f"{dancer_index}_{part_name}" + + prev_effect_id = prev_effect_ids.setdefault(dancer_name, {}).setdefault( + part_name, [-1] + ) + + if isinstance(part_data, LEDData): + part_parent = data_objects[part_obj_name] + part_alpha = part_data.alpha + + if part_data.effect_id > 0: + part_effect = led_effect_table[part_data.effect_id].effect + led_rgb_floats = [ + rgba_to_float(color_map[led_data.color_id].rgb, part_alpha) + for led_data in part_effect + ] + + prev_effect_id[0] = part_data.effect_id + + elif prev_effect_id[0] > 0: + prev_effect = led_effect_table[prev_effect_id[0]].effect + led_rgb_floats = [ + rgba_to_float(color_map[led_data.color_id].rgb, part_alpha) + for led_data in prev_effect + ] + + else: + led_rgb_floats = [(0, 0, 0)] * len(part_parent.children) + + for led_obj in part_parent.children: + position: int = getattr(led_obj, "ld_led_pos") + led_rgb_float = led_rgb_floats[position] + + action = ensure_action( + led_obj, f"{part_obj_name}Action.{position:03}" + ) + curves = action.fcurves + + for d in range(3): + curve = ensure_curve( + action, + "color", + index=d, + keyframe_points=ctrl_frame_number, + clear=i == 0, + ) + + _, kpoints_list = get_keyframe_points(curve) + point = kpoints_list[i] + + point.co = frame_start, led_rgb_float[d] + point.interpolation = "LINEAR" if fade else "CONSTANT" + + else: + if effect_only: + continue + + part_obj = data_objects[part_obj_name] + + part_rgb = color_map[part_data.color_id].rgb + fiber_rgb_float = rgba_to_float(part_rgb, part_data.alpha) + + action = ensure_action(part_obj, f"{part_obj_name}Action") + curves = action.fcurves + + for d in range(3): + curve = ensure_curve( + action, + "color", + index=d, + keyframe_points=ctrl_frame_number, + clear=i == 0, + ) + + _, kpoints_list = get_keyframe_points(curve) + point = kpoints_list[i] + + point.co = frame_start, fiber_rgb_float[d] + point.interpolation = "LINEAR" if fade else "CONSTANT" + + # insert fake frame + scene = bpy.context.scene + action = ensure_action(scene, "SceneAction") + + curve = ensure_curve( + action, "ld_control_frame", keyframe_points=ctrl_frame_number, clear=i == 0 + ) + _, kpoints_list = get_keyframe_points(curve) + + point = kpoints_list[i] + point.co = frame_start, frame_start + point.interpolation = "CONSTANT" + + # set revision + rev = ctrl_map_element.rev + + # curve = ensure_curve( + # action, "ld_control__meta", keyframe_points=ctrl_frame_number, clear=i == 0 + # ) + # _, kpoints_list = get_keyframe_points(curve) + # + # point = kpoints_list[i] + # point.co = frame_start, rev.meta + # point.interpolation = "CONSTANT" + # + # curve = ensure_curve( + # action, "ld_control_data", keyframe_points=ctrl_frame_number, clear=i == 0 + # ) + # _, kpoints_list = get_keyframe_points(curve) + # + # point = kpoints_list[i] + # point.co = frame_start, rev.data + # point.interpolation = "CONSTANT" + + ctrl_rev_item: RevisionPropertyItemType = getattr( + bpy.context.scene, "ld_ctrl_rev" + ).add() + + ctrl_rev_item.data = rev.data if rev else -1 + ctrl_rev_item.meta = rev.meta if rev else -1 + + ctrl_rev_item.frame_id = id + ctrl_rev_item.frame_start = frame_start + + curves = bpy.context.scene.animation_data.action.fcurves + curve = curves.find("ld_control_frame") + # curve.keyframe_points.sort() + + fade_seq.sort(key=lambda item: item[0]) + _, kpoints_list = get_keyframe_points(curve) + + for frame_start, fade in fade_seq: + if fade: + point_index = next( + index + for index, point in enumerate(kpoints_list) + if point.co[0] == frame_start + ) + + if point_index == ctrl_frame_number - 1: + continue + + point = kpoints_list[point_index] + next_point = kpoints_list[point_index + 1] + + next_point.co = (next_point.co[0], point.co[1]) + + +""" +update control keyframes +""" + + +def add_single_ctrl_keyframe(id: MapID, ctrl_element: ControlMapElement): + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + color_map = state.color_map + led_effect_table = state.led_effect_id_table + + ctrl_status = ctrl_element.status + frame_start = ctrl_element.start + fade = ctrl_element.fade + + inv_sorted_ctrl_map = sorted( + state.control_map.items(), key=lambda item: -item[1].start + ) + + for dancer_name, ctrl in ctrl_status.items(): + dancer_index = state.dancer_part_index_map[dancer_name].index + + for part_name, part_data in ctrl.items(): + part_obj_name = f"{dancer_index}_{part_name}" + + if isinstance(part_data, LEDData): + part_parent = data_objects[part_obj_name] + + if part_data.effect_id > 0: + part_effect = led_effect_table[part_data.effect_id].effect + led_rgb_floats = [ + rgba_to_float(color_map[led_data.color_id].rgb, part_data.alpha) + for led_data in part_effect + ] + + else: + try: + prev_effect_frame = next( + frame + for _, frame in inv_sorted_ctrl_map + if frame.start < frame_start + and cast( + LEDData, frame.status[dancer_name][part_name] + ).effect_id + > 0 + ) + prev_effect_id = cast( + LEDData, prev_effect_frame.status[dancer_name][part_name] + ).effect_id + + prev_effect = led_effect_table[prev_effect_id].effect + led_rgb_floats = [ + rgba_to_float( + color_map[led_data.color_id].rgb, part_data.alpha + ) + for led_data in prev_effect + ] + + except StopIteration: + led_rgb_floats = [(0, 0, 0)] * len(part_parent.children) + + for led_obj in part_parent.children: + position: int = getattr(led_obj, "ld_led_pos") + led_rgb_float = led_rgb_floats[position] + + curves = led_obj.animation_data.action.fcurves + for d in range(3): + point = curves.find("color", index=d).keyframe_points.insert( + frame_start, led_rgb_float[d] + ) + point.interpolation = "LINEAR" if fade else "CONSTANT" + + else: + part_obj = data_objects[part_obj_name] + part_rgb = color_map[part_data.color_id].rgb + part_rgba = rgba_to_float(part_rgb, part_data.alpha) + + action = ensure_action(part_obj, f"{part_obj_name}Action") + curves = action.fcurves + + for d in range(3): + point = curves.find("color", index=d).keyframe_points.insert( + frame_start, part_rgba[d] + ) + point.interpolation = "LINEAR" if fade else "CONSTANT" + + # insert fake frame + scene = bpy.context.scene + + curves = scene.animation_data.action.fcurves + curve = curves.find("ld_control_frame") + + _, kpoints_list = get_keyframe_points(curve) + point = curve.keyframe_points.insert(frame_start, frame_start) + + # update new ld_control_frame + try: + new_next_point = next(p for p in kpoints_list if p.co[0] > frame_start) + new_next_fade_points = [ + point + for point in kpoints_list + if point.co[0] > frame_start and point.co[1] == new_next_point.co[1] + ] + + if new_next_point.co[0] != new_next_point.co[1]: # new co's previous point fade + point.co = frame_start, new_next_point.co[1] + else: + point.co = frame_start, frame_start + + if fade: # propagate fade to next points + for new_p in new_next_fade_points: + new_p.co = new_p.co[0], point.co[1] + else: # reset next point to frame_start + for new_p in new_next_fade_points: + new_p.co = new_p.co[0], new_next_point.co[0] + + except StopIteration: + pass + + point.interpolation = "CONSTANT" + curve.keyframe_points.sort() + + # insert rev frame (meta & data) + rev = ctrl_element.rev + + # curve = curves.find("ld_ctrl_meta") + # point = curve.keyframe_points.insert(frame_start, (rev.meta if rev else -1)) + # point.interpolation = "CONSTANT" + # + # curve = curves.find("ld_ctrl_data") + # point = curve.keyframe_points.insert(frame_start, (rev.data if rev else -1)) + # point.interpolation = "CONSTANT" + + ctrl_rev: RevisionPropertyItemType = getattr(bpy.context.scene, "ld_ctrl_rev").add() + + ctrl_rev.data = rev.data if rev else -1 + ctrl_rev.meta = rev.meta if rev else -1 + + ctrl_rev.frame_id = id + ctrl_rev.frame_start = frame_start + + +def edit_single_ctrl_keyframe( + ctrl_id: MapID, ctrl_element: ControlMapElement, only_meta: bool = False +): + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + color_map = state.color_map + led_effect_table = state.led_effect_id_table + + old_ctrl_map = state.control_map # control_map before update + old_frame_start = old_ctrl_map[ctrl_id].start + + new_frame_start = ctrl_element.start + new_ctrl_status = ctrl_element.status + new_fade = ctrl_element.fade + + old_inv_sorted_ctrl_map = sorted( + state.control_map.items(), key=lambda item: -item[1].start + ) + + for dancer_name, ctrl in new_ctrl_status.items(): + dancer_index = state.dancer_part_index_map[dancer_name].index + + for part_name, part_data in ctrl.items(): + part_obj_name = f"{dancer_index}_{part_name}" + part_alpha = part_data.alpha + + if isinstance(part_data, LEDData): + part_parent = data_objects[part_obj_name] + + if part_data.effect_id > 0: + part_effect = led_effect_table[part_data.effect_id].effect + led_rgb_floats = [ + rgba_to_float(color_map[led_data.color_id].rgb, part_alpha) + for led_data in part_effect + ] + + else: + try: + prev_effect_frame = next( + frame + for _, frame in old_inv_sorted_ctrl_map + if frame.start < new_frame_start + and cast( + LEDData, frame.status[dancer_name][part_name] + ).effect_id + > 0 + ) + prev_effect_id = cast( + LEDData, prev_effect_frame.status[dancer_name][part_name] + ).effect_id + + prev_effect = led_effect_table[prev_effect_id].effect + led_rgb_floats = [ + rgba_to_float(color_map[led_data.color_id].rgb, part_alpha) + for led_data in prev_effect + ] + + except StopIteration: + led_rgb_floats = [(0, 0, 0)] * len(part_parent.children) + + for led_obj in part_parent.children: + position: int = getattr(led_obj, "ld_led_pos") + led_rgb_float = led_rgb_floats[position] + + curves = led_obj.animation_data.action.fcurves + for d in range(3): + curve = curves.find("color", index=d) + kpoints, kpoints_list = get_keyframe_points(curve) + point = next( + point + for point in kpoints_list + if point.co[0] == old_frame_start + ) + + point.co = new_frame_start, led_rgb_float[d] + point.interpolation = "LINEAR" if new_fade else "CONSTANT" + + # TODO: Delete and insert instead of sorting + kpoints.sort() + + else: + part_obj = data_objects[part_obj_name] + part_rgb = color_map[part_data.color_id].rgb + part_rgb_float = rgba_to_float(part_rgb, part_alpha) + + curves = part_obj.animation_data.action.fcurves + for d in range(3): + curve = curves.find("color", index=d) + kpoints, kpoints_list = get_keyframe_points(curve) + + point = next( + point + for point in kpoints_list + if point.co[0] == old_frame_start + ) + point.co = new_frame_start, part_rgb_float[d] + point.interpolation = "LINEAR" if new_fade else "CONSTANT" + + # TODO: Delete and insert instead of sorting + kpoints.sort() + + # update fake frame + scene = bpy.context.scene + + curves = scene.animation_data.action.fcurves + curve = curves.find("ld_control_frame") + + kpoints, kpoints_list = get_keyframe_points(curve) + point = next(point for point in kpoints_list if point.co[0] == old_frame_start) + + # update old ld_control_frame + try: + old_next_point = next(p for p in kpoints_list if p.co[0] > old_frame_start) + old_next_fade_points = [ + point + for point in kpoints_list + if point.co[0] > old_frame_start and point.co[1] == old_next_point.co[1] + ] + + if point.co[0] != point.co[1]: # old co's previous point fade + for old_p in old_next_fade_points: + old_p.co = old_p.co[0], point.co[1] + elif ( + old_next_point.co[0] != old_next_point.co[1] + ): # reset next point to frame_start + for old_p in old_next_fade_points: + old_p.co = old_p.co[0], old_next_point.co[0] + + except StopIteration: + pass + + # update new ld_control_frame + try: + new_next_point = next(p for p in kpoints_list if p.co[0] > new_frame_start) + new_next_fade_points = [ + point + for point in kpoints_list + if point.co[0] > new_frame_start and point.co[1] == new_next_point.co[1] + ] + + if new_next_point.co[0] != new_next_point.co[1]: # new co's previous point fade + point.co = new_frame_start, new_next_point.co[1] + else: + point.co = new_frame_start, new_frame_start + + if new_fade: # propagate fade to next points + for new_p in new_next_fade_points: + new_p.co = new_p.co[0], point.co[1] + else: # reset next point to frame_start + for new_p in new_next_fade_points: + new_p.co = new_p.co[0], new_next_point.co[0] + + except StopIteration: + pass + + point.interpolation = "CONSTANT" + kpoints.sort() + + # update rev frame (meta & data) + rev = ctrl_element.rev + + # TODO: update rev + + ctrl_rev: RevisionPropertyType = getattr(bpy.context.scene, "ld_ctrl_rev") + try: + ctrl_rev_item = next( + item for item in ctrl_rev if getattr(item, "frame_id") == ctrl_id + ) + + ctrl_rev_item.data = rev.data if rev else -1 + ctrl_rev_item.meta = rev.meta if rev else -1 + + ctrl_rev_item.frame_id = ctrl_id + ctrl_rev_item.frame_start = new_frame_start + + except StopIteration: + pass + + +def delete_single_ctrl_keyframe( + ctrl_id: MapID, incoming_frame_start: Optional[int] = None +): + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + old_ctrl_map = state.control_map # only for checking dancer list + old_frame_start = ( + incoming_frame_start if incoming_frame_start else old_ctrl_map[ctrl_id].start + ) + dancers_array = state.dancers_array + + for dancer_item in dancers_array: + dancer_name = dancer_item.name + dancer_parts = dancer_item.parts + dancer_index = state.dancer_part_index_map[dancer_name].index + + for part_item in dancer_parts: + part_name = part_item.name + part_type = part_item.type + part_obj_name = f"{dancer_index}_{part_name}" + + match part_type: + case PartType.LED: + part_parent = data_objects[part_obj_name] + + for led_obj in part_parent.children: + curves = led_obj.animation_data.action.fcurves + index: Optional[int] = None + + for d in range(3): + curve = curves.find("color", index=d) + kpoints, kpoints_list = get_keyframe_points(curve) + + if index is None: + index, point = next( + (i, point) + for i, point in enumerate(kpoints_list) + if point.co[0] == old_frame_start + ) + else: + point = kpoints_list[index] + + kpoints.remove(point) + + case PartType.FIBER: + part_obj = data_objects[part_obj_name] + + curves = part_obj.animation_data.action.fcurves + for d in range(3): + curve = curves.find("color", index=d) + kpoints, kpoints_list = get_keyframe_points(curve) + + point = next( + point + for point in kpoints_list + if point.co[0] == old_frame_start + ) + kpoints.remove(point) + + # delete fake frame + scene = bpy.context.scene + + curves = scene.animation_data.action.fcurves + curve = curves.find("ld_control_frame") + kpoints, kpoints_list = get_keyframe_points(curve) + + point = next(point for point in kpoints_list if point.co[0] == old_frame_start) + + # update old ld_control_frame + try: + old_next_point = next(p for p in kpoints_list if p.co[0] > old_frame_start) + old_next_fade_points = [ + point + for point in kpoints_list + if point.co[0] > old_frame_start and point.co[1] == old_next_point.co[1] + ] + + if point.co[0] != point.co[1]: # old co's previous point fade + for old_p in old_next_fade_points: + old_p.co = old_p.co[0], point.co[1] + elif ( + old_next_point.co[0] != old_next_point.co[1] + ): # reset next point to frame_start + for old_p in old_next_fade_points: + old_p.co = old_p.co[0], old_next_point.co[0] + + except StopIteration: + pass + + kpoints.remove(point) + + # TODO: delete rev + + ctrl_rev: RevisionPropertyType = getattr(bpy.context.scene, "ld_ctrl_rev") + try: + ctrl_rev_index = next( + i for i, item in enumerate(ctrl_rev) if getattr(item, "frame_id") == ctrl_id + ) + ctrl_rev.remove(ctrl_rev_index) # type: ignore + + except StopIteration: + pass diff --git a/editor-blender/core/actions/property/animation_data/position.py b/editor-blender/core/actions/property/animation_data/position.py new file mode 100644 index 000000000..45f035c49 --- /dev/null +++ b/editor-blender/core/actions/property/animation_data/position.py @@ -0,0 +1,277 @@ +from typing import Dict, cast + +import bpy + +from .....properties.types import RevisionPropertyItemType, RevisionPropertyType +from ....models import MapID, PosMapElement +from ....states import state +from .utils import ensure_action, ensure_curve, get_keyframe_points + +""" +setups & update colormap(===setups) +""" + + +def set_pos_keyframes_from_state(): + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + pos_map = state.pos_map + pos_frame_number = len(pos_map) + + sorted_pos_map = sorted(pos_map.items(), key=lambda item: item[1].start) + + for i, (id, pos_map_element) in enumerate(sorted_pos_map): + frame_start = pos_map_element.start + pos_status = pos_map_element.pos + + for dancer_name, pos in pos_status.items(): + dancer_location = (pos.x, pos.y, pos.z) + + dancer_obj = data_objects[dancer_name] + action = ensure_action(dancer_obj, dancer_name + "Action") + + curves = action.fcurves + for d in range(3): + curve = ensure_curve( + action, + "location", + index=d, + keyframe_points=pos_frame_number, + clear=i == 0, + ) + + _, kpoints_list = get_keyframe_points(curve) + point = kpoints_list[i] + point.co = frame_start, dancer_location[d] + + point.interpolation = "LINEAR" + if i == pos_frame_number - 1: + curves.find("location", index=d).keyframe_points.sort() + + # insert fake frame + scene = bpy.context.scene + action = ensure_action(scene, "SceneAction") + + curve = ensure_curve( + action, "ld_pos_frame", keyframe_points=pos_frame_number, clear=i == 0 + ) + _, kpoints_list = get_keyframe_points(curve) + + point = kpoints_list[i] + point.co = frame_start, frame_start + point.interpolation = "CONSTANT" + + # set revision + rev = pos_map_element.rev + + # curve = ensure_curve( + # action, "ld_pos_meta", keyframe_points=pos_frame_number, clear=i == 0 + # ) + # _, kpoints_list = get_keyframe_points(curve) + # + # point = kpoints_list[i] + # point.co = frame_start, rev.meta + # point.interpolation = "CONSTANT" + # + # curve = ensure_curve( + # action, "ld_pos_data", keyframe_points=pos_frame_number, clear=i == 0 + # ) + # _, kpoints_list = get_keyframe_points(curve) + # + # point = kpoints_list[i] + # point.co = frame_start, rev.data + # point.interpolation = "CONSTANT" + + pos_rev_item: RevisionPropertyItemType = getattr( + bpy.context.scene, "ld_pos_rev" + ).add() + + pos_rev_item.data = rev.data if rev else -1 + pos_rev_item.meta = rev.meta if rev else -1 + + pos_rev_item.frame_id = id + pos_rev_item.frame_start = frame_start + + +""" +update position keyframes +""" + + +def add_single_pos_keyframe(id: MapID, pos_element: PosMapElement): + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + frame_start = pos_element.start + pos_status = pos_element.pos + + for dancer_name, pos in pos_status.items(): + dancer_location = (pos.x, pos.y, pos.z) + dancer_obj = data_objects[dancer_name] + + curves = dancer_obj.animation_data.action.fcurves + for d in range(3): + point = curves.find("location", index=d).keyframe_points.insert( + frame_start, dancer_location[d] + ) + point.interpolation = "LINEAR" + + scene = bpy.context.scene + curves = scene.animation_data.action.fcurves + + curve = curves.find("ld_pos_frame") + point = curve.keyframe_points.insert(frame_start, frame_start) + point.interpolation = "CONSTANT" + + # insert rev frame (meta & data) + rev = pos_element.rev + + # curve = curves.find("ld_pos_meta") + # point = curve.keyframe_points.insert(frame_start, (rev.meta if rev else -1)) + # point.interpolation = "CONSTANT" + # + # curve = curves.find("ld_pos_data") + # point = curve.keyframe_points.insert(frame_start, (rev.data if rev else -1)) + # point.interpolation = "CONSTANT" + + pos_rev: RevisionPropertyItemType = getattr(bpy.context.scene, "ld_pos_rev").add() + + pos_rev.data = rev.data if rev else -1 + pos_rev.meta = rev.meta if rev else -1 + + pos_rev.frame_id = id + pos_rev.frame_start = frame_start + + +def edit_single_pos_keyframe(pos_id: MapID, pos_element: PosMapElement): + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + old_pos_map = state.pos_map # pos_map before update + old_pos_frame = old_pos_map[pos_id] + old_frame_start = old_pos_frame.start + + new_pos_status = pos_element.pos + new_frame_start = pos_element.start + + update_time = old_frame_start != new_frame_start + + for dancer_name, pos in new_pos_status.items(): + dancer_location = (pos.x, pos.y, pos.z) + dancer_obj = data_objects[dancer_name] + + curves = dancer_obj.animation_data.action.fcurves + for d in range(3): + curve = curves.find("location", index=d) + kpoints, kpoints_list = get_keyframe_points(curve) + + point = next(p for p in kpoints_list if p.co[0] == old_frame_start) + point.co = new_frame_start, dancer_location[d] + + point.interpolation = "LINEAR" + + if update_time: + kpoints.sort() + + scene = bpy.context.scene + + curves = scene.animation_data.action.fcurves + curve = curves.find("ld_pos_frame") + kpoints, kpoints_list = get_keyframe_points(curve) + + point = next(p for p in kpoints_list if p.co[0] == old_frame_start) + point.co = new_frame_start, new_frame_start + + if update_time: + kpoints.sort() + + # insert rev frame (meta & data) + rev = pos_element.rev + + # meta keyframe + # curve = curves.find("ld_pos_meta") + # kpoints, kpoints_list = get_keyframe_points(curve) + # + # point = next(p for p in kpoints_list if p.co[0] == old_frame_start) + # point.co = new_frame_start, rev.meta + # + # if update_time: + # kpoints.sort() + + # data keyframe + # curve = curves.find("ld_pos_data") + # kpoints, kpoints_list = get_keyframe_points(curve) + # + # point = next(p for p in kpoints_list if p.co[0] == old_frame_start) + # point.co = new_frame_start, rev.data + # + # if update_time: + # kpoints.sort() + + # update rev frame + pos_rev: RevisionPropertyType = getattr(bpy.context.scene, "ld_pos_rev") + try: + pos_rev_item = next( + item for item in pos_rev if getattr(item, "frame_id") == pos_id + ) + + pos_rev_item.data = rev.data if rev else -1 + pos_rev_item.meta = rev.meta if rev else -1 + + pos_rev_item.frame_id = pos_id + pos_rev_item.frame_start = new_frame_start + + except StopIteration: + pass + + +def delete_single_pos_keyframe(pos_id: MapID, incoming_frame_start: int | None = None): + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + old_pos_map = state.pos_map # pos_map before update + old_frame_start = ( + incoming_frame_start if incoming_frame_start else old_pos_map[pos_id].start + ) + + dancers_array = state.dancers_array + for dancer_item in dancers_array: + dancer_name = dancer_item.name + dancer_obj = data_objects[dancer_name] + + curves = dancer_obj.animation_data.action.fcurves + for d in range(3): + curve = curves.find("location", index=d) + kpoints, kpoints_list = get_keyframe_points(curve) + + point = next(p for p in kpoints_list if p.co[0] == old_frame_start) + kpoints.remove(point) + + scene = bpy.context.scene + + curves = scene.animation_data.action.fcurves + curve = curves.find("ld_pos_frame") + kpoints, kpoints_list = get_keyframe_points(curve) + + point = next(p for p in kpoints_list if p.co[0] == old_frame_start) + kpoints.remove(point) + + # delete rev frame (meta & data) + # curve = curves.find("ld_pos_meta") + # kpoints, kpoints_list = get_keyframe_points(curve) + # + # point = next(p for p in kpoints_list if p.co[0] == old_frame_start) + # kpoints.remove(point) + # + # curve = curves.find("ld_pos_data") + # kpoints, kpoints_list = get_keyframe_points(curve) + # + # point = next(p for p in kpoints_list if p.co[0] == old_frame_start) + # kpoints.remove(point) + + pos_rev: RevisionPropertyType = getattr(bpy.context.scene, "ld_pos_rev") + try: + pos_rev_index = next( + i for i, item in enumerate(pos_rev) if getattr(item, "frame_id") == pos_id + ) + pos_rev.remove(pos_rev_index) # type: ignore + + except StopIteration: + pass diff --git a/editor-blender/core/actions/property/animation_data/utils.py b/editor-blender/core/actions/property/animation_data/utils.py new file mode 100644 index 000000000..8523cf1cd --- /dev/null +++ b/editor-blender/core/actions/property/animation_data/utils.py @@ -0,0 +1,46 @@ +from typing import List, Optional, Tuple, Union, cast + +import bpy + + +def ensure_action( + obj: Union[bpy.types.Object, bpy.types.Scene], action_name: str +) -> bpy.types.Action: + anim_data = cast(Optional[bpy.types.AnimData], obj.animation_data) + if anim_data is None: + obj.animation_data_create() + anim_data = obj.animation_data + + action = cast(Optional[bpy.types.Action], anim_data.action) + if action is None: + obj.animation_data.action = bpy.data.actions.new(action_name) + action = obj.animation_data.action + + return action + + +def ensure_curve( + action: bpy.types.Action, + data_path: str, + index: int = 0, + keyframe_points: int = 0, + clear: bool = False, +) -> bpy.types.FCurve: + curves = action.fcurves + curve = cast(Optional[bpy.types.FCurve], curves.find(data_path, index=index)) + + if curve is None: + curves.new(data_path, index=index) + curve = curves.find(data_path, index=index) + curve.keyframe_points.add(keyframe_points) + elif clear: + curve.keyframe_points.clear() + curve.keyframe_points.add(keyframe_points) + + return curve + + +def get_keyframe_points( + curve: bpy.types.FCurve, +) -> Tuple[bpy.types.FCurveKeyframePoints, List[bpy.types.Keyframe]]: + return curve.keyframe_points, cast(List[bpy.types.Keyframe], curve.keyframe_points) diff --git a/editor-blender/core/actions/property/lights.py b/editor-blender/core/actions/property/lights.py index e58ebf7c5..fc8f25fb0 100644 --- a/editor-blender/core/actions/property/lights.py +++ b/editor-blender/core/actions/property/lights.py @@ -12,13 +12,17 @@ def update_current_color(self: bpy.types.Object, context: bpy.types.Context): if state.edit_state != EditMode.EDITING or state.current_editing_detached: return + ld_alpha: int = getattr(self, "ld_alpha") + color_id: int = self["ld_color"] color = state.color_map[color_id] color_float = rgb_to_float(color.rgb) - self.color[0] = color_float[0] - self.color[1] = color_float[1] - self.color[2] = color_float[2] + setattr(self, "ld_color_float", color_float[:3]) + + self.color[0] = color_float[0] * (ld_alpha / 255) + self.color[1] = color_float[1] * (ld_alpha / 255) + self.color[2] = color_float[2] * (ld_alpha / 255) def update_current_effect(self: bpy.types.Object, context: bpy.types.Context): @@ -76,10 +80,16 @@ def update_current_alpha(self: bpy.types.Object, context: bpy.types.Context): ld_light_type: str = getattr(self, "ld_light_type") ld_alpha: int = getattr(self, "ld_alpha") + ld_color_float: List[float] = getattr(self, "ld_color_float") if ld_light_type == LightType.LED.value: led_bulb_objs: List[bpy.types.Object] = getattr(self, "children") for led_bulb_obj in led_bulb_objs: - led_bulb_obj.color[3] = ld_alpha / 255 + bulb_ld_color_float: List[float] = getattr(led_bulb_obj, "ld_color_float") + led_bulb_obj.color[0] = bulb_ld_color_float[0] * (ld_alpha / 255) + led_bulb_obj.color[1] = bulb_ld_color_float[1] * (ld_alpha / 255) + led_bulb_obj.color[2] = bulb_ld_color_float[2] * (ld_alpha / 255) else: - self.color[3] = ld_alpha / 255 + self.color[0] = ld_color_float[0] * (ld_alpha / 255) + self.color[1] = ld_color_float[1] * (ld_alpha / 255) + self.color[2] = ld_color_float[2] * (ld_alpha / 255) diff --git a/editor-blender/core/actions/property/revision.py b/editor-blender/core/actions/property/revision.py new file mode 100644 index 000000000..840cabbac --- /dev/null +++ b/editor-blender/core/actions/property/revision.py @@ -0,0 +1,70 @@ +import bpy + +from ....properties.types import RevisionPropertyType +from ...models import ControlMap, MapID, PosMap +from .animation_data import ( + add_single_ctrl_keyframe, + add_single_pos_keyframe, + delete_single_ctrl_keyframe, + delete_single_pos_keyframe, + edit_single_ctrl_keyframe, + edit_single_pos_keyframe, +) + + +def update_rev_changes(incoming_pos_map: PosMap, incoming_control_map: ControlMap): + # position + local_rev: RevisionPropertyType = getattr(bpy.context.scene, "ld_pos_rev") + incoming_rev = {id: element.rev for id, element in incoming_pos_map.items()} + + for rev in local_rev: + local_id: MapID = rev.frame_id + + incoming_rev_item = incoming_rev.get(local_id) + if incoming_rev_item is None: # delete + delete_single_pos_keyframe(local_id, rev.frame_start) + del incoming_rev[local_id] + + else: + if incoming_rev_item: + if ( + incoming_rev_item.data == rev.data + and incoming_rev_item.meta == rev.meta + and rev.data > 0 + and rev.meta > 0 + ): # local animation data matches incoming + continue + else: + edit_single_pos_keyframe(local_id, incoming_pos_map[rev.frame_id]) + del incoming_rev[local_id] + + for id in incoming_rev.keys(): + add_single_pos_keyframe(id, incoming_pos_map[id]) + + # control + # local_rev: RevisionPropertyType = getattr(bpy.context.scene, "ld_ctrl_rev") + # incoming_rev = {id: element.rev for id, element in incoming_pos_map.items()} + # + # for rev in local_rev: + # local_id: MapID = rev.frame_id + # if local_id not in incoming_rev.keys(): # delete + # delete_single_ctrl_keyframe(local_id, rev.frame_start) + # + # else: + # incoming_rev_item = incoming_rev[local_id] + # if incoming_rev_item: + # if ( + # incoming_rev_item.data == rev.data + # and incoming_rev_item.meta == rev.meta + # and rev.data > 0 + # and rev.meta > 0 + # ): # local animation data matches incoming + # continue + # else: + # edit_single_ctrl_keyframe( + # local_id, incoming_control_map[rev.frame_id] + # ) + # del incoming_rev[local_id] + # + # for id in incoming_rev.keys(): + # add_single_ctrl_keyframe(id, incoming_control_map[id]) diff --git a/editor-blender/core/actions/state/color_map.py b/editor-blender/core/actions/state/color_map.py index 7a77d348b..235f1582d 100644 --- a/editor-blender/core/actions/state/color_map.py +++ b/editor-blender/core/actions/state/color_map.py @@ -1,8 +1,11 @@ -from ...models import Color, ColorID, ColorMap, EditMode +import bpy + +from ...models import Color, ColorID, ColorMap, EditMode, FiberData, LEDData from ...states import state from ...utils.notification import notify from ...utils.ui import redraw_area from .color_palette import setup_color_palette_from_state +from .load import set_ctrl_keyframes_from_state def set_color_map(color_map: ColorMap): @@ -87,6 +90,7 @@ def apply_color_map_updates(): for color in color_map_updates.updated: state.color_map[color.id] = color + set_ctrl_keyframes_from_state() # TODO: test this for color_id in color_map_updates.deleted: del state.color_map[color_id] @@ -95,8 +99,6 @@ def apply_color_map_updates(): color_map_updates.updated.clear() color_map_updates.deleted.clear() - # TODO: Update animation data - setup_color_palette_from_state(state.color_map) state.color_map_pending = False diff --git a/editor-blender/core/actions/state/control_editor.py b/editor-blender/core/actions/state/control_editor.py index 512b17d06..b9c52d4d5 100644 --- a/editor-blender/core/actions/state/control_editor.py +++ b/editor-blender/core/actions/state/control_editor.py @@ -65,6 +65,8 @@ async def add_control_frame(): start = bpy.context.scene.frame_current controlData = control_status_state_to_mut(state.current_status) + print(controlData) + try: set_requesting(True) await control_agent.add_frame(start, False, controlData) @@ -118,8 +120,7 @@ async def save_control_frame(start: Optional[int] = None): if part.type == PartType.FIBER: partControlData.append((default_color, 0)) elif part.type == PartType.LED: - default_effect = list(state.led_map[part.name].values())[0].id - partControlData.append((default_effect, 0)) + partControlData.append((-1, 0)) controlData.append(partControlData) diff --git a/editor-blender/core/actions/state/control_map.py b/editor-blender/core/actions/state/control_map.py index c09dce2c4..6fb9596d0 100644 --- a/editor-blender/core/actions/state/control_map.py +++ b/editor-blender/core/actions/state/control_map.py @@ -2,6 +2,11 @@ from ...states import state from ...utils.notification import notify from ...utils.ui import redraw_area +from ..property.animation_data import ( + add_single_ctrl_keyframe, + delete_single_ctrl_keyframe, + edit_single_ctrl_keyframe, +) from .current_status import ( calculate_current_status_index, update_current_status_by_index, @@ -51,7 +56,7 @@ def delete_control(id: MapID): control_map_updates.deleted.append(id) if state.edit_state == EditMode.EDITING: - state.pos_map_pending = True + state.control_map_pending = True redraw_area({"VIEW_3D", "DOPESHEET_EDITOR"}) else: apply_control_map_updates() @@ -86,15 +91,16 @@ def update_control(id: MapID, frame: ControlMapElement): def apply_control_map_updates(): control_map_updates = state.control_map_updates - # TODO: Update animation data - for status in control_map_updates.added: + add_single_ctrl_keyframe(status[0], status[1]) state.control_map[status[0]] = status[1] for status in control_map_updates.updated: + edit_single_ctrl_keyframe(status[0], status[1]) state.control_map[status[0]] = status[1] for id in control_map_updates.deleted: + delete_single_ctrl_keyframe(id) del state.control_map[id] control_map_updates.added.clear() diff --git a/editor-blender/core/actions/state/current_status.py b/editor-blender/core/actions/state/current_status.py index b21e85546..37900af16 100644 --- a/editor-blender/core/actions/state/current_status.py +++ b/editor-blender/core/actions/state/current_status.py @@ -81,6 +81,3 @@ def update_current_status_by_index(): alpha = part_status.alpha setattr(part_obj, "ld_alpha", alpha) - - # WARNING: Testing - break diff --git a/editor-blender/core/actions/state/editor.py b/editor-blender/core/actions/state/editor.py index 00c4c630f..73b50a9d0 100644 --- a/editor-blender/core/actions/state/editor.py +++ b/editor-blender/core/actions/state/editor.py @@ -34,7 +34,7 @@ def setup_control_editor(): outliner_hide_one_level() outliner_hide_one_level() - set_dopesheet_filter("control") + set_dopesheet_filter("control_frame") state.editor = Editor.CONTROL_EDITOR @@ -52,7 +52,7 @@ def setup_pos_editor(): outliner_hide_one_level() outliner_hide_one_level() - set_dopesheet_filter("pos") + set_dopesheet_filter("pos_frame") state.editor = Editor.POS_EDITOR diff --git a/editor-blender/core/actions/state/fake_pos_map.py b/editor-blender/core/actions/state/fake_pos_map.py deleted file mode 100644 index 25c6832d5..000000000 --- a/editor-blender/core/actions/state/fake_pos_map.py +++ /dev/null @@ -1,529 +0,0 @@ -pos_map = { - "iObgAxPJyZ": { - "start": 0, - "pos": { - "1_vinc": { - "x": -3.94509765680373, - "y": 0.18350854652128135, - "z": 2.7169543365060287, - }, - "2_xN": { - "x": 3.4183320029838677, - "y": 0.14627327555104763, - "z": 2.750329012738364, - }, - }, - }, - "WtyofZmACB": { - "start": 13669, - "pos": { - "1_vinc": { - "x": -3.94509765680373, - "y": 0.18350854652128135, - "z": 2.7169543365060287, - }, - "2_xN": { - "x": 3.4183320029838677, - "y": 0.14627327555104763, - "z": 2.750329012738364, - }, - }, - }, - "LMHSPOGZhP": { - "start": 18614, - "pos": { - "1_vinc": { - "x": -3.94509765680373, - "y": 0.18350854652128135, - "z": 2.7169543365060287, - }, - "2_xN": { - "x": 3.4183320029838677, - "y": 0.14627327555104763, - "z": 2.750329012738364, - }, - }, - }, - "ZrarwrhuZc": { - "start": 19592, - "pos": { - "1_vinc": { - "x": -3.5815290312427486, - "y": 0.0017048250870850268, - "z": 5.865143718624838, - }, - "2_xN": {"x": 3.7819006285448493, "y": 0, "z": 5.898518394857173}, - }, - }, - "4dReA02Kgp": { - "start": 23630, - "pos": { - "1_vinc": { - "x": -3.5815290312427486, - "y": 0.0017048250870850268, - "z": 5.865143718624838, - }, - "2_xN": {"x": 3.7819006285448493, "y": 0, "z": 5.898518394857173}, - }, - }, - "AmatVCjM16": { - "start": 24038, - "pos": { - "1_vinc": { - "x": -6.204725100929196, - "y": 0.10038808200167537, - "z": 5.841817780147679, - }, - "2_xN": { - "x": 1.1587045588584015, - "y": 0.09868325691459034, - "z": 5.875192456380014, - }, - }, - }, - "Mq3uqdJ89I": { - "start": 25842, - "pos": { - "1_vinc": { - "x": -6.204725100929196, - "y": 0.10038808200167537, - "z": 5.841817780147679, - }, - "2_xN": { - "x": 1.1587045588584015, - "y": 0.09868325691459034, - "z": 5.875192456380014, - }, - }, - }, - "pWMN91TsLn": { - "start": 26391, - "pos": { - "1_vinc": { - "x": -4.199219592629044, - "y": 1.0931582804240563, - "z": 3.283970151753964, - }, - "2_xN": {"x": -2.8242776302394947, "y": 0, "z": 6.880267258061807}, - }, - }, - "h0sdGh8wKB": { - "start": 27181, - "pos": { - "1_vinc": { - "x": -1.9728998501132846, - "y": 1.3783305838335167, - "z": 2.4877907065354723, - }, - "2_xN": { - "x": 1.4072217689177453, - "y": 0.5861884526761472, - "z": 5.2524081755554874, - }, - }, - }, - "plna0tmrOO": { - "start": 27540, - "pos": { - "1_vinc": { - "x": 3.4194599566720134, - "y": 1.045630973357511, - "z": 3.2142823666700977, - }, - "2_xN": { - "x": 1.3632018401910653, - "y": 0.5861884526761472, - "z": 5.25352320458277, - }, - }, - }, - "PfoiRWFPBq": { - "start": 28401, - "pos": { - "1_vinc": { - "x": 3.4194599566720134, - "y": 1.045630973357511, - "z": 3.2142823666700977, - }, - "2_xN": { - "x": 1.3632018401910653, - "y": 0.5861884526761472, - "z": 5.25352320458277, - }, - }, - }, - "asixLnwKrt": { - "start": 28849, - "pos": { - "1_vinc": { - "x": 5.312347118466655, - "y": 1.045630973357511, - "z": 3.1663353528554854, - }, - "2_xN": { - "x": 1.3632018401910653, - "y": 0.5861884526761472, - "z": 5.25352320458277, - }, - }, - }, - "UV-W5rU3q6": { - "start": 30621, - "pos": { - "1_vinc": { - "x": 5.312347118466655, - "y": 1.045630973357511, - "z": 3.1663353528554854, - }, - "2_xN": { - "x": 1.3632018401910653, - "y": 0.5861884526761472, - "z": 5.25352320458277, - }, - }, - }, - "SbcV6c387U": { - "start": 32533, - "pos": { - "1_vinc": {"x": -9.47330541720127, "y": 0, "z": 8.965949850886457}, - "2_xN": { - "x": -0.10332828636914115, - "y": 0.12674532763688262, - "z": 6.48254592617835, - }, - }, - }, - "vRI_2M11y-": { - "start": 33930, - "pos": { - "1_vinc": {"x": -9.47330541720127, "y": 0, "z": 8.965949850886457}, - "2_xN": {"x": 6.737383067763911, "y": 0, "z": 3.6783244443206433}, - }, - }, - "iQvFiqudr3": { - "start": 35665, - "pos": { - "1_vinc": { - "x": -0.04671888140864411, - "y": 1.916987498937992, - "z": 3.754174533754755, - }, - "2_xN": { - "x": 6.610300998441749, - "y": 1.1248465764963882, - "z": 3.723240822030035, - }, - }, - }, - "F3igVDzRnR": { - "start": 39276, - "pos": { - "1_vinc": {"x": -0.0991302734445425, "y": 0, "z": 3.4801489855840106}, - "2_xN": {"x": 6.639860212004217, "y": 0, "z": 3.54175824421021}, - }, - }, - "Ou9ykxU3pS": { - "start": 40247, - "pos": { - "1_vinc": {"x": -3.486778028108315, "y": 0, "z": 3.350442620642963}, - "2_xN": {"x": 3.3733616289213417, "y": 0, "z": 3.416386574040933}, - }, - }, - "9h8oOkdhaI": { - "start": 42998, - "pos": { - "1_vinc": {"x": -3.5642434295146126, "y": 0, "z": 3.3098243910106344}, - "2_xN": {"x": 3.12834946880868, "y": 0, "z": 3.3896871451059374}, - }, - }, - "ATysaupjwb": { - "start": 43753, - "pos": { - "1_vinc": {"x": -2.4708894947548465, "y": 0, "z": 3.292906595996774}, - "2_xN": {"x": 4.364676626731061, "y": 0, "z": 3.3546521782235663}, - }, - }, - "JEEei0C0zF": { - "start": 50377, - "pos": { - "1_vinc": {"x": -2.4708894947548465, "y": 0, "z": 3.292906595996774}, - "2_xN": {"x": 4.364676626731061, "y": 0, "z": 3.3546521782235663}, - }, - }, - "iDXb7CiEy8": { - "start": 51065, - "pos": { - "1_vinc": {"x": -0.17975510258053795, "y": 0, "z": 4.538902905480883}, - "2_xN": { - "x": 0.02317921921542343, - "y": 0.14004599425267372, - "z": 2.562943346768564, - }, - }, - }, - "o9iFtF1SLJ": { - "start": 51377, - "pos": { - "1_vinc": {"x": -0.17975510258053795, "y": 0, "z": 4.538902905480883}, - "2_xN": { - "x": 0.02317921921542343, - "y": 0.14004599425267372, - "z": 2.562943346768564, - }, - }, - }, - "wuDnYBqmIJ": { - "start": 52076, - "pos": { - "1_vinc": {"x": -0.17975510258053795, "y": 0, "z": 4.538902905480883}, - "2_xN": { - "x": -1.5531689540720355, - "y": 0.14004599425267372, - "z": 2.648750025457026, - }, - }, - }, - "wEcJSqHEoG": { - "start": 52486, - "pos": { - "1_vinc": {"x": -0.17975510258053795, "y": 0, "z": 4.538902905480883}, - "2_xN": { - "x": -1.5531689540720355, - "y": 0.14004599425267372, - "z": 2.648750025457026, - }, - }, - }, - "6n-J03w6iq": { - "start": 52905, - "pos": { - "1_vinc": { - "x": 0.5591588083311925, - "y": 1.4210854715202004e-14, - "z": 4.498680986487039, - }, - "2_xN": { - "x": -1.5531689540720355, - "y": 0.14004599425267372, - "z": 2.648750025457026, - }, - }, - }, - "YQp7s8nVOQ": { - "start": 54585, - "pos": { - "1_vinc": { - "x": 0.5591588083311925, - "y": 1.4210854715202004e-14, - "z": 4.498680986487039, - }, - "2_xN": { - "x": -1.5531689540720355, - "y": 0.14004599425267372, - "z": 2.648750025457026, - }, - }, - }, - "UANnd4UQFU": { - "start": 61436, - "pos": { - "1_vinc": { - "x": -0.3197833435264119, - "y": 0.16838218438558172, - "z": 1.1499602699261784, - }, - "2_xN": { - "x": -0.21707421743489774, - "y": 0.09582455596656558, - "z": 3.6505779088244457, - }, - }, - }, - "bUQ9lVevf0": { - "start": 70253, - "pos": { - "1_vinc": { - "x": -0.3197833435264119, - "y": 0.16838218438558172, - "z": 1.1499602699261784, - }, - "2_xN": { - "x": -0.21707421743489774, - "y": 0.09582455596656558, - "z": 3.6505779088244457, - }, - }, - }, - "6GTDW-DgyJ": { - "start": 71090, - "pos": { - "1_vinc": { - "x": 4.13943343218631, - "y": 0.06463141772477954, - "z": 3.6470569377863216, - }, - "2_xN": { - "x": -0.21707421743489774, - "y": 0.09582455596656558, - "z": 3.6505779088244457, - }, - }, - }, - "XZAIkL7CCX": { - "start": 73240, - "pos": { - "1_vinc": { - "x": 4.13943343218631, - "y": 0.06463141772477954, - "z": 3.6470569377863216, - }, - "2_xN": { - "x": -0.21707421743489774, - "y": 0.09582455596656558, - "z": 3.6505779088244457, - }, - }, - }, - "ftbQP6kB9K": { - "start": 74141, - "pos": { - "1_vinc": { - "x": 2.002778193826425, - "y": 0.09376857751961776, - "z": 3.7040653398322876, - }, - "2_xN": { - "x": -2.353729455794783, - "y": 0.1249617157614038, - "z": 3.7075863108704117, - }, - }, - }, - "T9qYxNiftG": { - "start": 75371, - "pos": { - "1_vinc": { - "x": 2.002778193826425, - "y": 0.09376857751961776, - "z": 3.7040653398322876, - }, - "2_xN": { - "x": -2.353729455794783, - "y": 0.1249617157614038, - "z": 3.7075863108704117, - }, - }, - }, - "lNcrrqkeYm": { - "start": 75709, - "pos": { - "1_vinc": { - "x": 4.168630218114863, - "y": 0.035494257929923556, - "z": 3.6434322192152395, - }, - "2_xN": { - "x": -0.18787743150634428, - "y": 0.0666873961717096, - "z": 3.6469531902533636, - }, - }, - }, - "cBnbaP2VlJ": { - "start": 79009, - "pos": { - "1_vinc": { - "x": 4.168630218114863, - "y": 0.035494257929923556, - "z": 3.6434322192152395, - }, - "2_xN": { - "x": -0.18787743150634428, - "y": 0.0666873961717096, - "z": 3.6469531902533636, - }, - }, - }, - "5QRj1SdOH_": { - "start": 79164, - "pos": { - "1_vinc": { - "x": 4.168630218114863, - "y": 0.035494257929923556, - "z": 3.6434322192152395, - }, - "2_xN": { - "x": -0.18787743150634428, - "y": 0.0666873961717096, - "z": 3.6469531902533636, - }, - }, - }, - "OvINgARnts": { - "start": 82866, - "pos": { - "1_vinc": { - "x": -3.2367464206002925, - "y": 0.03549425792992533, - "z": 3.831016596993792, - }, - "2_xN": { - "x": -7.5932540702215, - "y": 0.06668739617171138, - "z": 3.8345375680319163, - }, - }, - }, - "e_yxH7ySvQ": { - "start": 85544, - "pos": { - "1_vinc": { - "x": 2.002851229959149, - "y": 0.12290573731445154, - "z": 3.706948629354323, - }, - "2_xN": { - "x": -2.353656419662058, - "y": 0.15409887555623758, - "z": 3.710469600392447, - }, - }, - }, - "fWPELc7HZH": { - "start": 56576, - "pos": { - "1_vinc": {"x": 7.962666570745725, "y": 0, "z": 7.922116578644899}, - "2_xN": {"x": -8.792359622175441, "y": 0, "z": 7.772143418579503}, - }, - }, - "6RTL7hBhHE": { - "start": 57930, - "pos": { - "1_vinc": { - "x": -3.159888444215924, - "y": 0.3197398140343397, - "z": 2.488359230308325, - }, - "2_xN": { - "x": 2.867816751454317, - "y": 0.3326726500534124, - "z": 2.717345115420425, - }, - }, - }, - "3fDpaECOJj": { - "start": 60692, - "pos": { - "1_vinc": { - "x": -3.159888444215924, - "y": 0.3197398140343397, - "z": 2.488359230308325, - }, - "2_xN": { - "x": 2.867816751454317, - "y": 0.3326726500534124, - "z": 2.717345115420425, - }, - }, - }, -} diff --git a/editor-blender/core/actions/state/initialize.py b/editor-blender/core/actions/state/initialize.py index a09c1296a..f7748d314 100644 --- a/editor-blender/core/actions/state/initialize.py +++ b/editor-blender/core/actions/state/initialize.py @@ -5,6 +5,7 @@ from ....api.auth_agent import auth_agent from ....api.color_agent import color_agent +from ....api.control_agent import control_agent from ....api.dancer_agent import dancer_agent from ....api.led_agent import led_agent from ....api.model_agent import model_agent @@ -19,6 +20,7 @@ set_running, ) from ....core.actions.state.color_map import set_color_map +from ....core.actions.state.control_map import set_control_map # from ....core.actions.state.color_map import set_color_map # from ....core.actions.state.control_map import set_control_map @@ -156,9 +158,6 @@ async def init_blender(): state.init_editor_task.cancel() state.init_editor_task = AsyncTask(init_editor).exec() - # Setup control editor UI - setup_control_editor() - def close_blender(): set_running(False) @@ -189,10 +188,10 @@ async def init_editor(): empty_task = asyncio.create_task(asyncio.sleep(0)) batches_functions = [ - # [load_data, init_color_map], [init_models, init_dancers], [init_color_map, init_led_map], [init_current_pos, init_current_status], + [load_data], # [init_current_status, init_current_pos, init_current_led_status, sync_led_effect_record], # [sync_current_led_status], ] @@ -244,6 +243,9 @@ async def init_editor(): state.current_control_index = calculate_current_status_index() update_current_status_by_index() + # Setup control editor UI + setup_control_editor() + redraw_area({"VIEW_3D", "DOPESHEET_EDITOR"}) @@ -315,13 +317,6 @@ async def init_dancers(): index=index, parts=parts ) - # selected: Selected = dict( - # [ - # (dancer_name, SelectedItem(selected=False, parts=[])) - # for dancer_name in dancer_names - # ] - # ) - state.dancers = dancers state.dancer_names = dancer_names state.part_type_map = part_type_map @@ -330,8 +325,6 @@ async def init_dancers(): state.dancers_array = dancers_array state.dancer_part_index_map = dancer_part_index_map - # state.selected = selected - print("Dancers initialized") @@ -355,6 +348,16 @@ async def init_led_map(): print("LED map initialized") +async def init_control_map(): + control_map = await control_agent.get_control_map() + if control_map is None: + raise Exception("Control map not found") + + set_control_map(control_map) + + print("Control map initialized") + + async def init_current_status(): control_map, control_record = await get_control() diff --git a/editor-blender/core/actions/state/load.py b/editor-blender/core/actions/state/load.py index dd66ceda0..62f76af07 100644 --- a/editor-blender/core/actions/state/load.py +++ b/editor-blender/core/actions/state/load.py @@ -1,14 +1,22 @@ import os -from typing import Any +from typing import Any, Dict, List, Set, cast import bpy from ....client import client -from ....properties.types import ObjectType -from ...models import FiberData, LEDData +from ....properties.types import LightType, ObjectType +from ...actions.property.revision import update_rev_changes +from ...models import DancersArrayPartsItem, PartType from ...states import state +from ...utils.ui import set_dopesheet_filter +from ..property.animation_data import ( + set_ctrl_keyframes_from_state, + set_pos_keyframes_from_state, +) -asset_path = bpy.context.preferences.filepaths.asset_libraries["User Library"].path +asset_path = cast( + str, bpy.context.preferences.filepaths.asset_libraries["User Library"].path +) target_path = os.path.join(asset_path, "LightDance") @@ -23,119 +31,257 @@ async def fetch_data(reload: bool = False): param reload: Fetch assets again even they already exist is true, otherwise only fetch missing assets. """ print("fetching data") - if client.http_client: - async with client.http_client.get("/data/load.json") as response: - assets_load = await response.json() + use_draco = False + + if client.file_client: + assets_load: Dict[str, Any] = await client.download_json("/data/load.json") + try: - url_set = set() + url_set: Set[str] = set() for tag in ["Music", "LightPresets", "PosPresets"]: url_set.add(assets_load[tag]) + for key in assets_load["DancerMap"]: - url_set.add(assets_load["DancerMap"][key]["url"]) + raw_url = assets_load["DancerMap"][key]["url"] + if use_draco: + model_url = raw_url + else: + model_url = "".join(raw_url.split(".draco")) + assets_load["DancerMap"][key]["url"] = model_url + + url_set.add(model_url) + for url in url_set: file_path = os.path.normpath(target_path + url) file_dir = os.path.dirname(file_path) if os.path.isfile(file_path) and not reload: continue + if not os.path.exists(file_dir): os.makedirs(file_dir) print("created folder: ", file_dir) - async with client.http_client.get(url) as response: - data = await response.content.read() - print("fetched file ", url, "from server") - with open(file_path, "w+b") as file: - file.write(data) + + data = await client.download_binary(url) + print("fetched file ", url, "from server") + with open(file_path, "w+b") as file: + file.write(data) + except Exception as e: print(e) - """ - temp fetch control map - """ - from ....api.control_agent import control_agent - from ...utils.convert import control_map_query_to_state - control_query = await control_agent.get_control_map_payload() - if control_query is None: - raise Exception("Control map not found") - - state.control_map = control_map_query_to_state(control_query) else: - raise Exception("HTTP client is not initialized") + raise Exception("File client is not initialized") + return assets_load -def setup_assets(assets_load): +def import_model_to_asset( + model_name: str, model_filepath: str, parts: List[DancersArrayPartsItem] +): + """ + set dancer collection asset + """ + bpy.ops.import_scene.gltf( + filepath=model_filepath + ) # here all parts of dancer is selected + model_objs = bpy.context.selected_objects + + col = bpy.data.collections.new(model_name) + for obj in model_objs: + for old_col in obj.users_collection: + old_col.objects.unlink(obj) + col.objects.link(obj) + + # avoid part name conflict + obj.name = f"{model_name}.{obj.name}" + + # Clean meshes + sphere_mesh = find_first_mesh("Sphere") + sphere_mesh.name = f"{model_name}.Sphere" + + for obj in model_objs: + if obj.type == "EMPTY": + continue + if "Sphere" in obj.data.name and obj.data != sphere_mesh: + bpy.data.meshes.remove(cast(bpy.types.Mesh, obj.data), do_unlink=True) + obj.data = sphere_mesh + + human_mesh = find_first_mesh("human") + human_mesh.name = f"{model_name}.Human" + + for obj in model_objs: + if obj.type == "EMPTY": + continue + mesh = obj.data + if "BezierCurve" in mesh.name and model_name not in mesh.name: + mesh.name = f"{model_name}.{mesh.name}" + + col.asset_mark() + for part in parts: + part_objects = [ + part_obj for part_obj in model_objs if part_obj.name.find(part.name) >= 0 + ] + if len(part_objects) == 0: + print("Dancer part not found (maybe should reload asset)") + + bpy.ops.outliner.orphans_purge(do_recursive=True) + print(f"Model: {model_name} imported") + + +def find_first_mesh(mesh_name: str) -> bpy.types.Mesh: + data_meshes = cast(Dict[str, bpy.types.Mesh], bpy.data.meshes) + mesh = data_meshes.get(mesh_name) + + if mesh is None: + candidates = [name for name in data_meshes.keys() if name.find(mesh_name) == 0] + numbers = [int(name.split(".")[-1]) for name in candidates] + mesh = data_meshes[candidates[numbers.index(min(numbers))]] + + return mesh + + +def setup_objects(assets_load: Dict[str, Any]): """ - clear all objects + clear all objects in viewport """ - # bpy.ops.object.select_all(action='DESELECT') - # bpy.ops.object.select_all() - # bpy.ops.object.delete() + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + for old_obj in data_objects.values(): + if old_obj.visible_get() and not hasattr(old_obj, "ld_dancer_name"): + bpy.data.objects.remove(old_obj) + """ set dancer objects """ + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + if check_local_object_list(): + print("local objects detected") + return + else: + for old_obj in data_objects.values(): + if old_obj.visible_get(): + bpy.data.objects.remove(old_obj) + dancer_array = state.dancers_array for dancer in dancer_array: dancer_name = dancer.name + dancer_index = dancer_name.split("_")[0] dancer_load = assets_load["DancerMap"][dancer_name] if dancer_name in bpy.context.scene.objects.keys(): continue - dancer_file = dancer_load["url"] - dancer_filepath = os.path.normpath(target_path + dancer_file) - dancer_parent = bpy.data.objects.new(dancer_name, None) - bpy.context.object.empty_display_size = 0 - setattr(dancer_parent, "ld_object_type", ObjectType.DANCER.value) - bpy.context.scene.collection.objects.link(dancer_parent) - bpy.ops.import_scene.gltf( - filepath=dancer_filepath - ) # here all parts of dancer is selected - dancer_objects = bpy.context.selected_objects - dancer_human = next(obj for obj in dancer_objects if obj.name[0:5] == "Human") + + model_file: str = dancer_load["url"] + model_filepath = os.path.normpath(target_path + model_file) + model_name: str = dancer_load["modelName"] + + if model_name not in bpy.data.collections.keys(): + import_model_to_asset(model_name, model_filepath, dancer.parts) + + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + dancer_asset = cast(bpy.types.Collection, bpy.data.collections[model_name]) + + dancer_asset_objects_dict = { + obj.name: cast(bpy.types.Object, obj.copy()) + for obj in cast(List[bpy.types.Object], dancer_asset.all_objects) + } + + for name, obj in dancer_asset_objects_dict.items(): + pure_name = ".".join(name.split(".")[1:]) + new_name = f"{dancer_index}_{pure_name}" + if pure_name == model_name: + new_name = dancer_name + obj.name = new_name + + dancer_asset_objects = { + obj.name: obj + for obj in cast(List[bpy.types.Object], dancer_asset_objects_dict.values()) + } + + # for name, obj in dancer_asset_objects.items(): + # print(name, obj) + # break + + # dancer_parent = bpy.data.objects.new(dancer_name, None) + dancer_obj = dancer_asset_objects[dancer_name] set_bpy_props( - dancer_human, - name=f"{dancer.name}.Human", - parent=dancer_parent, - ld_object_type=ObjectType.HUMAN.value, + dancer_obj, + empty_display_size=0, + ld_dancer_name=dancer.name, + ld_model_name=model_name, + ld_object_type=ObjectType.DANCER.value, + ) + bpy.context.scene.collection.objects.link(dancer_obj) + + human_name = f"{dancer_index}_Human" + human_obj = dancer_asset_objects[human_name] + set_bpy_props( + human_obj, + parent=dancer_obj, color=(0, 0, 0, 1), - data=bpy.data.meshes["human"], + ld_object_type=ObjectType.HUMAN.value, + ld_dancer_name=dancer.name, + ld_model_name=model_name, ) - for item in dancer.parts: - part_objects = [i for i in dancer_objects if i.name.find(item.name) >= 0] - if len(part_objects) == 0: + bpy.context.scene.collection.objects.link(human_obj) + + for part_item in dancer.parts: + part_obj_name = f"{dancer_index}_{part_item.name}" + part_obj = dancer_asset_objects.get(part_obj_name) + + if part_obj is None: print("Dancer part not found (maybe should reload asset)") - if item.type.value == "LED": - parts_parent = bpy.data.objects.new( - f"{dancer.name}.{item.name}.parent", None - ) - bpy.context.scene.collection.objects.link(parts_parent) + continue + + if part_item.type.value == "LED": set_bpy_props( - parts_parent, - parent=dancer_parent, + part_obj, + parent=dancer_obj, + empty_display_size=0, ld_object_type=ObjectType.LIGHT.value, - ld_light_type=item.type.value.lower(), - ld_part_name=item.name, + ld_light_type=LightType.LED.value, + ld_part_name=part_item.name, + ld_dancer_name=dancer.name, + ld_model_name=model_name, ) - bpy.context.object.empty_display_size = 0 - for obj in part_objects: - obj.name = f"{dancer.name}.{item.name}" + bpy.context.scene.collection.objects.link(part_obj) + + led_objs = [ + obj + for obj_name, obj in dancer_asset_objects.items() + if f"{part_obj_name}." in obj_name + ] + for led_obj in led_objs: + position = int(led_obj.name.split(".")[-1]) set_bpy_props( - obj, - parent=parts_parent, + led_obj, + parent=part_obj, + color=(0, 0, 0, 1), ld_object_type=ObjectType.LIGHT.value, - ld_light_type=item.type.value.lower(), - ld_part_name=item.name, - data=bpy.data.meshes["Sphere.001"], + ld_light_type=LightType.LED_BULB.value, + ld_part_name=part_item.name, + ld_dancer_name=dancer.name, + ld_model_name=model_name, + ld_led_pos=position, ) - elif item.type.value == "FIBER": - obj = part_objects[0] - obj.name = f"{dancer.name}.{item.name}" - obj.parent = dancer_parent - setattr(obj, "ld_object_type", "light") - setattr(obj, "ld_light_type", item.type.value.lower()) - setattr(obj, "ld_part_name", item.name) - bpy.ops.outliner.orphans_purge(do_recursive=True) + bpy.context.scene.collection.objects.link(led_obj) + + elif part_item.type.value == "FIBER": + set_bpy_props( + part_obj, + parent=dancer_obj, + name=part_obj_name, + color=(0, 0, 0, 1), + ld_object_type=ObjectType.LIGHT.value, + ld_light_type=LightType.FIBER.value, + ld_part_name=part_item.name, + ld_dancer_name=dancer.name, + ld_model_name=model_name, + ) + bpy.context.scene.collection.objects.link(part_obj) -def set_music_from_load(assets_load): +def setup_music(assets_load: Dict[str, Any]): """ set music """ @@ -143,188 +289,102 @@ def set_music_from_load(assets_load): if not scene.sequence_editor: scene.sequence_editor_create() music_filepath = os.path.normpath(target_path + assets_load["Music"]) + if scene.sequence_editor.sequences: + sequence = cast(bpy.types.SoundSequence, scene.sequence_editor.sequences[0]) + scene.sequence_editor.sequences.remove(sequence) + scene.sequence_editor.sequences.new_sound( "music", filepath=music_filepath, channel=1, frame_start=0 ) -""" -init position keyframes -""" - - -def init_pos_keyframes_from_state(): - # pos_map = state.pos_map - from .fake_pos_map import pos_map # TODO: remove fake pos map - - pos_frame_number = len(pos_map) - for i, (_, pos_map_element) in enumerate( - pos_map.items() - ): # change to enumerate over dancers - frame_start = pos_map_element["start"] # type: ignore - pos_status = pos_map_element["pos"] # type: ignore - for dancer_name, pos in pos_status.items(): - dancer_obj = bpy.data.objects[dancer_name] - dancer_location = (pos["x"], pos["y"], pos["z"]) - if dancer_obj.animation_data is None: - dancer_obj.animation_data_create() - if dancer_obj.animation_data.action is None: - dancer_obj.animation_data.action = bpy.data.actions.new( - dancer_name + "Action" - ) - curves = dancer_obj.animation_data.action.fcurves - for d in range(3): - if curves.find("location", index=d) is None: - curves.new("location", index=d) - curves.find("location", index=d).keyframe_points.add( - pos_frame_number - ) - point = curves.find("location", index=d).keyframe_points[i] - point.co = frame_start, dancer_location[d] - point.interpolation = "LINEAR" - if i == pos_frame_number - 1: - curves.find("location", index=d).keyframe_points.sort() - # insert fake frame - scene = bpy.context.scene - if scene.animation_data is None: - scene.animation_data_create() - if scene.animation_data.action is None: - scene.animation_data.action = bpy.data.actions.new("SceneAction") - curves = scene.animation_data.action.fcurves - if curves.find("ld_pos_frame") is None: - curves.new("ld_pos_frame") - curves.find("ld_pos_frame").keyframe_points.add(pos_frame_number) - curves.find("ld_pos_frame").keyframe_points[i].co = frame_start, i % 2 - curves.find("ld_pos_frame").keyframe_points[i].interpolation = "CONSTANT" - if i == pos_frame_number - 1: - curves.find("ld_pos_frame").keyframe_points.sort() - - -""" -init control keyframes -""" - - -def init_ctrl_keyframes_from_state(): - ctrl_map = state.control_map - color_map = state.color_map - led_effect_table = state.led_effect_id_table - print(led_effect_table) - ctrl_frame_number = len(ctrl_map) - for i, (id, ctrl_map_element) in enumerate(ctrl_map.items()): - frame_start = ctrl_map_element.start - fade = ctrl_map_element.fade - ctrl_status = ctrl_map_element.status - for dancer_name, ctrl in ctrl_status.items(): - for part_name, part_data in ctrl.items(): - if isinstance(part_data, LEDData): - part_parent = bpy.data.objects[f"{dancer_name}.{part_name}.parent"] - # part_effect = led_effect_table[part_data.effect_id] - # part_effect_frames = part_effect.effects - # for effect_frame in part_effect_frames: - # effect_frame_start = frame_start + effect_frame.start - # effect_list = effect_frame.effect - # effect_fade = effect_frame.fade - # for i in range(len(part_parent.children)): - # led_obj = part_parent.children[i] - # led_data = effect_list[i] - # led_rgb = color_map[led_data.color_id].rgb - # led_rgba = ( - # led_rgb[0]/255, - # led_rgb[1]/255, - # led_rgb[2]/255, - # led_data.alpha/10 - # ) - # if led_obj.animation_data is None: - # led_obj.animation_data_create() - # if led_obj.animation_data.action is None: - # led_obj.animation_data.action = bpy.data.actions.new(part_name+"Action") - # curves = led_obj.animation_data.action.fcurves - # for d in range(4): - # if curves.find("color", index=d) is None: - # curves.new("color", index=d) - # curves.find("color", index=d).keyframe_points.add(ctrl_frame_number) - # point = curves.find("color", index=d).keyframe_points[i] - # point.co = effect_frame_start, led_rgba[d] - # point.interpolation = "LINEAR" if effect_fade else "CONSTANT" - # if i == ctrl_frame_number - 1: - # curves.find("color", index=d).keyframe_points.sort() - - elif isinstance(part_data, FiberData): - part_obj = bpy.data.objects[f"{dancer_name}.{part_name}"] - part_rgb = color_map[part_data.color_id].rgb - part_rgba = ( - part_rgb[0] / 255, - part_rgb[1] / 255, - part_rgb[2] / 255, - part_data.alpha / 10, - ) - if part_obj.animation_data is None: - part_obj.animation_data_create() - if part_obj.animation_data.action is None: - part_obj.animation_data.action = bpy.data.actions.new( - part_name + "Action" - ) - curves = part_obj.animation_data.action.fcurves - for d in range(4): - if curves.find("color", index=d) is None: - curves.new("color", index=d) - curves.find("color", index=d).keyframe_points.add( - ctrl_frame_number - ) - point = curves.find("color", index=d).keyframe_points[i] - point.co = frame_start, part_rgba[d] - point.interpolation = "LINEAR" if fade else "CONSTANT" - if i == ctrl_frame_number - 1: - curves.find("color", index=d).keyframe_points.sort() - else: - print("Invalid part data") - # insert fake frame - scene = bpy.context.scene - if scene.animation_data is None: - scene.animation_data_create() - if scene.animation_data.action is None: - scene.animation_data.action = bpy.data.actions.new("SceneAction") - curves = scene.animation_data.action.fcurves - if curves.find("ld_control_frame") is None: - curves.new("ld_control_frame") - curves.find("ld_control_frame").keyframe_points.add(ctrl_frame_number) - curves.find("ld_control_frame").keyframe_points[i].co = frame_start, i % 2 - curves.find("ld_control_frame").keyframe_points[i].interpolation = "CONSTANT" - if i == ctrl_frame_number - 1: - curves.find("ld_control_frame").keyframe_points.sort() - - def setup_viewport(): """ 3d viewport """ - view_3d = next(a for a in bpy.context.screen.areas if a.ui_type == "VIEW_3D") - setattr(view_3d.spaces.active.overlay, "show_relationship_lines", False) # type: ignore - setattr(view_3d.spaces.active.shading, "background_type", "VIEWPORT") # type: ignore - setattr(view_3d.spaces.active.shading, "background_color", (0, 0, 0)) # type: ignore - setattr(view_3d.spaces.active.shading, "color_type", "OBJECT") # type: ignore - setattr(view_3d.spaces.active.shading, "light", "FLAT") # type: ignore + view_3d = next( + area + for area in cast(List[bpy.types.Area], bpy.context.screen.areas) + if area.ui_type == "VIEW_3D" + ) + + space = cast(bpy.types.SpaceView3D, view_3d.spaces.active) + space.overlay.show_relationship_lines = False + space.shading.background_type = "VIEWPORT" + space.shading.background_color = (0, 0, 0) + space.shading.color_type = "OBJECT" + space.shading.light = "FLAT" """ timeline """ bpy.context.scene.render.fps = 1000 + bpy.context.scene.frame_start = 0 bpy.context.scene.frame_end = bpy.context.scene.sequence_editor.sequences[ 0 ].frame_duration + bpy.context.scene.show_keys_from_selected_only = False bpy.context.scene.sync_mode = "AUDIO_SYNC" - timeline = next(a for a in bpy.context.screen.areas if a.ui_type == "TIMELINE") - setattr(timeline.spaces.active, "show_seconds", True) # type: ignore - setattr(timeline.spaces.active.dopesheet, "filter_text", "ld") # type: ignore + timeline = next( + area + for area in cast(List[bpy.types.Area], bpy.context.screen.areas) + if area.ui_type == "TIMELINE" + ) + + space = cast(bpy.types.SpaceSequenceEditor, timeline.spaces.active) + space.show_seconds = True + + set_dopesheet_filter("control_frame") # follow default editor + + +def setup_animation_data(): + if not getattr(bpy.context.scene, "ld_anidata"): + set_pos_keyframes_from_state() + set_ctrl_keyframes_from_state() + setattr(bpy.context.scene, "ld_anidata", True) + else: + print("local animation data detected") + # update_rev_changes(state.pos_map, state.control_map) # TODO: test this + + +def check_local_object_list(): + data_objects = cast(Dict[str, bpy.types.Object], bpy.data.objects) + + for dancer_item in state.dancers_array: + dancer_name = dancer_item.name + if dancer_name not in bpy.data.objects.keys(): + return False + + dancer_parts = dancer_item.parts + dancer_index = dancer_name.split("_")[0] + for part_item in dancer_parts: + part_name = part_item.name + part_type = part_item.type + part_obj_name = f"{dancer_index}_{part_name}" + + match part_type: + case PartType.LED: + part_parent = data_objects.get(part_obj_name) + if part_parent is None: + return False + + if len(part_parent.children) != part_item.length: + return False + + case PartType.FIBER: + if part_obj_name not in data_objects.keys(): + return False + + return True async def load_data() -> None: assets_load = await fetch_data() - setup_assets(assets_load) - set_music_from_load(assets_load) - init_pos_keyframes_from_state() - init_ctrl_keyframes_from_state() + + setup_objects(assets_load) + setup_music(assets_load) + setup_animation_data() setup_viewport() - print("data loaded") + + print("Data loaded") diff --git a/editor-blender/core/actions/state/pos_map.py b/editor-blender/core/actions/state/pos_map.py index 43b15ad8a..4c0e5b44f 100644 --- a/editor-blender/core/actions/state/pos_map.py +++ b/editor-blender/core/actions/state/pos_map.py @@ -2,6 +2,11 @@ from ...states import state from ...utils.notification import notify from ...utils.ui import redraw_area +from ..property.animation_data import ( + add_single_pos_keyframe, + delete_single_pos_keyframe, + edit_single_pos_keyframe, +) from .current_pos import calculate_current_pos_index, update_current_pos_by_index @@ -83,15 +88,16 @@ def update_pos(id: MapID, frame: PosMapElement): def apply_pos_map_updates(): pos_map_updates = state.pos_map_updates - # TODO: Update animation data - for pos in pos_map_updates.added: + add_single_pos_keyframe(pos[0], pos[1]) state.pos_map[pos[0]] = pos[1] for pos in pos_map_updates.updated: + edit_single_pos_keyframe(pos[0], pos[1]) state.pos_map[pos[0]] = pos[1] for pos_id in pos_map_updates.deleted: + delete_single_pos_keyframe(pos_id) del state.pos_map[pos_id] pos_map_updates.added.clear() diff --git a/editor-blender/core/models/__init__.py b/editor-blender/core/models/__init__.py index aeb43a045..c46aea8f9 100644 --- a/editor-blender/core/models/__init__.py +++ b/editor-blender/core/models/__init__.py @@ -81,8 +81,8 @@ class Revision: class ControlMapElement: start: int fade: bool + rev: Revision status: ControlMapStatus - rev: Optional[Revision] = None ControlMap = Dict[MapID, ControlMapElement] @@ -103,8 +103,8 @@ class Location: @dataclass class PosMapElement: start: int + rev: Revision pos: PosMapStatus - rev: Optional[Revision] = None PosMap = Dict[MapID, PosMapElement] diff --git a/editor-blender/core/utils/convert.py b/editor-blender/core/utils/convert.py index f8a1264bb..bab345e04 100644 --- a/editor-blender/core/utils/convert.py +++ b/editor-blender/core/utils/convert.py @@ -83,9 +83,7 @@ def dancers_query_to_state(payload: QueryDancersPayload) -> DancersArray: def pos_frame_query_to_state(payload: QueryPosFrame) -> PosMapElement: - rev = None - if payload.rev is not None: - rev = Revision(meta=payload.rev.meta, data=payload.rev.data) + rev = Revision(meta=payload.rev.meta, data=payload.rev.data) pos_map_element = PosMapElement(start=payload.start, pos={}, rev=rev) pos_map_element.pos = pos_status_query_to_state(payload.pos) @@ -94,9 +92,7 @@ def pos_frame_query_to_state(payload: QueryPosFrame) -> PosMapElement: def pos_frame_sub_to_query(data: SubPositionFrame) -> QueryPosFrame: - rev = None - if data.rev is not None: - rev = QueryRevision(meta=data.rev.meta, data=data.rev.data) + rev = QueryRevision(meta=data.rev.meta, data=data.rev.data) response = QueryPosFrame(start=data.start, pos=[], rev=rev) response.pos = [(pos[0], pos[1], pos[2]) for pos in data.pos] @@ -171,9 +167,7 @@ def control_status_query_to_state( def control_frame_query_to_state(payload: QueryControlFrame) -> ControlMapElement: - rev = None - if payload.rev is not None: - rev = Revision(meta=payload.rev.meta, data=payload.rev.data) + rev = Revision(meta=payload.rev.meta, data=payload.rev.data) control_map_element = ControlMapElement( start=payload.start, fade=payload.fade, status={}, rev=rev @@ -194,9 +188,7 @@ def control_map_query_to_state(frames: QueryControlMapPayload) -> ControlMap: def control_frame_sub_to_query(data: SubControlFrame) -> QueryControlFrame: - rev = None - if data.rev is not None: - rev = QueryRevision(meta=data.rev.meta, data=data.rev.data) + rev = QueryRevision(meta=data.rev.meta, data=data.rev.data) response = QueryControlFrame(start=data.start, fade=data.fade, status=[], rev=rev) @@ -309,3 +301,13 @@ def rgb_to_float(rgb: Tuple[int, ...]) -> Tuple[float, ...]: def float_to_rgb(color_float: Tuple[float, ...]) -> Tuple[int, ...]: return tuple([round(color * 255) for color in color_float]) + + +def rgba_to_float(rgb: Union[Tuple[int, ...], List[int]], a: int) -> Tuple[float, ...]: + r, g, b = rgb + a_float = a / 255 + return ( + r / 255 * a_float, + g / 255 * a_float, + b / 255 * a_float, + ) diff --git a/editor-blender/graphqls/mutations.py b/editor-blender/graphqls/mutations.py index db63182fa..41d9195ef 100644 --- a/editor-blender/graphqls/mutations.py +++ b/editor-blender/graphqls/mutations.py @@ -208,7 +208,7 @@ class MutDeletePositionFrameResponse(JSONWizard): @dataclass class MutEditPositionFrameTimeInput(JSONWizard): - frameID: MapID + frameId: MapID start: int @@ -316,7 +316,7 @@ class MutDeleteControlFrameInput(JSONWizard): @dataclass class MutEditControlFrameTimeInput(JSONWizard): - frameID: MapID + frameId: MapID start: int diff --git a/editor-blender/graphqls/queries.py b/editor-blender/graphqls/queries.py index 38c610881..c50231f7b 100644 --- a/editor-blender/graphqls/queries.py +++ b/editor-blender/graphqls/queries.py @@ -188,8 +188,8 @@ class QueryRevision(JSONWizard): @dataclass class QueryPosFrame(JSONWizard): start: int + rev: QueryRevision pos: List[QueryCoordinatesPayload] - rev: Optional[QueryRevision] = None QueryPosMapPayload = Dict[ID, QueryPosFrame] @@ -236,8 +236,8 @@ class QueryPosMapData(JSONWizard): class QueryControlFrame(JSONWizard): start: int fade: bool + rev: QueryRevision status: List[QueryDancerStatusPayload] - rev: Optional[QueryRevision] = None QueryControlMapPayload = Dict[ID, QueryControlFrame] diff --git a/editor-blender/graphqls/subscriptions.py b/editor-blender/graphqls/subscriptions.py index f6d934938..eb21b9896 100644 --- a/editor-blender/graphqls/subscriptions.py +++ b/editor-blender/graphqls/subscriptions.py @@ -74,8 +74,8 @@ class SubPositionRecordData(JSONWizard): class SubPositionFrame(JSONWizard): start: int pos: List[SubPosition] + rev: SubRevision editing: Optional[str] = None - rev: Optional[SubRevision] = None @dataclass @@ -151,9 +151,9 @@ class SubControlRecordData(JSONWizard): class SubControlFrame(JSONWizard): fade: bool start: int + rev: SubRevision status: List[List[SubPartControl]] editing: Optional[str] = None - rev: Optional[SubRevision] = None @dataclass diff --git a/editor-blender/properties/__init__.py b/editor-blender/properties/__init__.py index 0daf6a724..c703c3c9f 100644 --- a/editor-blender/properties/__init__.py +++ b/editor-blender/properties/__init__.py @@ -1,4 +1,4 @@ -from . import color_palette, lights, objects, position, scene, timeline, ui +from . import color_palette, lights, objects, position, revision, scene, timeline, ui def register(): @@ -9,6 +9,7 @@ def register(): color_palette.register() timeline.register() scene.register() + revision.register() def unregister(): @@ -19,3 +20,4 @@ def unregister(): color_palette.unregister() timeline.unregister() scene.unregister() + revision.unregister() diff --git a/editor-blender/properties/lights.py b/editor-blender/properties/lights.py index 4c84b57ca..bd1d607d9 100644 --- a/editor-blender/properties/lights.py +++ b/editor-blender/properties/lights.py @@ -83,6 +83,14 @@ def register(): update=update_current_color, ), ) + setattr( + bpy.types.Object, + "ld_color_float", + bpy.props.FloatVectorProperty( + name="Color float", + description="Part fiber color", + ), + ) setattr( bpy.types.Object, "ld_effect", diff --git a/editor-blender/properties/revision.py b/editor-blender/properties/revision.py new file mode 100644 index 000000000..8eb47bf70 --- /dev/null +++ b/editor-blender/properties/revision.py @@ -0,0 +1,36 @@ +import bpy + + +class KeyframeRevisionItem(bpy.types.PropertyGroup): + frame_id: bpy.props.IntProperty() # type: ignore + frame_start: bpy.props.IntProperty() # type: ignore + meta: bpy.props.IntProperty(default=-1) # type: ignore + data: bpy.props.IntProperty(default=-1) # type: ignore + + +def register(): + bpy.utils.register_class(KeyframeRevisionItem) + setattr( + bpy.types.Scene, + "ld_pos_rev", + bpy.props.CollectionProperty(type=KeyframeRevisionItem), + ) + setattr( + bpy.types.Scene, + "ld_ctrl_rev", + bpy.props.CollectionProperty(type=KeyframeRevisionItem), + ) + setattr(bpy.types.Scene, "ld_anidata", bpy.props.BoolProperty(default=False)) + + +def unregister(): + bpy.utils.unregister_class(KeyframeRevisionItem) + delattr( + bpy.types.Scene, + "ld_pos_rev", + ) + delattr( + bpy.types.Scene, + "ld_ctrl_rev", + ) + delattr(bpy.types.Scene, "ld_anidata") diff --git a/editor-blender/properties/types.py b/editor-blender/properties/types.py index a0bda3105..7cc0ff365 100644 --- a/editor-blender/properties/types.py +++ b/editor-blender/properties/types.py @@ -30,3 +30,13 @@ class ColorPaletteItemType: ColorPaletteType = List[ColorPaletteItemType] + + +class RevisionPropertyItemType: + frame_id: int + frame_start: int + meta: int + data: int + + +RevisionPropertyType = List[RevisionPropertyItemType] diff --git a/files/asset/models/0_agent.draco.glb b/files/asset/models/0_agent.draco.glb index 23288d191..510aa22ac 100644 Binary files a/files/asset/models/0_agent.draco.glb and b/files/asset/models/0_agent.draco.glb differ diff --git a/files/asset/models/0_agent.glb b/files/asset/models/0_agent.glb index acd7eaf87..291ff3894 100644 Binary files a/files/asset/models/0_agent.glb and b/files/asset/models/0_agent.glb differ diff --git a/files/asset/models/1_hank.draco.glb b/files/asset/models/1_hank.draco.glb index e96755868..1f9d8d0c4 100644 Binary files a/files/asset/models/1_hank.draco.glb and b/files/asset/models/1_hank.draco.glb differ diff --git a/files/asset/models/1_hank.glb b/files/asset/models/1_hank.glb index f5283ab12..9f23b4894 100644 Binary files a/files/asset/models/1_hank.glb and b/files/asset/models/1_hank.glb differ diff --git a/utils/generateInitialExport.js b/utils/generateInitialExport.js index e1f67b1c9..03c5746aa 100644 --- a/utils/generateInitialExport.js +++ b/utils/generateInitialExport.js @@ -25,9 +25,9 @@ const io = new NodeIO(); const modelPartsCache = new Map(); -async function getParts(modelPath, dancerName, modelName) { - if (modelPartsCache.has(modelPath)) { - return modelPartsCache.get(modelPath); +async function getParts(modelPath, modelName) { + if (modelPartNameCache.has(modelPath)) { + return modelPartNameCache.get(modelPath); } const document = await io.read(modelPath); // → Document @@ -38,7 +38,7 @@ async function getParts(modelPath, dancerName, modelName) { // remove dancer root object .filter((name) => name !== modelName) // drop first '_' - .map((name) => name.split("_").slice(1).join("_")) + // .map((name) => name.split("_").slice(1).join("_")) // drop after '.' .map((name) => name.split(".")[0]) // remove duplicates @@ -61,9 +61,9 @@ async function getParts(modelPath, dancerName, modelName) { const LEDs = root .listNodes() .map((node) => node.getName()) - .filter((name) => name.includes("_LED.")) + .filter((name) => name.includes("_LED.")); // drop first '_' - .map((name) => name.split("_").slice(1).join("_")); + // .map((name) => name.split("_").slice(1).join("_")); const LEDcounter = LEDs.reduce((acc, LEDname) => { const partName = LEDname.split(".")[0]; @@ -195,7 +195,7 @@ function generateEmptyLEDEffects(modelParts) { return { name: dancerName, model: modelName, - parts: await getParts(modelUrl, dancerName, modelName), + parts: await getParts(modelUrl, modelName), }; }) ); diff --git a/utils/jsons/exportDataEmpty.json b/utils/jsons/exportDataEmpty.json index e22099ab6..0967ef424 100644 --- a/utils/jsons/exportDataEmpty.json +++ b/utils/jsons/exportDataEmpty.json @@ -1,5176 +1 @@ -{ - "dancer": [ - { - "name": "0_agent", - "model": "0_agent", - "parts": [ - { - "name": "arm_inner_left", - "type": "FIBER" - }, - { - "name": "arm_inner_right", - "type": "FIBER" - }, - { - "name": "arm_outer_left", - "type": "FIBER" - }, - { - "name": "arm_outer_right", - "type": "FIBER" - }, - { - "name": "beret", - "type": "FIBER" - }, - { - "name": "brim", - "type": "FIBER" - }, - { - "name": "collar_back", - "type": "FIBER" - }, - { - "name": "collar_left", - "type": "FIBER" - }, - { - "name": "collar_right", - "type": "FIBER" - }, - { - "name": "hem", - "type": "FIBER" - }, - { - "name": "lapel_left", - "type": "FIBER" - }, - { - "name": "lapel_right", - "type": "FIBER" - }, - { - "name": "leg_back_left", - "type": "FIBER" - }, - { - "name": "leg_back_right", - "type": "FIBER" - }, - { - "name": "leg_front_left", - "type": "FIBER" - }, - { - "name": "leg_front_right", - "type": "FIBER" - }, - { - "name": "leg_inner_left", - "type": "FIBER" - }, - { - "name": "leg_inner_right", - "type": "FIBER" - }, - { - "name": "leg_outer_left", - "type": "FIBER" - }, - { - "name": "leg_outer_right", - "type": "FIBER" - }, - { - "name": "pocket", - "type": "FIBER" - }, - { - "name": "waist_left", - "type": "FIBER" - }, - { - "name": "waist_right", - "type": "FIBER" - }, - { - "name": "buttons_LED", - "type": "LED", - "length": 8 - }, - { - "name": "glasses_LED", - "type": "LED", - "length": 25 - }, - { - "name": "glove_left_LED", - "type": "LED", - "length": 42 - }, - { - "name": "glove_right_LED", - "type": "LED", - "length": 42 - }, - { - "name": "oxford_left_LED", - "type": "LED", - "length": 77 - }, - { - "name": "oxford_right_LED", - "type": "LED", - "length": 77 - }, - { - "name": "tie_LED", - "type": "LED", - "length": 73 - } - ] - }, - { - "name": "1_hank", - "model": "1_hank", - "parts": [ - { - "name": "arm_inner_left", - "type": "FIBER" - }, - { - "name": "arm_inner_right", - "type": "FIBER" - }, - { - "name": "arm_outer_left", - "type": "FIBER" - }, - { - "name": "arm_outer_right", - "type": "FIBER" - }, - { - "name": "beret", - "type": "FIBER" - }, - { - "name": "brim", - "type": "FIBER" - }, - { - "name": "collar_back", - "type": "FIBER" - }, - { - "name": "collar_left", - "type": "FIBER" - }, - { - "name": "collar_right", - "type": "FIBER" - }, - { - "name": "hem", - "type": "FIBER" - }, - { - "name": "lapel_left", - "type": "FIBER" - }, - { - "name": "lapel_right", - "type": "FIBER" - }, - { - "name": "leg_back_left", - "type": "FIBER" - }, - { - "name": "leg_back_right", - "type": "FIBER" - }, - { - "name": "leg_front_left", - "type": "FIBER" - }, - { - "name": "leg_front_right", - "type": "FIBER" - }, - { - "name": "leg_inner_left", - "type": "FIBER" - }, - { - "name": "leg_inner_right", - "type": "FIBER" - }, - { - "name": "leg_outer_left", - "type": "FIBER" - }, - { - "name": "leg_outer_right", - "type": "FIBER" - }, - { - "name": "pocket", - "type": "FIBER" - }, - { - "name": "waist_left", - "type": "FIBER" - }, - { - "name": "waist_right", - "type": "FIBER" - }, - { - "name": "buttons_LED", - "type": "LED", - "length": 8 - }, - { - "name": "glasses_LED", - "type": "LED", - "length": 25 - }, - { - "name": "glove_left_LED", - "type": "LED", - "length": 42 - }, - { - "name": "glove_right_LED", - "type": "LED", - "length": 42 - }, - { - "name": "oxford_left_LED", - "type": "LED", - "length": 77 - }, - { - "name": "oxford_right_LED", - "type": "LED", - "length": 77 - }, - { - "name": "tie_LED", - "type": "LED", - "length": 73 - } - ] - }, - { - "name": "2_henning", - "model": "0_agent", - "parts": [ - { - "name": "arm_inner_left", - "type": "FIBER" - }, - { - "name": "arm_inner_right", - "type": "FIBER" - }, - { - "name": "arm_outer_left", - "type": "FIBER" - }, - { - "name": "arm_outer_right", - "type": "FIBER" - }, - { - "name": "beret", - "type": "FIBER" - }, - { - "name": "brim", - "type": "FIBER" - }, - { - "name": "collar_back", - "type": "FIBER" - }, - { - "name": "collar_left", - "type": "FIBER" - }, - { - "name": "collar_right", - "type": "FIBER" - }, - { - "name": "hem", - "type": "FIBER" - }, - { - "name": "lapel_left", - "type": "FIBER" - }, - { - "name": "lapel_right", - "type": "FIBER" - }, - { - "name": "leg_back_left", - "type": "FIBER" - }, - { - "name": "leg_back_right", - "type": "FIBER" - }, - { - "name": "leg_front_left", - "type": "FIBER" - }, - { - "name": "leg_front_right", - "type": "FIBER" - }, - { - "name": "leg_inner_left", - "type": "FIBER" - }, - { - "name": "leg_inner_right", - "type": "FIBER" - }, - { - "name": "leg_outer_left", - "type": "FIBER" - }, - { - "name": "leg_outer_right", - "type": "FIBER" - }, - { - "name": "pocket", - "type": "FIBER" - }, - { - "name": "waist_left", - "type": "FIBER" - }, - { - "name": "waist_right", - "type": "FIBER" - }, - { - "name": "buttons_LED", - "type": "LED", - "length": 8 - }, - { - "name": "glasses_LED", - "type": "LED", - "length": 25 - }, - { - "name": "glove_left_LED", - "type": "LED", - "length": 42 - }, - { - "name": "glove_right_LED", - "type": "LED", - "length": 42 - }, - { - "name": "oxford_left_LED", - "type": "LED", - "length": 77 - }, - { - "name": "oxford_right_LED", - "type": "LED", - "length": 77 - }, - { - "name": "tie_LED", - "type": "LED", - "length": 73 - } - ] - } - ], - "control": { - "1": { - "fade": false, - "start": 0, - "status": [ - [ - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10] - ], - [ - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10] - ], - [ - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["black", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10], - ["", 10] - ] - ] - }, - "2": { - "fade": false, - "start": 1000, - "status": [ - [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10] - ], - [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10] - ], - [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10], - ["all_white", 10] - ] - ] - }, - "3": { - "fade": false, - "start": 2000, - "status": [ - [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10] - ], - [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10] - ], - [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10], - ["all_red", 10] - ] - ] - }, - "4": { - "fade": false, - "start": 3000, - "status": [ - [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10] - ], - [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10] - ], - [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10], - ["all_green", 10] - ] - ] - }, - "5": { - "fade": false, - "start": 4000, - "status": [ - [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10] - ], - [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10] - ], - [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10], - ["all_blue", 10] - ] - ] - } - }, - "position": { - "1": { - "start": 0, - "pos": [ - [-3, 0, 0], - [0, 0, 0], - [3, 0, 0] - ] - } - }, - "color": { - "black": [0, 0, 0], - "white": [255, 255, 255], - "red": [255, 0, 0], - "green": [0, 255, 0], - "blue": [0, 0, 255] - }, - "LEDEffects": { - "0_agent": { - "buttons_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "glasses_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "glove_left_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "glove_right_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "oxford_left_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "oxford_right_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "tie_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - } - }, - "1_hank": { - "buttons_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "glasses_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "glove_left_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "glove_right_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "oxford_left_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "oxford_right_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - }, - "tie_LED": { - "all_black": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0], - ["black", 0] - ] - } - ] - }, - "all_white": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10], - ["white", 10] - ] - } - ] - }, - "all_red": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10], - ["red", 10] - ] - } - ] - }, - "all_green": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10], - ["green", 10] - ] - } - ] - }, - "all_blue": { - "repeat": 0, - "frames": [ - { - "start": 0, - "fade": false, - "LEDs": [ - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10], - ["blue", 10] - ] - } - ] - } - } - } - } -} +{}