import logging import sys from uuid import uuid4 import json import bpy import mathutils from . import draw from .libs import dump_anything CORRESPONDANCE = {'Collection': 'collections', 'Mesh': 'meshes', 'Object': 'objects', 'Material': 'materials', 'Texture': 'textures', 'Scene': 'scenes', 'Light': 'lights', 'Camera': 'cameras', 'Action': 'actions', 'Armature': 'armatures', 'Grease Pencil': 'grease_pencils'} SUPPORTED_TYPES = [ 'Armature', 'Material', 'Texture', 'Light', 'Camera', 'Mesh', 'Grease Pencil', 'Object', 'Action', 'Collection', 'Scene'] logger = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) # UTILITY FUNCTIONS def revers(d): l = [] for i in d: l.append(i) return l[::-1] def refresh_window(): import bpy bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) def get_armature_edition_context(armature): override = {} # Set correct area for area in bpy.data.window_managers[0].windows[0].screen.areas : if area.type == 'VIEW_3D': override = bpy.context.copy() override['area'] = area break # Set correct armature settings override['window'] = bpy.data.window_managers[0].windows[0] override['screen'] = bpy.data.window_managers[0].windows[0].screen override['mode'] = 'EDIT_ARMATURE' override['active_object'] = armature override['selected_objects'] = [armature] for o in bpy.data.objects: if o.data == armature: override['edit_object'] = o break return override def get_selected_objects(scene): selected_objects = [] for obj in scene.objects: if obj.select_get(): selected_objects.append(obj.name) return selected_objects def get_all_datablocks(): datas = [] for datatype in SUPPORTED_TYPES: for item in getattr(bpy.data, CORRESPONDANCE[datatype]): item.id = bpy.context.scene.session_settings.username datas.append("{}/{}".format(datatype, item.name)) return datas # LOAD HELPERS def load(key, value): target = resolve_bpy_path(key) target_type = key.split('/')[0] if value == "None": return if target_type == 'Object': load_object(target=target, data=value, create=True) elif target_type == 'Mesh': load_mesh(target=target, data=value, create=True) elif target_type == 'Collection': load_collection(target=target, data=value, create=True) elif target_type == 'Material': load_material(target=target, data=value, create=True) elif target_type == 'Grease Pencil': load_gpencil(target=target, data=value, create=True) elif target_type == 'Scene': load_scene(target=target, data=value, create=True) elif 'Light' in target_type: load_light(target=target, data=value, create=True) elif target_type == 'Camera': load_default(target=target, data=value, create=True, type=target_type) elif target_type == 'Armature': load_armature(target=target, data=value, create=True) elif target_type == 'Client': load_client(key.split('/')[1], value) def resolve_bpy_path(path): """ Get bpy property value from path """ item = None try: path = path.split('/') item = getattr(bpy.data, CORRESPONDANCE[path[0]])[path[1]] except: pass return item def load_client(client=None, data=None): C = bpy.context D = bpy.data net_settings = C.scene.session_settings if client and data: if net_settings.enable_draw: draw.renderer.draw_client(data) draw.renderer.draw_client_selected_objects(data) def load_armature(target=None, data=None, create=False): file = "cache_{}.json".format(data['name']) context = bpy.context if not target: target = bpy.data.armatures.new(data['name']) dump_anything.load(target, data) with open(file, 'w') as fp: json.dump(data, fp) fp.close() target.id = data['id'] else: # Construct a correct execution context file = "cache_{}.json".format(target.name) with open(file, 'r') as fp: data = json.load(fp) if data: ob = None for o in bpy.data.objects: if o.data == target: ob = o if ob: bpy.context.view_layer.objects.active = ob bpy.ops.object.mode_set(mode='EDIT', toggle=False) for eb in data['edit_bones']: if eb in target.edit_bones.keys(): # Update the bone pass else: # Add new edit bone and load it target_new_b = target.edit_bones.new[eb] dump_anything.load(target_new_b, data['bones'][eb]) logger.info(eb) bpy.ops.object.mode_set(mode='OBJECT', toggle=False) fp.close() import os os.remove(file) def load_mesh(target=None, data=None, create=False): import bmesh if not target or not target.is_editmode: # LOAD GEOMETRY mesh_buffer = bmesh.new() for i in data["vertices"]: mesh_buffer.verts.new(data["vertices"][i]["co"]) mesh_buffer.verts.ensure_lookup_table() for i in data["edges"]: verts = mesh_buffer.verts v1 = data["edges"][i]["vertices"][0] v2 = data["edges"][i]["vertices"][1] 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: f = mesh_buffer.faces.new(verts) f.material_index= data["polygons"][p]['material_index'] if target is None and create: target = bpy.data.meshes.new(data["name"]) mesh_buffer.to_mesh(target) # LOAD METADATA dump_anything.load(target, data) material_to_load = [] material_to_load = revers(data["materials"]) target.materials.clear() # SLots i = 0 while len(material_to_load) > len(target.materials): target.materials.append(bpy.data.materials[material_to_load[i]]) i+=1 target.id = data['id'] else: logger.debug("Mesh can't be loaded") def load_object(target=None, data=None, create=False): try: if target is None and create: pointer = None # Object specific constructor... if data["data"] in bpy.data.meshes.keys(): pointer = bpy.data.meshes[data["data"]] elif data["data"] in bpy.data.lights.keys(): pointer = bpy.data.lights[data["data"]] elif data["data"] in bpy.data.cameras.keys(): pointer = bpy.data.cameras[data["data"]] elif data["data"] in bpy.data.curves.keys(): pointer = bpy.data.curves[data["data"]] elif data["data"] in bpy.data.armatures.keys(): pointer = bpy.data.armatures[data["data"]] elif data["data"] in bpy.data.grease_pencils.keys(): pointer = bpy.data.grease_pencils[data["data"]] target = bpy.data.objects.new(data["name"], pointer) # Load other meshes metadata dump_anything.load(target, data) target.matrix_world = mathutils.Matrix(data["matrix_world"]) target.id = data['id'] client = bpy.context.scene.session_settings.username if target.id == client: target.hide_select = False else: target.hide_select = True except Exception as e: logger.error("Object {} loading error: {} ".format(data["name"],e)) def load_collection(target=None, data=None, create=False): try: if target is None and create: target = bpy.data.collections.new(data["name"]) # Load other meshes metadata # dump_anything.load(target, data) # link objects for object in data["objects"]: target.objects.link(bpy.data.objects[object]) for object in target.objects.keys(): if object not in data["objects"]: target.objects.unlink(bpy.data.objects[object]) # Link childrens for collection in data["children"]: if collection not in target.children.keys(): target.children.link( bpy.data.collections[collection]) target.id = data['id'] except Exception as e: logger.error("Collection loading error: {}".format(e)) def load_scene(target=None, data=None, create=False): try: if target is None and create: target = bpy.data.scenes.new(data["name"]) # Load other meshes metadata dump_anything.load(target, data) # Load master collection for object in data["collection"]["objects"]: if object not in target.collection.objects.keys(): target.collection.objects.link(bpy.data.objects[object]) for object in target.collection.objects.keys(): if object not in data["collection"]["objects"]: target.collection.objects.unlink(bpy.data.objects[object]) # load collections # TODO: Recursive link for collection in data["collection"]["children"]: if collection not in target.collection.children.keys(): target.collection.children.link( bpy.data.collections[collection]) target.id = data['id'] # Load annotation # if data["grease_pencil"]: # target.grease_pencil = bpy.data.grease_pencils[data["grease_pencil"]["name"]] except: logger.error("Scene loading error") def load_material(target=None, data=None, create=False): try: if target is None: target = bpy.data.materials.new(data["name"]) # Load other meshes metadata dump_anything.load(target, data) # load nodes if data["node_tree"]: for node in data["node_tree"]["nodes"]: # fix None node tree error if target.node_tree is None: target.use_nodes = True index = target.node_tree.nodes.find(node) if index is -1: node_type = data["node_tree"]["nodes"][node]["bl_idname"] target.node_tree.nodes.new(type=node_type) dump_anything.load( target.node_tree.nodes[index], data["node_tree"]["nodes"][node]) for input in data["node_tree"]["nodes"][node]["inputs"]: try: target.node_tree.nodes[index].inputs[input].default_value = data[ "node_tree"]["nodes"][node]["inputs"][input]["default_value"] except: pass # Load nodes links target.node_tree.links.clear() for link in data["node_tree"]["links"]: current_link = data["node_tree"]["links"][link] input_socket = target.node_tree.nodes[current_link['to_node'] ['name']].inputs[current_link['to_socket']['name']] output_socket = target.node_tree.nodes[current_link['from_node'] ['name']].outputs[current_link['from_socket']['name']] target.node_tree.links.new(input_socket, output_socket) target.id = data['id'] except Exception as e: logger.error("Material loading error: {}".format(e)) def load_gpencil_layer(target=None, data=None, create=False): dump_anything.load(target, data) for frame in data["frames"]: try: tframe = target.frames[frame] except: tframe = target.frames.new(frame) dump_anything.load(tframe, data["frames"][frame]) for stroke in data["frames"][frame]["strokes"]: try: tstroke = tframe.strokes[stroke] except: tstroke = tframe.strokes.new() dump_anything.load( tstroke, data["frames"][frame]["strokes"][stroke]) for point in data["frames"][frame]["strokes"][stroke]["points"]: p = data["frames"][frame]["strokes"][stroke]["points"][point] try: tpoint = tstroke.points[point] except: tpoint = tstroke.points.add(1) tpoint = tstroke.points[len(tstroke.points)-1] dump_anything.load(tpoint, p) def load_gpencil(target=None, data=None, create=False): try: if target is None and create: target = bpy.data.grease_pencils.new(data["name"]) if "layers" in data.keys(): for layer in data["layers"]: if layer not in target.layers.keys(): gp_layer = target.layers.new(data["layers"][layer]["info"]) else: gp_layer = target.layers[layer] load_gpencil_layer( target=gp_layer, data=data["layers"][layer], create=create) dump_anything.load(target, data) target.id = data['id'] except: logger.error("default loading error") def load_light(target=None, data=None, create=False, type=None): try: if target is None and create: target = bpy.data.lights.new(data["name"], data["type"]) dump_anything.load(target, data) target.id = data['id'] except Exception as e: logger.error("light loading error: {}".format(e)) def load_default(target=None, data=None, create=False, type=None): try: if target is None and create: target = getattr(bpy.data, CORRESPONDANCE[type]).new(data["name"]) dump_anything.load(target, data) target.id = data['id'] except Exception as e: logger.error("default loading error {}".format(e)) # DUMP HELPERS def dump(key): target = resolve_bpy_path(key) target_type = key.split('/')[0] data = None if target_type == 'Material': data = dump_datablock_attibute(target, ['name', 'node_tree', 'id'], 7) elif target_type == 'Grease Pencil': data = dump_datablock_attibute( target, ['name', 'layers', 'materials', 'id'], 9) elif target_type == 'Camera': data = dump_datablock(target, 1) elif target_type == 'Light': data = dump_datablock(target, 1) elif target_type == 'Mesh': data = dump_datablock(target, 2) dump_datablock_attibute( target, ['name', 'polygons', 'edges', 'vertices', 'id'], 6,data) elif target_type == 'Object': data = dump_datablock(target, 1) elif target_type == 'Collection': data = dump_datablock(target, 4) elif target_type == 'Scene': data = dump_datablock_attibute( target, ['name', 'collection', 'id', 'camera', 'grease_pencil'], 4) # elif target_type == 'Armature': # data = dump_datablock(target, 4) return data def dump_datablock(datablock, depth): if datablock: dumper = dump_anything.Dumper() dumper.type_subset = dumper.match_subset_all dumper.depth = depth datablock_type = datablock.bl_rna.name key = "{}/{}".format(datablock_type, datablock.name) data = dumper.dump(datablock) return data def dump_datablock_attibute(datablock, attributes, depth=1,dickt=None): if datablock: dumper = dump_anything.Dumper() dumper.type_subset = dumper.match_subset_all dumper.depth = depth datablock_type = datablock.bl_rna.name key = "{}/{}".format(datablock_type, datablock.name) data = {} if dickt: data = dickt for attr in attributes: try: data[attr] = dumper.dump(getattr(datablock, attr)) except: pass return data def init_client(key=None): client_dict = {} C = bpy.context Net = C.scene.session_settings client_dict['uuid'] = str(uuid4()) client_dict['location'] = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] client_dict['color'] = [Net.client_color.r, Net.client_color.g, Net.client_color.b, 1] client_dict['active_objects'] = get_selected_objects(C.view_layer) return client_dict