diff --git a/net_components.py b/net_components.py index fc6a808..5a2b4c0 100644 --- a/net_components.py +++ b/net_components.py @@ -1,41 +1,39 @@ -# import zmq import asyncio -import logging -from .libs.esper import esper -from .libs import zmq -from .libs import umsgpack -import time -import random -import struct import collections +import logging +import time from enum import Enum +from .libs import umsgpack, zmq + logger = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) -CONNECT_TIMEOUT = 2 +CONNECT_TIMEOUT = 2 WAITING_TIME = 0.001 + class RCFStatus(Enum): IDLE = 1 CONNECTING = 2 CONNECTED = 3 + class RCFFactory(object): """ Abstract layer used to bridge external and inter """ + def init(self, data): """ set the RCFMessage pointer to local data """ print("Default setter") - #Setup data accessor + # Setup data accessor data.get = self.load_getter(data) data.set = self.load_setter(data) # TODO: Setup local pointer - def load_getter(self, data): """ @@ -52,7 +50,7 @@ class RCFFactory(object): print("Default setter") return None - def apply(self,data): + def apply(self, data): pass def diff(self, data): @@ -61,26 +59,31 @@ class RCFFactory(object): """ pass -class RCFStore(collections.MutableMapping,dict): - + +class RCFStore(collections.MutableMapping, dict): + def __init__(self, custom_factory=RCFFactory()): - super().__init__() + super().__init__() self.factory = custom_factory - - def __getitem__(self,key): - return dict.__getitem__(self,key) + + def __getitem__(self, key): + return dict.__getitem__(self, key) def __setitem__(self, key, value): - dict.__setitem__(self,key,value) - + dict.__setitem__(self, key, value) + def __delitem__(self, key): - dict.__delitem__(self,key) + dict.__delitem__(self, key) + def __iter__(self): return dict.__iter__(self) + def __len__(self): return dict.__len__(self) + def __contains__(self, x): - return dict.__contains__(self,x) + return dict.__contains__(self, x) + class RCFMessage(object): """ @@ -101,7 +104,7 @@ class RCFMessage(object): self.mtype = mtype self.body = body self.id = id - + def store(self, dikt): """Store me in a dict if I have anything to store""" # this currently erasing old value @@ -109,7 +112,6 @@ class RCFMessage(object): dikt[self.key] = self elif self.key in dikt: del dikt[self.key] - def send(self, socket): """Send key-value message to socket; any empty frames are sent as such.""" @@ -148,16 +150,17 @@ class RCFMessage(object): data=data, )) -class Client(): + +class RCFClient(): def __init__( - self, - context=zmq.Context(), - id="default", - on_recv=None, - on_post_init=None, - is_admin=False, - factory=None, - address="localhost"): + self, + context=zmq.Context(), + id="default", + on_recv=None, + on_post_init=None, + is_admin=False, + factory=None, + address="localhost"): # 0MQ vars self.context = context @@ -165,7 +168,7 @@ class Client(): self.req_sock = None self.poller = None - # Client configuration + # Client configuration self.id = id.encode() self.on_recv = on_recv self.on_post_init = on_post_init @@ -233,16 +236,18 @@ class Client(): for f in self.on_post_init: f() - + logger.info("{} client running".format(id)) - self.push_update("net/clients/{}".format(self.id.decode()),"client",self.id) - self.push_update("net/objects/{}".format(self.id.decode()),"client_object","None") - + self.push_update( + "net/clients/{}".format(self.id.decode()), "client", self.id) + self.push_update( + "net/objects/{}".format(self.id.decode()), "client_object", "None") + self.tick_task = asyncio.ensure_future(self.tick()) self.status = RCFStatus.CONNECTED - + async def tick(self): # Main loop while True: @@ -270,9 +275,12 @@ class Client(): self.push_sock.close() self.pull_sock.close() self.load_task.cancel() - self.tick_task.cancel() -class Server(): + if self.tick_task: + self.tick_task.cancel() + + +class RCFServer(): def __init__(self, context=zmq.Context(), id="admin"): self.context = context @@ -281,11 +289,11 @@ class Server(): self.collector_sock = None self.poller = None - self.property_map = RCFStore() + self.property_map = RCFStore() self.id = id self.bind_ports() # Main client loop registration - self.task = asyncio.ensure_future(self.main()) + self.task = asyncio.ensure_future(self.tick()) logger.info("{} client initialized".format(id)) @@ -312,11 +320,11 @@ class Server(): self.poller.register(self.request_sock, zmq.POLLIN) self.poller.register(self.collector_sock, zmq.POLLIN) - async def main(self): + async def tick(self): logger.info("{} server launched".format(id)) while True: - # Non blocking poller + # Non blocking poller socks = dict(self.poller.poll(1)) # Snapshot system for late join (Server - Client) @@ -350,7 +358,7 @@ class Server(): msg.store(self.property_map) msg.send(self.pub_sock) else: - await asyncio.sleep(0.0001) + await asyncio.sleep(WAITING_TIME) def stop(self): logger.debug("Stopping server") @@ -360,4 +368,4 @@ class Server(): self.collector_sock.close() self.task.cancel() - self.status= RCFStatus.IDLE + self.status = RCFStatus.IDLE diff --git a/net_operators.py b/net_operators.py index e89c641..011b3c3 100644 --- a/net_operators.py +++ b/net_operators.py @@ -1,19 +1,19 @@ -from bpy_extras import view3d_utils -import bpy -from . import net_components -from . import net_ui -from . import rna_translation -from .libs import dump_anything -import time import logging -import mathutils import random import string +import time + import bgl import blf +import bpy import gpu +import mathutils +from bpy_extras import view3d_utils from gpu_extras.batch import batch_for_shader +from . import net_components, net_ui, rna_translation +from .libs import dump_anything + logger = logging.getLogger(__name__) client = None @@ -22,7 +22,15 @@ context = None COLOR_TABLE = [(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1), (0, 0.5, 1, 1), (0.5, 0, 1, 1)] +SUPPORTED_DATABLOCKS = ['collections', 'meshes', 'objects', 'materials', 'textures', 'lights', 'cameras', 'actions', 'armatures'] +# UTILITY FUNCTIONS + +def clean_scene(elements=SUPPORTED_DATABLOCKS): + for datablock in elements: + datablock_ref = getattr(bpy.data, datablock) + for item in datablock_ref: + datablock_ref.remove(item) def view3d_find(): @@ -98,13 +106,6 @@ def get_client_2d(coords): return view3d_utils.location_3d_to_region_2d(region, rv3d, coords) -def on_scene_evalutation(scene): - # TODO: viewer representation - # TODO: Live update only selected object - # TODO: Scene representation - pass - - def randomStringDigits(stringLength=6): """Generate a random string of letters and digits """ lettersAndDigits = string.ascii_letters + string.digits @@ -126,31 +127,7 @@ def resolve_bpy_path(path): return item -def observer(scene): - global client - pass - # if client: - # for key, values in client.property_map.items(): - # try: - # obj, attr = resolve_bpy_path(key) - # if attr != to_bpy(client.property_map[key]): - # value_type, value = from_bpy(attr) - # client.push_update(key, value_type, value) - # except: - # pass - - return bpy.context.scene.session_settings.update_frequency - - -def mark_objects_for_update(scene): - for item in dir(bpy.data): - # if item in SUPPORTED_DATABLOCKS: - for datablock in getattr(bpy.data,item): - if bpy.context.object.is_evaluated: - print("EVALUATED: {}:{}".format(item,datablock.name)) - - def refresh_window(): import bpy bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) @@ -158,12 +135,18 @@ def refresh_window(): def init_scene(): global client - -def load_mesh(target,data): + + + for mesh in bpy.data.meshes: + pass + + +def load_mesh(target, data): import bmesh - + + # TODO: handle error mesh_buffer = bmesh.new() - + for i in data["vertices"]: mesh_buffer.verts.new(data["vertices"][i]["co"]) @@ -173,20 +156,24 @@ def load_mesh(target,data): verts = mesh_buffer.verts v1 = data["edges"][i]["vertices"][0] v2 = data["edges"][i]["vertices"][1] - mesh_buffer.edges.new([verts[v1],verts[v2]]) - + mesh_buffer.edges.new([verts[v1], verts[v2]]) + for p in data["polygons"]: verts = [] for v in data["polygons"][p]["vertices"]: verts.append(mesh_buffer.verts[v]) - if len(verts) > 0: mesh_buffer.faces.new(verts) + if not target: + target = bpy.data.meshes.new(data["name"]) mesh_buffer.to_mesh(target) +def load_object(target,data): + pass + def update_scene(msg): global client @@ -197,41 +184,26 @@ def update_scene(msg): if bpy.context.scene.session_settings.active_object.name in msg.key: raise ValueError() - if msg.mtype == 'Mesh' or 'Object': - item = resolve_bpy_path(msg.key) - - if item: - loader = dump_anything.Loader() - loader.load(item,msg.body) + if msg.mtype in SUPPORTED_DATABLOCKS: + item = resolve_bpy_path(msg.key) - if msg.mtype == 'Mesh': - load_mesh(item, msg.body) - - # print(msg.get) - # logger.debug("Updating scene:\n object: {} attribute: {} , value: {}".format( - # obj, attr_name, value)) + if item is None: + pass + loader = dump_anything.Loader() + loader.load(item, msg.body) - # setattr(obj, attr_name, value) - # except: - # passñ - else: - pass - # logger.debug('no need to update scene on our own') + if msg.mtype == 'Mesh': + load_mesh(item, msg.body) -def update_ui(msg): - """ - Update collaborative UI elements - """ - pass - - -recv_callbacks = [update_scene, update_ui] +recv_callbacks = [update_scene] post_init_callbacks = [refresh_window] +# OPERATORS + class session_join(bpy.types.Operator): - + bl_idname = "session.join" bl_label = "join" bl_description = "connect to a net server" @@ -245,25 +217,27 @@ class session_join(bpy.types.Operator): global client net_settings = context.scene.session_settings + # Scene setup + if net_settings.session_mode == "CONNECT": + clean_scene() + # Session setup if net_settings.username == "DefaultUser": net_settings.username = "{}_{}".format( net_settings.username, randomStringDigits()) username = str(context.scene.session_settings.username) - client_factory = rna_translation.RNAFactory() - print("{}".format(client_factory.__class__.__name__)) - client = net_components.Client( + + client = net_components.RCFClient( id=username, on_recv=recv_callbacks, on_post_init=post_init_callbacks, factory=client_factory, - address=net_settings.ip) - # time.sleep(1) + address=net_settings.ip, + is_admin=net_settings.session_mode == "HOST") bpy.ops.asyncio.loop() - # bpy.app.timers.register(observer) net_settings.is_running = True @@ -278,7 +252,8 @@ class session_add_property(bpy.types.Operator): bl_options = {"REGISTER"} property_path: bpy.props.StringProperty(default="None") - + depth: bpy.props.IntProperty(default=1) + @classmethod def poll(cls, context): return True @@ -290,12 +265,12 @@ class session_add_property(bpy.types.Operator): print(item) - if item : + if item: key = self.property_path dumper = dump_anything.Dumper() dumper.type_subset = dumper.match_subset_all - dumper.depth = 4 + dumper.depth = self.depth data = dumper.dump(item) data_type = item.__class__.__name__ @@ -342,14 +317,13 @@ class session_create(bpy.types.Operator): global server global client - server = net_components.Server() + server = net_components.RCFServer() time.sleep(0.1) bpy.ops.session.join() - # init_scene() + init_scene() - bpy.app.timers.register(observer) return {"FINISHED"} @@ -369,8 +343,6 @@ class session_stop(bpy.types.Operator): net_settings = context.scene.session_settings - # bpy.app.timers.unregister(observer) - if server: server.stop() del server @@ -390,14 +362,13 @@ class session_stop(bpy.types.Operator): class session_settings(bpy.types.PropertyGroup): username = bpy.props.StringProperty( name="Username", default="user_{}".format(randomStringDigits())) - ip = bpy.props.StringProperty(name="localhost") + ip = bpy.props.StringProperty(name="ip") port = bpy.props.IntProperty(name="5555") + + add_property_depth = bpy.props.IntProperty(name="add_property_depth",default=1) buffer = bpy.props.StringProperty(name="None") is_running = bpy.props.BoolProperty(name="is_running", default=False) - hide_users = bpy.props.BoolProperty(name="is_running", default=False) - hide_settings = bpy.props.BoolProperty(name="hide_settings", default=False) - hide_properties = bpy.props.BoolProperty( - name="hide_properties", default=True) + load_data = bpy.props.BoolProperty(name="load_data", default=True) update_frequency = bpy.props.FloatProperty( name="update_frequency", default=0.008) active_object = bpy.props.PointerProperty( @@ -622,6 +593,7 @@ class session_snapview(bpy.types.Operator): pass + # TODO: Rename to match official blender convention classes = ( session_join, @@ -634,6 +606,7 @@ classes = ( session_snapview, ) + def depsgraph_update(scene): for c in bpy.context.depsgraph.updates.items(): # print(c[1].id) @@ -653,22 +626,11 @@ def register(): bpy.types.Scene.session_settings = bpy.props.PointerProperty( type=session_settings) - # bpy.app.handlers.depsgraph_update_post.append(depsgraph_update) - # bpy.app.handlers.depsgraph_update_post.append(observer) - def unregister(): - try: - bpy.app.handlers.depsgraph_update_post.remove(observer) - except: - pass global server global client - # bpy.app.handlers.depsgraph_update_post.remove(depsgraph_update) - # bpy.app.handlers.depsgraph_update_post.remove(observer) - # bpy.app.handlers.depsgraph_update_post.remove(mark_objects_for_update) - if server: server.stop() del server @@ -682,8 +644,6 @@ def unregister(): for cls in reversed(classes): unregister_class(cls) - - del bpy.types.Scene.session_settings diff --git a/net_ui.py b/net_ui.py index 4c93e3f..681f0d1 100644 --- a/net_ui.py +++ b/net_ui.py @@ -38,9 +38,12 @@ class SessionSettingsPanel(bpy.types.Panel): if scene.session_settings.session_mode == 'HOST': row.operator("session.create",text="HOST") else: - - row.prop(net_settings,"ip",text="server ip") - + box = row.box() + box.prop(net_settings,"ip",text="server ip") + box = box.row() + box.label(text="load data:") + box.prop(net_settings,"load_data",text="") + row = layout.row() row.operator("session.join",text="CONNECT") @@ -131,8 +134,10 @@ class SessionPropertiesPanel(bpy.types.Panel): if net_operators.client: row = layout.row(align=True) row.prop(net_settings, "buffer", text="") + row.prop(net_settings,"add_property_depth",text="") row.operator("session.add_prop", text="", icon="ADD").property_path = net_settings.buffer + row = layout.row() # Property area area_msg = row.box()