refactor: protocol refactoring part 1 (mesh, object, action, scene)

This commit is contained in:
Swann
2021-05-18 23:14:09 +02:00
parent 26140eefb2
commit ffb70ab74c
32 changed files with 500 additions and 545 deletions

View File

@ -49,8 +49,18 @@ if bpy.app.version[1] >= 91:
__all__.append('bl_volume') __all__.append('bl_volume')
from . import * from . import *
from replication.data import DataTranslationProtocol
def types_to_register(): def types_to_register():
return __all__ return __all__
from replication.protocol import DataTranslationProtocol
def get_data_translation_protocol()-> DataTranslationProtocol:
""" Return a data translation protocol from implemented bpy types
"""
bpy_protocol = DataTranslationProtocol()
for module_name in __all__:
impl = globals().get(module_name)
if impl and hasattr(impl, "_type") and hasattr(impl, "_type"):
bpy_protocol.register_implementation(impl._type, impl._class)
return bpy_protocol

View File

@ -25,8 +25,8 @@ from enum import Enum
from .. import utils from .. import utils
from .dump_anything import ( from .dump_anything import (
Dumper, Loader, np_dump_collection, np_load_collection, remove_items_from_dict) Dumper, Loader, np_dump_collection, np_load_collection, remove_items_from_dict)
from .bl_datablock import BlDatablock, has_action, has_driver, dump_driver, load_driver from replication.protocol import ReplicatedDatablock
from .bl_datablock import resolve_datablock_from_uuid
KEYFRAME = [ KEYFRAME = [
'amplitude', 'amplitude',
@ -41,6 +41,66 @@ KEYFRAME = [
'interpolation', 'interpolation',
] ]
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]
src_id = src_target_data.get('id')
if src_id:
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: def dump_fcurve(fcurve: bpy.types.FCurve, use_numpy: bool = True) -> dict:
""" Dump a sigle curve to a dict """ Dump a sigle curve to a dict
@ -198,26 +258,28 @@ def resolve_animation_dependencies(datablock):
return [] return []
class BlAction(BlDatablock): class BlAction(ReplicatedDatablock):
bl_id = "actions" bl_id = "actions"
bl_class = bpy.types.Action bl_class = bpy.types.Action
bl_check_common = False bl_check_common = False
bl_icon = 'ACTION_TWEAK' bl_icon = 'ACTION_TWEAK'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): @staticmethod
def construct(data: dict) -> object:
return bpy.data.actions.new(data["name"]) 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"]: for dumped_fcurve in data["fcurves"]:
dumped_data_path = dumped_fcurve["data_path"] dumped_data_path = dumped_fcurve["data_path"]
dumped_array_index = dumped_fcurve["dumped_array_index"] dumped_array_index = dumped_fcurve["dumped_array_index"]
# create fcurve if needed # create fcurve if needed
fcurve = target.fcurves.find( fcurve = datablock.fcurves.find(
dumped_data_path, index=dumped_array_index) dumped_data_path, index=dumped_array_index)
if fcurve is None: if fcurve is None:
fcurve = target.fcurves.new( fcurve = datablock.fcurves.new(
dumped_data_path, index=dumped_array_index) dumped_data_path, index=dumped_array_index)
load_fcurve(dumped_fcurve, fcurve) load_fcurve(dumped_fcurve, fcurve)
@ -225,9 +287,10 @@ class BlAction(BlDatablock):
id_root = data.get('id_root') id_root = data.get('id_root')
if id_root: if id_root:
target.id_root = id_root datablock.id_root = id_root
def _dump_implementation(self, data, instance=None): @staticmethod
def dump(datablock: object) -> dict:
dumper = Dumper() dumper = Dumper()
dumper.exclude_filter = [ dumper.exclude_filter = [
'name_full', 'name_full',
@ -242,11 +305,26 @@ class BlAction(BlDatablock):
'users' 'users'
] ]
dumper.depth = 1 dumper.depth = 1
data = dumper.dump(instance) data = dumper.dump(datablock)
data["fcurves"] = [] data["fcurves"] = []
for fcurve in instance.fcurves: for fcurve in datablock.fcurves:
data["fcurves"].append(dump_fcurve(fcurve, use_numpy=True)) data["fcurves"].append(dump_fcurve(fcurve, use_numpy=True))
return data return data
@staticmethod
def resolve(data: dict) -> object:
uuid = data.get('uuid')
name = data.get('name')
datablock = resolve_datablock_from_uuid(uuid, bpy.data.actions)
if datablock is None:
bpy.data.actions.get(name)
@staticmethod
def resolve_deps(datablock: object) -> [object]:
return []
_type = bpy.types.Action
_class = BlAction

View File

@ -22,7 +22,7 @@ import mathutils
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .. import presence, operators, utils from .. import presence, operators, utils
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
def get_roll(bone: bpy.types.Bone) -> float: def get_roll(bone: bpy.types.Bone) -> float:
@ -35,17 +35,17 @@ def get_roll(bone: bpy.types.Bone) -> float:
return bone.AxisRollFromMatrix(bone.matrix_local.to_3x3())[1] return bone.AxisRollFromMatrix(bone.matrix_local.to_3x3())[1]
class BlArmature(BlDatablock): class BlArmature(ReplicatedDatablock):
bl_id = "armatures" bl_id = "armatures"
bl_class = bpy.types.Armature bl_class = bpy.types.Armature
bl_check_common = False bl_check_common = False
bl_icon = 'ARMATURE_DATA' bl_icon = 'ARMATURE_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.armatures.new(data["name"]) return bpy.data.armatures.new(data["name"])
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
# Load parent object # Load parent object
parent_object = utils.find_from_attr( parent_object = utils.find_from_attr(
'uuid', 'uuid',
@ -119,7 +119,7 @@ class BlArmature(BlDatablock):
if 'EDIT' in current_mode: if 'EDIT' in current_mode:
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
dumper = Dumper() dumper = Dumper()

View File

@ -20,21 +20,21 @@ import bpy
import mathutils import mathutils
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
class BlCamera(BlDatablock): class BlCamera(ReplicatedDatablock):
bl_id = "cameras" bl_id = "cameras"
bl_class = bpy.types.Camera bl_class = bpy.types.Camera
bl_check_common = False bl_check_common = False
bl_icon = 'CAMERA_DATA' bl_icon = 'CAMERA_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.cameras.new(data["name"]) return bpy.data.cameras.new(data["name"])
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(target, data)
@ -61,7 +61,7 @@ class BlCamera(BlDatablock):
loader.load(target_img.image_user, img_user) loader.load(target_img.image_user, img_user)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
# TODO: background image support # TODO: background image support
@ -119,7 +119,7 @@ class BlCamera(BlDatablock):
if image.image_user: if image.image_user:
data['background_images'][index]['image_user'] = dumper.dump(image.image_user) data['background_images'][index]['image_user'] = dumper.dump(image.image_user)
return data return data
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
deps = [] deps = []
for background in self.instance.background_images: for background in self.instance.background_images:
if background.image: if background.image:

View File

@ -20,7 +20,7 @@ import bpy
import mathutils import mathutils
from .. import utils from .. import utils
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
@ -81,14 +81,14 @@ def resolve_collection_dependencies(collection):
return deps return deps
class BlCollection(BlDatablock): class BlCollection(ReplicatedDatablock):
bl_id = "collections" bl_id = "collections"
bl_icon = 'FILE_FOLDER' bl_icon = 'FILE_FOLDER'
bl_class = bpy.types.Collection bl_class = bpy.types.Collection
bl_check_common = True bl_check_common = True
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
if self.is_library: if self.is_library:
with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData): with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData):
targetData.collections = [ targetData.collections = [
@ -101,7 +101,7 @@ class BlCollection(BlDatablock):
instance = bpy.data.collections.new(data["name"]) instance = bpy.data.collections.new(data["name"])
return instance return instance
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(target, data)
@ -115,7 +115,7 @@ class BlCollection(BlDatablock):
# Keep other user from deleting collection object by flushing their history # Keep other user from deleting collection object by flushing their history
utils.flush_history() utils.flush_history()
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
dumper = Dumper() dumper = Dumper()
@ -134,5 +134,5 @@ class BlCollection(BlDatablock):
return data return data
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
return resolve_collection_dependencies(self.instance) return resolve_collection_dependencies(self.instance)

View File

@ -22,11 +22,10 @@ import mathutils
import logging import logging
from .. import utils from .. import utils
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
from .dump_anything import (Dumper, Loader, from .dump_anything import (Dumper, Loader,
np_load_collection, np_load_collection,
np_dump_collection) np_dump_collection)
from .bl_datablock import get_datablock_from_uuid
from .bl_material import dump_materials_slots, load_materials_slots from .bl_material import dump_materials_slots, load_materials_slots
SPLINE_BEZIER_POINT = [ SPLINE_BEZIER_POINT = [
@ -134,17 +133,17 @@ SPLINE_METADATA = [
] ]
class BlCurve(BlDatablock): class BlCurve(ReplicatedDatablock):
bl_id = "curves" bl_id = "curves"
bl_class = bpy.types.Curve bl_class = bpy.types.Curve
bl_check_common = False bl_check_common = False
bl_icon = 'CURVE_DATA' bl_icon = 'CURVE_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.curves.new(data["name"], data["type"]) return bpy.data.curves.new(data["name"], data["type"])
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(target, data)
@ -175,7 +174,7 @@ class BlCurve(BlDatablock):
if src_materials: if src_materials:
load_materials_slots(src_materials, target.materials) load_materials_slots(src_materials, target.materials)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
dumper = Dumper() dumper = Dumper()
# Conflicting attributes # Conflicting attributes
@ -222,7 +221,7 @@ class BlCurve(BlDatablock):
return data return data
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
# TODO: resolve material # TODO: resolve material
deps = [] deps = []
curve = self.instance curve = self.instance

View File

@ -22,73 +22,11 @@ from collections.abc import Iterable
import bpy import bpy
import mathutils import mathutils
from replication.constants import DIFF_BINARY, DIFF_JSON, UP from replication.constants import DIFF_BINARY, DIFF_JSON, UP
from replication.data import ReplicatedDatablock from replication.protocol import ReplicatedDatablock
from .. import utils from .. import utils
from .dump_anything import Dumper, Loader 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]
src_id = src_target_data.get('id')
if src_id:
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=[]): def get_datablock_from_uuid(uuid, default, ignore=[]):
if not uuid: if not uuid:
return default return default
@ -100,133 +38,30 @@ def get_datablock_from_uuid(uuid, default, ignore=[]):
return item return item
return default return default
def resolve_datablock_from_uuid(uuid, bpy_collection):
for item in bpy_collection:
if getattr(item, 'uuid', None) == uuid:
return item
return None
class BlDatablock(ReplicatedDatablock): def resolve_from_root(data: dict, root: str, construct = True):
"""BlDatablock datablock_root = getattr(bpy.data, self.bl_id)
datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root)
bl_id : blender internal storage identifier if not datablock_ref:
bl_class : blender internal type try:
bl_icon : type icon (blender icon name) datablock_ref = datablock_root[self.data['name']]
bl_check_common: enable check even in common rights except Exception:
bl_reload_parent: reload parent pass
"""
if construct and not datablock_ref:
name = self.data.get('name')
logging.debug(f"Constructing {name}")
datablock_ref = self._construct(data=self.data)
def __init__(self, *args, **kwargs): if datablock_ref is not None:
super().__init__(*args, **kwargs) setattr(datablock_ref, 'uuid', self.uuid)
instance = kwargs.get('instance', None) self.instance = datablock_ref
return True
self.preferences = utils.get_preferences() else:
return False
# 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 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 _dump(self, instance=None):
dumper = Dumper()
data = {}
animation_data = {}
# Dump animation data
if has_action(instance):
animation_data['action'] = instance.animation_data.action.name
if has_driver(instance):
animation_data['drivers'] = []
for driver in instance.animation_data.drivers:
animation_data['drivers'].append(dump_driver(driver))
if animation_data:
data['animation_data'] = animation_data
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']]
elif target.animation_data.action:
target.animation_data.action = None
# 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):
dependencies = []
if has_action(self.instance):
dependencies.append(self.instance.animation_data.action)
if not self.is_library:
dependencies.extend(self._resolve_deps_implementation())
logging.debug(f"{self.instance} dependencies: {dependencies}")
return dependencies
def _resolve_deps_implementation(self):
return []
def is_valid(self):
return getattr(bpy.data, self.bl_id).get(self.data['name'])

View File

@ -24,7 +24,7 @@ from pathlib import Path
import bpy import bpy
import mathutils import mathutils
from replication.constants import DIFF_BINARY, UP from replication.constants import DIFF_BINARY, UP
from replication.data import ReplicatedDatablock from replication.protocol import ReplicatedDatablock
from .. import utils from .. import utils
from .dump_anything import Dumper, Loader from .dump_anything import Dumper, Loader

View File

@ -22,19 +22,19 @@ from pathlib import Path
import bpy import bpy
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
from .bl_file import get_filepath, ensure_unpacked from .bl_file import get_filepath, ensure_unpacked
from .dump_anything import Dumper, Loader from .dump_anything import Dumper, Loader
class BlFont(BlDatablock): class BlFont(ReplicatedDatablock):
bl_id = "fonts" bl_id = "fonts"
bl_class = bpy.types.VectorFont bl_class = bpy.types.VectorFont
bl_check_common = False bl_check_common = False
bl_icon = 'FILE_FONT' bl_icon = 'FILE_FONT'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
filename = data.get('filename') filename = data.get('filename')
if filename == '<builtin>': if filename == '<builtin>':
@ -62,7 +62,7 @@ class BlFont(BlDatablock):
def diff(self): def diff(self):
return False return False
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
deps = [] deps = []
if self.instance.filepath and self.instance.filepath != '<builtin>': if self.instance.filepath and self.instance.filepath != '<builtin>':
ensure_unpacked(self.instance) ensure_unpacked(self.instance)

View File

@ -24,7 +24,7 @@ from .dump_anything import (Dumper,
Loader, Loader,
np_dump_collection, np_dump_collection,
np_load_collection) np_load_collection)
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
# GPencil data api is structured as it follow: # GPencil data api is structured as it follow:
# GP-Object --> GP-Layers --> GP-Frames --> GP-Strokes --> GP-Stroke-Points # GP-Object --> GP-Layers --> GP-Frames --> GP-Strokes --> GP-Stroke-Points
@ -228,17 +228,17 @@ def load_layer(layer_data, layer):
load_frame(frame_data, target_frame) load_frame(frame_data, target_frame)
class BlGpencil(BlDatablock): class BlGpencil(ReplicatedDatablock):
bl_id = "grease_pencils" bl_id = "grease_pencils"
bl_class = bpy.types.GreasePencil bl_class = bpy.types.GreasePencil
bl_check_common = False bl_check_common = False
bl_icon = 'GREASEPENCIL' bl_icon = 'GREASEPENCIL'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.grease_pencils.new(data["name"]) return bpy.data.grease_pencils.new(data["name"])
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
target.materials.clear() target.materials.clear()
if "materials" in data.keys(): if "materials" in data.keys():
for mat in data['materials']: 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) assert(instance)
dumper = Dumper() dumper = Dumper()
dumper.depth = 2 dumper.depth = 2
@ -290,7 +290,7 @@ class BlGpencil(BlDatablock):
data["eval_frame"] = bpy.context.scene.frame_current data["eval_frame"] = bpy.context.scene.frame_current
return data return data
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
deps = [] deps = []
for material in self.instance.materials: for material in self.instance.materials:

View File

@ -24,7 +24,7 @@ import bpy
import mathutils import mathutils
from .. import utils from .. import utils
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
from .dump_anything import Dumper, Loader from .dump_anything import Dumper, Loader
from .bl_file import get_filepath, ensure_unpacked from .bl_file import get_filepath, ensure_unpacked
@ -48,14 +48,14 @@ format_to_ext = {
} }
class BlImage(BlDatablock): class BlImage(ReplicatedDatablock):
bl_id = "images" bl_id = "images"
bl_class = bpy.types.Image bl_class = bpy.types.Image
bl_check_common = False bl_check_common = False
bl_icon = 'IMAGE_DATA' bl_icon = 'IMAGE_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.images.new( return bpy.data.images.new(
name=data['name'], name=data['name'],
width=data['size'][0], width=data['size'][0],
@ -105,7 +105,7 @@ class BlImage(BlDatablock):
else: else:
return None return None
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
deps = [] deps = []
if self.instance.packed_file: if self.instance.packed_file:

View File

@ -20,23 +20,23 @@ import bpy
import mathutils import mathutils
from .dump_anything import Dumper, Loader, np_dump_collection, np_load_collection from .dump_anything import Dumper, Loader, np_dump_collection, np_load_collection
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
from replication.exception import ContextError from replication.exception import ContextError
POINT = ['co', 'weight_softbody', 'co_deform'] POINT = ['co', 'weight_softbody', 'co_deform']
class BlLattice(BlDatablock): class BlLattice(ReplicatedDatablock):
bl_id = "lattices" bl_id = "lattices"
bl_class = bpy.types.Lattice bl_class = bpy.types.Lattice
bl_check_common = False bl_check_common = False
bl_icon = 'LATTICE_DATA' bl_icon = 'LATTICE_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.lattices.new(data["name"]) return bpy.data.lattices.new(data["name"])
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
if target.is_editmode: if target.is_editmode:
raise ContextError("lattice is in edit mode") raise ContextError("lattice is in edit mode")
@ -45,7 +45,7 @@ class BlLattice(BlDatablock):
np_load_collection(data['points'], target.points, POINT) np_load_collection(data['points'], target.points, POINT)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
if instance.is_editmode: if instance.is_editmode:
raise ContextError("lattice is in edit mode") raise ContextError("lattice is in edit mode")

View File

@ -20,17 +20,17 @@ import bpy
import mathutils import mathutils
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
class BlLibrary(BlDatablock): class BlLibrary(ReplicatedDatablock):
bl_id = "libraries" bl_id = "libraries"
bl_class = bpy.types.Library bl_class = bpy.types.Library
bl_check_common = False bl_check_common = False
bl_icon = 'LIBRARY_DATA_DIRECT' bl_icon = 'LIBRARY_DATA_DIRECT'
bl_reload_parent = False 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): with bpy.data.libraries.load(filepath=data["filepath"], link=True) as (sourceData, targetData):
targetData = sourceData targetData = sourceData
return sourceData return sourceData

View File

@ -20,24 +20,24 @@ import bpy
import mathutils import mathutils
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
class BlLight(BlDatablock): class BlLight(ReplicatedDatablock):
bl_id = "lights" bl_id = "lights"
bl_class = bpy.types.Light bl_class = bpy.types.Light
bl_check_common = False bl_check_common = False
bl_icon = 'LIGHT_DATA' bl_icon = 'LIGHT_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.lights.new(data["name"], data["type"]) return bpy.data.lights.new(data["name"], data["type"])
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(target, data)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
dumper = Dumper() dumper = Dumper()
dumper.depth = 3 dumper.depth = 3

View File

@ -21,17 +21,17 @@ import mathutils
import logging import logging
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
class BlLightprobe(BlDatablock): class BlLightprobe(ReplicatedDatablock):
bl_id = "lightprobes" bl_id = "lightprobes"
bl_class = bpy.types.LightProbe bl_class = bpy.types.LightProbe
bl_check_common = False bl_check_common = False
bl_icon = 'LIGHTPROBE_GRID' bl_icon = 'LIGHTPROBE_GRID'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type'] type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type']
# See https://developer.blender.org/D6396 # See https://developer.blender.org/D6396
if bpy.app.version[1] >= 83: if bpy.app.version[1] >= 83:
@ -39,11 +39,11 @@ class BlLightprobe(BlDatablock):
else: else:
logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396") 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 = Loader()
loader.load(target, data) loader.load(target, data)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
if bpy.app.version[1] < 83: if bpy.app.version[1] < 83:
logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396") logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")

View File

@ -24,7 +24,9 @@ import re
from uuid import uuid4 from uuid import uuid4
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock, get_datablock_from_uuid from replication.protocol import ReplicatedDatablock
from .bl_datablock import get_datablock_from_uuid
NODE_SOCKET_INDEX = re.compile('\[(\d*)\]') NODE_SOCKET_INDEX = re.compile('\[(\d*)\]')
IGNORED_SOCKETS = ['GEOMETRY', 'SHADER', 'CUSTOM'] IGNORED_SOCKETS = ['GEOMETRY', 'SHADER', 'CUSTOM']
@ -389,17 +391,17 @@ def load_materials_slots(src_materials: list, dst_materials: bpy.types.bpy_prop_
dst_materials.append(mat_ref) dst_materials.append(mat_ref)
class BlMaterial(BlDatablock): class BlMaterial(ReplicatedDatablock):
bl_id = "materials" bl_id = "materials"
bl_class = bpy.types.Material bl_class = bpy.types.Material
bl_check_common = False bl_check_common = False
bl_icon = 'MATERIAL_DATA' bl_icon = 'MATERIAL_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.materials.new(data["name"]) return bpy.data.materials.new(data["name"])
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
is_grease_pencil = data.get('is_grease_pencil') is_grease_pencil = data.get('is_grease_pencil')
@ -417,7 +419,7 @@ class BlMaterial(BlDatablock):
load_node_tree(data['node_tree'], target.node_tree) load_node_tree(data['node_tree'], target.node_tree)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
mat_dumper = Dumper() mat_dumper = Dumper()
mat_dumper.depth = 2 mat_dumper.depth = 2
@ -486,7 +488,7 @@ class BlMaterial(BlDatablock):
return data return data
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
# TODO: resolve node group deps # TODO: resolve node group deps
deps = [] deps = []

View File

@ -25,8 +25,13 @@ 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.constants import DIFF_BINARY
from replication.exception import ContextError from replication.exception import ContextError
from .bl_datablock import BlDatablock, get_datablock_from_uuid from replication.protocol import ReplicatedDatablock
from .bl_datablock import get_datablock_from_uuid
from .bl_material import dump_materials_slots, load_materials_slots from .bl_material import dump_materials_slots, load_materials_slots
from ..utils import get_preferences
from .bl_datablock import resolve_datablock_from_uuid
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
VERTICE = ['co'] VERTICE = ['co']
@ -49,76 +54,79 @@ POLYGON = [
'material_index', 'material_index',
] ]
class BlMesh(BlDatablock): class BlMesh(ReplicatedDatablock):
bl_id = "meshes" bl_id = "meshes"
bl_class = bpy.types.Mesh bl_class = bpy.types.Mesh
bl_check_common = False bl_check_common = False
bl_icon = 'MESH_DATA' bl_icon = 'MESH_DATA'
bl_reload_parent = True bl_reload_parent = True
def _construct(self, data): @staticmethod
instance = bpy.data.meshes.new(data["name"]) def construct(data: dict) -> object:
instance.uuid = self.uuid instance = bpy.data.meshes.new(data.get("name"))
instance.uuid = data.get("uuid")
return instance return instance
def _load_implementation(self, data, target): @staticmethod
if not target or target.is_editmode: def load(data: dict, datablock: object):
if not datablock or datablock.is_editmode:
raise ContextError raise ContextError
else: else:
load_animation_data(datablock.get('animation_data'), datablock)
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(datablock, data)
# MATERIAL SLOTS # MATERIAL SLOTS
src_materials = data.get('materials', None) src_materials = data.get('materials', None)
if src_materials: if src_materials:
load_materials_slots(src_materials, target.materials) load_materials_slots(src_materials, datablock.materials)
# CLEAR GEOMETRY # CLEAR GEOMETRY
if target.vertices: if datablock.vertices:
target.clear_geometry() datablock.clear_geometry()
target.vertices.add(data["vertex_count"]) datablock.vertices.add(data["vertex_count"])
target.edges.add(data["egdes_count"]) datablock.edges.add(data["egdes_count"])
target.loops.add(data["loop_count"]) datablock.loops.add(data["loop_count"])
target.polygons.add(data["poly_count"]) datablock.polygons.add(data["poly_count"])
# LOADING # LOADING
np_load_collection(data['vertices'], target.vertices, VERTICE) np_load_collection(data['vertices'], datablock.vertices, VERTICE)
np_load_collection(data['edges'], target.edges, EDGE) np_load_collection(data['edges'], datablock.edges, EDGE)
np_load_collection(data['loops'], target.loops, LOOP) np_load_collection(data['loops'], datablock.loops, LOOP)
np_load_collection(data["polygons"],target.polygons, POLYGON) np_load_collection(data["polygons"],datablock.polygons, POLYGON)
# UV Layers # UV Layers
if 'uv_layers' in data.keys(): if 'uv_layers' in data.keys():
for layer in data['uv_layers']: for layer in data['uv_layers']:
if layer not in target.uv_layers: if layer not in datablock.uv_layers:
target.uv_layers.new(name=layer) datablock.uv_layers.new(name=layer)
np_load_collection_primitives( np_load_collection_primitives(
target.uv_layers[layer].data, datablock.uv_layers[layer].data,
'uv', 'uv',
data["uv_layers"][layer]['data']) data["uv_layers"][layer]['data'])
# Vertex color # Vertex color
if 'vertex_colors' in data.keys(): if 'vertex_colors' in data.keys():
for color_layer in data['vertex_colors']: for color_layer in data['vertex_colors']:
if color_layer not in target.vertex_colors: if color_layer not in datablock.vertex_colors:
target.vertex_colors.new(name=color_layer) datablock.vertex_colors.new(name=color_layer)
np_load_collection_primitives( np_load_collection_primitives(
target.vertex_colors[color_layer].data, datablock.vertex_colors[color_layer].data,
'color', 'color',
data["vertex_colors"][color_layer]['data']) data["vertex_colors"][color_layer]['data'])
target.validate() datablock.validate()
target.update() datablock.update()
def _dump_implementation(self, data, instance=None): @staticmethod
assert(instance) def dump(datablock: object) -> dict:
if (datablock.is_editmode or bpy.context.mode == "SCULPT") and not get_preferences().sync_flags.sync_during_editmode:
if (instance.is_editmode or bpy.context.mode == "SCULPT") and not self.preferences.sync_flags.sync_during_editmode:
raise ContextError("Mesh is in edit mode") raise ContextError("Mesh is in edit mode")
mesh = instance mesh = datablock
dumper = Dumper() dumper = Dumper()
dumper.depth = 1 dumper.depth = 1
@ -132,6 +140,8 @@ class BlMesh(BlDatablock):
data = dumper.dump(mesh) data = dumper.dump(mesh)
data['animation_data'] = dump_animation_data(datablock)
# VERTICES # VERTICES
data["vertex_count"] = len(mesh.vertices) data["vertex_count"] = len(mesh.vertices)
data["vertices"] = np_dump_collection(mesh.vertices, VERTICE) data["vertices"] = np_dump_collection(mesh.vertices, VERTICE)
@ -163,21 +173,37 @@ class BlMesh(BlDatablock):
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 # Materials
data['materials'] = dump_materials_slots(instance.materials) data['materials'] = dump_materials_slots(datablock.materials)
return data return data
def _resolve_deps_implementation(self): @staticmethod
def resolve_deps(datablock: object) -> [object]:
deps = [] deps = []
for material in self.instance.materials: for material in datablock.materials:
if material: if material:
deps.append(material) deps.append(material)
deps.extend(resolve_animation_dependencies(datablock))
return deps return deps
@staticmethod
def resolve(data: dict) -> object:
uuid = data.get('uuid')
name = data.get('name')
datablock = resolve_datablock_from_uuid(uuid, bpy.data.meshes)
if datablock is None:
datablock = bpy.data.meshes.get(name)
return datablock
def diff(self): def diff(self):
if 'EDIT' in bpy.context.mode \ 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 return False
else: else:
return super().diff() return super().diff()
_type = bpy.types.Mesh
_class = BlMesh

View File

@ -23,7 +23,7 @@ from .dump_anything import (
Dumper, Loader, np_dump_collection_primitive, np_load_collection_primitives, Dumper, Loader, np_dump_collection_primitive, np_load_collection_primitives,
np_dump_collection, np_load_collection) np_dump_collection, np_load_collection)
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
ELEMENT = [ ELEMENT = [
@ -62,17 +62,17 @@ def load_metaball_elements(elements_data, elements):
np_load_collection(elements_data, elements, ELEMENT) np_load_collection(elements_data, elements, ELEMENT)
class BlMetaball(BlDatablock): class BlMetaball(ReplicatedDatablock):
bl_id = "metaballs" bl_id = "metaballs"
bl_class = bpy.types.MetaBall bl_class = bpy.types.MetaBall
bl_check_common = False bl_check_common = False
bl_icon = 'META_BALL' bl_icon = 'META_BALL'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.metaballs.new(data["name"]) return bpy.data.metaballs.new(data["name"])
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(target, data)
@ -83,7 +83,7 @@ class BlMetaball(BlDatablock):
load_metaball_elements(data['elements'], target.elements) load_metaball_elements(data['elements'], target.elements)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
dumper = Dumper() dumper = Dumper()
dumper.depth = 1 dumper.depth = 1

View File

@ -20,26 +20,26 @@ import bpy
import mathutils import mathutils
from .dump_anything import Dumper, Loader, np_dump_collection, np_load_collection from .dump_anything import Dumper, Loader, np_dump_collection, np_load_collection
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
from .bl_material import (dump_node_tree, from .bl_material import (dump_node_tree,
load_node_tree, load_node_tree,
get_node_tree_dependencies) get_node_tree_dependencies)
class BlNodeGroup(BlDatablock): class BlNodeGroup(ReplicatedDatablock):
bl_id = "node_groups" bl_id = "node_groups"
bl_class = bpy.types.NodeTree bl_class = bpy.types.NodeTree
bl_check_common = False bl_check_common = False
bl_icon = 'NODETREE' bl_icon = 'NODETREE'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.node_groups.new(data["name"], data["type"]) 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) load_node_tree(data, target)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
return dump_node_tree(instance) return dump_node_tree(instance)
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
return get_node_tree_dependencies(self.instance) return get_node_tree_dependencies(self.instance)

View File

@ -22,7 +22,8 @@ import bpy
import mathutils import mathutils
from replication.exception import ContextError from replication.exception import ContextError
from .bl_datablock import BlDatablock, get_datablock_from_uuid from replication.protocol import ReplicatedDatablock
from .bl_datablock import get_datablock_from_uuid, resolve_datablock_from_uuid
from .bl_material import IGNORED_SOCKETS from .bl_material import IGNORED_SOCKETS
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
from .dump_anything import ( from .dump_anything import (
@ -44,6 +45,8 @@ SHAPEKEY_BLOCK_ATTR = [
'slider_min', 'slider_min',
'slider_max', 'slider_max',
] ]
if bpy.app.version[1] >= 93: if bpy.app.version[1] >= 93:
SUPPORTED_GEOMETRY_NODE_PARAMETERS = (int, str, float) SUPPORTED_GEOMETRY_NODE_PARAMETERS = (int, str, float)
else: else:
@ -51,6 +54,7 @@ else:
logging.warning("Geometry node Float parameter not supported in \ logging.warning("Geometry node Float parameter not supported in \
blender 2.92.") blender 2.92.")
def get_node_group_inputs(node_group): def get_node_group_inputs(node_group):
inputs = [] inputs = []
for inpt in node_group.inputs: for inpt in node_group.inputs:
@ -89,6 +93,7 @@ def dump_physics(target: bpy.types.Object)->dict:
return physics_data return physics_data
def load_physics(dumped_settings: dict, target: bpy.types.Object): def load_physics(dumped_settings: dict, target: bpy.types.Object):
""" Load all physics settings from a given object excluding modifier """ Load all physics settings from a given object excluding modifier
related physics settings (such as softbody, cloth, dynapaint and fluid) related physics settings (such as softbody, cloth, dynapaint and fluid)
@ -114,7 +119,8 @@ def load_physics(dumped_settings: dict, target: bpy.types.Object):
loader.load(target.rigid_body_constraint, dumped_settings['rigid_body_constraint']) loader.load(target.rigid_body_constraint, dumped_settings['rigid_body_constraint'])
elif target.rigid_body_constraint: elif target.rigid_body_constraint:
bpy.ops.rigidbody.constraint_remove({"object": target}) bpy.ops.rigidbody.constraint_remove({"object": target})
def dump_modifier_geometry_node_inputs(modifier: bpy.types.Modifier) -> list: def dump_modifier_geometry_node_inputs(modifier: bpy.types.Modifier) -> list:
""" Dump geometry node modifier input properties """ Dump geometry node modifier input properties
@ -295,6 +301,7 @@ def load_vertex_groups(dumped_vertex_groups: dict, target_object: bpy.types.Obje
for index, weight in vg['vertices']: for index, weight in vg['vertices']:
vertex_group.add([index], weight, 'REPLACE') vertex_group.add([index], weight, 'REPLACE')
def dump_shape_keys(target_key: bpy.types.Key)->dict: def dump_shape_keys(target_key: bpy.types.Key)->dict:
""" Dump the target shape_keys datablock to a dict using numpy """ Dump the target shape_keys datablock to a dict using numpy
@ -436,25 +443,17 @@ def load_modifiers_custom_data(dumped_modifiers: dict, modifiers: bpy.types.bpy_
else: else:
logging.error("Could't load projector target object {projector_object}") logging.error("Could't load projector target object {projector_object}")
class BlObject(BlDatablock): class BlObject(ReplicatedDatablock):
bl_id = "objects" bl_id = "objects"
bl_class = bpy.types.Object bl_class = bpy.types.Object
bl_check_common = False bl_check_common = False
bl_icon = 'OBJECT_DATA' bl_icon = 'OBJECT_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): @staticmethod
def construct(data: dict) -> object:
instance = None 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
# TODO: refactoring # TODO: refactoring
object_name = data.get("name") object_name = data.get("name")
data_uuid = data.get("data_uuid") data_uuid = data.get("data_uuid")
@ -467,70 +466,71 @@ class BlObject(BlDatablock):
ignore=['images']) # TODO: use resolve_from_id ignore=['images']) # TODO: use resolve_from_id
if data_type != 'EMPTY' and object_data is None: if data_type != 'EMPTY' and object_data is None:
raise Exception(f"Fail to load object {data['name']}({self.uuid})") raise Exception(f"Fail to load object {data['name']})")
instance = bpy.data.objects.new(object_name, object_data) instance = bpy.data.objects.new(object_name, object_data)
instance.uuid = self.uuid instance.uuid = data.get("uuid")
return instance return instance
def _load_implementation(self, data, target): @staticmethod
def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
data_uuid = data.get("data_uuid") data_uuid = data.get("data_uuid")
data_id = data.get("data") data_id = data.get("data")
if target.data and (target.data.name != data_id): if datablock.data and (datablock.data.name != data_id):
target.data = get_datablock_from_uuid( datablock.data = get_datablock_from_uuid(
data_uuid, find_data_from_name(data_id), ignore=['images']) data_uuid, find_data_from_name(data_id), ignore=['images'])
# vertex groups # vertex groups
vertex_groups = data.get('vertex_groups', None) vertex_groups = data.get('vertex_groups', None)
if vertex_groups: 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 # SHAPE KEYS
shape_keys = data.get('shape_keys') shape_keys = data.get('shape_keys')
if shape_keys: if shape_keys:
load_shape_keys(shape_keys, target) load_shape_keys(shape_keys, datablock)
# Load transformation data # Load transformation data
loader.load(target, data) loader.load(datablock, data)
# Object display fields # Object display fields
if 'display' in data: if 'display' in data:
loader.load(target.display, data['display']) loader.load(datablock.display, data['display'])
# Parenting # Parenting
parent_id = data.get('parent_uid') parent_id = data.get('parent_uid')
if parent_id: if parent_id:
parent = get_datablock_from_uuid(parent_id[0], bpy.data.objects[parent_id[1]]) parent = get_datablock_from_uuid(parent_id[0], bpy.data.objects[parent_id[1]])
# Avoid reloading # Avoid reloading
if target.parent != parent and parent is not None: if datablock.parent != parent and parent is not None:
target.parent = parent datablock.parent = parent
elif target.parent: elif datablock.parent:
target.parent = None datablock.parent = None
# Pose # Pose
if 'pose' in data: if 'pose' in data:
if not target.pose: if not datablock.pose:
raise Exception('No pose data yet (Fixed in a near futur)') raise Exception('No pose data yet (Fixed in a near futur)')
# Bone groups # Bone groups
for bg_name in data['pose']['bone_groups']: for bg_name in data['pose']['bone_groups']:
bg_data = data['pose']['bone_groups'].get(bg_name) bg_data = data['pose']['bone_groups'].get(bg_name)
bg_target = target.pose.bone_groups.get(bg_name) bg_target = datablock.pose.bone_groups.get(bg_name)
if not bg_target: if not bg_target:
bg_target = target.pose.bone_groups.new(name=bg_name) bg_target = datablock.pose.bone_groups.new(name=bg_name)
loader.load(bg_target, bg_data) loader.load(bg_target, bg_data)
# target.pose.bone_groups.get # datablock.pose.bone_groups.get
# Bones # Bones
for bone in data['pose']['bones']: for bone in data['pose']['bones']:
target_bone = target.pose.bones.get(bone) target_bone = datablock.pose.bones.get(bone)
bone_data = data['pose']['bones'].get(bone) bone_data = data['pose']['bones'].get(bone)
if 'constraints' in bone_data.keys(): if 'constraints' in bone_data.keys():
@ -539,13 +539,13 @@ class BlObject(BlDatablock):
load_pose(target_bone, bone_data) load_pose(target_bone, bone_data)
if 'bone_index' in bone_data.keys(): if 'bone_index' in bone_data.keys():
target_bone.bone_group = target.pose.bone_group[bone_data['bone_group_index']] target_bone.bone_group = datablock.pose.bone_group[bone_data['bone_group_index']]
# TODO: find another way... # TODO: find another way...
if target.empty_display_type == "IMAGE": if datablock.empty_display_type == "IMAGE":
img_uuid = data.get('data_uuid') img_uuid = data.get('data_uuid')
if target.data is None and img_uuid: if datablock.data is None and img_uuid:
target.data = get_datablock_from_uuid(img_uuid, None) datablock.data = get_datablock_from_uuid(img_uuid, None)
if hasattr(object_data, 'skin_vertices') \ if hasattr(object_data, 'skin_vertices') \
and object_data.skin_vertices\ and object_data.skin_vertices\
@ -556,30 +556,29 @@ class BlObject(BlDatablock):
skin_data.data, skin_data.data,
SKIN_DATA) SKIN_DATA)
if hasattr(target, 'cycles_visibility') \ if hasattr(datablock, 'cycles_visibility') \
and 'cycles_visibility' in data: and 'cycles_visibility' in data:
loader.load(target.cycles_visibility, data['cycles_visibility']) loader.load(datablock.cycles_visibility, data['cycles_visibility'])
if hasattr(target, 'modifiers'): if hasattr(datablock, 'modifiers'):
load_modifiers_custom_data(data['modifiers'], target.modifiers) load_modifiers_custom_data(data['modifiers'], datablock.modifiers)
# PHYSICS # PHYSICS
load_physics(data, target) load_physics(data, datablock)
transform = data.get('transforms', None) transform = data.get('transforms', None)
if transform: if transform:
target.matrix_parent_inverse = mathutils.Matrix( datablock.matrix_parent_inverse = mathutils.Matrix(
transform['matrix_parent_inverse']) transform['matrix_parent_inverse'])
target.matrix_basis = mathutils.Matrix(transform['matrix_basis']) datablock.matrix_basis = mathutils.Matrix(transform['matrix_basis'])
target.matrix_local = mathutils.Matrix(transform['matrix_local']) datablock.matrix_local = mathutils.Matrix(transform['matrix_local'])
def _dump_implementation(self, data, instance=None): @staticmethod
assert(instance) def dump(datablock: object) -> dict:
if _is_editmode(datablock):
if _is_editmode(instance):
if self.preferences.sync_flags.sync_during_editmode: if self.preferences.sync_flags.sync_during_editmode:
instance.update_from_editmode() datablock.update_from_editmode()
else: else:
raise ContextError("Object is in edit-mode.") raise ContextError("Object is in edit-mode.")
@ -618,32 +617,30 @@ class BlObject(BlDatablock):
'type' 'type'
] ]
data = dumper.dump(instance) data = dumper.dump(datablock)
dumper.include_filter = [ dumper.include_filter = [
'matrix_parent_inverse', 'matrix_parent_inverse',
'matrix_local', 'matrix_local',
'matrix_basis'] 'matrix_basis']
data['transforms'] = dumper.dump(instance) data['transforms'] = dumper.dump(datablock)
dumper.include_filter = [ dumper.include_filter = [
'show_shadows', 'show_shadows',
] ]
data['display'] = dumper.dump(instance.display) data['display'] = dumper.dump(datablock.display)
data['data_uuid'] = getattr(instance.data, 'uuid', None) data['data_uuid'] = getattr(datablock.data, 'uuid', None)
if self.is_library:
return data
# PARENTING # PARENTING
if instance.parent: if datablock.parent:
data['parent_uid'] = (instance.parent.uuid, instance.parent.name) data['parent_uid'] = (datablock.parent.uuid, datablock.parent.name)
# MODIFIERS # MODIFIERS
modifiers = getattr(instance, 'modifiers', None) modifiers = getattr(datablock, 'modifiers', None)
if hasattr(instance, 'modifiers'): if hasattr(datablock, 'modifiers'):
data['modifiers'] = dump_modifiers(modifiers) data['modifiers'] = dump_modifiers(modifiers)
gp_modifiers = getattr(instance, 'grease_pencil_modifiers', None) gp_modifiers = getattr(datablock, 'grease_pencil_modifiers', None)
if gp_modifiers: if gp_modifiers:
dumper.include_filter = None dumper.include_filter = None
@ -666,16 +663,16 @@ class BlObject(BlDatablock):
# CONSTRAINTS # CONSTRAINTS
if hasattr(instance, 'constraints'): if hasattr(datablock, 'constraints'):
dumper.include_filter = None dumper.include_filter = None
dumper.depth = 3 dumper.depth = 3
data["constraints"] = dumper.dump(instance.constraints) data["constraints"] = dumper.dump(datablock.constraints)
# POSE # POSE
if hasattr(instance, 'pose') and instance.pose: if hasattr(datablock, 'pose') and datablock.pose:
# BONES # BONES
bones = {} bones = {}
for bone in instance.pose.bones: for bone in datablock.pose.bones:
bones[bone.name] = {} bones[bone.name] = {}
dumper.depth = 1 dumper.depth = 1
rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler' rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler'
@ -700,7 +697,7 @@ class BlObject(BlDatablock):
# GROUPS # GROUPS
bone_groups = {} bone_groups = {}
for group in instance.pose.bone_groups: for group in datablock.pose.bone_groups:
dumper.depth = 3 dumper.depth = 3
dumper.include_filter = [ dumper.include_filter = [
'name', 'name',
@ -710,11 +707,11 @@ class BlObject(BlDatablock):
data['pose']['bone_groups'] = bone_groups data['pose']['bone_groups'] = bone_groups
# VERTEx GROUP # VERTEx GROUP
if len(instance.vertex_groups) > 0: if len(datablock.vertex_groups) > 0:
data['vertex_groups'] = dump_vertex_groups(instance) data['vertex_groups'] = dump_vertex_groups(datablock)
# SHAPE KEYS # SHAPE KEYS
object_data = instance.data object_data = datablock.data
if hasattr(object_data, 'shape_keys') and object_data.shape_keys: if hasattr(object_data, 'shape_keys') and object_data.shape_keys:
data['shape_keys'] = dump_shape_keys(object_data.shape_keys) data['shape_keys'] = dump_shape_keys(object_data.shape_keys)
@ -727,7 +724,7 @@ class BlObject(BlDatablock):
data['skin_vertices'] = skin_vertices data['skin_vertices'] = skin_vertices
# CYCLE SETTINGS # CYCLE SETTINGS
if hasattr(instance, 'cycles_visibility'): if hasattr(datablock, 'cycles_visibility'):
dumper.include_filter = [ dumper.include_filter = [
'camera', 'camera',
'diffuse', 'diffuse',
@ -736,38 +733,49 @@ class BlObject(BlDatablock):
'scatter', 'scatter',
'shadow', 'shadow',
] ]
data['cycles_visibility'] = dumper.dump(instance.cycles_visibility) data['cycles_visibility'] = dumper.dump(datablock.cycles_visibility)
# PHYSICS # PHYSICS
data.update(dump_physics(instance)) data.update(dump_physics(datablock))
return data return data
def _resolve_deps_implementation(self): @staticmethod
def resolve_deps(datablock: object) -> [object]:
deps = [] deps = []
# Avoid Empty case # Avoid Empty case
if self.instance.data: if datablock.data:
deps.append(self.instance.data) deps.append(datablock.data)
# Particle systems # Particle systems
for particle_slot in self.instance.particle_systems: for particle_slot in datablock.particle_systems:
deps.append(particle_slot.settings) deps.append(particle_slot.settings)
if self.is_library: if datablock.parent:
deps.append(self.instance.library) deps.append(datablock.parent)
if self.instance.parent: if datablock.instance_type == 'COLLECTION':
deps.append(self.instance.parent)
if self.instance.instance_type == 'COLLECTION':
# TODO: uuid based # TODO: uuid based
deps.append(self.instance.instance_collection) deps.append(datablock.instance_collection)
if self.instance.modifiers: if datablock.modifiers:
deps.extend(find_textures_dependencies(self.instance.modifiers)) deps.extend(find_textures_dependencies(datablock.modifiers))
deps.extend(find_geometry_nodes_dependencies(self.instance.modifiers)) deps.extend(find_geometry_nodes_dependencies(datablock.modifiers))
if hasattr(self.instance.data, 'shape_keys') and self.instance.data.shape_keys: if hasattr(datablock.data, 'shape_keys') and datablock.data.shape_keys:
deps.extend(resolve_animation_dependencies(self.instance.data.shape_keys)) deps.extend(resolve_animation_dependencies(datablock.data.shape_keys))
return deps return deps
@staticmethod
def resolve(data: dict) -> object:
uuid = data.get('uuid')
name = data.get('name')
datablock = resolve_datablock_from_uuid(uuid, bpy.data.objects)
if datablock is None:
datablock = bpy.data.objects.get(name)
return datablock
_type = bpy.types.Object
_class = BlObject

View File

@ -2,7 +2,8 @@ import bpy
import mathutils import mathutils
from . import dump_anything from . import dump_anything
from .bl_datablock import BlDatablock, get_datablock_from_uuid from replication.protocol import ReplicatedDatablock
from .bl_datablock import get_datablock_from_uuid
def dump_textures_slots(texture_slots: bpy.types.bpy_prop_collection) -> list: def dump_textures_slots(texture_slots: bpy.types.bpy_prop_collection) -> list:
@ -37,19 +38,19 @@ IGNORED_ATTR = [
"users" "users"
] ]
class BlParticle(BlDatablock): class BlParticle(ReplicatedDatablock):
bl_id = "particles" bl_id = "particles"
bl_class = bpy.types.ParticleSettings bl_class = bpy.types.ParticleSettings
bl_icon = "PARTICLES" bl_icon = "PARTICLES"
bl_check_common = False bl_check_common = False
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
instance = bpy.data.particles.new(data["name"]) instance = bpy.data.particles.new(data["name"])
instance.uuid = self.uuid instance.uuid = self.uuid
return instance return instance
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
dump_anything.load(target, data) dump_anything.load(target, data)
dump_anything.load(target.effector_weights, data["effector_weights"]) dump_anything.load(target.effector_weights, data["effector_weights"])
@ -66,7 +67,7 @@ class BlParticle(BlDatablock):
# Texture slots # Texture slots
load_texture_slots(data["texture_slots"], target.texture_slots) load_texture_slots(data["texture_slots"], target.texture_slots)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert instance assert instance
dumper = dump_anything.Dumper() dumper = dump_anything.Dumper()
@ -86,5 +87,5 @@ class BlParticle(BlDatablock):
return data return data
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
return [t.texture for t in self.instance.texture_slots if t and t.texture] return [t.texture for t in self.instance.texture_slots if t and t.texture]

View File

@ -18,7 +18,7 @@
import logging import logging
from pathlib import Path from pathlib import Path
from uuid import uuid4
import bpy import bpy
import mathutils import mathutils
from deepdiff import DeepDiff, Delta from deepdiff import DeepDiff, Delta
@ -28,9 +28,12 @@ from ..utils import flush_history
from .bl_collection import (dump_collection_children, dump_collection_objects, from .bl_collection import (dump_collection_children, dump_collection_objects,
load_collection_childrens, load_collection_objects, load_collection_childrens, load_collection_objects,
resolve_collection_dependencies) resolve_collection_dependencies)
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
from .bl_file import get_filepath from .bl_file import get_filepath
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
from .dump_anything import Dumper, Loader from .dump_anything import Dumper, Loader
from ..utils import get_preferences
from .bl_datablock import resolve_datablock_from_uuid
RENDER_SETTINGS = [ RENDER_SETTINGS = [
'dither_intensity', 'dither_intensity',
@ -367,7 +370,7 @@ def load_sequence(sequence_data: dict, sequence_editor: bpy.types.SequenceEditor
sequence.select = False sequence.select = False
class BlScene(BlDatablock): class BlScene(ReplicatedDatablock):
is_root = True is_root = True
bl_id = "scenes" bl_id = "scenes"
@ -376,58 +379,62 @@ class BlScene(BlDatablock):
bl_icon = 'SCENE_DATA' bl_icon = 'SCENE_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): @staticmethod
def construct(data: dict) -> object:
instance = bpy.data.scenes.new(data["name"]) instance = bpy.data.scenes.new(data["name"])
instance.uuid = self.uuid instance.uuid = data.get('uuid')
return instance return instance
def _load_implementation(self, data, target): @staticmethod
def load(data: dict, datablock: object):
load_animation_data(datablock.get('animation_data'), datablock)
# Load other meshes metadata # Load other meshes metadata
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(datablock, data)
# Load master collection # Load master collection
load_collection_objects( load_collection_objects(
data['collection']['objects'], target.collection) data['collection']['objects'], datablock.collection)
load_collection_childrens( load_collection_childrens(
data['collection']['children'], target.collection) data['collection']['children'], datablock.collection)
if 'world' in data.keys(): if 'world' in data.keys():
target.world = bpy.data.worlds[data['world']] datablock.world = bpy.data.worlds[data['world']]
# Annotation # Annotation
if 'grease_pencil' in data.keys(): 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(): if 'eevee' in data.keys():
loader.load(target.eevee, data['eevee']) loader.load(datablock.eevee, data['eevee'])
if 'cycles' in data.keys(): if 'cycles' in data.keys():
loader.load(target.cycles, data['cycles']) loader.load(datablock.cycles, data['cycles'])
if 'render' in data.keys(): if 'render' in data.keys():
loader.load(target.render, data['render']) loader.load(datablock.render, data['render'])
if 'view_settings' in data.keys(): if 'view_settings' in data.keys():
loader.load(target.view_settings, data['view_settings']) loader.load(datablock.view_settings, data['view_settings'])
if target.view_settings.use_curve_mapping and \ if datablock.view_settings.use_curve_mapping and \
'curve_mapping' in data['view_settings']: 'curve_mapping' in data['view_settings']:
# TODO: change this ugly fix # 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'] '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'] 'view_settings']['curve_mapping']['black_level']
target.view_settings.curve_mapping.update() datablock.view_settings.curve_mapping.update()
# Sequencer # Sequencer
sequences = data.get('sequences') sequences = data.get('sequences')
if sequences: if sequences:
# Create sequencer data # Create sequencer data
target.sequence_editor_create() datablock.sequence_editor_create()
vse = target.sequence_editor vse = datablock.sequence_editor
# Clear removed sequences # Clear removed sequences
for seq in vse.sequences_all: for seq in vse.sequences_all:
@ -437,15 +444,17 @@ class BlScene(BlDatablock):
for seq_name, seq_data in sequences.items(): for seq_name, seq_data in sequences.items():
load_sequence(seq_data, vse) load_sequence(seq_data, vse)
# If the sequence is no longer used, clear it # If the sequence is no longer used, clear it
elif target.sequence_editor and not sequences: elif datablock.sequence_editor and not sequences:
target.sequence_editor_clear() datablock.sequence_editor_clear()
# FIXME: Find a better way after the replication big refacotoring # FIXME: Find a better way after the replication big refacotoring
# Keep other user from deleting collection object by flushing their history # Keep other user from deleting collection object by flushing their history
flush_history() flush_history()
def _dump_implementation(self, data, instance=None): @staticmethod
assert(instance) def dump(datablock: object) -> dict:
data = {}
data['animation_data'] = dump_animation_data(datablock)
# Metadata # Metadata
scene_dumper = Dumper() scene_dumper = Dumper()
@ -459,40 +468,40 @@ class BlScene(BlDatablock):
'frame_end', 'frame_end',
'frame_step', 'frame_step',
] ]
if self.preferences.sync_flags.sync_active_camera: if get_preferences().sync_flags.sync_active_camera:
scene_dumper.include_filter.append('camera') scene_dumper.include_filter.append('camera')
data.update(scene_dumper.dump(instance)) data.update(scene_dumper.dump(datablock))
# Master collection # Master collection
data['collection'] = {} data['collection'] = {}
data['collection']['children'] = dump_collection_children( data['collection']['children'] = dump_collection_children(
instance.collection) datablock.collection)
data['collection']['objects'] = dump_collection_objects( data['collection']['objects'] = dump_collection_objects(
instance.collection) datablock.collection)
scene_dumper.depth = 1 scene_dumper.depth = 1
scene_dumper.include_filter = None scene_dumper.include_filter = None
# Render settings # Render settings
if self.preferences.sync_flags.sync_render_settings: if get_preferences().sync_flags.sync_render_settings:
scene_dumper.include_filter = 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 scene_dumper.include_filter = EVEE_SETTINGS
data['eevee'] = scene_dumper.dump(instance.eevee) data['eevee'] = scene_dumper.dump(datablock.eevee)
elif instance.render.engine == 'CYCLES': elif datablock.render.engine == 'CYCLES':
scene_dumper.include_filter = CYCLES_SETTINGS 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 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( data['view_settings']['curve_mapping'] = scene_dumper.dump(
instance.view_settings.curve_mapping) datablock.view_settings.curve_mapping)
scene_dumper.depth = 5 scene_dumper.depth = 5
scene_dumper.include_filter = [ scene_dumper.include_filter = [
'curves', 'curves',
@ -500,10 +509,10 @@ class BlScene(BlDatablock):
'location', 'location',
] ]
data['view_settings']['curve_mapping']['curves'] = scene_dumper.dump( data['view_settings']['curve_mapping']['curves'] = scene_dumper.dump(
instance.view_settings.curve_mapping.curves) datablock.view_settings.curve_mapping.curves)
# Sequence # Sequence
vse = instance.sequence_editor vse = datablock.sequence_editor
if vse: if vse:
dumped_sequences = {} dumped_sequences = {}
for seq in vse.sequences_all: for seq in vse.sequences_all:
@ -513,22 +522,25 @@ class BlScene(BlDatablock):
return data return data
def _resolve_deps_implementation(self): @staticmethod
def resolve_deps(datablock: object) -> [object]:
deps = [] deps = []
# Master Collection # Master Collection
deps.extend(resolve_collection_dependencies(self.instance.collection)) deps.extend(resolve_collection_dependencies(datablock.collection))
# world # world
if self.instance.world: if datablock.world:
deps.append(self.instance.world) deps.append(datablock.world)
# annotations # annotations
if self.instance.grease_pencil: if datablock.grease_pencil:
deps.append(self.instance.grease_pencil) deps.append(datablock.grease_pencil)
deps.extend(resolve_animation_dependencies(datablock))
# Sequences # Sequences
vse = self.instance.sequence_editor vse = datablock.sequence_editor
if vse: if vse:
for sequence in vse.sequences_all: for sequence in vse.sequences_all:
if sequence.type == 'MOVIE' and sequence.filepath: if sequence.type == 'MOVIE' and sequence.filepath:
@ -543,6 +555,16 @@ class BlScene(BlDatablock):
return deps return deps
@staticmethod
def resolve(data: dict) -> object:
uuid = data.get('uuid')
name = data.get('name')
datablock = resolve_datablock_from_uuid(uuid, bpy.data.scenes)
if datablock is None:
datablock = bpy.data.scenes.get(name)
return datablock
def diff(self): def diff(self):
exclude_path = [] exclude_path = []
@ -564,4 +586,6 @@ class BlScene(BlDatablock):
'mutate':True 'mutate':True
} }
return super().diff(diff_params=diff_params) return super().diff(diff_params=diff_params)
# return Delta(DeepDiff(self.data, self._dump(instance=self.instance),))
_type = bpy.types.Scene
_class = BlScene

View File

@ -23,18 +23,18 @@ from pathlib import Path
import bpy import bpy
from .bl_file import get_filepath, ensure_unpacked from .bl_file import get_filepath, ensure_unpacked
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
from .dump_anything import Dumper, Loader from .dump_anything import Dumper, Loader
class BlSound(BlDatablock): class BlSound(ReplicatedDatablock):
bl_id = "sounds" bl_id = "sounds"
bl_class = bpy.types.Sound bl_class = bpy.types.Sound
bl_check_common = False bl_check_common = False
bl_icon = 'SOUND' bl_icon = 'SOUND'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
filename = data.get('filename') filename = data.get('filename')
return bpy.data.sounds.load(get_filepath(filename)) return bpy.data.sounds.load(get_filepath(filename))
@ -57,7 +57,7 @@ class BlSound(BlDatablock):
'name': instance.name 'name': instance.name
} }
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
deps = [] deps = []
if self.instance.filepath and self.instance.filepath != '<builtin>': if self.instance.filepath and self.instance.filepath != '<builtin>':
ensure_unpacked(self.instance) ensure_unpacked(self.instance)

View File

@ -20,24 +20,24 @@ import bpy
import mathutils import mathutils
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
class BlSpeaker(BlDatablock): class BlSpeaker(ReplicatedDatablock):
bl_id = "speakers" bl_id = "speakers"
bl_class = bpy.types.Speaker bl_class = bpy.types.Speaker
bl_check_common = False bl_check_common = False
bl_icon = 'SPEAKER' bl_icon = 'SPEAKER'
bl_reload_parent = False bl_reload_parent = False
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(target, data)
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.speakers.new(data["name"]) return bpy.data.speakers.new(data["name"])
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
dumper = Dumper() dumper = Dumper()
@ -60,7 +60,7 @@ class BlSpeaker(BlDatablock):
return dumper.dump(instance) return dumper.dump(instance)
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
# TODO: resolve material # TODO: resolve material
deps = [] deps = []

View File

@ -20,24 +20,24 @@ import bpy
import mathutils import mathutils
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
class BlTexture(BlDatablock): class BlTexture(ReplicatedDatablock):
bl_id = "textures" bl_id = "textures"
bl_class = bpy.types.Texture bl_class = bpy.types.Texture
bl_check_common = False bl_check_common = False
bl_icon = 'TEXTURE' bl_icon = 'TEXTURE'
bl_reload_parent = False bl_reload_parent = False
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(target, data)
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.textures.new(data["name"], data["type"]) return bpy.data.textures.new(data["name"], data["type"])
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
dumper = Dumper() dumper = Dumper()
@ -61,7 +61,7 @@ class BlTexture(BlDatablock):
return data return data
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
# TODO: resolve material # TODO: resolve material
deps = [] deps = []

View File

@ -21,17 +21,18 @@ import mathutils
from pathlib import Path from pathlib import Path
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock, get_datablock_from_uuid from replication.protocol import ReplicatedDatablock
from .bl_datablock import get_datablock_from_uuid
from .bl_material import dump_materials_slots, load_materials_slots from .bl_material import dump_materials_slots, load_materials_slots
class BlVolume(BlDatablock): class BlVolume(ReplicatedDatablock):
bl_id = "volumes" bl_id = "volumes"
bl_class = bpy.types.Volume bl_class = bpy.types.Volume
bl_check_common = False bl_check_common = False
bl_icon = 'VOLUME_DATA' bl_icon = 'VOLUME_DATA'
bl_reload_parent = False bl_reload_parent = False
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(target, data)
loader.load(target.display, data['display']) loader.load(target.display, data['display'])
@ -41,10 +42,10 @@ class BlVolume(BlDatablock):
if src_materials: if src_materials:
load_materials_slots(src_materials, target.materials) load_materials_slots(src_materials, target.materials)
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.volumes.new(data["name"]) return bpy.data.volumes.new(data["name"])
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
dumper = Dumper() dumper = Dumper()
@ -69,7 +70,7 @@ class BlVolume(BlDatablock):
return data return data
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
# TODO: resolve material # TODO: resolve material
deps = [] deps = []

View File

@ -20,23 +20,23 @@ import bpy
import mathutils import mathutils
from .dump_anything import Loader, Dumper from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock from replication.protocol import ReplicatedDatablock
from .bl_material import (load_node_tree, from .bl_material import (load_node_tree,
dump_node_tree, dump_node_tree,
get_node_tree_dependencies) get_node_tree_dependencies)
class BlWorld(BlDatablock): class BlWorld(ReplicatedDatablock):
bl_id = "worlds" bl_id = "worlds"
bl_class = bpy.types.World bl_class = bpy.types.World
bl_check_common = True bl_check_common = True
bl_icon = 'WORLD_DATA' bl_icon = 'WORLD_DATA'
bl_reload_parent = False bl_reload_parent = False
def _construct(self, data): def construct(data: dict) -> object:
return bpy.data.worlds.new(data["name"]) return bpy.data.worlds.new(data["name"])
def _load_implementation(self, data, target): def load(data: dict, datablock: object):
loader = Loader() loader = Loader()
loader.load(target, data) loader.load(target, data)
@ -46,7 +46,7 @@ class BlWorld(BlDatablock):
load_node_tree(data['node_tree'], target.node_tree) load_node_tree(data['node_tree'], target.node_tree)
def _dump_implementation(self, data, instance=None): def dump(datablock: object) -> dict:
assert(instance) assert(instance)
world_dumper = Dumper() world_dumper = Dumper()
@ -62,7 +62,7 @@ class BlWorld(BlDatablock):
return data return data
def _resolve_deps_implementation(self): def resolve_deps(datablock: object) -> [object]:
deps = [] deps = []
if self.instance.use_nodes: if self.instance.use_nodes:

View File

@ -45,11 +45,12 @@ from bpy.app.handlers import persistent
from bpy_extras.io_utils import ExportHelper, ImportHelper from bpy_extras.io_utils import ExportHelper, ImportHelper
from replication.constants import (COMMITED, FETCHED, RP_COMMON, STATE_ACTIVE, from replication.constants import (COMMITED, FETCHED, RP_COMMON, STATE_ACTIVE,
STATE_INITIAL, STATE_SYNCING, UP) STATE_INITIAL, STATE_SYNCING, UP)
from replication.data import DataTranslationProtocol from replication.protocol import DataTranslationProtocol
from replication.exception import ContextError, NonAuthorizedOperationError from replication.exception import ContextError, NonAuthorizedOperationError
from replication.interface import session from replication.interface import session
from replication import porcelain from replication import porcelain
from replication.repository import Repository from replication.repository import Repository
from replication.objects import Node
from . import bl_types, environment, timers, ui, utils from . import bl_types, environment, timers, ui, utils
from .presence import SessionStatusWidget, renderer, view3d_find from .presence import SessionStatusWidget, renderer, view3d_find
@ -88,8 +89,10 @@ def initialize_session():
if node_ref is None: if node_ref is None:
logging.error(f"Can't construct node {node}") logging.error(f"Can't construct node {node}")
elif node_ref.state == FETCHED: elif node_ref.state == FETCHED:
node_ref.resolve() node_ref.instance = session.repository.rdp.resolve(node_ref.data)
if node_ref.instance is None:
node_ref.instance = session.repository.rdp.construct(node_ref.data)
# Step 2: Load nodes # Step 2: Load nodes
logging.info("Loading nodes") logging.info("Loading nodes")
for node in session.repository.list_ordered(): for node in session.repository.list_ordered():
@ -184,29 +187,16 @@ class SessionStartOperator(bpy.types.Operator):
handler.setFormatter(formatter) handler.setFormatter(formatter)
bpy_protocol = DataTranslationProtocol() bpy_protocol = bl_types.get_data_translation_protocol()
supported_bl_types = []
# init the factory with supported types # Check if supported_datablocks are up to date before starting the
for type in bl_types.types_to_register(): # the session
type_module = getattr(bl_types, type) for impl in bpy_protocol.implementations.values():
name = [e.capitalize() for e in type.split('_')[1:]] if impl.__name__ not in settings.supported_datablocks:
type_impl_name = 'Bl'+''.join(name) logging.info(f"{impl.__name__} not found, \
type_module_class = getattr(type_module, type_impl_name)
supported_bl_types.append(type_module_class.bl_id)
if type_impl_name not in settings.supported_datablocks:
logging.info(f"{type_impl_name} not found, \
regenerate type settings...") regenerate type settings...")
settings.generate_supported_types() settings.generate_supported_types()
type_local_config = settings.supported_datablocks[type_impl_name]
bpy_protocol.register_type(
type_module_class.bl_class,
type_module_class,
check_common=type_module_class.bl_check_common)
if bpy.app.version[1] >= 91: if bpy.app.version[1] >= 91:
python_binary_path = sys.executable python_binary_path = sys.executable
@ -214,7 +204,7 @@ class SessionStartOperator(bpy.types.Operator):
python_binary_path = bpy.app.binary_path_python python_binary_path = bpy.app.binary_path_python
repo = Repository( repo = Repository(
data_protocol=bpy_protocol, rdp=bpy_protocol,
username=settings.username) username=settings.username)
# Host a session # Host a session
@ -850,28 +840,13 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
# init the factory with supported types # init the factory with supported types
bpy_protocol = DataTranslationProtocol() bpy_protocol = bl_types.get_data_translation_protocol()
for type in bl_types.types_to_register():
type_module = getattr(bl_types, type)
name = [e.capitalize() for e in type.split('_')[1:]]
type_impl_name = 'Bl'+''.join(name)
type_module_class = getattr(type_module, type_impl_name)
bpy_protocol.register_type(
type_module_class.bl_class,
type_module_class)
graph = Repository() graph = Repository()
for node, node_data in nodes: for node, node_data in nodes:
node_type = node_data.get('str_type')
impl = bpy_protocol.get_implementation_from_net(node_type)
if impl:
logging.info(f"Loading {node}") logging.info(f"Loading {node}")
instance = impl(owner=node_data['owner'], instance = Node(owner=node_data['owner'],
uuid=node, uuid=node,
dependencies=node_data['dependencies'], dependencies=node_data['dependencies'],
data=node_data['data']) data=node_data['data'])
@ -990,20 +965,20 @@ def depsgraph_evaluation(scene):
if update.id.uuid: if update.id.uuid:
# Retrieve local version # Retrieve local version
node = session.repository.get_node(update.id.uuid) node = session.repository.get_node(update.id.uuid)
check_common = session.repository.rdp.get_implementation(update.id).bl_check_common
# Check our right on this update: # Check our right on this update:
# - if its ours or ( under common and diff), launch the # - if its ours or ( under common and diff), launch the
# update process # update process
# - if its to someone else, ignore the update # - if its to someone else, ignore the update
if node and (node.owner == session.id or node.bl_check_common): if node and (node.owner == session.id or check_common):
if node.state == UP: if node.state == UP:
try: try:
porcelain.commit(session.repository, node.uuid) porcelain.commit(session.repository, node.uuid)
porcelain.push(session.repository, 'origin', node.uuid) porcelain.push(session.repository, 'origin', node.uuid)
except ReferenceError: except ReferenceError:
logging.debug(f"Reference error {node.uuid}") logging.debug(f"Reference error {node.uuid}")
if not node.is_valid(): # if not node.is_valid():
session.remove(node.uuid) # session.remove(node.uuid)
except ContextError as e: except ContextError as e:
logging.debug(e) logging.debug(e)
except Exception as e: except Exception as e:

View File

@ -407,18 +407,18 @@ class SessionPrefs(bpy.types.AddonPreferences):
def generate_supported_types(self): def generate_supported_types(self):
self.supported_datablocks.clear() self.supported_datablocks.clear()
for type in bl_types.types_to_register(): bpy_protocol = bl_types.get_data_translation_protocol()
# init the factory with supported types
for impl in bpy_protocol.implementations.values():
new_db = self.supported_datablocks.add() new_db = self.supported_datablocks.add()
type_module = getattr(bl_types, type) new_db.name = impl.__name__
name = [e.capitalize() for e in type.split('_')[1:]] new_db.type_name = impl.__name__
type_impl_name = 'Bl'+''.join(name)
type_module_class = getattr(type_module, type_impl_name)
new_db.name = type_impl_name
new_db.type_name = type_impl_name
new_db.use_as_filter = True new_db.use_as_filter = True
new_db.icon = type_module_class.bl_icon new_db.icon = impl.bl_icon
new_db.bl_name = type_module_class.bl_id new_db.bl_name = impl.bl_id
def client_list_callback(scene, context): def client_list_callback(scene, context):

View File

@ -121,7 +121,8 @@ class ApplyTimer(Timer):
logging.error(f"Fail to apply {node_ref.uuid}") logging.error(f"Fail to apply {node_ref.uuid}")
traceback.print_exc() traceback.print_exc()
else: else:
if node_ref.bl_reload_parent: impl = session.repository.rdp.get_implementation(node_ref.instance)
if impl.bl_reload_parent:
for parent in session.repository.get_parents(node): for parent in session.repository.get_parents(node):
logging.debug("Refresh parent {node}") logging.debug("Refresh parent {node}")
porcelain.apply(session.repository, porcelain.apply(session.repository,

View File

@ -453,8 +453,8 @@ def draw_property(context, parent, property_uuid, level=0):
detail_item_box = line.row(align=True) detail_item_box = line.row(align=True)
detail_item_box.label(text="", detail_item_box.label(text="")
icon=settings.supported_datablocks[item.str_type].icon) # icon=settings.supported_datablocks].icon)
detail_item_box.label(text=f"{name}") detail_item_box.label(text=f"{name}")
# Operations # Operations
@ -561,12 +561,7 @@ class SESSION_PT_repository(bpy.types.Panel):
types_filter = [t.type_name for t in settings.supported_datablocks types_filter = [t.type_name for t in settings.supported_datablocks
if t.use_as_filter] if t.use_as_filter]
key_to_filter = session.list( client_keys = session.list()
filter_owner=settings.username) if runtime_settings.filter_owned else session.list()
client_keys = [key for key in key_to_filter
if session.repository.get_node(key).str_type
in types_filter]
if client_keys: if client_keys:
col = layout.column(align=True) col = layout.column(align=True)