From e659b7da94d0f89cda5a133b23fd8bc0b74eb771 Mon Sep 17 00:00:00 2001 From: Swann Date: Fri, 26 Mar 2021 12:30:15 +0100 Subject: [PATCH] refactor: move implementation to static def --- multi_user/__init__.py | 9 +- multi_user/bl_types/__init__.py | 44 +++--- multi_user/bl_types/bl_action.py | 136 ++++++++++++++-- multi_user/bl_types/bl_armature.py | 6 +- multi_user/bl_types/bl_camera.py | 8 +- multi_user/bl_types/bl_collection.py | 45 +++--- multi_user/bl_types/bl_curve.py | 8 +- multi_user/bl_types/bl_datablock.py | 187 ++-------------------- multi_user/bl_types/bl_font.py | 4 +- multi_user/bl_types/bl_gpencil.py | 8 +- multi_user/bl_types/bl_image.py | 4 +- multi_user/bl_types/bl_lattice.py | 6 +- multi_user/bl_types/bl_library.py | 2 +- multi_user/bl_types/bl_light.py | 6 +- multi_user/bl_types/bl_lightprobe.py | 6 +- multi_user/bl_types/bl_material.py | 57 ++++--- multi_user/bl_types/bl_mesh.py | 100 +++++++----- multi_user/bl_types/bl_metaball.py | 6 +- multi_user/bl_types/bl_node_group.py | 8 +- multi_user/bl_types/bl_object.py | 225 ++++++++++++++------------- multi_user/bl_types/bl_scene.py | 156 ++++++++++--------- multi_user/bl_types/bl_sound.py | 4 +- multi_user/bl_types/bl_speaker.py | 8 +- multi_user/bl_types/bl_texture.py | 8 +- multi_user/bl_types/bl_volume.py | 8 +- multi_user/bl_types/bl_world.py | 8 +- multi_user/libs/replication | 2 +- multi_user/preferences.py | 2 + 28 files changed, 534 insertions(+), 537 deletions(-) diff --git a/multi_user/__init__.py b/multi_user/__init__.py index 7b5a9fa..e390704 100644 --- a/multi_user/__init__.py +++ b/multi_user/__init__.py @@ -41,7 +41,7 @@ import bpy from bpy.app.handlers import persistent from . import environment - +from uuid import uuid4 LIBS = os.path.dirname(os.path.abspath(__file__))+"/libs/replication" module_error_msg = "Insufficient rights to install the multi-user \ @@ -53,10 +53,11 @@ def register(): datefmt='%H:%M:%S', level=logging.INFO) + for module_name in list(sys.modules.keys()): + if 'replication' in module_name: + del sys.modules[module_name] - if LIBS in sys.path: - logging.debug('Third party module already added') - else: + if LIBS not in sys.path: logging.info('Adding local modules dir to the path') sys.path.insert(0, LIBS) diff --git a/multi_user/bl_types/__init__.py b/multi_user/bl_types/__init__.py index 53ab9bb..9fd35c4 100644 --- a/multi_user/bl_types/__init__.py +++ b/multi_user/bl_types/__init__.py @@ -20,34 +20,34 @@ import bpy __all__ = [ 'bl_object', 'bl_mesh', - 'bl_camera', + # 'bl_camera', 'bl_collection', - 'bl_curve', - 'bl_gpencil', - 'bl_image', - 'bl_light', + # 'bl_curve', + # 'bl_gpencil', + # 'bl_image', + # 'bl_light', 'bl_scene', 'bl_material', - 'bl_library', - 'bl_armature', - 'bl_action', - 'bl_world', - 'bl_metaball', - 'bl_lattice', - 'bl_lightprobe', - 'bl_speaker', - 'bl_font', - 'bl_sound', - 'bl_file', - # 'bl_sequencer', - 'bl_node_group', - 'bl_texture', + # 'bl_library', + # 'bl_armature', + # 'bl_action', + # 'bl_world', + # 'bl_metaball', + # 'bl_lattice', + # 'bl_lightprobe', + # 'bl_speaker', + # 'bl_font', + # 'bl_sound', + # 'bl_file', + # # 'bl_sequencer', + # 'bl_node_group', + # 'bl_texture', ] # Order here defines execution order -if bpy.app.version[1] >= 91: - __all__.append('bl_volume') +# if bpy.app.version[1] >= 91: +# __all__.append('bl_volume') -from . import * +from . import bl_object, bl_action, bl_scene, bl_mesh, bl_collection from replication.protocol import DataTranslationProtocol def types_to_register(): diff --git a/multi_user/bl_types/bl_action.py b/multi_user/bl_types/bl_action.py index ec75d1c..10cc13d 100644 --- a/multi_user/bl_types/bl_action.py +++ b/multi_user/bl_types/bl_action.py @@ -24,9 +24,14 @@ from enum import Enum from .. import utils from .dump_anything import ( - Dumper, Loader, np_dump_collection, np_load_collection, remove_items_from_dict) -from .bl_datablock import BlDatablock - + Dumper, + Loader, + np_dump_collection, + np_load_collection, + remove_items_from_dict) +from .bl_datablock import stamp_uuid +from replication.protocol import ReplicatedDatablock +from replication.objects import Node KEYFRAME = [ 'amplitude', @@ -42,6 +47,68 @@ KEYFRAME = [ ] +def has_action(datablock): + """ Check if the datablock datablock has actions + """ + return (hasattr(datablock, 'animation_data') + and datablock.animation_data + and datablock.animation_data.action) + + +def has_driver(datablock): + """ Check if the datablock datablock is driven + """ + return (hasattr(datablock, 'animation_data') + and datablock.animation_data + and datablock.animation_data.drivers) + + +def dump_driver(driver): + dumper = Dumper() + dumper.depth = 6 + data = dumper.dump(driver) + + return data + + +def load_driver(target_datablock, src_driver): + loader = Loader() + drivers = target_datablock.animation_data.drivers + src_driver_data = src_driver['driver'] + new_driver = drivers.new( + src_driver['data_path'], index=src_driver['array_index']) + + # Settings + new_driver.driver.type = src_driver_data['type'] + new_driver.driver.expression = src_driver_data['expression'] + loader.load(new_driver, src_driver) + + # Variables + for src_variable in src_driver_data['variables']: + src_var_data = src_driver_data['variables'][src_variable] + new_var = new_driver.driver.variables.new() + new_var.name = src_var_data['name'] + new_var.type = src_var_data['type'] + + for src_target in src_var_data['targets']: + src_target_data = src_var_data['targets'][src_target] + new_var.targets[src_target].id = utils.resolve_from_id( + src_target_data['id'], src_target_data['id_type']) + loader.load( + new_var.targets[src_target], src_target_data) + + # Fcurve + new_fcurve = new_driver.keyframe_points + for p in reversed(new_fcurve): + new_fcurve.remove(p, fast=True) + + new_fcurve.add(len(src_driver['keyframe_points'])) + + for index, src_point in enumerate(src_driver['keyframe_points']): + new_point = new_fcurve[index] + loader.load(new_point, src_driver['keyframe_points'][src_point]) + + def dump_fcurve(fcurve: bpy.types.FCurve, use_numpy: bool = True) -> dict: """ Dump a sigle curve to a dict @@ -129,26 +196,68 @@ def load_fcurve(fcurve_data, fcurve): fcurve.update() -class BlAction(BlDatablock): +def dump_animation_data(datablock, data): + if has_action(datablock): + dumper = Dumper() + dumper.include_filter = ['action'] + data['animation_data'] = dumper.dump(datablock.animation_data) + + if has_driver(datablock): + dumped_drivers = {'animation_data': {'drivers': []}} + for driver in datablock.animation_data.drivers: + dumped_drivers['animation_data']['drivers'].append( + dump_driver(driver)) + + data.update(dumped_drivers) + + +def load_animation_data(data, datablock): + # Load animation data + if 'animation_data' in data.keys(): + if datablock.animation_data is None: + datablock.animation_data_create() + + for d in datablock.animation_data.drivers: + datablock.animation_data.drivers.remove(d) + + if 'drivers' in data['animation_data']: + for driver in data['animation_data']['drivers']: + load_driver(datablock, driver) + + if 'action' in data['animation_data']: + datablock.animation_data.action = bpy.data.actions[data['animation_data']['action']] + # Remove existing animation data if there is not more to load + elif hasattr(datablock, 'animation_data') and datablock.animation_data: + datablock.animation_data_clear() + + +def get_animation_dependencies(datablock): + if has_action(datablock): + return datablock.animation_data.action + + +class BlAction(ReplicatedDatablock): bl_id = "actions" bl_class = bpy.types.Action bl_check_common = False bl_icon = 'ACTION_TWEAK' bl_reload_parent = False - def construct(self, data): + @staticmethod + def construct(data: dict) -> object: return bpy.data.actions.new(data["name"]) - def _load_implementation(self, data, target): + @staticmethod + def load(data: dict, datablock: object): for dumped_fcurve in data["fcurves"]: dumped_data_path = dumped_fcurve["data_path"] dumped_array_index = dumped_fcurve["dumped_array_index"] # create fcurve if needed - fcurve = target.fcurves.find( + fcurve = datablock.fcurves.find( dumped_data_path, index=dumped_array_index) if fcurve is None: - fcurve = target.fcurves.new( + fcurve = datablock.fcurves.new( dumped_data_path, index=dumped_array_index) load_fcurve(dumped_fcurve, fcurve) @@ -156,9 +265,12 @@ class BlAction(BlDatablock): id_root = data.get('id_root') if id_root: - target.id_root = id_root + datablock.id_root = id_root + + @staticmethod + def dump(datablock: object) -> dict: + stamp_uuid(datablock) - def _dump_implementation(self, data, instance=None): dumper = Dumper() dumper.exclude_filter = [ 'name_full', @@ -173,11 +285,11 @@ class BlAction(BlDatablock): 'users' ] dumper.depth = 1 - data = dumper.dump(instance) + data = dumper.dump(datablock) data["fcurves"] = [] - for fcurve in instance.fcurves: + for fcurve in datablock.fcurves: data["fcurves"].append(dump_fcurve(fcurve, use_numpy=True)) return data diff --git a/multi_user/bl_types/bl_armature.py b/multi_user/bl_types/bl_armature.py index 4a6b793..ccf0c8e 100644 --- a/multi_user/bl_types/bl_armature.py +++ b/multi_user/bl_types/bl_armature.py @@ -42,10 +42,10 @@ class BlArmature(BlDatablock): bl_icon = 'ARMATURE_DATA' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.armatures.new(data["name"]) - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): # Load parent object parent_object = utils.find_from_attr( 'uuid', @@ -119,7 +119,7 @@ class BlArmature(BlDatablock): if 'EDIT' in current_mode: bpy.ops.object.mode_set(mode='EDIT') - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) dumper = Dumper() diff --git a/multi_user/bl_types/bl_camera.py b/multi_user/bl_types/bl_camera.py index 9c8adf2..b420386 100644 --- a/multi_user/bl_types/bl_camera.py +++ b/multi_user/bl_types/bl_camera.py @@ -30,11 +30,11 @@ class BlCamera(BlDatablock): bl_icon = 'CAMERA_DATA' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.cameras.new(data["name"]) - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): loader = Loader() loader.load(target, data) @@ -56,7 +56,7 @@ class BlCamera(BlDatablock): target_img.image = bpy.data.images[img_id] loader.load(target_img, img_data) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) # TODO: background image support @@ -106,7 +106,7 @@ class BlCamera(BlDatablock): return dumper.dump(instance) @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] for background in datablock.background_images: if background.image: diff --git a/multi_user/bl_types/bl_collection.py b/multi_user/bl_types/bl_collection.py index e40bfff..cc451cd 100644 --- a/multi_user/bl_types/bl_collection.py +++ b/multi_user/bl_types/bl_collection.py @@ -20,9 +20,9 @@ import bpy import mathutils from .. import utils -from .bl_datablock import BlDatablock from .dump_anything import Loader, Dumper - +from replication.protocol import ReplicatedDatablock +from replication.objects import Node def dump_collection_children(collection): collection_children = [] @@ -81,42 +81,37 @@ def resolve_collection_dependencies(collection): return deps -class BlCollection(BlDatablock): +class BlCollection(ReplicatedDatablock): bl_id = "collections" bl_icon = 'FILE_FOLDER' bl_class = bpy.types.Collection bl_check_common = True bl_reload_parent = False - def construct(self, data): - if self.is_library: - with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData): - targetData.collections = [ - name for name in sourceData.collections if name == self.data['name']] + @staticmethod + def construct(data: dict) -> object: + datablock = bpy.data.collections.new(node.data["name"]) + return datablock - instance = bpy.data.collections[self.data['name']] - - return instance - - instance = bpy.data.collections.new(data["name"]) - return instance - - def _load_implementation(self, data, target): + @staticmethod + def load(data: dict, datablock: object): + data = node.data loader = Loader() - loader.load(target, data) + loader.load(datablock, data) # Objects - load_collection_objects(data['objects'], target) + load_collection_objects(data['objects'], datablock) # Link childrens - load_collection_childrens(data['children'], target) + load_collection_childrens(data['children'], datablock) # FIXME: Find a better way after the replication big refacotoring # Keep other user from deleting collection object by flushing their history utils.flush_history() - def _dump_implementation(self, data, instance=None): - assert(instance) + @staticmethod + def dump(datablock: object) -> dict: + assert(datablock) dumper = Dumper() dumper.depth = 1 @@ -124,16 +119,16 @@ class BlCollection(BlDatablock): "name", "instance_offset" ] - data = dumper.dump(instance) + data = dumper.dump(datablock) # dump objects - data['objects'] = dump_collection_objects(instance) + data['objects'] = dump_collection_objects(datablock) # dump children collections - data['children'] = dump_collection_children(instance) + data['children'] = dump_collection_children(datablock) return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: return resolve_collection_dependencies(datablock) diff --git a/multi_user/bl_types/bl_curve.py b/multi_user/bl_types/bl_curve.py index 332267e..27a8e63 100644 --- a/multi_user/bl_types/bl_curve.py +++ b/multi_user/bl_types/bl_curve.py @@ -141,10 +141,10 @@ class BlCurve(BlDatablock): bl_icon = 'CURVE_DATA' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.curves.new(data["name"], data["type"]) - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): loader = Loader() loader.load(target, data) @@ -175,7 +175,7 @@ class BlCurve(BlDatablock): if src_materials: load_materials_slots(src_materials, target.materials) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) dumper = Dumper() # Conflicting attributes @@ -223,7 +223,7 @@ class BlCurve(BlDatablock): return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] curve = datablock diff --git a/multi_user/bl_types/bl_datablock.py b/multi_user/bl_types/bl_datablock.py index c59d2f6..cce04bc 100644 --- a/multi_user/bl_types/bl_datablock.py +++ b/multi_user/bl_types/bl_datablock.py @@ -23,72 +23,14 @@ import bpy import mathutils from replication.constants import DIFF_BINARY, DIFF_JSON, UP from replication.protocol import ReplicatedDatablock +from replication.objects import Node + +from uuid import uuid4 from .. import utils from .dump_anything import Dumper, Loader -def has_action(target): - """ Check if the target datablock has actions - """ - return (hasattr(target, 'animation_data') - and target.animation_data - and target.animation_data.action) - - -def has_driver(target): - """ Check if the target datablock is driven - """ - return (hasattr(target, 'animation_data') - and target.animation_data - and target.animation_data.drivers) - - -def dump_driver(driver): - dumper = Dumper() - dumper.depth = 6 - data = dumper.dump(driver) - - return data - - -def load_driver(target_datablock, src_driver): - loader = Loader() - drivers = target_datablock.animation_data.drivers - src_driver_data = src_driver['driver'] - new_driver = drivers.new(src_driver['data_path'], index=src_driver['array_index']) - - # Settings - new_driver.driver.type = src_driver_data['type'] - new_driver.driver.expression = src_driver_data['expression'] - loader.load(new_driver, src_driver) - - # Variables - for src_variable in src_driver_data['variables']: - src_var_data = src_driver_data['variables'][src_variable] - new_var = new_driver.driver.variables.new() - new_var.name = src_var_data['name'] - new_var.type = src_var_data['type'] - - for src_target in src_var_data['targets']: - src_target_data = src_var_data['targets'][src_target] - new_var.targets[src_target].id = utils.resolve_from_id( - src_target_data['id'], src_target_data['id_type']) - loader.load( - new_var.targets[src_target], src_target_data) - - # Fcurve - new_fcurve = new_driver.keyframe_points - for p in reversed(new_fcurve): - new_fcurve.remove(p, fast=True) - - new_fcurve.add(len(src_driver['keyframe_points'])) - - for index, src_point in enumerate(src_driver['keyframe_points']): - new_point = new_fcurve[index] - loader.load(new_point, src_driver['keyframe_points'][src_point]) - - def get_datablock_from_uuid(uuid, default, ignore=[]): if not uuid: return default @@ -101,118 +43,17 @@ def get_datablock_from_uuid(uuid, default, ignore=[]): return default -class BlDatablock(ReplicatedDatablock): - """BlDatablock +def resolve_datablock_from_root(node:Node, root)->object: + datablock_ref = utils.find_from_attr('uuid', node.uuid, root) - bl_id : blender internal storage identifier - bl_class : blender internal type - bl_icon : type icon (blender icon name) - bl_check_common: enable check even in common rights - bl_reload_parent: reload parent - """ + if not datablock_ref: + try: + datablock_ref = root[node.data['name']] + except Exception: + pass + return datablock_ref - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - instance = kwargs.get('instance', None) - - self.preferences = utils.get_preferences() - - # TODO: use is_library_indirect - self.is_library = (instance and hasattr(instance, 'library') and - instance.library) or \ - (hasattr(self,'data') and self.data and 'library' in self.data) - - if instance and hasattr(instance, 'uuid'): - instance.uuid = self.uuid - - def resolve(self, construct = True): - datablock_root = getattr(bpy.data, self.bl_id) - datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root) - - if not datablock_ref: - try: - datablock_ref = datablock_root[self.data['name']] - except Exception: - pass - - if construct and not datablock_ref: - name = self.data.get('name') - logging.debug(f"Constructing {name}") - datablock_ref = self._construct(data=self.data) - - if datablock_ref is not None: - setattr(datablock_ref, 'uuid', self.uuid) - self.instance = datablock_ref - return True - else: - return False - - def dump(self, instance=None): - dumper = Dumper() - data = {} - # Dump animation data - if has_action(instance): - dumper = Dumper() - dumper.include_filter = ['action'] - data['animation_data'] = dumper.dump(instance.animation_data) - - if has_driver(instance): - dumped_drivers = {'animation_data': {'drivers': []}} - for driver in instance.animation_data.drivers: - dumped_drivers['animation_data']['drivers'].append( - dump_driver(driver)) - - data.update(dumped_drivers) - - if self.is_library: - data.update(dumper.dump(instance)) - else: - data.update(self._dump_implementation(data, instance=instance)) - - return data - - def _dump_implementation(self, data, target): - raise NotImplementedError - - def load(self, data, target): - # Load animation data - if 'animation_data' in data.keys(): - if target.animation_data is None: - target.animation_data_create() - - for d in target.animation_data.drivers: - target.animation_data.drivers.remove(d) - - if 'drivers' in data['animation_data']: - for driver in data['animation_data']['drivers']: - load_driver(target, driver) - - if 'action' in data['animation_data']: - target.animation_data.action = bpy.data.actions[data['animation_data']['action']] - # Remove existing animation data if there is not more to load - elif hasattr(target, 'animation_data') and target.animation_data: - target.animation_data_clear() - - if self.is_library: - return - else: - self._load_implementation(data, target) - - def _load_implementation(self, data, target): - raise NotImplementedError - - - def resolve_deps(self, datablock): - dependencies = [] - - if has_action(datablock): - dependencies.append(datablock.animation_data.action) - - dependencies.extend(self._resolve_deps_implementation(datablock)) - - return dependencies - - @staticmethod - def _resolve_deps_implementation(datablock): - return [] +def stamp_uuid(datablock): + if not datablock.uuid: + datablock.uuid = str(uuid4()) \ No newline at end of file diff --git a/multi_user/bl_types/bl_font.py b/multi_user/bl_types/bl_font.py index f2addd9..c69f545 100644 --- a/multi_user/bl_types/bl_font.py +++ b/multi_user/bl_types/bl_font.py @@ -34,7 +34,7 @@ class BlFont(BlDatablock): bl_icon = 'FILE_FONT' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: filename = data.get('filename') if filename == '': @@ -63,7 +63,7 @@ class BlFont(BlDatablock): return False @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] if datablock.filepath and datablock.filepath != '': ensure_unpacked(datablock) diff --git a/multi_user/bl_types/bl_gpencil.py b/multi_user/bl_types/bl_gpencil.py index 5f37c5a..691e8e7 100644 --- a/multi_user/bl_types/bl_gpencil.py +++ b/multi_user/bl_types/bl_gpencil.py @@ -235,10 +235,10 @@ class BlGpencil(BlDatablock): bl_icon = 'GREASEPENCIL' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.grease_pencils.new(data["name"]) - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): target.materials.clear() if "materials" in data.keys(): for mat in data['materials']: @@ -267,7 +267,7 @@ class BlGpencil(BlDatablock): - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) dumper = Dumper() dumper.depth = 2 @@ -291,7 +291,7 @@ class BlGpencil(BlDatablock): return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] for material in datablock.materials: diff --git a/multi_user/bl_types/bl_image.py b/multi_user/bl_types/bl_image.py index 1bc9d37..622c38f 100644 --- a/multi_user/bl_types/bl_image.py +++ b/multi_user/bl_types/bl_image.py @@ -55,7 +55,7 @@ class BlImage(BlDatablock): bl_icon = 'IMAGE_DATA' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.images.new( name=data['name'], width=data['size'][0], @@ -102,7 +102,7 @@ class BlImage(BlDatablock): return False @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] if datablock.packed_file: diff --git a/multi_user/bl_types/bl_lattice.py b/multi_user/bl_types/bl_lattice.py index 5d06aa1..32ad469 100644 --- a/multi_user/bl_types/bl_lattice.py +++ b/multi_user/bl_types/bl_lattice.py @@ -33,10 +33,10 @@ class BlLattice(BlDatablock): bl_icon = 'LATTICE_DATA' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.lattices.new(data["name"]) - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): if target.is_editmode: raise ContextError("lattice is in edit mode") @@ -45,7 +45,7 @@ class BlLattice(BlDatablock): np_load_collection(data['points'], target.points, POINT) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: if instance.is_editmode: raise ContextError("lattice is in edit mode") diff --git a/multi_user/bl_types/bl_library.py b/multi_user/bl_types/bl_library.py index 90cc130..89bb0fd 100644 --- a/multi_user/bl_types/bl_library.py +++ b/multi_user/bl_types/bl_library.py @@ -30,7 +30,7 @@ class BlLibrary(BlDatablock): bl_icon = 'LIBRARY_DATA_DIRECT' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: with bpy.data.libraries.load(filepath=data["filepath"], link=True) as (sourceData, targetData): targetData = sourceData return sourceData diff --git a/multi_user/bl_types/bl_light.py b/multi_user/bl_types/bl_light.py index de694d5..0445a2b 100644 --- a/multi_user/bl_types/bl_light.py +++ b/multi_user/bl_types/bl_light.py @@ -30,14 +30,14 @@ class BlLight(BlDatablock): bl_icon = 'LIGHT_DATA' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.lights.new(data["name"], data["type"]) - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): loader = Loader() loader.load(target, data) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) dumper = Dumper() dumper.depth = 3 diff --git a/multi_user/bl_types/bl_lightprobe.py b/multi_user/bl_types/bl_lightprobe.py index 53b264c..8f6ca56 100644 --- a/multi_user/bl_types/bl_lightprobe.py +++ b/multi_user/bl_types/bl_lightprobe.py @@ -31,7 +31,7 @@ class BlLightprobe(BlDatablock): bl_icon = 'LIGHTPROBE_GRID' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type'] # See https://developer.blender.org/D6396 if bpy.app.version[1] >= 83: @@ -39,11 +39,11 @@ class BlLightprobe(BlDatablock): else: logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396") - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): loader = Loader() loader.load(target, data) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) if bpy.app.version[1] < 83: logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396") diff --git a/multi_user/bl_types/bl_material.py b/multi_user/bl_types/bl_material.py index bccfb2d..9afba31 100644 --- a/multi_user/bl_types/bl_material.py +++ b/multi_user/bl_types/bl_material.py @@ -24,7 +24,9 @@ import re from uuid import uuid4 from .dump_anything import Loader, Dumper -from .bl_datablock import BlDatablock, get_datablock_from_uuid +from .bl_datablock import get_datablock_from_uuid, stamp_uuid +from replication.protocol import ReplicatedDatablock +from replication.objects import Node NODE_SOCKET_INDEX = re.compile('\[(\d*)\]') IGNORED_SOCKETS = ['GEOMETRY', 'SHADER'] @@ -34,7 +36,7 @@ def load_node(node_data: dict, node_tree: bpy.types.ShaderNodeTree): :arg node_data: dumped node data :type node_data: dict - :arg node_tree: target node_tree + :arg node_tree: datablock node_tree :type node_tree: bpy.types.NodeTree """ loader = Loader() @@ -84,7 +86,7 @@ def load_node(node_data: dict, node_tree: bpy.types.ShaderNodeTree): def dump_node(node: bpy.types.ShaderNode) -> dict: """ Dump a single node to a dict - :arg node: target node + :arg node: datablock node :type node: bpy.types.Node :retrun: dict """ @@ -241,7 +243,7 @@ def dump_node_tree(node_tree: bpy.types.ShaderNodeTree) -> dict: def dump_node_tree_sockets(sockets: bpy.types.Collection) -> dict: """ dump sockets of a shader_node_tree - :arg target_node_tree: target node_tree + :arg target_node_tree: datablock node_tree :type target_node_tree: bpy.types.NodeTree :arg socket_id: socket identifer :type socket_id: str @@ -264,7 +266,7 @@ def load_node_tree_sockets(sockets: bpy.types.Collection, sockets_data: dict): """ load sockets of a shader_node_tree - :arg target_node_tree: target node_tree + :arg target_node_tree: datablock node_tree :type target_node_tree: bpy.types.NodeTree :arg socket_id: socket identifer :type socket_id: str @@ -292,7 +294,7 @@ def load_node_tree(node_tree_data: dict, target_node_tree: bpy.types.ShaderNodeT :arg node_tree_data: dumped node data :type node_tree_data: dict - :arg target_node_tree: target node_tree + :arg target_node_tree: datablock node_tree :type target_node_tree: bpy.types.NodeTree """ # TODO: load only required nodes @@ -353,7 +355,7 @@ def load_materials_slots(src_materials: list, dst_materials: bpy.types.bpy_prop_ :arg src_materials: dumped material collection (ex: object.materials) :type src_materials: list of tuples (uuid, name) - :arg dst_materials: target material collection pointer + :arg dst_materials: datablock material collection pointer :type dst_materials: bpy.types.bpy_prop_collection """ # MATERIAL SLOTS @@ -372,36 +374,41 @@ def load_materials_slots(src_materials: list, dst_materials: bpy.types.bpy_prop_ dst_materials.append(mat_ref) -class BlMaterial(BlDatablock): +class BlMaterial(ReplicatedDatablock): bl_id = "materials" bl_class = bpy.types.Material bl_check_common = False bl_icon = 'MATERIAL_DATA' bl_reload_parent = False - def construct(self, data): + @staticmethod + def construct(data: dict) -> object: return bpy.data.materials.new(data["name"]) - def _load_implementation(self, data, target): + @staticmethod + def load(data: dict, datablock: object): + data = data loader = Loader() is_grease_pencil = data.get('is_grease_pencil') use_nodes = data.get('use_nodes') - loader.load(target, data) + loader.load(datablock, data) if is_grease_pencil: - if not target.is_grease_pencil: - bpy.data.materials.create_gpencil_data(target) - loader.load(target.grease_pencil, data['grease_pencil']) + if not datablock.is_grease_pencil: + bpy.data.materials.create_gpencil_data(datablock) + loader.load(datablock.grease_pencil, data['grease_pencil']) elif use_nodes: - if target.node_tree is None: - target.use_nodes = True + if datablock.node_tree is None: + datablock.use_nodes = True - load_node_tree(data['node_tree'], target.node_tree) + load_node_tree(data['node_tree'], datablock.node_tree) + + @staticmethod + def dump(datablock: object) -> dict: + stamp_uuid(datablock) - def _dump_implementation(self, data, instance=None): - assert(instance) mat_dumper = Dumper() mat_dumper.depth = 2 mat_dumper.include_filter = [ @@ -427,9 +434,9 @@ class BlMaterial(BlDatablock): 'line_priority', 'is_grease_pencil' ] - data = mat_dumper.dump(instance) + data = mat_dumper.dump(datablock) - if instance.is_grease_pencil: + if datablock.is_grease_pencil: gp_mat_dumper = Dumper() gp_mat_dumper.depth = 3 @@ -463,14 +470,14 @@ class BlMaterial(BlDatablock): 'use_overlap_strokes', 'use_fill_holdout', ] - data['grease_pencil'] = gp_mat_dumper.dump(instance.grease_pencil) - elif instance.use_nodes: - data['node_tree'] = dump_node_tree(instance.node_tree) + data['grease_pencil'] = gp_mat_dumper.dump(datablock.grease_pencil) + elif datablock.use_nodes: + data['node_tree'] = dump_node_tree(datablock.node_tree) return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: # TODO: resolve node group deps deps = [] diff --git a/multi_user/bl_types/bl_mesh.py b/multi_user/bl_types/bl_mesh.py index 27f2106..9ffc703 100644 --- a/multi_user/bl_types/bl_mesh.py +++ b/multi_user/bl_types/bl_mesh.py @@ -22,12 +22,21 @@ import mathutils import logging import numpy as np -from .dump_anything import Dumper, Loader, np_load_collection_primitives, np_dump_collection_primitive, np_load_collection, np_dump_collection +from .dump_anything import (Dumper, + Loader, + np_load_collection_primitives, + np_dump_collection_primitive, + np_load_collection, np_dump_collection) from replication.constants import DIFF_BINARY from replication.exception import ContextError -from .bl_datablock import BlDatablock, get_datablock_from_uuid +from replication.protocol import ReplicatedDatablock +from replication.objects import Node + +from .bl_datablock import get_datablock_from_uuid, stamp_uuid from .bl_material import dump_materials_slots, load_materials_slots +from ..preferences import get_preferences + VERTICE = ['co'] EDGE = [ @@ -49,80 +58,87 @@ POLYGON = [ 'material_index', ] -class BlMesh(BlDatablock): + +class BlMesh(ReplicatedDatablock): bl_id = "meshes" bl_class = bpy.types.Mesh bl_check_common = False bl_icon = 'MESH_DATA' bl_reload_parent = True - def construct(self, data): - instance = bpy.data.meshes.new(data["name"]) - instance.uuid = self.uuid - return instance + @staticmethod + def construct(data: dict) -> object: + datablock = bpy.data.meshes.new(data["name"]) + datablock.uuid = data['uuid'] + return datablock - def _load_implementation(self, data, target): - if not target or target.is_editmode: + @staticmethod + def load(data: dict, datablock: object): + data = data + + if not datablock or datablock.is_editmode: raise ContextError else: loader = Loader() - loader.load(target, data) + loader.load(datablock, data) # MATERIAL SLOTS src_materials = data.get('materials', None) if src_materials: - load_materials_slots(src_materials, target.materials) + load_materials_slots(src_materials, datablock.materials) # CLEAR GEOMETRY - if target.vertices: - target.clear_geometry() + if datablock.vertices: + datablock.clear_geometry() - target.vertices.add(data["vertex_count"]) - target.edges.add(data["egdes_count"]) - target.loops.add(data["loop_count"]) - target.polygons.add(data["poly_count"]) + datablock.vertices.add(data["vertex_count"]) + datablock.edges.add(data["egdes_count"]) + datablock.loops.add(data["loop_count"]) + datablock.polygons.add(data["poly_count"]) # LOADING - np_load_collection(data['vertices'], target.vertices, VERTICE) - np_load_collection(data['edges'], target.edges, EDGE) - np_load_collection(data['loops'], target.loops, LOOP) - np_load_collection(data["polygons"],target.polygons, POLYGON) + np_load_collection(data['vertices'], datablock.vertices, VERTICE) + np_load_collection(data['edges'], datablock.edges, EDGE) + np_load_collection(data['loops'], datablock.loops, LOOP) + np_load_collection(data["polygons"], datablock.polygons, POLYGON) # UV Layers if 'uv_layers' in data.keys(): for layer in data['uv_layers']: - if layer not in target.uv_layers: - target.uv_layers.new(name=layer) + if layer not in datablock.uv_layers: + datablock.uv_layers.new(name=layer) np_load_collection_primitives( - target.uv_layers[layer].data, - 'uv', + datablock.uv_layers[layer].data, + 'uv', data["uv_layers"][layer]['data']) - + # Vertex color if 'vertex_colors' in data.keys(): for color_layer in data['vertex_colors']: - if color_layer not in target.vertex_colors: - target.vertex_colors.new(name=color_layer) + if color_layer not in datablock.vertex_colors: + datablock.vertex_colors.new(name=color_layer) np_load_collection_primitives( - target.vertex_colors[color_layer].data, - 'color', + datablock.vertex_colors[color_layer].data, + 'color', data["vertex_colors"][color_layer]['data']) - target.validate() - target.update() + datablock.validate() + datablock.update() - def _dump_implementation(self, data, instance=None): - assert(instance) + @staticmethod + def dump(datablock: object) -> dict: + stamp_uuid(datablock) - if (instance.is_editmode or bpy.context.mode == "SCULPT") and not self.preferences.sync_flags.sync_during_editmode: + if (datablock.is_editmode or bpy.context.mode == "SCULPT") and not get_preferences().sync_flags.sync_during_editmode: raise ContextError("Mesh is in edit mode") - mesh = instance + mesh = datablock dumper = Dumper() dumper.depth = 1 dumper.include_filter = [ + 'uuid' 'name', 'use_auto_smooth', 'auto_smooth_angle', @@ -153,21 +169,23 @@ class BlMesh(BlDatablock): data['uv_layers'] = {} for layer in mesh.uv_layers: data['uv_layers'][layer.name] = {} - data['uv_layers'][layer.name]['data'] = np_dump_collection_primitive(layer.data, 'uv') + data['uv_layers'][layer.name]['data'] = np_dump_collection_primitive( + layer.data, 'uv') # Vertex color if mesh.vertex_colors: data['vertex_colors'] = {} for color_map in mesh.vertex_colors: data['vertex_colors'][color_map.name] = {} - data['vertex_colors'][color_map.name]['data'] = np_dump_collection_primitive(color_map.data, 'color') + data['vertex_colors'][color_map.name]['data'] = np_dump_collection_primitive( + color_map.data, 'color') # Materials - data['materials'] = dump_materials_slots(instance.materials) + data['materials'] = dump_materials_slots(datablock.materials) return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] for material in datablock.materials: @@ -178,7 +196,7 @@ class BlMesh(BlDatablock): def diff(self): if 'EDIT' in bpy.context.mode \ - and not self.preferences.sync_flags.sync_during_editmode: + and not get_preferences().sync_flags.sync_during_editmode: return False else: return super().diff() diff --git a/multi_user/bl_types/bl_metaball.py b/multi_user/bl_types/bl_metaball.py index 3aacf51..f02ef99 100644 --- a/multi_user/bl_types/bl_metaball.py +++ b/multi_user/bl_types/bl_metaball.py @@ -69,10 +69,10 @@ class BlMetaball(BlDatablock): bl_icon = 'META_BALL' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.metaballs.new(data["name"]) - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): loader = Loader() loader.load(target, data) @@ -83,7 +83,7 @@ class BlMetaball(BlDatablock): load_metaball_elements(data['elements'], target.elements) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) dumper = Dumper() dumper.depth = 1 diff --git a/multi_user/bl_types/bl_node_group.py b/multi_user/bl_types/bl_node_group.py index 7f4eefd..424c2a9 100644 --- a/multi_user/bl_types/bl_node_group.py +++ b/multi_user/bl_types/bl_node_group.py @@ -32,15 +32,15 @@ class BlNodeGroup(BlDatablock): bl_icon = 'NODETREE' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.node_groups.new(data["name"], data["type"]) - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): load_node_tree(data, target) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: return dump_node_tree(instance) @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: return get_node_tree_dependencies(datablock) \ No newline at end of file diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index 4ed444c..f59fc1c 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -21,8 +21,15 @@ import re import bpy import mathutils from replication.exception import ContextError +from replication.objects import Node +from replication.protocol import ReplicatedDatablock -from .bl_datablock import BlDatablock, get_datablock_from_uuid +from .bl_datablock import get_datablock_from_uuid, stamp_uuid +from .bl_action import (load_animation_data, + dump_animation_data, + get_animation_dependencies) + +from ..preferences import get_preferences from .dump_anything import ( Dumper, Loader, @@ -30,13 +37,13 @@ from .dump_anything import ( np_dump_collection) - SKIN_DATA = [ 'radius', 'use_loose', 'use_root' ] + def get_input_index(e): return int(re.findall('[0-9]+', e)[0]) @@ -95,45 +102,40 @@ def load_pose(target_bone, data): def find_data_from_name(name=None): - instance = None + datablock = None if not name: pass elif name in bpy.data.meshes.keys(): - instance = bpy.data.meshes[name] + datablock = bpy.data.meshes[name] elif name in bpy.data.lights.keys(): - instance = bpy.data.lights[name] + datablock = bpy.data.lights[name] elif name in bpy.data.cameras.keys(): - instance = bpy.data.cameras[name] + datablock = bpy.data.cameras[name] elif name in bpy.data.curves.keys(): - instance = bpy.data.curves[name] + datablock = bpy.data.curves[name] elif name in bpy.data.metaballs.keys(): - instance = bpy.data.metaballs[name] + datablock = bpy.data.metaballs[name] elif name in bpy.data.armatures.keys(): - instance = bpy.data.armatures[name] + datablock = bpy.data.armatures[name] elif name in bpy.data.grease_pencils.keys(): - instance = bpy.data.grease_pencils[name] + datablock = bpy.data.grease_pencils[name] elif name in bpy.data.curves.keys(): - instance = bpy.data.curves[name] + datablock = bpy.data.curves[name] elif name in bpy.data.lattices.keys(): - instance = bpy.data.lattices[name] + datablock = bpy.data.lattices[name] elif name in bpy.data.speakers.keys(): - instance = bpy.data.speakers[name] + datablock = bpy.data.speakers[name] elif name in bpy.data.lightprobes.keys(): # Only supported since 2.83 if bpy.app.version[1] >= 83: - instance = bpy.data.lightprobes[name] + datablock = bpy.data.lightprobes[name] else: logging.warning( "Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396") elif bpy.app.version[1] >= 91 and name in bpy.data.volumes.keys(): # Only supported since 2.91 - instance = bpy.data.volumes[name] - return instance - - -def load_data(object, name): - logging.info("loading data") - pass + datablock = bpy.data.volumes[name] + return datablock def _is_editmode(object: bpy.types.Object) -> bool: @@ -175,6 +177,7 @@ def find_geometry_nodes(modifiers: bpy.types.bpy_prop_collection) -> [bpy.types. return nodes_groups + def dump_vertex_groups(src_object: bpy.types.Object) -> dict: """ Dump object's vertex groups @@ -219,130 +222,127 @@ def load_vertex_groups(dumped_vertex_groups: dict, target_object: bpy.types.Obje for index, weight in vg['vertices']: vertex_group.add([index], weight, 'REPLACE') -class BlObject(BlDatablock): + +class BlObject(ReplicatedDatablock): bl_id = "objects" bl_class = bpy.types.Object bl_check_common = False bl_icon = 'OBJECT_DATA' bl_reload_parent = False - def construct(self, data): - instance = None - - if self.is_library: - with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData): - targetData.objects = [ - name for name in sourceData.objects if name == self.data['name']] - - instance = bpy.data.objects[self.data['name']] - instance.uuid = self.uuid - return instance + @staticmethod + def construct(data: dict) -> bpy.types.Object: + datablock = None # TODO: refactoring object_name = data.get("name") data_uuid = data.get("data_uuid") data_id = data.get("data") - + object_uuid = data.get('uuid') object_data = get_datablock_from_uuid( data_uuid, find_data_from_name(data_id), ignore=['images']) # TODO: use resolve_from_id if object_data is None and data_uuid: - raise Exception(f"Fail to load object {data['name']}({self.uuid})") + raise Exception(f"Fail to load object {data['name']}({object_uuid})") - instance = bpy.data.objects.new(object_name, object_data) - instance.uuid = self.uuid + datablock = bpy.data.objects.new(object_name, object_data) + datablock.uuid = object_uuid - return instance + return datablock + + @staticmethod + def load(data: dict, datablock: bpy.types.Object): + data = node.data + load_animation_data(data, datablock) - def _load_implementation(self, data, target): loader = Loader() data_uuid = data.get("data_uuid") data_id = data.get("data") - if target.data and (target.data.name != data_id): - target.data = get_datablock_from_uuid( + if datablock.data and (datablock.data.name != data_id): + datablock.data = get_datablock_from_uuid( data_uuid, find_data_from_name(data_id), ignore=['images']) # vertex groups vertex_groups = data.get('vertex_groups', None) if vertex_groups: - load_vertex_groups(vertex_groups, target) + load_vertex_groups(vertex_groups, datablock) - object_data = target.data + object_data = datablock.data # SHAPE KEYS if 'shape_keys' in data: - target.shape_key_clear() + datablock.shape_key_clear() # Create keys and load vertices coords for key_block in data['shape_keys']['key_blocks']: key_data = data['shape_keys']['key_blocks'][key_block] - target.shape_key_add(name=key_block) + datablock.shape_key_add(name=key_block) loader.load( - target.data.shape_keys.key_blocks[key_block], key_data) + datablock.data.shape_keys.key_blocks[key_block], key_data) for vert in key_data['data']: - target.data.shape_keys.key_blocks[key_block].data[vert].co = key_data['data'][vert]['co'] + datablock.data.shape_keys.key_blocks[key_block].data[vert].co = key_data['data'][vert]['co'] # Load relative key after all for key_block in data['shape_keys']['key_blocks']: reference = data['shape_keys']['key_blocks'][key_block]['relative_key'] - target.data.shape_keys.key_blocks[key_block].relative_key = target.data.shape_keys.key_blocks[reference] + datablock.data.shape_keys.key_blocks[key_block].relative_key = datablock.data.shape_keys.key_blocks[reference] # Load transformation data - loader.load(target, data) + loader.load(datablock, data) # Object display fields if 'display' in data: - loader.load(target.display, data['display']) + loader.load(datablock.display, data['display']) # Parenting parent_id = data.get('parent_id') if parent_id: parent = bpy.data.objects[parent_id] # Avoid reloading - if target.parent != parent and parent is not None: - target.parent = parent - elif target.parent: - target.parent = None + if datablock.parent != parent and parent is not None: + datablock.parent = parent + elif datablock.parent: + datablock.parent = None # Pose if 'pose' in data: - if not target.pose: + if not datablock.pose: raise Exception('No pose data yet (Fixed in a near futur)') # Bone groups for bg_name in data['pose']['bone_groups']: bg_data = data['pose']['bone_groups'].get(bg_name) - bg_target = target.pose.bone_groups.get(bg_name) + bg_datablock = datablock.pose.bone_groups.get(bg_name) - if not bg_target: - bg_target = target.pose.bone_groups.new(name=bg_name) + if not bg_datablock: + bg_datablock = datablock.pose.bone_groups.new(name=bg_name) - loader.load(bg_target, bg_data) - # target.pose.bone_groups.get + loader.load(bg_datablock, bg_data) + # datablock.pose.bone_groups.get # Bones for bone in data['pose']['bones']: - target_bone = target.pose.bones.get(bone) + datablock_bone = datablock.pose.bones.get(bone) bone_data = data['pose']['bones'].get(bone) if 'constraints' in bone_data.keys(): - loader.load(target_bone, bone_data['constraints']) + loader.load(datablock_bone, bone_data['constraints']) - load_pose(target_bone, bone_data) + load_pose(datablock_bone, bone_data) if 'bone_index' in bone_data.keys(): - target_bone.bone_group = target.pose.bone_group[bone_data['bone_group_index']] + datablock_bone.bone_group = datablock.pose.bone_group[bone_data['bone_group_index']] # TODO: find another way... - if target.empty_display_type == "IMAGE": + if datablock.empty_display_type == "IMAGE": img_uuid = data.get('data_uuid') - if target.data is None and img_uuid: - target.data = get_datablock_from_uuid(img_uuid, None) + if datablock.data is None and img_uuid: + datablock.data = get_datablock_from_uuid(img_uuid, None) if hasattr(object_data, 'skin_vertices') \ and object_data.skin_vertices\ @@ -353,34 +353,43 @@ class BlObject(BlDatablock): skin_data.data, SKIN_DATA) - if hasattr(target, 'cycles_visibility') \ - and 'cycles_visibility' in data: - loader.load(target.cycles_visibility, data['cycles_visibility']) + if hasattr(datablock, 'cycles_visibility') \ + and 'cycles_visibility' in data: + loader.load(datablock.cycles_visibility, data['cycles_visibility']) # TODO: handle geometry nodes input from dump_anything - if hasattr(target, 'modifiers'): - nodes_modifiers = [mod for mod in target.modifiers if mod.type == 'NODES'] + if hasattr(datablock, 'modifiers'): + nodes_modifiers = [ + mod for mod in datablock.modifiers if mod.type == 'NODES'] for modifier in nodes_modifiers: - load_modifier_geometry_node_inputs(data['modifiers'][modifier.name], modifier) + load_modifier_geometry_node_inputs( + data['modifiers'][modifier.name], modifier) transform = data.get('transforms', None) if transform: - target.matrix_parent_inverse = mathutils.Matrix(transform['matrix_parent_inverse']) - target.matrix_basis = mathutils.Matrix(transform['matrix_basis']) - target.matrix_local = mathutils.Matrix(transform['matrix_local']) + datablock.matrix_parent_inverse = mathutils.Matrix( + transform['matrix_parent_inverse']) + datablock.matrix_basis = mathutils.Matrix( + transform['matrix_basis']) + datablock.matrix_local = mathutils.Matrix( + transform['matrix_local']) - def _dump_implementation(self, data, instance=None): - assert(instance) + @staticmethod + def dump(datablock: bpy.types.Object) -> dict: + assert(datablock) - if _is_editmode(instance): - if self.preferences.sync_flags.sync_during_editmode: - instance.update_from_editmode() + stamp_uuid(datablock) + + if _is_editmode(datablock): + if get_preferences().sync_flags.sync_during_editmode: + datablock.update_from_editmode() else: raise ContextError("Object is in edit-mode.") dumper = Dumper() dumper.depth = 1 dumper.include_filter = [ + "uuid", "name", "rotation_mode", "data", @@ -413,30 +422,28 @@ class BlObject(BlDatablock): 'type' ] - data = dumper.dump(instance) + data = dumper.dump(datablock) dumper.include_filter = [ 'matrix_parent_inverse', 'matrix_local', 'matrix_basis'] - data['transforms'] = dumper.dump(instance) + data['transforms'] = dumper.dump(datablock) dumper.include_filter = [ 'show_shadows', ] - data['display'] = dumper.dump(instance.display) + data['display'] = dumper.dump(datablock.display) - data['data_uuid'] = getattr(instance.data, 'uuid', None) - if self.is_library: - return data + data['data_uuid'] = getattr(datablock.data, 'uuid', None) # PARENTING - if instance.parent: - data['parent_id'] = instance.parent.name + if datablock.parent: + data['parent_id'] = datablock.parent.name # MODIFIERS - if hasattr(instance, 'modifiers'): + if hasattr(datablock, 'modifiers'): data["modifiers"] = {} - modifiers = getattr(instance, 'modifiers', None) + modifiers = getattr(datablock, 'modifiers', None) if modifiers: dumper.include_filter = None dumper.depth = 1 @@ -444,9 +451,10 @@ class BlObject(BlDatablock): data["modifiers"][modifier.name] = dumper.dump(modifier) # hack to dump geometry nodes inputs if modifier.type == 'NODES': - dumped_inputs = dump_modifier_geometry_node_inputs(modifier) + dumped_inputs = dump_modifier_geometry_node_inputs( + modifier) data["modifiers"][modifier.name]['inputs'] = dumped_inputs - gp_modifiers = getattr(instance, 'grease_pencil_modifiers', None) + gp_modifiers = getattr(datablock, 'grease_pencil_modifiers', None) if gp_modifiers: dumper.include_filter = None @@ -468,16 +476,16 @@ class BlObject(BlDatablock): gp_mod_data['curve'] = curve_dumper.dump(modifier.curve) # CONSTRAINTS - if hasattr(instance, 'constraints'): + if hasattr(datablock, 'constraints'): dumper.include_filter = None dumper.depth = 3 - data["constraints"] = dumper.dump(instance.constraints) + data["constraints"] = dumper.dump(datablock.constraints) # POSE - if hasattr(instance, 'pose') and instance.pose: + if hasattr(datablock, 'pose') and datablock.pose: # BONES bones = {} - for bone in instance.pose.bones: + for bone in datablock.pose.bones: bones[bone.name] = {} dumper.depth = 1 rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler' @@ -502,7 +510,7 @@ class BlObject(BlDatablock): # GROUPS bone_groups = {} - for group in instance.pose.bone_groups: + for group in datablock.pose.bone_groups: dumper.depth = 3 dumper.include_filter = [ 'name', @@ -511,13 +519,12 @@ class BlObject(BlDatablock): bone_groups[group.name] = dumper.dump(group) data['pose']['bone_groups'] = bone_groups - # VERTEx GROUP - if len(instance.vertex_groups) > 0: - data['vertex_groups'] = dump_vertex_groups(instance) + if len(datablock.vertex_groups) > 0: + data['vertex_groups'] = dump_vertex_groups(datablock) # SHAPE KEYS - object_data = instance.data + object_data = datablock.data if hasattr(object_data, 'shape_keys') and object_data.shape_keys: dumper = Dumper() dumper.depth = 2 @@ -548,11 +555,12 @@ class BlObject(BlDatablock): if hasattr(object_data, 'skin_vertices') and object_data.skin_vertices: skin_vertices = list() for skin_data in object_data.skin_vertices: - skin_vertices.append(np_dump_collection(skin_data.data, SKIN_DATA)) + skin_vertices.append( + np_dump_collection(skin_data.data, SKIN_DATA)) data['skin_vertices'] = skin_vertices # CYCLE SETTINGS - if hasattr(instance, 'cycles_visibility'): + if hasattr(datablock, 'cycles_visibility'): dumper.include_filter = [ 'camera', 'diffuse', @@ -561,25 +569,28 @@ class BlObject(BlDatablock): 'scatter', 'shadow', ] - data['cycles_visibility'] = dumper.dump(instance.cycles_visibility) + data['cycles_visibility'] = dumper.dump( + datablock.cycles_visibility) return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: bpy.types.Object) -> list: deps = [] # Avoid Empty case if datablock.data: deps.append(datablock.data) - if datablock.parent : + if datablock.parent: deps.append(datablock.parent) if datablock.instance_type == 'COLLECTION': # TODO: uuid based deps.append(datablock.instance_collection) + deps.append(get_animation_dependencies(datablock)) + if datablock.modifiers: deps.extend(find_textures_dependencies(datablock.modifiers)) deps.extend(find_geometry_nodes(datablock.modifiers)) diff --git a/multi_user/bl_types/bl_scene.py b/multi_user/bl_types/bl_scene.py index 3e57364..c971ca7 100644 --- a/multi_user/bl_types/bl_scene.py +++ b/multi_user/bl_types/bl_scene.py @@ -23,14 +23,21 @@ import bpy import mathutils from deepdiff import DeepDiff from replication.constants import DIFF_JSON, MODIFIED +from replication.protocol import ReplicatedDatablock +from replication.objects import Node from ..utils import flush_history from .bl_collection import (dump_collection_children, dump_collection_objects, load_collection_childrens, load_collection_objects, resolve_collection_dependencies) -from .bl_datablock import BlDatablock +from .bl_action import (load_animation_data, + dump_animation_data, + get_animation_dependencies) +from .bl_datablock import stamp_uuid + from .bl_file import get_filepath from .dump_anything import Dumper, Loader +from ..preferences import get_preferences RENDER_SETTINGS = [ 'dither_intensity', @@ -286,12 +293,10 @@ def dump_sequence(sequence: bpy.types.Sequence) -> dict: dumper.depth = 1 data = dumper.dump(sequence) - # TODO: Support multiple images if sequence.type == 'IMAGE': data['filenames'] = [e.filename for e in sequence.elements] - # Effect strip inputs input_count = getattr(sequence, 'input_count', None) if input_count: @@ -321,53 +326,54 @@ def load_sequence(sequence_data: dict, sequence_editor: bpy.types.SequenceEditor if strip_type == 'SCENE': strip_scene = bpy.data.scenes.get(sequence_data.get('scene')) sequence = sequence_editor.sequences.new_scene(strip_name, - strip_scene, - strip_channel, - strip_frame_start) + strip_scene, + strip_channel, + strip_frame_start) elif strip_type == 'MOVIE': filepath = get_filepath(Path(sequence_data['filepath']).name) sequence = sequence_editor.sequences.new_movie(strip_name, - filepath, - strip_channel, - strip_frame_start) + filepath, + strip_channel, + strip_frame_start) elif strip_type == 'SOUND': filepath = bpy.data.sounds[sequence_data['sound']].filepath sequence = sequence_editor.sequences.new_sound(strip_name, - filepath, - strip_channel, - strip_frame_start) + filepath, + strip_channel, + strip_frame_start) elif strip_type == 'IMAGE': images_name = sequence_data.get('filenames') filepath = get_filepath(images_name[0]) sequence = sequence_editor.sequences.new_image(strip_name, - filepath, - strip_channel, - strip_frame_start) + filepath, + strip_channel, + strip_frame_start) # load other images - if len(images_name)>1: - for img_idx in range(1,len(images_name)): + if len(images_name) > 1: + for img_idx in range(1, len(images_name)): sequence.elements.append((images_name[img_idx])) else: seq = {} for i in range(sequence_data['input_count']): - seq[f"seq{i+1}"] = sequence_editor.sequences_all.get(sequence_data.get(f"input_{i+1}", None)) + seq[f"seq{i+1}"] = sequence_editor.sequences_all.get( + sequence_data.get(f"input_{i+1}", None)) sequence = sequence_editor.sequences.new_effect(name=strip_name, - type=strip_type, - channel=strip_channel, - frame_start=strip_frame_start, - frame_end=sequence_data['frame_final_end'], - **seq) + type=strip_type, + channel=strip_channel, + frame_start=strip_frame_start, + frame_end=sequence_data['frame_final_end'], + **seq) loader = Loader() - # TODO: Support filepath updates - loader.exclure_filter = ['filepath', 'sound', 'filenames','fps'] + # TODO: Support filepath updates + loader.exclure_filter = ['filepath', 'sound', 'filenames', 'fps'] loader.load(sequence, sequence_data) sequence.select = False -class BlScene(BlDatablock): +class BlScene(ReplicatedDatablock): is_root = True bl_id = "scenes" @@ -376,58 +382,60 @@ class BlScene(BlDatablock): bl_icon = 'SCENE_DATA' bl_reload_parent = False - def construct(self, data): - instance = bpy.data.scenes.new(data["name"]) - instance.uuid = self.uuid + @staticmethod + def construct(data: dict) -> object: + datablock = bpy.data.scenes.new(data["name"]) + datablock.uuid = data.get("uuid") - return instance + return datablock - def _load_implementation(self, data, target): + @staticmethod + def load(data: dict, datablock: object): # Load other meshes metadata loader = Loader() - loader.load(target, data) + loader.load(datablock, data) # Load master collection load_collection_objects( - data['collection']['objects'], target.collection) + data['collection']['objects'], datablock.collection) load_collection_childrens( - data['collection']['children'], target.collection) + data['collection']['children'], datablock.collection) if 'world' in data.keys(): - target.world = bpy.data.worlds[data['world']] + datablock.world = bpy.data.worlds[data['world']] # Annotation if 'grease_pencil' in data.keys(): - target.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']] + datablock.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']] - if self.preferences.sync_flags.sync_render_settings: + if get_preferences().sync_flags.sync_render_settings: if 'eevee' in data.keys(): - loader.load(target.eevee, data['eevee']) + loader.load(datablock.eevee, data['eevee']) if 'cycles' in data.keys(): - loader.load(target.cycles, data['cycles']) + loader.load(datablock.cycles, data['cycles']) if 'render' in data.keys(): - loader.load(target.render, data['render']) + loader.load(datablock.render, data['render']) if 'view_settings' in data.keys(): - loader.load(target.view_settings, data['view_settings']) - if target.view_settings.use_curve_mapping and \ + loader.load(datablock.view_settings, data['view_settings']) + if datablock.view_settings.use_curve_mapping and \ 'curve_mapping' in data['view_settings']: # TODO: change this ugly fix - target.view_settings.curve_mapping.white_level = data[ + datablock.view_settings.curve_mapping.white_level = data[ 'view_settings']['curve_mapping']['white_level'] - target.view_settings.curve_mapping.black_level = data[ + datablock.view_settings.curve_mapping.black_level = data[ 'view_settings']['curve_mapping']['black_level'] - target.view_settings.curve_mapping.update() + datablock.view_settings.curve_mapping.update() # Sequencer sequences = data.get('sequences') - + if sequences: # Create sequencer data - target.sequence_editor_create() - vse = target.sequence_editor + datablock.sequence_editor_create() + vse = datablock.sequence_editor # Clear removed sequences for seq in vse.sequences_all: @@ -437,15 +445,16 @@ class BlScene(BlDatablock): for seq_name, seq_data in sequences.items(): load_sequence(seq_data, vse) # If the sequence is no longer used, clear it - elif target.sequence_editor and not sequences: - target.sequence_editor_clear() + elif datablock.sequence_editor and not sequences: + datablock.sequence_editor_clear() # FIXME: Find a better way after the replication big refacotoring # Keep other user from deleting collection object by flushing their history flush_history() - def _dump_implementation(self, data, instance=None): - assert(instance) + @staticmethod + def dump(datablock: object) -> dict: + stamp_uuid(datablock) # Metadata scene_dumper = Dumper() @@ -458,41 +467,43 @@ class BlScene(BlDatablock): 'frame_start', 'frame_end', 'frame_step', + 'uuid' ] - if self.preferences.sync_flags.sync_active_camera: + if get_preferences().sync_flags.sync_active_camera: scene_dumper.include_filter.append('camera') - data.update(scene_dumper.dump(instance)) + data = scene_dumper.dump(datablock) + dump_animation_data(datablock, data) # Master collection data['collection'] = {} data['collection']['children'] = dump_collection_children( - instance.collection) + datablock.collection) data['collection']['objects'] = dump_collection_objects( - instance.collection) + datablock.collection) scene_dumper.depth = 1 scene_dumper.include_filter = None # Render settings - if self.preferences.sync_flags.sync_render_settings: + if get_preferences().sync_flags.sync_render_settings: scene_dumper.include_filter = RENDER_SETTINGS - data['render'] = scene_dumper.dump(instance.render) + data['render'] = scene_dumper.dump(datablock.render) - if instance.render.engine == 'BLENDER_EEVEE': + if datablock.render.engine == 'BLENDER_EEVEE': scene_dumper.include_filter = EVEE_SETTINGS - data['eevee'] = scene_dumper.dump(instance.eevee) - elif instance.render.engine == 'CYCLES': + data['eevee'] = scene_dumper.dump(datablock.eevee) + elif datablock.render.engine == 'CYCLES': scene_dumper.include_filter = CYCLES_SETTINGS - data['cycles'] = scene_dumper.dump(instance.cycles) + data['cycles'] = scene_dumper.dump(datablock.cycles) scene_dumper.include_filter = VIEW_SETTINGS - data['view_settings'] = scene_dumper.dump(instance.view_settings) + data['view_settings'] = scene_dumper.dump(datablock.view_settings) - if instance.view_settings.use_curve_mapping: + if datablock.view_settings.use_curve_mapping: data['view_settings']['curve_mapping'] = scene_dumper.dump( - instance.view_settings.curve_mapping) + datablock.view_settings.curve_mapping) scene_dumper.depth = 5 scene_dumper.include_filter = [ 'curves', @@ -500,21 +511,20 @@ class BlScene(BlDatablock): 'location', ] data['view_settings']['curve_mapping']['curves'] = scene_dumper.dump( - instance.view_settings.curve_mapping.curves) + datablock.view_settings.curve_mapping.curves) # Sequence - vse = instance.sequence_editor + vse = datablock.sequence_editor if vse: dumped_sequences = {} for seq in vse.sequences_all: dumped_sequences[seq.name] = dump_sequence(seq) data['sequences'] = dumped_sequences - return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] # Master Collection @@ -540,20 +550,20 @@ class BlScene(BlDatablock): for elem in sequence.elements: sequence.append( Path(bpy.path.abspath(sequence.directory), - elem.filename)) + elem.filename)) return deps def diff(self): exclude_path = [] - if not self.preferences.sync_flags.sync_render_settings: + if not get_preferences().sync_flags.sync_render_settings: exclude_path.append("root['eevee']") exclude_path.append("root['cycles']") exclude_path.append("root['view_settings']") exclude_path.append("root['render']") - if not self.preferences.sync_flags.sync_active_camera: + if not get_preferences().sync_flags.sync_active_camera: exclude_path.append("root['camera']") - return DeepDiff(self.data, self._dump(instance=self.instance), exclude_paths=exclude_path) + return DeepDiff(self.data, self._dump(datablock=self.datablock), exclude_paths=exclude_path) diff --git a/multi_user/bl_types/bl_sound.py b/multi_user/bl_types/bl_sound.py index 30a645c..a010269 100644 --- a/multi_user/bl_types/bl_sound.py +++ b/multi_user/bl_types/bl_sound.py @@ -34,7 +34,7 @@ class BlSound(BlDatablock): bl_icon = 'SOUND' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: filename = data.get('filename') return bpy.data.sounds.load(get_filepath(filename)) @@ -58,7 +58,7 @@ class BlSound(BlDatablock): } @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] if datablock.filepath and datablock.filepath != '': ensure_unpacked(datablock) diff --git a/multi_user/bl_types/bl_speaker.py b/multi_user/bl_types/bl_speaker.py index 90be1c9..1bfaf9c 100644 --- a/multi_user/bl_types/bl_speaker.py +++ b/multi_user/bl_types/bl_speaker.py @@ -30,14 +30,14 @@ class BlSpeaker(BlDatablock): bl_icon = 'SPEAKER' bl_reload_parent = False - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): loader = Loader() loader.load(target, data) - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.speakers.new(data["name"]) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) dumper = Dumper() @@ -61,7 +61,7 @@ class BlSpeaker(BlDatablock): return dumper.dump(instance) @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] sound = datablock.sound diff --git a/multi_user/bl_types/bl_texture.py b/multi_user/bl_types/bl_texture.py index 05b1e67..a8442a5 100644 --- a/multi_user/bl_types/bl_texture.py +++ b/multi_user/bl_types/bl_texture.py @@ -30,14 +30,14 @@ class BlTexture(BlDatablock): bl_icon = 'TEXTURE' bl_reload_parent = False - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): loader = Loader() loader.load(target, data) - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.textures.new(data["name"], data["type"]) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) dumper = Dumper() @@ -62,7 +62,7 @@ class BlTexture(BlDatablock): return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] image = getattr(datablock,"image", None) diff --git a/multi_user/bl_types/bl_volume.py b/multi_user/bl_types/bl_volume.py index 5a470bc..72c1770 100644 --- a/multi_user/bl_types/bl_volume.py +++ b/multi_user/bl_types/bl_volume.py @@ -31,7 +31,7 @@ class BlVolume(BlDatablock): bl_icon = 'VOLUME_DATA' bl_reload_parent = False - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): loader = Loader() loader.load(target, data) loader.load(target.display, data['display']) @@ -41,10 +41,10 @@ class BlVolume(BlDatablock): if src_materials: load_materials_slots(src_materials, target.materials) - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.volumes.new(data["name"]) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) dumper = Dumper() @@ -70,7 +70,7 @@ class BlVolume(BlDatablock): return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] external_vdb = Path(bpy.path.abspath(datablock.filepath)) diff --git a/multi_user/bl_types/bl_world.py b/multi_user/bl_types/bl_world.py index cd69604..63b8e59 100644 --- a/multi_user/bl_types/bl_world.py +++ b/multi_user/bl_types/bl_world.py @@ -33,10 +33,10 @@ class BlWorld(BlDatablock): bl_icon = 'WORLD_DATA' bl_reload_parent = False - def construct(self, data): + def construct(data: dict) -> object: return bpy.data.worlds.new(data["name"]) - def _load_implementation(self, data, target): + def load(data: dict, datablock: object): loader = Loader() loader.load(target, data) @@ -46,7 +46,7 @@ class BlWorld(BlDatablock): load_node_tree(data['node_tree'], target.node_tree) - def _dump_implementation(self, data, instance=None): + def dump(datablock: object) -> dict: assert(instance) world_dumper = Dumper() @@ -63,7 +63,7 @@ class BlWorld(BlDatablock): return data @staticmethod - def _resolve_deps_implementation(datablock): + def resolve_deps(datablock: object) -> [object]: deps = [] if datablock.use_nodes: diff --git a/multi_user/libs/replication b/multi_user/libs/replication index 63093ec..ebfe0e9 160000 --- a/multi_user/libs/replication +++ b/multi_user/libs/replication @@ -1 +1 @@ -Subproject commit 63093ecf453a6e253c828ae15e5a3281cfcc2358 +Subproject commit ebfe0e9e853cd10e74c4102a59199b9fc150bb31 diff --git a/multi_user/preferences.py b/multi_user/preferences.py index 7df200b..2efddc5 100644 --- a/multi_user/preferences.py +++ b/multi_user/preferences.py @@ -517,6 +517,8 @@ class SessionProps(bpy.types.PropertyGroup): default=False ) +def get_preferences(): + return bpy.context.preferences.addons[__package__].preferences classes = ( SessionUser,