diff --git a/.gitignore b/.gitignore index 64a209e..71e2f84 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ __pycache__/ *$py.classs *.blend1 .vscode -cache \ No newline at end of file +cache +config \ No newline at end of file diff --git a/__init__.py b/__init__.py index dfe127f..73abc99 100644 --- a/__init__.py +++ b/__init__.py @@ -10,6 +10,7 @@ bl_info = { import addon_utils +import logging import random import string import sys @@ -24,6 +25,8 @@ DEPENDENCIES = { "PyYAML" } +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) # UTILITY FUNCTIONS def client_list_callback(scene, context): @@ -59,31 +62,49 @@ def randomColor(): b = random.random() return [r, v, b] +def save_session_config(self,context): + config = environment.load_config() + + config["username"] = self.username + config["ip"] = self.ip + config["port"] = self.port + config["start_empty"] = self.start_empty + config["enable_presence"] = self.enable_presence + + environment.save_config(config) class SessionProps(bpy.types.PropertyGroup): username: bpy.props.StringProperty( name="Username", - default="user_{}".format(randomStringDigits()) + default="user_{}".format(randomStringDigits()), + update=save_session_config ) ip: bpy.props.StringProperty( name="ip", description='Distant host ip', - default="127.0.0.1") + default="127.0.0.1", + update=save_session_config + ) port: bpy.props.IntProperty( name="port", description='Distant host port', - default=5554) + default=5555, + update=save_session_config + ) add_property_depth: bpy.props.IntProperty( name="add_property_depth", - default=1) + default=1 + ) buffer: bpy.props.StringProperty(name="None") is_admin: bpy.props.BoolProperty(name="is_admin", default=False) load_data: bpy.props.BoolProperty(name="load_data", default=True) init_scene: bpy.props.BoolProperty(name="load_data", default=True) - clear_scene: bpy.props.BoolProperty(name="clear_scene", default=False) + start_empty: bpy.props.BoolProperty(name="start_empty", default=False) update_frequency: bpy.props.FloatProperty( - name="update_frequency", default=0.008) + name="update_frequency", + default=0.008 + ) active_object: bpy.props.PointerProperty( name="active_object", type=bpy.types.Object) session_mode: bpy.props.EnumProperty( @@ -102,11 +123,26 @@ class SessionProps(bpy.types.PropertyGroup): description="client enum", items=client_list_callback ) - enable_draw: bpy.props.BoolProperty( - name="enable_draw", + enable_presence: bpy.props.BoolProperty( + name="enable_presence", description='Enable overlay drawing module', - default=True) + default=True, + update=save_session_config + ) + def load(self): + config = environment.load_config() + logger.info(config) + if config: + self.username = config["username"] + self.ip = config["ip"] + self.port = config["port"] + self.start_empty = config["start_empty"] + self.enable_presence = config["enable_presence"] + else: + logger.error("Fail to read config") + + classes = { SessionProps, @@ -114,7 +150,7 @@ classes = { def register(): - environment.setup(DEPENDENCIES) + environment.setup(DEPENDENCIES,bpy.app.binary_path_python) from . import operators from . import ui @@ -127,6 +163,8 @@ def register(): bpy.types.WindowManager.session = bpy.props.PointerProperty( type=SessionProps) + bpy.context.window_manager.session.load() + operators.register() ui.register() diff --git a/client.py b/client.py index bb25023..ec7ba2f 100644 --- a/client.py +++ b/client.py @@ -12,7 +12,7 @@ from random import randint import zmq import json -from . import helpers, message +from . import environment, helpers, message from .libs import dump_anything, umsgpack CONNECT_TIMEOUT = 2 @@ -23,7 +23,7 @@ DUMP_AGENTS_NUMBER = 1 lock = threading.Lock() logger = logging.getLogger(__name__) -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=environment) instance = None @@ -202,6 +202,7 @@ class Client(object): line = json.dumps(value.body) fp.write(line) + class Server(object): address = None # Server address port = None # Server port diff --git a/environment.py b/environment.py index 419e13e..3145891 100644 --- a/environment.py +++ b/environment.py @@ -1,14 +1,32 @@ -import sys -import subprocess import os +import subprocess +import sys from pathlib import Path -import bpy +import logging +import yaml + +logger = logging.getLogger(__name__) + +CONFIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config") +CONFIG = os.path.join(CONFIG_DIR, "app.yaml") + +THIRD_PARTY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "libs") +PYTHON_PATH = None +SUBPROCESS_DIR = None + + +def load_config(): + logger.info("loading config") + with open(CONFIG, 'r') as config_file: + return yaml.safe_load(config_file) + + return None + +def save_config(config): + logger.info("saving config") + with open(CONFIG, 'w') as outfile: + yaml.dump(config, outfile, default_flow_style=False) -CONFIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),"config") -APP_CONFIG = os.path.join(CONFIG_DIR,"config.yaml") -THIRD_PARTY = os.path.join(os.path.dirname(os.path.abspath(__file__)),"libs") -PYTHON_PATH = Path(bpy.app.binary_path_python) -SUBPROCESS_DIR = PYTHON_PATH.parent def module_can_be_imported(name): try: @@ -32,11 +50,17 @@ def install_pip(): def install_package(name): target = get_package_install_directory() - - subprocess.run([str(PYTHON_PATH), "-m", "pip", "install", - name, '--target', target], cwd=SUBPROCESS_DIR) -def setup(dependencies): + subprocess.run([str(PYTHON_PATH), "-m", "pip", "install", + name, '--target', target], cwd=SUBPROCESS_DIR) + + +def setup(dependencies, python_path): + global PYTHON_PATH, SUBPROCESS_DIR + + PYTHON_PATH = Path(python_path) + SUBPROCESS_DIR = PYTHON_PATH.parent + if not module_can_be_imported("pip"): install_pip() diff --git a/helpers.py b/helpers.py index 0ddf152..aa8aa11 100644 --- a/helpers.py +++ b/helpers.py @@ -130,7 +130,7 @@ def load_client(client=None, data=None): net_settings = C.window_manager.session if client and data: - if net_settings.enable_draw: + if net_settings.enable_presence: draw.renderer.draw_client(data) draw.renderer.draw_client_selected_objects(data) diff --git a/operators.py b/operators.py index 17573fe..4327dfc 100644 --- a/operators.py +++ b/operators.py @@ -88,7 +88,6 @@ def update_client_selected_object(context): # TODO: cleanup - def init_datablocks(): for datatype in helpers.BPY_TYPES.keys(): for item in getattr(bpy.data, helpers.BPY_TYPES[datatype]): @@ -134,7 +133,7 @@ class SessionJoinOperator(bpy.types.Operator): global execution_queue net_settings = context.window_manager.session # Scene setup - if net_settings.clear_scene: + if net_settings.start_empty: clean_scene() # Session setup @@ -157,7 +156,7 @@ class SessionJoinOperator(bpy.types.Operator): register_ticks() # Launch drawing module - if net_settings.enable_draw: + if net_settings.enable_presence: draw.renderer.run() return {"FINISHED"} @@ -394,7 +393,22 @@ class SessionDumpDatabase(bpy.types.Operator, ExportHelper): return {"CANCELLED"} pass -# TODO: Rename to match official blender convention + + +class SessionSaveConfig(bpy.types.Operator): + bl_idname = "session.save" + bl_label = "Save session configuration" + bl_description = "Save session configuration" + bl_options = {"REGISTER"} + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + context.window_manager.session.save() + + classes = ( SessionJoinOperator, SessionPropertyAddOperator, @@ -405,6 +419,7 @@ classes = ( SessionSnapUserOperator, SessionPropertyRightOperator, SessionDumpDatabase, + SessionSaveConfig, ) diff --git a/server.py b/server.py index de3c9b5..9c64e92 100644 --- a/server.py +++ b/server.py @@ -1,5 +1,6 @@ import logging import time +import environment from operator import itemgetter @@ -14,7 +15,8 @@ SUPPORTED_TYPES = ['Client','Curve','Material','Texture', 'Light', 'Camera', 'Me class ServerAgent(): def __init__(self, context=zmq.Context.instance(), id="admin"): self.context = context - + self.config = environment.load_config() + self.port = int(self.config['port']) self.pub_sock = None self.request_sock = None self.collector_sock = None @@ -32,19 +34,19 @@ class ServerAgent(): # Update all clients self.pub_sock = self.context.socket(zmq.PUB) self.pub_sock.setsockopt(zmq.SNDHWM, 60) - self.pub_sock.bind("tcp://*:5555") + self.pub_sock.bind("tcp://*:"+str(self.port+1)) time.sleep(0.2) # Update request self.request_sock = self.context.socket(zmq.ROUTER) self.request_sock.setsockopt(zmq.IDENTITY, b'SERVER') self.request_sock.setsockopt(zmq.RCVHWM, 60) - self.request_sock.bind("tcp://*:5554") + self.request_sock.bind("tcp://*:"+str(self.port)) # Update collector self.collector_sock = self.context.socket(zmq.PULL) self.collector_sock.setsockopt(zmq.RCVHWM, 60) - self.collector_sock.bind("tcp://*:5556") + self.collector_sock.bind("tcp://*:"+str(self.port+2)) # poller for socket aggregation self.poller = zmq.Poller() diff --git a/ui.py b/ui.py index 6457e1b..b81d66f 100644 --- a/ui.py +++ b/ui.py @@ -9,11 +9,15 @@ ICONS = {'Curve':'CURVE_DATA', 'Client':'SOLO_ON','Collection': 'FILE_FOLDER', ' class SESSION_PT_settings(bpy.types.Panel): bl_idname = "MULTIUSER_SETTINGS_PT_panel" - bl_label = "Network" + bl_label = "Settings" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "Multiuser" + def draw_header(self, context): + self.layout.label(text="", icon='TOOL_SETTINGS') + + def draw(self, context): layout = self.layout @@ -41,10 +45,10 @@ class SESSION_PT_settings(bpy.types.Panel): row = box.row() row.label(text="draw overlay:") - row.prop(net_settings, "enable_draw", text="") + row.prop(net_settings, "enable_presence", text="") row = box.row() row.label(text="clear blend:") - row.prop(net_settings, "clear_scene", text="") + row.prop(net_settings, "start_empty", text="") row = box.row() row = box.row() @@ -82,7 +86,7 @@ class SESSION_PT_settings(bpy.types.Panel): # row = layout.row(align=True) # row.operator("session.dump", icon='QUIT', text="Dump") # row.operator("session.dump", icon='QUIT', text="Load") - # row = layout.row() + row = layout.row() box = row.box() row = box.row() @@ -158,6 +162,9 @@ class SESSION_PT_properties(bpy.types.Panel): def poll(cls, context): return client.instance and client.instance.state() == 3 + def draw_header(self, context): + self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE') + def draw(self, context): layout = self.layout