diff --git a/editor-blender/core/actions/property/animation_data.py b/editor-blender/core/actions/property/animation_data.py new file mode 100644 index 000000000..3288e0eaf --- /dev/null +++ b/editor-blender/core/actions/property/animation_data.py @@ -0,0 +1,642 @@ +from typing import List, Optional, Tuple + +import bpy + +from ....properties.types import RevisionPropertyItemType, RevisionPropertyType +from ...models import ( + ControlMapElement, + FiberData, + LEDData, + MapID, + PartType, + PosMapElement, +) +from ...states import state + +""" +setups & update colormap(===setups) +""" + + +def set_ctrl_keyframes_from_state(effect_only: bool = False): + ctrl_map = state.control_map + color_map = state.color_map + led_effect_table = state.led_effect_id_table + ctrl_frame_number = len(ctrl_map) + fade_seq: List[Tuple[int, bool]] = [(0, False)] * ctrl_frame_number + 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 + fade_seq[i] = frame_start, fade + for dancer_name, ctrl in ctrl_status.items(): + dancer_index = dancer_name.split("_")[0] + for part_name, part_data in ctrl.items(): + if isinstance(part_data, LEDData): + part_parent = bpy.data.objects[f"{dancer_index}.{part_name}.parent"] + if part_data.effect_id != -1: + part_effect = led_effect_table[part_data.effect_id].effect + for j, led_obj in enumerate(part_parent.children): + led_data = part_effect[j] + led_rgb = color_map[led_data.color_id].rgb + led_rgba = ( + (led_rgb[0] / 255) + * (led_data.alpha / 10), # TODO: change to 255 + (led_rgb[1] / 255) * (led_data.alpha / 10), + (led_rgb[2] / 255) * (led_data.alpha / 10), + 1, + ) + 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( + f"{dancer_index}.{part_name}Action.{j:03}" + ) + 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 = frame_start, led_rgba[d] + point.interpolation = "LINEAR" if fade else "CONSTANT" + if i == ctrl_frame_number - 1: + curves.find("color", index=d).keyframe_points.sort() + else: # no change + for j, led_obj in enumerate(part_parent.children): + 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 + ) + elif i == 0: + curves.find( + "location", index=d + ).keyframe_points.clear() + curves.find( + "location", index=d + ).keyframe_points.add(ctrl_frame_number) + point = curves.find("color", index=d).keyframe_points[i] + last_point = curves.find( + "color", index=d + ).keyframe_points[i - 1] + point.co = frame_start, last_point.co[1] + point.interpolation = "LINEAR" if fade else "CONSTANT" + if i == ctrl_frame_number - 1: + curves.find("color", index=d).keyframe_points.sort() + + elif isinstance(part_data, FiberData): + if effect_only: + continue + part_obj = bpy.data.objects[f"{dancer_index}.{part_name}"] + part_rgb = color_map[part_data.color_id].rgb + part_rgba = ( + (part_rgb[0] / 255) + * (part_data.alpha / 10), # TODO: change to 255 + (part_rgb[1] / 255) * (part_data.alpha / 10), + (part_rgb[2] / 255) * (part_data.alpha / 10), + 1, + ) + 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( + f"{dancer_index}.{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 + ) + elif i == 0: + curves.find("location", index=d).keyframe_points.clear() + curves.find("location", 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) + curve_points = curves.find("ld_control_frame").keyframe_points + curve_points[i].co = frame_start, frame_start + curve_points[i].interpolation = "CONSTANT" + # set revision + rev = ctrl_map_element.rev + 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 + curves.find("ld_control_frame").keyframe_points.sort() + fade_seq.sort(key=lambda item: item[0]) + curve_points = curves.find("ld_control_frame").keyframe_points + for frame_start, fade in fade_seq: + if fade: + point_index = next( + list(curve_points).index(p) + for p in curve_points + if p.co[0] == frame_start + ) + if point_index == ctrl_frame_number - 1: + continue + curve_points[point_index + 1].co = ( + curve_points[point_index + 1].co[0], + curve_points[point_index].co[1], + ) + + +def set_pos_keyframes_from_state(): + pos_map = state.pos_map + pos_frame_number = len(pos_map) + for i, (id, pos_map_element) in enumerate(pos_map.items()): + frame_start = pos_map_element.start + pos_status = pos_map_element.pos + 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 + ) + elif i == 0: + curves.find("location", index=d).keyframe_points.clear() + 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, frame_start + curves.find("ld_pos_frame").keyframe_points[i].interpolation = "CONSTANT" + if i == pos_frame_number - 1: + curves.find("ld_pos_frame").keyframe_points.sort() + # set revision + rev = pos_map_element.rev + 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 + + +""" +update position keyframes +""" + + +def add_single_pos_keyframe(id: MapID, pos_element: PosMapElement): + frame_start = pos_element.start + pos_status = pos_element.pos + for dancer_name, pos in pos_status.items(): + dancer_obj = bpy.data.objects[dancer_name] + dancer_location = (pos.x, pos.y, pos.z) + 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 + point = curves.find("ld_pos_frame").keyframe_points.insert(frame_start, frame_start) + point.interpolation = "CONSTANT" + # insert rev frame (meta & data) + rev = pos_element.rev + point = curves.find("ld_pos_meta").keyframe_points.insert( + frame_start, (rev.meta if rev else -1) + ) + point.interpolation = "CONSTANT" + point = curves.find("ld_pos_data").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): + old_pos_map = state.pos_map # pos_map before update + old_frame_start = old_pos_map[pos_id].start + new_frame_start = pos_element.start + new_pos_status = pos_element.pos + for dancer_name, pos in new_pos_status.items(): + dancer_obj = bpy.data.objects[dancer_name] + dancer_location = (pos.x, pos.y, pos.z) + curves = dancer_obj.animation_data.action.fcurves + for d in range(3): + curve_points = curves.find("location", index=d).keyframe_points + point = next(p for p in curve_points if p.co[0] == old_frame_start) + point.co = new_frame_start, dancer_location[d] + point.interpolation = "LINEAR" + curve_points.sort() + scene = bpy.context.scene + curves = scene.animation_data.action.fcurves + curve_points = curves.find("ld_pos_frame").keyframe_points + point = next(p for p in curve_points if p.co[0] == old_frame_start) + point.co = new_frame_start, new_frame_start + point.interpolation = "CONSTANT" + curve_points.sort() + # insert rev frame (meta & data) + rev = pos_element.rev + 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): + 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 = bpy.data.objects[dancer_name] + curves = dancer_obj.animation_data.action.fcurves + for d in range(3): + curve_points = curves.find("location", index=d).keyframe_points + point = next(p for p in curve_points if p.co[0] == old_frame_start) + curve_points.remove(point) + scene = bpy.context.scene + curves = scene.animation_data.action.fcurves + curve_points = curves.find("ld_pos_frame").keyframe_points + point = next(p for p in curve_points if p.co[0] == old_frame_start) + curve_points.remove(point) + # insert rev frame (meta & data) + curve_points = curves.find("ld_pos_meta").keyframe_points + point = next(p for p in curve_points if p.co[0] == old_frame_start) + curve_points.remove(point) + curve_points = curves.find("ld_pos_data").keyframe_points + point = next(p for p in curve_points if p.co[0] == old_frame_start) + curve_points.remove(point) + 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.remove(pos_rev_item) + except StopIteration: + pass + + +""" +update control keyframes +""" + + +def add_single_ctrl_keyframe(id: MapID, ctrl_element: ControlMapElement): + color_map = state.color_map + led_effect_table = state.led_effect_id_table + frame_start = ctrl_element.start + fade = ctrl_element.fade + ctrl_status = ctrl_element.status + for dancer_name, ctrl in ctrl_status.items(): + dancer_index = dancer_name.split("_")[0] + for part_name, part_data in ctrl.items(): + if isinstance(part_data, LEDData): + part_parent = bpy.data.objects[f"{dancer_index}.{part_name}.parent"] + if part_data.effect_id != -1: + part_effect = led_effect_table[part_data.effect_id].effect + for j, led_obj in enumerate(part_parent.children): + led_data = part_effect[j] + led_rgb = color_map[led_data.color_id].rgb + led_rgba = ( + (led_rgb[0] / 255) + * (led_data.alpha / 10), # TODO: change to 255 + (led_rgb[1] / 255) * (led_data.alpha / 10), + (led_rgb[2] / 255) * (led_data.alpha / 10), + 1, + ) + curves = led_obj.animation_data.action.fcurves + for d in range(4): + curve_points = curves.find("color", index=d).keyframe_points + point = curve_points.insert(frame_start, led_rgba[d]) + point.interpolation = "LINEAR" if fade else "CONSTANT" + curve_points.sort() + else: # no change + for j, led_obj in enumerate(part_parent.children): + curves = led_obj.animation_data.action.fcurves + for d in range(4): + curve_points = curves.find("color", index=d).keyframe_points + last_point = [ + p for p in curve_points if p.co[0] < frame_start + ][-1] + point = curve_points.insert(frame_start, last_point.co[1]) + point.interpolation = "LINEAR" if fade else "CONSTANT" + curve_points.sort() + + elif isinstance(part_data, FiberData): + part_obj = bpy.data.objects[f"{dancer_index}.{part_name}"] + part_rgb = color_map[part_data.color_id].rgb + part_rgba = ( + (part_rgb[0] / 255) * (part_data.alpha / 10), # TODO: change to 255 + (part_rgb[1] / 255) * (part_data.alpha / 10), + (part_rgb[2] / 255) * (part_data.alpha / 10), + 1, + ) + 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( + f"{dancer_index}.{part_name}Action" + ) + curves = part_obj.animation_data.action.fcurves + for d in range(4): + curve_points = curves.find("color", index=d).keyframe_points + point = curve_points.insert(frame_start, part_rgba[d]) + point.interpolation = "LINEAR" if fade else "CONSTANT" + curve_points.sort() + else: + print("Invalid part data") + # insert fake frame + scene = bpy.context.scene + curves = scene.animation_data.action.fcurves + curve_points = curves.find("ld_control_frame").keyframe_points + point = curve_points.insert(frame_start, frame_start) + # update new ld_control_frame + try: + new_next_point = next(p for p in curve_points if p.co[0] > frame_start) + new_next_fade_points = [ + p + for p in curve_points + if p.co[0] > frame_start and p.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 + curve_points.sort() + point.interpolation = "CONSTANT" + # insert rev frame (meta & data) + rev = ctrl_element.rev + 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 +): + 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 + for dancer_name, ctrl in new_ctrl_status.items(): + dancer_index = dancer_name.split("_")[0] + for part_name, part_data in ctrl.items(): + if isinstance(part_data, LEDData): + part_parent = bpy.data.objects[f"{dancer_index}.{part_name}.parent"] + if part_data.effect_id != -1: + part_effect = led_effect_table[part_data.effect_id].effect + for j, led_obj in enumerate(part_parent.children): + led_data = part_effect[j] + led_rgb = color_map[led_data.color_id].rgb + led_rgba = ( + (led_rgb[0] / 255) + * (led_data.alpha / 10), # TODO: change to 255 + (led_rgb[1] / 255) * (led_data.alpha / 10), + (led_rgb[2] / 255) * (led_data.alpha / 10), + 1, + ) + curves = led_obj.animation_data.action.fcurves + for d in range(4): + curve_points = curves.find("color", index=d).keyframe_points + point = next( + p for p in curve_points if p.co[0] == old_frame_start + ) + point.co = new_frame_start, led_rgba[d] + point.interpolation = "LINEAR" if new_fade else "CONSTANT" + curve_points.sort() + else: # no change + for j, led_obj in enumerate(part_parent.children): + curves = led_obj.animation_data.action.fcurves + for d in range(4): + curve_points = curves.find("color", index=d).keyframe_points + last_point = [ + p for p in curve_points if p.co[0] < old_frame_start + ][-1] + point = next( + p for p in curve_points if p.co[0] == old_frame_start + ) + point.co = new_frame_start, last_point.co[1] + point.interpolation = "LINEAR" if new_fade else "CONSTANT" + curve_points.sort() + + elif isinstance(part_data, FiberData): + part_obj = bpy.data.objects[f"{dancer_index}.{part_name}"] + part_rgb = color_map[part_data.color_id].rgb + part_rgba = ( + (part_rgb[0] / 255) * (part_data.alpha / 10), # TODO: change to 255 + (part_rgb[1] / 255) * (part_data.alpha / 10), + (part_rgb[2] / 255) * (part_data.alpha / 10), + 1, + ) + curves = part_obj.animation_data.action.fcurves + for d in range(4): + curve_points = curves.find("color", index=d).keyframe_points + point = next(p for p in curve_points if p.co[0] == old_frame_start) + point.co = new_frame_start, part_rgba[d] + point.interpolation = "LINEAR" if new_fade else "CONSTANT" + curve_points.sort() + else: + print("Invalid part data") + # update fake frame + scene = bpy.context.scene + curves = scene.animation_data.action.fcurves + curve_points = curves.find("ld_control_frame").keyframe_points + point = next(p for p in curve_points if p.co[0] == old_frame_start) + # update old ld_control_frame + try: + old_next_point = next(p for p in curve_points if p.co[0] > old_frame_start) + old_next_fade_points = [ + p + for p in curve_points + if p.co[0] > old_frame_start and p.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 curve_points if p.co[0] > new_frame_start) + new_next_fade_points = [ + p + for p in curve_points + if p.co[0] > new_frame_start and p.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" + curve_points.sort() + # insert rev frame (meta & data) + rev = ctrl_element.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 +): + 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 = dancer_name.split("_")[0] + for part_item in dancer_parts: + part_name = part_item.name + part_type = part_item.type + match part_type: + case PartType.LED: + part_parent = bpy.data.objects[f"{dancer_index}.{part_name}.parent"] + for led_obj in part_parent.children: + curves = led_obj.animation_data.action.fcurves + for d in range(4): + curve_points = curves.find("color", index=d).keyframe_points + point = next( + p for p in curve_points if p.co[0] == old_frame_start + ) + curve_points.remove(point) + + case PartType.FIBER: + part_obj = bpy.data.objects[f"{dancer_index}.{part_name}"] + curves = part_obj.animation_data.action.fcurves + for d in range(4): + curve_points = curves.find("color", index=d).keyframe_points + point = next( + p for p in curve_points if p.co[0] == old_frame_start + ) + curve_points.remove(point) + case _: + print("Invalid part data") + # insert fake frame + scene = bpy.context.scene + curves = scene.animation_data.action.fcurves + curve_points = curves.find("ld_control_frame").keyframe_points + point = next(p for p in curve_points if p.co[0] == old_frame_start) + # update old ld_control_frame + try: + old_next_point = next(p for p in curve_points if p.co[0] > old_frame_start) + old_next_fade_points = [ + p + for p in curve_points + if p.co[0] > old_frame_start and p.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 + curve_points.remove(point) + + 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.remove(ctrl_rev_item) + except StopIteration: + pass diff --git a/editor-blender/core/actions/property/lights.py b/editor-blender/core/actions/property/lights.py index e58ebf7c5..5d3fa01cd 100644 --- a/editor-blender/core/actions/property/lights.py +++ b/editor-blender/core/actions/property/lights.py @@ -13,12 +13,13 @@ def update_current_color(self: bpy.types.Object, context: bpy.types.Context): return color_id: int = self["ld_color"] + ld_alpha: int = self["ld_alpha"] 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] + 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 +77,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") - + color_id: int = self["ld_color"] + color = state.color_map[color_id] + color_float = rgb_to_float(color.rgb) 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 + led_bulb_obj.color[0] = color_float[0] * (ld_alpha / 255) + led_bulb_obj.color[1] = color_float[1] * (ld_alpha / 255) + led_bulb_obj.color[2] = color_float[2] * (ld_alpha / 255) else: - self.color[3] = ld_alpha / 255 + 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) diff --git a/editor-blender/core/actions/property/revision.py b/editor-blender/core/actions/property/revision.py new file mode 100644 index 000000000..d71740e39 --- /dev/null +++ b/editor-blender/core/actions/property/revision.py @@ -0,0 +1,65 @@ +from typing import List + +import bpy + +from ....properties.revision import KeyframeRevisionItem +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: List[KeyframeRevisionItem] = 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 + if local_id not in incoming_rev.keys(): # delete + delete_single_pos_keyframe(local_id, rev.frame_start) + del incoming_rev[local_id] + 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_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: List[KeyframeRevisionItem] = 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) + del incoming_rev[local_id] + 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_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..97bdee039 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 @@ -193,6 +195,7 @@ async def init_editor(): [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], ] @@ -355,6 +358,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..3e6790a8b 100644 --- a/editor-blender/core/actions/state/load.py +++ b/editor-blender/core/actions/state/load.py @@ -4,9 +4,15 @@ 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 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 target_path = os.path.join(asset_path, "LightDance") @@ -23,15 +29,21 @@ 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 = await client.download_json("/data/load.json") try: url_set = 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) @@ -40,63 +52,95 @@ async def fetch_data(reload: bool = False): 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, model_filepath, parts): + """ + set dancer collection asset + """ + bpy.ops.import_scene.gltf( + filepath=model_filepath + ) # here all parts of dancer is selected + model_parts = bpy.context.selected_objects + col = bpy.data.collections.new(model_name) + for obj in model_parts: + for old_col in obj.users_collection: + old_col.objects.unlink(obj) + col.objects.link(obj) + col.asset_mark() + for item in parts: + part_objects = [i for i in model_parts if i.name.find(item.name) >= 0] + if len(part_objects) == 0: + print("Dancer part not found (maybe should reload asset)") + if item.type.value == "LED": + for _, obj in enumerate(part_objects): + set_bpy_props(obj, data=bpy.data.meshes["Sphere.001"]) # type: ignore + bpy.ops.outliner.orphans_purge(do_recursive=True) + print(f"model {model_name} imported") + + +def setup_objects(assets_load): """ - clear all objects + clear all objects in viewport """ - # bpy.ops.object.select_all(action='DESELECT') - # bpy.ops.object.select_all() - # bpy.ops.object.delete() + for old_obj in bpy.data.objects: + if old_obj.visible_get() and not getattr(old_obj, "ld_dancer_name"): + bpy.data.objects.remove(old_obj) """ set dancer objects """ + if check_local_object_list(): + print("local objects detected") + return + else: + for old_obj in bpy.data.objects: + 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) + model_file = dancer_load["url"] + model_filepath = os.path.normpath(target_path + model_file) + model_name = dancer_load["modelName"] dancer_parent = bpy.data.objects.new(dancer_name, None) - bpy.context.object.empty_display_size = 0 + set_bpy_props( + dancer_parent, + ld_dancer_name=dancer.name, + empty_display_size=0, + ld_model_name=model_name, + ) + dancer_parent.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 + if model_name not in bpy.data.collections.keys(): + import_model_to_asset(model_name, model_filepath, dancer.parts) + dancer_asset = bpy.data.collections[model_name] + dancer_objects = [obj.copy() for obj in dancer_asset.all_objects] dancer_human = next(obj for obj in dancer_objects if obj.name[0:5] == "Human") + bpy.context.scene.collection.objects.link(dancer_human) # type: ignore set_bpy_props( - dancer_human, - name=f"{dancer.name}.Human", + dancer_human, # type: ignore + name=f"{dancer_index}.Human", parent=dancer_parent, ld_object_type=ObjectType.HUMAN.value, color=(0, 0, 0, 1), data=bpy.data.meshes["human"], + 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] @@ -104,38 +148,50 @@ def setup_assets(assets_load): 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 + f"{dancer_index}.{item.name}.parent", None ) bpy.context.scene.collection.objects.link(parts_parent) set_bpy_props( parts_parent, parent=dancer_parent, ld_object_type=ObjectType.LIGHT.value, - ld_light_type=item.type.value.lower(), + ld_light_type=LightType.LED.value, ld_part_name=item.name, + empty_display_size=0, + 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}" + for i, obj in enumerate(part_objects): + bpy.context.scene.collection.objects.link(obj) # type: ignore + # obj.name = f"{dancer_index}.{item.name}.{i:03}" set_bpy_props( - obj, + obj, # type: ignore + name=f"{dancer_index}.{item.name}.{i:03}", parent=parts_parent, ld_object_type=ObjectType.LIGHT.value, - ld_light_type=item.type.value.lower(), + ld_light_type=LightType.LED_BULB.value, ld_part_name=item.name, data=bpy.data.meshes["Sphere.001"], + ld_led_pos=i, + ld_dancer_name=dancer.name, + ld_model_name=model_name, ) 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(obj) # type: ignore + set_bpy_props( + obj, # type: ignore + name=f"{dancer_index}.{item.name}", + parent=dancer_parent, + ld_object_type=ObjectType.LIGHT.value, + ld_light_type=LightType.FIBER.value, + ld_part_name=item.name, + ld_dancer_name=dancer.name, + ld_model_name=model_name, + ) -def set_music_from_load(assets_load): +def setup_music(assets_load): """ set music """ @@ -143,158 +199,13 @@ 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: + scene.sequence_editor.sequences.remove(scene.sequence_editor.sequences[0]) 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 @@ -310,6 +221,7 @@ def setup_viewport(): 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 @@ -317,14 +229,52 @@ def setup_viewport(): 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 + 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(): + 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 + match part_type: + case PartType.LED: + if ( + f"{dancer_index}.{part_name}.parent" + not in bpy.data.objects.keys() + ): + return False + part_parent = bpy.data.objects[f"{dancer_index}.{part_name}.parent"] + + if len(part_parent.children) != part_item.length: + return False + case PartType.FIBER: + if f"{dancer_index}.{part_name}" not in bpy.data.objects.keys(): + return False + case _: + 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") 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/properties/__init__.py b/editor-blender/properties/__init__.py index 0daf6a724..a5229d598 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.register() diff --git a/editor-blender/properties/objects.py b/editor-blender/properties/objects.py index 4cd66d760..dc8d21d73 100644 --- a/editor-blender/properties/objects.py +++ b/editor-blender/properties/objects.py @@ -21,6 +21,7 @@ def register(): setattr(bpy.types.Object, "ld_model_name", bpy.props.StringProperty()) setattr(bpy.types.Object, "ld_dancer_name", bpy.props.StringProperty()) setattr(bpy.types.Object, "ld_part_name", bpy.props.StringProperty()) + setattr(bpy.types.Object, "ld_model_name", bpy.props.StringProperty()) def unregister(): @@ -28,3 +29,4 @@ def unregister(): delattr(bpy.types.Object, "ld_model_name") delattr(bpy.types.Object, "ld_dancer_name") delattr(bpy.types.Object, "ld_part_name") + delattr(bpy.types.Object, "ld_model_name") \ No newline at end of file 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]