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))
def get_camera_buffer(obj): cam = obj.data focal = cam.lens front_clip_plane = cam.clip_start far_clip_plane = cam.clip_end aperture = cam.dof.aperture_fstop sensor_fit_name = cam.sensor_fit sensor_fit = common.SensorFitMode.AUTO if sensor_fit_name == "AUTO": sensor_fit = common.SensorFitMode.AUTO elif sensor_fit_name == "HORIZONTAL": sensor_fit = common.SensorFitMode.HORIZONTAL elif sensor_fit_name == "VERTICAL": sensor_fit = common.SensorFitMode.VERTICAL sensor_width = cam.sensor_width sensor_height = cam.sensor_height path = get_object_path(obj) return ( common.encode_string(path) + common.encode_string(obj.name_full) + common.encode_float(focal) + common.encode_float(front_clip_plane) + common.encode_float(far_clip_plane) + common.encode_float(aperture) + common.encode_int(sensor_fit.value) + common.encode_float(sensor_width) + common.encode_float(sensor_height) )
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))
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: 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.CAMERA_ANIMATION, buffer, 0)) return
def soa_buffers(datablock_proxy: Optional[DatablockProxy]) -> List[bytes]: if datablock_proxy is None: # empty update, should not happen return [encode_int(0)] # Layout is # number of AosProxy: 2 # soa path in datablock : ("vertices") # number of SoaElement : 2 # element name: "co" # array # element name: "normals" # array # soa path in datablock : ("edges") # number of SoaElement : 1 # element name: "vertices" # array items: List[bytes] = [] items.append(encode_int(len(datablock_proxy._soas))) for path, soa_proxies in datablock_proxy._soas.items(): path_string = json.dumps(path) items.append(encode_string(path_string)) items.append(encode_int(len(soa_proxies))) for element_name, soa_element in soa_proxies: if soa_element._array is not None: items.append(encode_string(element_name)) items.append(encode_py_array(soa_element._array)) return items
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))
def get_light_buffer(obj): light = obj.data light_type_name = light.type light_type = common.LightType.SUN if light_type_name == "POINT": light_type = common.LightType.POINT elif light_type_name == "SPOT": light_type = common.LightType.SPOT elif light_type_name == "SUN": light_type = common.LightType.SUN elif light_type_name == "AREA": light_type = common.LightType.AREA else: return None color = light.color power = light.energy if bpy.context.scene.render.engine == "CYCLES": shadow = light.cycles.cast_shadow else: shadow = light.use_shadow spot_blend = 10.0 spot_size = 0.0 if light_type == common.LightType.SPOT: spot_size = light.spot_size spot_blend = light.spot_blend return (common.encode_string(get_object_path(obj)) + common.encode_string(light.name_full) + common.encode_int(light_type.value) + common.encode_int(shadow) + common.encode_color(color) + common.encode_float(power) + common.encode_float(spot_size) + common.encode_float(spot_blend))
def get_camera_buffer(obj): cam = obj.data focal = cam.lens front_clip_plane = cam.clip_start far_clip_plane = cam.clip_end dof_enabled = cam.dof.use_dof aperture = cam.dof.aperture_fstop colimator_name = cam.dof.focus_object.name_full if cam.dof.focus_object is not None else "" sensor_fit_name = cam.sensor_fit sensor_fit = common.SensorFitMode.AUTO if sensor_fit_name == "AUTO": sensor_fit = common.SensorFitMode.AUTO elif sensor_fit_name == "HORIZONTAL": sensor_fit = common.SensorFitMode.HORIZONTAL elif sensor_fit_name == "VERTICAL": sensor_fit = common.SensorFitMode.VERTICAL sensor_width = cam.sensor_width sensor_height = cam.sensor_height path = get_object_path(obj) return (common.encode_string(path) + common.encode_string(obj.name_full) + common.encode_float(focal) + common.encode_float(front_clip_plane) + common.encode_float(far_clip_plane) + common.encode_bool(dof_enabled) + common.encode_float(aperture) + common.encode_string(colimator_name) + common.encode_int(sensor_fit.value) + common.encode_float(sensor_width) + common.encode_float(sensor_height))
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))
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))
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 = encode_string(uuid) + encode_string(debug_info) command = Command(MessageType.BLENDER_DATA_REMOVE, buffer, 0) share_data.client.add_command(command)
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))
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))
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))
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)
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)
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))
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)
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))
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
def grab(self, host, port, room_name: str): with Client(host, port) as client: client.join_room(room_name) attempts_max = 20 attempts = 0 try: while attempts < attempts_max: received_commands = client.fetch_incoming_commands() attempts += 1 time.sleep(0.01) for command in received_commands: attempts = 0 if command.type <= MessageType.COMMAND: continue # Ignore command serial Id, that may not match command.id = 0 self.streams.data[command.type].append(command.data) except ClientDisconnectedException: print("Grabber: disconnected before received command stream.", file=sys.stderr) client.send_command( Command(MessageType.SET_ROOM_KEEP_OPEN, encode_string(room_name) + encode_bool(False))) client.send_command( Command(MessageType.LEAVE_ROOM, room_name.encode("utf8"))) if not client.wait(MessageType.LEAVE_ROOM): print("Grabber: disconnected before receiving LEAVE_ROOM.", file=sys.stderr)
def send_grease_pencil_layer(layer, name): buffer = common.encode_string(name) buffer += common.encode_bool(layer.hide) buffer += common.encode_int(len(layer.frames)) for frame in layer.frames: buffer += send_grease_pencil_frame(frame) return buffer
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))
def join_room( self, room_name: str, blender_version: str, mixer_version: str, ignore_version_check: bool, generic_protocol: bool, ): name = common.encode_string(room_name) bl_version = common.encode_string(blender_version) mix_version = common.encode_string(mixer_version) version_check = common.encode_bool(ignore_version_check) protocol = common.encode_bool(generic_protocol) return self.send_command( common.Command( common.MessageType.JOIN_ROOM, name + bl_version + mix_version + version_check + protocol, 0))
def get_transform_buffer(self, obj): path = self.get_object_path(obj) return ( common.encode_string(path) + common.encode_matrix(obj.matrix_parent_inverse) + common.encode_matrix(obj.matrix_basis) + common.encode_matrix(obj.matrix_local) )
def encode(datablock_proxy: DatablockProxy) -> bytes: media_desc = getattr(datablock_proxy, "_media", None) if media_desc is None: return b"" path, bytes_ = media_desc items = [encode_string(path), bytes_] return b"".join(items)
def send_media_creations(proxy: DatablockProxy): media_desc = getattr(proxy, "_media", None) if media_desc is None: return path, bytes_ = media_desc items = [encode_string(path), bytes_] command = Command(MessageType.BLENDER_DATA_MEDIA, b"".join(items), 0) share_data.client.add_command(command)
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))
def send_grease_pencil_mesh(client: Client, obj): grease_pencil = obj.data buffer = common.encode_string(grease_pencil.name_full) buffer += common.encode_int(len(grease_pencil.materials)) for material in grease_pencil.materials: if not material: material_name = "Default" else: material_name = material.name_full buffer += common.encode_string(material_name) buffer += common.encode_int(len(grease_pencil.layers)) for name, layer in grease_pencil.layers.items(): buffer += send_grease_pencil_layer(layer, name) client.add_command(common.Command(common.MessageType.GREASE_PENCIL_MESH, buffer, 0)) send_grease_pencil_time_offset(client, obj)
def encode_arrays(datablock_proxy: DatablockProxy) -> List[bytes]: if not hasattr(datablock_proxy, "_arrays"): return [encode_int(0)] items = [] items.append(encode_int(len(datablock_proxy._arrays))) for array_group_name, arrays in datablock_proxy._arrays.items(): # for vertex groups, _arrays layout is # { "vertex_groups: [ # ([0, "i"], indices_array_of_vertex_group_0), # ([0, "w"], weights_array_of_vertex_group_0), # ... # ]} items.append(encode_string(array_group_name)) items.append(encode_int(len(arrays))) for key, array_ in arrays: key_string = json.dumps(key) items.append(encode_string(key_string)) items.append(encode_py_array(array_)) return items