def network_consumer_timer(): if not share_data.client.is_connected(): error_msg = "Timer still registered but client disconnected." logger.error(error_msg) if get_mixer_prefs().env != "production": raise RuntimeError(error_msg) # Returning None from a timer unregister it return None # Encapsulate call to share_data.client.network_consumer because # if we register it directly, then bpy.app.timers.is_registered(share_data.client.network_consumer) # return False... # However, with a simple function bpy.app.timers.is_registered works. try: share_data.client.network_consumer() except (ClientDisconnectedException, SendSceneContentFailed) as e: logger.warning(e) share_data.client = None disconnect() return None except Exception as e: logger.error(e, stack_info=True) if get_mixer_prefs().env == "development": raise # Run every 1 / 100 seconds return 0.01
def execute(self, context): if sys.platform == "win32": os.startfile(get_mixer_prefs().statistics_directory) else: opener = "open" if sys.platform == "darwin" else "xdg-open" subprocess.call([opener, get_mixer_prefs().statistics_directory]) return {"FINISHED"}
def poll_functors(cls, context): return [ poll_is_client_connected, poll_already_in_a_room, (lambda: get_mixer_prefs().room != "", "Room name cannot be empty"), (lambda: get_mixer_prefs().room not in share_data.client.rooms_attributes, "Room already exists"), ]
def join_room(room_name: str): logger.info("join_room") assert share_data.client.current_room is None BlendData.instance().reset() share_data.session_id += 1 # todo tech debt -> current_room should be set when JOIN_ROOM is received # todo _joining_room_name should be set in client timer share_data.client.current_room = room_name share_data.client._joining_room_name = room_name set_client_attributes() share_data.client.join_room(room_name) share_data.client.send_set_current_scene(bpy.context.scene.name_full) share_data.current_statistics = { "session_id": share_data.session_id, "blendfile": bpy.data.filepath, "statsfile": get_stats_filename(share_data.run_id, share_data.session_id), "user": get_mixer_prefs().user, "room": room_name, "children": {}, } prefs = get_mixer_prefs() share_data.auto_save_statistics = prefs.auto_save_statistics share_data.statistics_directory = prefs.statistics_directory share_data.set_experimental_sync(prefs.experimental_sync) share_data.pending_test_update = False # join a room <==> want to track local changes HandlerManager.set_handlers(True)
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 execute(self, context): assert share_data.client.current_room is None if not is_client_connected(): return {"CANCELLED"} join_room(get_mixer_prefs().room, get_mixer_prefs().experimental_sync) return {"FINISHED"}
def execute(self, context): path = self.filepath if not Path(path).is_dir(): path = str(Path(path).parent) for item in get_mixer_prefs().shared_folders: if item.shared_folder == path: return {"FINISHED"} item = get_mixer_prefs().shared_folders.add() item.shared_folder = path return {"FINISHED"}
def connect(): prefs = get_mixer_prefs() logger.info(f"connect to {prefs.host}:{prefs.port}") if share_data.client is not None: # a server shutdown was not processed logger.debug("connect: share_data.client is not None") share_data.client = None if not create_main_client(prefs.host, prefs.port): if is_localhost(prefs.host): if prefs.no_start_server: raise RuntimeError( f"Cannot connect to existing server at {prefs.host}:{prefs.port} and MIXER_NO_START_SERVER environment variable exists" ) start_local_server() if not wait_for_server(prefs.host, prefs.port): raise RuntimeError("Unable to start local server") else: raise RuntimeError( f"Unable to connect to remote server {prefs.host}:{prefs.port}" ) assert is_client_connected() set_client_attributes() HandlerManager._set_connection_handler(True)
def draw(self, context): layout = self.layout mixer_prefs = get_mixer_prefs() layout.prop( mixer_prefs, "VRtist", text="Path", icon=("ERROR" if not os.path.exists(mixer_prefs.VRtist) else "NONE") ) layout.operator(bl_operators.LaunchVRtistOperator.bl_idname, text="Launch VRTist")
def poll(cls, context): return ( os.path.isfile(get_mixer_prefs().VRtist) and is_client_connected() and share_data.client.current_room is not None and share_data.client.client_id is not None )
def execute(self, context): bpy.data.window_managers["WinMan"].mixer.send_base_meshes = False mixer_prefs = get_mixer_prefs() if not share_data.client.current_room: try: connect() except Exception as e: self.report({"ERROR"}, f"vrtist.launch connect error : {e}") return {"CANCELLED"} join_room(mixer_prefs.room, mixer_prefs.experimental) args = [ mixer_prefs.VRtist, "--room", share_data.client.current_room, "--hostname", mixer_prefs.host, "--port", str(mixer_prefs.port), "--master", str(share_data.client.client_id), ] subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) return {"FINISHED"}
def draw_advanced_settings_ui(layout: bpy.types.UILayout): mixer_prefs = get_mixer_prefs() layout.prop(mixer_prefs, "data_directory", text="Data Directory") layout.prop(mixer_prefs, "ignore_version_check") layout.prop(mixer_prefs, "log_level") layout.prop(mixer_prefs, "show_server_console") layout.prop(mixer_prefs, "vrtist_protocol")
def connect(): logger.info("connect") BlendData.instance().reset() if share_data.client is not None: # a server shutdown was not processed logger.debug("connect: share_data.client is not None") share_data.client = None prefs = get_mixer_prefs() if not create_main_client(prefs.host, prefs.port): if is_localhost(prefs.host): start_local_server() if not wait_for_server(prefs.host, prefs.port): logger.error("Unable to start local server") return False else: logger.error("Unable to connect to remote server %s:%s", prefs.host, prefs.port) return False assert is_client_connected() set_client_attributes() return True
def send_scene_content(): """ Initial data send to fill a new room. """ from mixer.handlers import HandlerManager, send_scene_data_to_server if get_mixer_prefs().no_send_scene_content: return with HandlerManager(False): # mesh baking may trigger depsgraph_updatewhen more than one view layer and # cause to reenter send_scene_data_to_server() and send duplicate messages share_data.clear_before_state() share_data.init_proxy() share_data.client.send_group_begin() # Temporary waiting for material sync. Should move to send_scene_data_to_server for material in bpy.data.materials: share_data.client.send_material(material) send_scene_data_to_server(None, None) shot_manager.send_scene() share_data.client.send_frame_start_end(bpy.context.scene.frame_start, bpy.context.scene.frame_end) share_data.start_frame = bpy.context.scene.frame_start share_data.end_frame = bpy.context.scene.frame_end share_data.client.send_frame(bpy.context.scene.frame_current) share_data.client.send_group_end()
def draw_developer_settings_ui(layout: bpy.types.UILayout): mixer_prefs = get_mixer_prefs() layout.prop(mixer_prefs, "statistics_directory", text="Stats Directory") layout.operator(bl_operators.OpenStatsDirOperator.bl_idname, text="Open Directory") layout.operator(bl_operators.WriteStatisticsOperator.bl_idname, text="Write Statistics") layout.prop(mixer_prefs, "auto_save_statistics", text="Auto Save Statistics") layout.prop(mixer_prefs, "no_send_scene_content", text="No send_scene_content") layout.prop(mixer_prefs, "no_start_server", text="Do not start server on connect") layout.prop(mixer_prefs, "send_base_meshes", text="Send Base Meshes") layout.prop(mixer_prefs, "send_baked_meshes", text="Send Baked Meshes") layout.prop(mixer_prefs, "commands_send_interval") box = layout.box().column() box.label(text="Gizmos") box.prop(mixer_prefs, "display_own_gizmos") box.prop(mixer_prefs, "display_frustums_gizmos") box.prop(mixer_prefs, "display_names_gizmos") box.prop(mixer_prefs, "display_ids_gizmos") box.prop(mixer_prefs, "display_selections_gizmos")
def draw(self, context): layout = self.layout.column() mixer_prefs = get_mixer_prefs() draw_user_settings_ui(layout.row()) if not self.connected(): draw_connection_settings_ui(layout.row()) layout.operator(bl_operators.ConnectOperator.bl_idname, text="Connect") else: layout.label( text=f"Connected to {mixer_prefs.host}:{mixer_prefs.port} with ID {share_data.client.client_id}" ) layout.operator(bl_operators.DisconnectOperator.bl_idname, text="Disconnect") if not share_data.client.current_room: split = layout.split(factor=0.6) split.prop(mixer_prefs, "room", text="Room") split.operator(bl_operators.CreateRoomOperator.bl_idname) else: split = layout.split(factor=0.6) split.label(text=f"Room: {share_data.client.current_room}") split.label(text=f"Join: {get_mixer_props().joining_percentage * 100:.2f} %") split.operator(bl_operators.LeaveRoomOperator.bl_idname, text="Leave Room") self.draw_rooms(layout) self.draw_users(layout) self.draw_advanced_options(layout) self.draw_developer_options(layout)
def set_client_attributes(): prefs = get_mixer_prefs() username = prefs.user usercolor = prefs.color share_data.client.set_client_attributes( {ClientAttributes.USERNAME: username, ClientAttributes.USERCOLOR: list(usercolor)} )
def join_room(room_name: str, vrtist_protocol: bool = False, shared_folders=None, ignore_version_check: bool = False): prefs = get_mixer_prefs() logger.warning(f"join: room: {room_name}, user: {prefs.user}") for line in tech_infos(): logger.warning(line) assert share_data.client.current_room is None share_data.session_id += 1 # todo tech debt -> current_room should be set when JOIN_ROOM is received # todo _joining_room_name should be set in client timer share_data.client.current_room = room_name share_data.client._joining_room_name = room_name set_client_attributes() blender_version = bpy.app.version_string mixer_version = mixer.display_version share_data.client.join_room(room_name, blender_version, mixer_version, ignore_version_check, not vrtist_protocol) if shared_folders is None: shared_folders = [] share_data.init_protocol(vrtist_protocol, shared_folders) share_data.pending_test_update = False # join a room <==> want to track local changes HandlerManager.set_handlers(True)
def users_frustrum_draw(): prefs = get_mixer_prefs() if not prefs.display_frustums_gizmos or share_data.client.current_room is None: return import bgl import gpu from gpu_extras.batch import batch_for_shader shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR") shader.bind() bgl.glLineWidth(1.5) bgl.glEnable(bgl.GL_DEPTH_TEST) bgl.glEnable(bgl.GL_BLEND) bgl.glEnable(bgl.GL_LINE_SMOOTH) bgl.glPointSize(4) indices = ((1, 2), (2, 3), (3, 4), (4, 1), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5)) def per_user_callback(user_dict): user_color = user_dict.get(ClientAttributes.USERCOLOR, DEFAULT_COLOR) shader.uniform_float("color", (*user_color, 1)) return True def per_frustum_callback(user_dict, frustum): position = [tuple(coord) for coord in frustum] batch = batch_for_shader(shader, "LINES", {"pos": position}, indices=indices) batch.draw(shader) batch = batch_for_shader(shader, "POINTS", {"pos": position}, indices=(5,)) batch.draw(shader) users_frustrum_draw_iteration(per_user_callback, per_frustum_callback)
def users_selection_draw(): import bgl import gpu from gpu_extras.batch import batch_for_shader prefs = get_mixer_prefs() if not prefs.display_selections_gizmos or share_data.client.current_room is None: return shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR") shader.bind() bgl.glLineWidth(1.5) bgl.glEnable(bgl.GL_DEPTH_TEST) bgl.glEnable(bgl.GL_BLEND) bgl.glEnable(bgl.GL_LINE_SMOOTH) indices = ((0, 1), (1, 2), (2, 3), (0, 3), (4, 5), (5, 6), (6, 7), (4, 7), (0, 4), (1, 5), (2, 6), (3, 7)) def per_user_callback(user_dict): user_color = user_dict.get(ClientAttributes.USERCOLOR, DEFAULT_COLOR) shader.uniform_float("color", (*user_color, 1)) return True def per_object_callback(user_dict, object, matrix, local_bbox): bbox_corners = [matrix @ Vector(corner) for corner in local_bbox] batch = batch_for_shader(shader, "LINES", {"pos": bbox_corners}, indices=indices) batch.draw(shader) users_selection_draw_iteration(per_user_callback, per_object_callback)
def execute(self, context): assert not share_data.client.current_room share_data.set_dirty() props = get_mixer_props() room_index = props.room_index room = props.rooms[room_index].name logger.warning(f"JoinRoomOperator.execute({room})") room_attributes = get_selected_room_dict() logger.warning( f"Client Blender version: {room_attributes.get(RoomAttributes.BLENDER_VERSION, '')}" ) logger.warning( f"Client Mixer version: {room_attributes.get(RoomAttributes.MIXER_VERSION, '')}" ) clear_undo_history() mixer_prefs = get_mixer_prefs() shared_folders = [] for item in mixer_prefs.shared_folders: shared_folders.append(item.shared_folder) join_room( room, not room_attributes.get(RoomAttributes.GENERIC_PROTOCOL, True), shared_folders, mixer_prefs.ignore_version_check, ) return {"FINISHED"}
def execute(self, context): prefs = get_mixer_prefs() try: self.report({"INFO"}, f'Connecting to "{prefs.host}:{prefs.port}" ...') try: connect() except Exception as e: self.report({"ERROR"}, f"mixer.connect error : {e}") return {"CANCELLED"} self.report({"INFO"}, f'Connected to "{prefs.host}:{prefs.port}" ...') except socket.gaierror as e: msg = f'Cannot connect to "{prefs.host}": invalid host name or address' self.report({"ERROR"}, msg) if prefs.env != "production": raise e except Exception as e: self.report({"ERROR"}, repr(e)) if prefs.env != "production": raise e return {"CANCELLED"} return {"FINISHED"}
def draw(self, context): layout = self.layout.column() mixer_prefs = get_mixer_prefs() draw_user_settings_ui(layout.row()) layout.separator(factor=0.2) split = layout.split(factor=0.258, align=False) split.label(text="Host:") split.prop(mixer_prefs, "host", text="") split = layout.split(factor=0.258, align=False) split.label(text="Room:") split.prop(mixer_prefs, "room", text="") layout.separator(factor=1.0) row = layout.row() row.scale_y = 1.5 row.operator(bl_operators.LaunchVRtistOperator.bl_idname, text="Launch VRTist") layout.separator(factor=1) layout.prop(mixer_prefs, "VRtist", text="Path", icon=("ERROR" if not os.path.exists(mixer_prefs.VRtist) else "NONE")) layout.prop(mixer_prefs, "VRtist_suffix", text="Save Suffix") layout.separator(factor=0.5)
def users_selection_draw_iteration(per_user_callback, per_object_callback, collection_detail=True): if share_data.client is None: return prefs = get_mixer_prefs() for user_dict in share_data.client.clients_attributes.values(): scenes = user_dict.get(ClientAttributes.USERSCENES, None) if not scenes: continue user_id = user_dict[ClientAttributes.ID] user_room = user_dict[ClientAttributes.ROOM] if (not prefs.display_own_gizmos and share_data.client.client_id == user_id) or share_data.client.current_room != user_room: continue # don't draw my own selection or selection from users outside my room if not per_user_callback(user_dict): continue scene_dict = scenes.get(bpy.context.scene.name_full) if scene_dict is None: return selected_names = scene_dict.get( ClientAttributes.USERSCENES_SELECTED_OBJECTS, None) if selected_names is None: return selected_objects = (obj for obj in bpy.data.objects if obj.name_full in set(selected_names)) for obj in selected_objects: objects = [obj] parent_matrix = IDENTITY_MATRIX if obj.type == "EMPTY" and obj.instance_collection is not None: if collection_detail: objects = obj.instance_collection.objects else: objects = [] parent_matrix = obj.matrix_world per_object_callback(user_dict, obj, obj.matrix_world @ BBOX_SCALE_MATRIX, DEFAULT_BBOX) for obj in objects: bbox = obj.bound_box diag = Vector(bbox[2]) - Vector(bbox[4]) if diag.length_squared == 0: bbox = DEFAULT_BBOX per_object_callback( user_dict, obj, parent_matrix @ obj.matrix_world @ BBOX_SCALE_MATRIX, bbox)
def poll(cls, context): # Check VRtist process to auto disconnect if cls.vrtist_process is not None and cls.vrtist_process.poll() is not None: cls.vrtist_process = None leave_current_room() disconnect() # Manage button state return os.path.isfile(get_mixer_prefs().VRtist)
def draw_developer_settings_ui(layout: bpy.types.UILayout): mixer_prefs = get_mixer_prefs() layout.prop(mixer_prefs, "no_send_scene_content", text="No send_scene_content") layout.prop(mixer_prefs, "no_start_server", text="Do not start server on connect") layout.prop(mixer_prefs, "send_base_meshes", text="Send Base Meshes") layout.prop(mixer_prefs, "send_baked_meshes", text="Send Baked Meshes") layout.prop(mixer_prefs, "commands_send_interval") layout.prop(mixer_prefs, "display_own_gizmos") layout.prop(mixer_prefs, "display_ids_gizmos")
def draw_rooms(self, layout): mixer_props = get_mixer_props() if collapsable_panel(layout, mixer_props, "display_rooms", text="Server Rooms"): # main box should probably be removed # layout = layout.row() # layout.separator(factor=1.2) # layout = layout.column() layout = layout.box().column() ROOM_UL_ItemRenderer.draw_header(layout) layout.template_list("ROOM_UL_ItemRenderer", "", mixer_props, "rooms", mixer_props, "room_index", rows=2) row = layout.row() row.scale_y = 1.3 if share_data.client.current_room is None: row.operator(bl_operators.JoinRoomOperator.bl_idname, text="Join Selected Room") else: row.operator(bl_operators.LeaveRoomOperator.bl_idname, depress=True) if len(mixer_props.rooms): self.draw_current_room_properties(layout) prefs = get_mixer_prefs() if prefs.display_debugging_tools: if collapsable_panel(layout, mixer_props, "display_advanced_room_control", text="Room Debugging Tools"): box = layout.box() col = box.column() col.operator(bl_operators.DeleteRoomOperator.bl_idname) col.operator(bl_operators.DownloadRoomOperator.bl_idname) subbox = col.box() subbox.row().operator( bl_operators.UploadRoomOperator.bl_idname) row = subbox.row() row.prop(mixer_props, "upload_room_name", text="Name") row.prop( mixer_props, "internal_upload_room_filepath", text="File", icon=("ERROR" if not os.path.exists( mixer_props.upload_room_filepath) else "NONE"), )
def execute(self, context): from mixer.broadcaster.room_bake import load_room, upload_room prefs = get_mixer_prefs() props = get_mixer_props() attributes, commands = load_room(props.upload_room_filepath) upload_room(prefs.host, prefs.port, props.upload_room_name, attributes, commands) return {"FINISHED"}
def draw_shared_folders_settings_ui(layout: bpy.types.UILayout): mixer_props = get_mixer_props() mixer_prefs = get_mixer_prefs() row = layout.row() row.template_list( "SHAREDFOLDER_UL_ItemRenderer", "", mixer_prefs, "shared_folders", mixer_props, "shared_folder_index", rows=4 ) col = row.column(align=True) col.operator(bl_operators.SharedFoldersAddFolderOperator.bl_idname, text="", icon="ADD") col.operator(bl_operators.SharedFoldersRemoveFolderOperator.bl_idname, text="", icon="REMOVE")
def draw_user_settings_ui(layout: bpy.types.UILayout): mixer_prefs = get_mixer_prefs() split = layout.split(factor=0.258, align=False) split.label(text="User:"******"user", text="") sub_row = row.row() sub_row.scale_x = 0.4 sub_row.prop(mixer_prefs, "color", text="")