Exemple #1
0
    def connect(self):
        if self.is_connected():
            raise RuntimeError("Client.connect : already connected")

        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket = Socket(sock)
            self.socket.connect((self.host, self.port))
            local_address = self.socket.getsockname()
            logger.info(
                "Connecting from local %s:%s to %s:%s",
                local_address[0],
                local_address[1],
                self.host,
                self.port,
            )
            self.send_command(common.Command(common.MessageType.CLIENT_ID))
            self.send_command(common.Command(common.MessageType.LIST_CLIENTS))
            self.send_command(common.Command(common.MessageType.LIST_ROOMS))
        except ConnectionRefusedError:
            self.socket = None
        except common.ClientDisconnectedException:
            self.handle_connection_lost()
        except Exception as e:
            logger.error("Connection error %s", e, exc_info=True)
            self.socket = None
            raise
Exemple #2
0
    def __init__(self, server: Server, room_name: str, creator: Connection):
        self.name = room_name
        self.keep_open = False  # Should the room remain open when no more clients are inside ?
        self.byte_size = 0
        self.joinable = False  # A room becomes joinable when its first client has send all the initial content

        self.custom_attributes: Dict[str, Any] = {
        }  # custom attributes are used between clients, but not by the server

        self._commands: List[common.Command] = []

        self._commands_mutex: threading.RLock = threading.RLock()
        self._connections: List[Connection] = [creator]

        self.join_count: int = 0
        # this is used to ensure a room cannot be deleted while clients are joining (creator is not considered to be joining)
        # Server is responsible of increasing / decreasing join_count, with mutex protection

        creator.room = self
        creator.send_command(
            common.Command(common.MessageType.JOIN_ROOM,
                           common.encode_string(self.name)))
        creator.send_command(
            common.Command(common.MessageType.CONTENT)
        )  # self.joinable will be set to true by creator later
Exemple #3
0
 def send_animation_buffer(self, obj_name, animation_data, channel_name, channel_index=-1):
     if not animation_data:
         return
     action = animation_data.action
     if not action:
         buffer = (
             common.encode_string(obj_name)
             + common.encode_string(channel_name)
             + common.encode_int(channel_index)
             + common.int_to_bytes(0, 4)  # send empty buffer
         )
         self.add_command(common.Command(MessageType.ANIMATION, buffer, 0))
         return
     for fcurve in action.fcurves:
         if fcurve.data_path == channel_name:
             if channel_index == -1 or fcurve.array_index == channel_index:
                 key_count = len(fcurve.keyframe_points)
                 times = []
                 values = []
                 for keyframe in fcurve.keyframe_points:
                     times.append(int(keyframe.co[0]))
                     values.append(keyframe.co[1])
                 buffer = (
                     common.encode_string(obj_name)
                     + common.encode_string(channel_name)
                     + common.encode_int(channel_index)
                     + common.int_to_bytes(key_count, 4)
                     + struct.pack(f"{len(times)}i", *times)
                     + struct.pack(f"{len(values)}f", *values)
                 )
                 self.add_command(common.Command(MessageType.ANIMATION, buffer, 0))
                 return
Exemple #4
0
    def broadcast_client_update(self, connection: Connection, attributes: Dict[str, Any]):
        if attributes == {}:
            return

        self.broadcast_to_all_clients(
            common.Command(common.MessageType.CLIENT_UPDATE, common.encode_json({connection.unique_id: attributes}))
        )
Exemple #5
0
 def send_material(self, material):
     if not material:
         return
     if material.grease_pencil:
         grease_pencil_api.send_grease_pencil_material(self, material)
     else:
         self.add_command(common.Command(MessageType.MATERIAL, material_api.get_material_buffer(self, material), 0))
Exemple #6
0
def send_collection_instance(client: Client, obj):
    if not obj.instance_collection:
        return
    instance_name = obj.name_full
    instanciated_collection = obj.instance_collection.name_full
    buffer = common.encode_string(instance_name) + common.encode_string(instanciated_collection)
    client.add_command(common.Command(common.MessageType.INSTANCE_COLLECTION, buffer, 0))
Exemple #7
0
 def send_camera_attributes(self, obj):
     buffer = (common.encode_string(obj.name_full) +
               common.encode_float(obj.data.lens) +
               common.encode_float(obj.data.dof.aperture_fstop) +
               common.encode_float(obj.data.dof.focus_distance))
     self.add_command(
         common.Command(MessageType.CAMERA_ATTRIBUTES, buffer, 0))
Exemple #8
0
 def send_group_begin(self):
     # The integer sent is for future use: the server might fill it with the group size once all messages
     # have been received, and give the opportunity to future clients to know how many messages they need to process
     # in the group (en probably show a progress bar to their user if their is a lot of message, e.g. initial scene
     # creation)
     self.add_command(
         common.Command(MessageType.GROUP_BEGIN, common.encode_int(0)))
Exemple #9
0
 def send_texture_data(self, path, data):
     name_buffer = common.encode_string(path)
     self.textures.add(path)
     self.add_command(
         common.Command(MessageType.TEXTURE,
                        name_buffer + common.encode_int(len(data)) + data,
                        0))
Exemple #10
0
    def send_mesh(self, obj):
        logger.info("send_mesh %s", obj.name_full)
        mesh = obj.data
        mesh_name = self.get_mesh_name(mesh)
        path = self.get_object_path(obj)

        binary_buffer = common.encode_string(path) + common.encode_string(mesh_name)

        binary_buffer += mesh_api.encode_mesh(
            obj, get_mixer_prefs().send_base_meshes, get_mixer_prefs().send_baked_meshes
        )

        # For now include material slots in the same message, but maybe it should be a separated message
        # like Transform
        material_link_dict = {"OBJECT": 0, "DATA": 1}
        material_links = [material_link_dict[slot.link] for slot in obj.material_slots]
        assert len(material_links) == len(obj.data.materials)
        binary_buffer += struct.pack(f"{len(material_links)}I", *material_links)

        for slot in obj.material_slots:
            if slot.link == "DATA":
                binary_buffer += common.encode_string("")
            else:
                binary_buffer += common.encode_string(slot.material.name if slot.material is not None else "")

        self.add_command(common.Command(MessageType.MESH, binary_buffer, 0))
Exemple #11
0
def send_add_object_to_collection(client: Client, collection_name, obj_name):
    logger.info("send_add_object_to_collection %s <- %s", collection_name,
                obj_name)
    buffer = common.encode_string(collection_name) + common.encode_string(
        obj_name)
    client.add_command(
        common.Command(common.MessageType.ADD_OBJECT_TO_COLLECTION, buffer, 0))
Exemple #12
0
 def send_light_attributes(self, obj):
     buffer = (
         common.encode_string(obj.name_full)
         + common.encode_float(obj.data.energy)
         + common.encode_color(obj.data.color)
     )
     self.add_command(common.Command(MessageType.LIGHT_ATTRIBUTES, buffer, 0))
Exemple #13
0
 def _leave_room(command: common.Command):
     if self.room is None:
         _send_error("Received leave_room but no room is joined")
         return
     _ = command.data.decode()  # todo remove room_name from protocol
     self._server.leave_room(self)
     self.send_command(common.Command(common.MessageType.LEAVE_ROOM))
Exemple #14
0
    def add_client(self, connection: Connection):
        logger.info(f"Add Client {connection.unique_id} to Room {self.name}")

        connection.send_command(common.Command(common.MessageType.CLEAR_CONTENT))  # todo temporary size stored here

        offset = 0

        def _try_finish_sync():
            connection.fetch_outgoing_commands()
            with self._commands_mutex:
                # from here no one can add commands anymore to self._commands (clients can still join and read previous commands)
                command_count = self.command_count()
                if command_count - offset > MAX_BROADCAST_COMMAND_COUNT:
                    return False  # while still more than MAX_BROADCAST_COMMAND_COUNT commands to broadcast, release the mutex

                # now is time to synchronize all room participants: broadcast remaining commands to new client
                for i in range(offset, command_count):
                    command = self._commands[i]
                    connection.add_command(command)

                # now he's part of the room, let him/her know
                self._connections.append(connection)
                connection.room = self
                connection.add_command(common.Command(common.MessageType.JOIN_ROOM, common.encode_string(self.name)))
                return True

        while True:
            if _try_finish_sync():
                break  # all done
            # broadcast commands that were added since last check
            command_count = self.command_count()
            for i in range(offset, command_count):
                command = self._commands[i]  # atomic wrt. the GIL
                connection.add_command(command)
            offset = command_count
Exemple #15
0
def send_data_updates(updates: List[BpyIDProxy]):
    if not share_data.use_experimental_sync():
        return
    if not updates:
        return
    codec = Codec()
    for proxy in updates:
        # We send an ID, so we need to make sure that it includes a bp.data collection name
        # and the associated key
        try:
            collection_name, key = blenddata_path(proxy)
        except InvalidPath:
            logger.error("... update ignored")
            continue

        logger.info("send_data_update %s[%s]", collection_name, key)

        try:
            encoded_proxy = codec.encode(proxy)
        except InvalidPath:
            logger.error("send_update: Exception :")
            log_traceback(logger.error)
            logger.error(
                f"while processing bpy.data.{collection_name}[{key}]:")

        # For BpyIdProxy, the target is encoded in the proxy._blenddata_path
        buffer = common.encode_string(encoded_proxy)
        command = common.Command(common.MessageType.BLENDER_DATA_UPDATE,
                                 buffer, 0)
        share_data.client.add_command(command)
Exemple #16
0
    def set_client_attributes(self, attributes: dict):
        diff = update_attributes_and_get_diff(self.current_custom_attributes, attributes)
        if diff == {}:
            return True

        return self.send_command(
            common.Command(common.MessageType.SET_CLIENT_CUSTOM_ATTRIBUTES, common.encode_json(diff), 0)
        )
Exemple #17
0
 def get_list_rooms_command(self) -> common.Command:
     with self._mutex:
         result_dict = {
             room_name: value.attributes_dict()
             for room_name, value in self._rooms.items()
         }
         return common.Command(common.MessageType.LIST_ROOMS,
                               common.encode_json(result_dict))
Exemple #18
0
 def get_list_clients_command(self) -> common.Command:
     with self._mutex:
         result_dict = {
             cid: c.client_attributes()
             for cid, c in self._connections.items()
         }
         return common.Command(common.MessageType.LIST_CLIENTS,
                               common.encode_json(result_dict))
Exemple #19
0
def send_remove_object_from_scene(client: Client, scene_name: str,
                                  object_name: str):
    logger.info("send_remove_object_from_scene %s <- %s", scene_name,
                object_name)
    buffer = common.encode_string(scene_name) + common.encode_string(
        object_name)
    client.add_command(
        common.Command(common.MessageType.REMOVE_OBJECT_FROM_SCENE, buffer, 0))
Exemple #20
0
def send_add_collection_to_scene(client: Client, scene_name: str,
                                 collection_name: str):
    logger.info("send_add_collection_to_scene %s <- %s", scene_name,
                collection_name)

    buffer = common.encode_string(scene_name) + common.encode_string(
        collection_name)
    client.add_command(
        common.Command(common.MessageType.ADD_COLLECTION_TO_SCENE, buffer, 0))
Exemple #21
0
    def broadcast_room_update(self, room: Room, attributes: Dict[str, Any]):
        if attributes == {}:
            return

        self.broadcast_to_all_clients(
            common.Command(
                common.MessageType.ROOM_UPDATE,
                common.encode_json({room.name: attributes}),
            ))
Exemple #22
0
def send_remove_object_from_collection(client: Client, collection_name,
                                       obj_name):
    logger.info("send_remove_object_from_collection %s <- %s", collection_name,
                obj_name)
    buffer = common.encode_string(collection_name) + common.encode_string(
        obj_name)
    client.add_command(
        common.Command(common.MessageType.REMOVE_OBJECT_FROM_COLLECTION,
                       buffer, 0))
Exemple #23
0
def send_object_visibility(client: Client, object_: bpy.types.Object):
    logger.debug("send_object_visibility %s", object_.name_full)
    buffer = (common.encode_string(object_.name_full) +
              common.encode_bool(object_.hide_viewport) +
              common.encode_bool(object_.hide_select) +
              common.encode_bool(object_.hide_render) +
              common.encode_bool(object_.hide_get()))
    client.add_command(
        common.Command(common.MessageType.OBJECT_VISIBILITY, buffer, 0))
Exemple #24
0
def send_frame():
    sm_props = get_shot_manager()
    if sm_props is None:
        return
    current_shot_index = shot_manager.get_current_shot_index(sm_props)

    if share_data.shot_manager.current_shot_index != current_shot_index:
        share_data.shot_manager.current_shot_index = current_shot_index
        buffer = common.encode_int(share_data.shot_manager.current_shot_index)
        share_data.client.add_command(common.Command(common.MessageType.SHOT_MANAGER_CURRENT_SHOT, buffer, 0))
Exemple #25
0
def send_data_removals(removals: RemovalChangeset):
    if not share_data.use_experimental_sync():
        return

    for uuid, debug_info in removals:
        logger.info("send_removal: %s (%s)", uuid, debug_info)
        buffer = common.encode_string(uuid) + common.encode_string(debug_info)
        command = common.Command(common.MessageType.BLENDER_DATA_REMOVE,
                                 buffer, 0)
        share_data.client.add_command(command)
Exemple #26
0
def send_scene():
    get_state()
    buffer = common.encode_int(len(share_data.shot_manager.shots))
    for s in share_data.shot_manager.shots:
        buffer += (common.encode_string(s.name) +
                   common.encode_string(s.camera_name) +
                   common.encode_int(s.start) + common.encode_int(s.end) +
                   common.encode_bool(s.enabled))
    share_data.client.add_command(
        common.Command(common.MessageType.SHOT_MANAGER_CONTENT, buffer, 0))
Exemple #27
0
def send_remove_collection_from_scene(client: Client, scene_name: str,
                                      collection_name: str):
    logger.info("send_remove_collection_from_scene %s <- %s", scene_name,
                collection_name)

    buffer = common.encode_string(scene_name) + common.encode_string(
        collection_name)
    client.add_command(
        common.Command(common.MessageType.REMOVE_COLLECTION_FROM_SCENE, buffer,
                       0))
Exemple #28
0
def send_data_renames(renames: RenameChangeset):
    if not share_data.use_experimental_sync():
        return

    for uuid, new_name, debug_info in renames:
        logger.info("send_rename: %s (%s) into %s", uuid, debug_info, new_name)
        buffer = common.encode_string(uuid) + common.encode_string(
            new_name) + common.encode_string(debug_info)
        command = common.Command(common.MessageType.BLENDER_DATA_RENAME,
                                 buffer, 0)
        share_data.client.add_command(command)
Exemple #29
0
def send_data_removals(removals: List[Tuple[str, str]]):
    if not share_data.use_experimental_sync():
        return

    for collection_name, key in removals:
        logger.info("send_removal: %s[%s]", collection_name, key)
        buffer = common.encode_string(collection_name) + common.encode_string(
            key)
        command = common.Command(common.MessageType.BLENDER_DATA_REMOVE,
                                 buffer, 0)
        share_data.client.add_command(command)
Exemple #30
0
 def add_command(self, command: common.Command):
     # A wrapped message is a message emitted from a frame change event.
     # Right now we wrap this kind of messages adding the client_id.
     # In the future we will probably always add the client_id to all messages. But the difference
     # between synced time messages and the other must remain.
     if self.synced_time_messages:
         command = common.Command(
             MessageType.CLIENT_ID_WRAPPER,
             common.encode_string(self.client_id) + common.encode_int(command.type.value) + command.data,
             0,
         )
     super().add_command(command)