diff --git a/multi_user/bl_types/bl_datablock.py b/multi_user/bl_types/bl_datablock.py index 22ccb3a..27d3f69 100644 --- a/multi_user/bl_types/bl_datablock.py +++ b/multi_user/bl_types/bl_datablock.py @@ -113,7 +113,7 @@ class BlDatablock(ReplicatedDatablock): if instance and hasattr(instance, 'uuid'): instance.uuid = self.uuid - # self.diff_method = DIFF_BINARY + self.diff_method = DIFF_BINARY def resolve(self): datablock_ref = None diff --git a/multi_user/bl_types/bl_file_datablock.py b/multi_user/bl_types/bl_file_datablock.py new file mode 100644 index 0000000..66e6244 --- /dev/null +++ b/multi_user/bl_types/bl_file_datablock.py @@ -0,0 +1,166 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# ##### END GPL LICENSE BLOCK ##### + + +import bpy +import mathutils +import logging +import pathlib +import os + +from .. import utils +from .dump_anything import Loader, Dumper +from replication.data import ReplicatedDatablock +from replication.constants import (UP, DIFF_BINARY) + + +class BlFileDatablock(ReplicatedDatablock): + """BlDatablock + + bl_id : blender internal storage identifier + bl_class : blender internal type + bl_delay_refresh : refresh rate in second for observers + bl_delay_apply : refresh rate in sec for apply + bl_automatic_push : boolean + bl_icon : type icon (blender icon name) + bl_check_common: enable check even in common rights + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + instance = kwargs.get('instance', None) + + self.preferences = utils.get_preferences() + + if instance and hasattr(instance, 'uuid'): + instance.uuid = self.uuid + + self.diff_method = DIFF_BINARY + + def resolve(self): + datablock_ref = None + 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: + name = self.data.get('name') + logging.debug(f"Constructing {name}") + datablock_ref = self._construct(data=self.data) + + if datablock_ref: + setattr(datablock_ref, 'uuid', self.uuid) + + self.instance = datablock_ref + + def remove_instance(self): + """ + Remove instance from blender data + """ + assert(self.instance) + + datablock_root = getattr(bpy.data, self.bl_id) + datablock_root.remove(self.instance) + + def get_filepath(self): + ext = pathlib.Path(self.data['filepath']).suffix + if ext: + name = f"{self.uuid}{ext}" + return os.path.join(self.preferences.cache_directory, name) + else: + return self.data['filepath'] + + def _construct(self, data): + filepath = self.get_filepath() + + # Step 1: load content + if 'file' in data.keys(): + self._write_content(data['file'], filepath) + else: + logging.warning("No data to write, skipping.") + # Step 2: construct the file + root = getattr(bpy.data, self.bl_id) + + # Step 3: construct the datablock + return root.load(filepath) + + def _dump(self, instance=None): + # Step 1: dump related metadata + data = self._dump_metadata(instance=instance) + + # Step 2: dump file content + file_content = self._read_content(instance.filepath) + + if file_content: + data['file'] = file_content + + return data + + def _load(self, target, data): + self._load_metadata(target, data) + + def _dump_metadata(self, data, target): + """ + Dump datablock metadata + """ + raise NotImplementedError() + + def _read_content(self, filepath): + """ + Dump file content + """ + logging.info("Reading file content") + + content = None + + try: + file = open(bpy.path.abspath(self.instance.filepath), 'rb') + content = file.read() + except IOError: + logging.warning(f"{filepath} doesn't exist, skipping") + else: + file.close() + + return content + + def _load_metadata(self, target, data): + raise NotImplementedError + + def _write_content(self, content, filepath): + """ + Write content on the disk + """ + logging.info("Writing file content") + + try: + file = open(filepath, 'wb') + file.write(content) + except IOError: + logging.warning(f"{self.uuid} writing error, skipping.") + else: + file.close() + + def resolve_deps(self): + return [] + + def is_valid(self): + return getattr(bpy.data, self.bl_id).get(self.data['name']) + + def diff(self): + return False diff --git a/multi_user/bl_types/bl_font.py b/multi_user/bl_types/bl_font.py index 599f3ad..0029d1e 100644 --- a/multi_user/bl_types/bl_font.py +++ b/multi_user/bl_types/bl_font.py @@ -23,9 +23,10 @@ import logging import pathlib from .. import utils from .dump_anything import Loader, Dumper -from .bl_datablock import BlDatablock +from .bl_file_datablock import BlFileDatablock -class BlFont(BlDatablock): + +class BlFont(BlFileDatablock): bl_id = "fonts" bl_class = bpy.types.VectorFont bl_delay_refresh = 1 @@ -34,36 +35,15 @@ class BlFont(BlDatablock): bl_check_common = False bl_icon = 'FILE_FONT' - def _construct(self, data): - if data['filepath'] == '': - return bpy.data.fonts.load(data['filepath']) - elif 'font_file' in data.keys(): - prefs = utils.get_preferences() - ext = pathlib.Path(data['filepath']).suffix - font_name = f"{self.uuid}{ext}" - font_path = os.path.join(prefs.cache_directory, font_name) - - os.makedirs(prefs.cache_directory, exist_ok=True) - file = open(font_path, 'wb') - file.write(data["font_file"]) - file.close() - - logging.info(f'loading {font_path}') - return bpy.data.fonts.load(font_path) - - def _load(self, data, target): + def _load_metadata(self, data, target): + # No metadate for fonts pass - def _dump(self, instance=None): - data = { - 'filepath':instance.filepath, - 'name':instance.name + def _dump_metadata(self, instance=None): + return { + 'filepath': instance.filepath, + 'name': instance.name } - if instance.filepath != '' and not instance.is_embedded_data: - file = open(instance.filepath, "rb") - data['font_file'] = file.read() - file.close() - return data def diff(self): return False