refactor: move implementation to static def

This commit is contained in:
Swann
2021-03-26 12:30:15 +01:00
parent e3af69a9c8
commit e659b7da94
28 changed files with 534 additions and 537 deletions

View File

@ -41,7 +41,7 @@ import bpy
from bpy.app.handlers import persistent
from . import environment
from uuid import uuid4
LIBS = os.path.dirname(os.path.abspath(__file__))+"/libs/replication"
module_error_msg = "Insufficient rights to install the multi-user \
@ -53,10 +53,11 @@ def register():
datefmt='%H:%M:%S',
level=logging.INFO)
for module_name in list(sys.modules.keys()):
if 'replication' in module_name:
del sys.modules[module_name]
if LIBS in sys.path:
logging.debug('Third party module already added')
else:
if LIBS not in sys.path:
logging.info('Adding local modules dir to the path')
sys.path.insert(0, LIBS)

View File

@ -20,34 +20,34 @@ import bpy
__all__ = [
'bl_object',
'bl_mesh',
'bl_camera',
# 'bl_camera',
'bl_collection',
'bl_curve',
'bl_gpencil',
'bl_image',
'bl_light',
# 'bl_curve',
# 'bl_gpencil',
# 'bl_image',
# 'bl_light',
'bl_scene',
'bl_material',
'bl_library',
'bl_armature',
'bl_action',
'bl_world',
'bl_metaball',
'bl_lattice',
'bl_lightprobe',
'bl_speaker',
'bl_font',
'bl_sound',
'bl_file',
# 'bl_sequencer',
'bl_node_group',
'bl_texture',
# 'bl_library',
# 'bl_armature',
# 'bl_action',
# 'bl_world',
# 'bl_metaball',
# 'bl_lattice',
# 'bl_lightprobe',
# 'bl_speaker',
# 'bl_font',
# 'bl_sound',
# 'bl_file',
# # 'bl_sequencer',
# 'bl_node_group',
# 'bl_texture',
] # Order here defines execution order
if bpy.app.version[1] >= 91:
__all__.append('bl_volume')
# if bpy.app.version[1] >= 91:
# __all__.append('bl_volume')
from . import *
from . import bl_object, bl_action, bl_scene, bl_mesh, bl_collection
from replication.protocol import DataTranslationProtocol
def types_to_register():

View File

@ -24,9 +24,14 @@ from enum import Enum
from .. import utils
from .dump_anything import (
Dumper, Loader, np_dump_collection, np_load_collection, remove_items_from_dict)
from .bl_datablock import BlDatablock
Dumper,
Loader,
np_dump_collection,
np_load_collection,
remove_items_from_dict)
from .bl_datablock import stamp_uuid
from replication.protocol import ReplicatedDatablock
from replication.objects import Node
KEYFRAME = [
'amplitude',
@ -42,6 +47,68 @@ KEYFRAME = [
]
def has_action(datablock):
""" Check if the datablock datablock has actions
"""
return (hasattr(datablock, 'animation_data')
and datablock.animation_data
and datablock.animation_data.action)
def has_driver(datablock):
""" Check if the datablock datablock is driven
"""
return (hasattr(datablock, 'animation_data')
and datablock.animation_data
and datablock.animation_data.drivers)
def dump_driver(driver):
dumper = Dumper()
dumper.depth = 6
data = dumper.dump(driver)
return data
def load_driver(target_datablock, src_driver):
loader = Loader()
drivers = target_datablock.animation_data.drivers
src_driver_data = src_driver['driver']
new_driver = drivers.new(
src_driver['data_path'], index=src_driver['array_index'])
# Settings
new_driver.driver.type = src_driver_data['type']
new_driver.driver.expression = src_driver_data['expression']
loader.load(new_driver, src_driver)
# Variables
for src_variable in src_driver_data['variables']:
src_var_data = src_driver_data['variables'][src_variable]
new_var = new_driver.driver.variables.new()
new_var.name = src_var_data['name']
new_var.type = src_var_data['type']
for src_target in src_var_data['targets']:
src_target_data = src_var_data['targets'][src_target]
new_var.targets[src_target].id = utils.resolve_from_id(
src_target_data['id'], src_target_data['id_type'])
loader.load(
new_var.targets[src_target], src_target_data)
# Fcurve
new_fcurve = new_driver.keyframe_points
for p in reversed(new_fcurve):
new_fcurve.remove(p, fast=True)
new_fcurve.add(len(src_driver['keyframe_points']))
for index, src_point in enumerate(src_driver['keyframe_points']):
new_point = new_fcurve[index]
loader.load(new_point, src_driver['keyframe_points'][src_point])
def dump_fcurve(fcurve: bpy.types.FCurve, use_numpy: bool = True) -> dict:
""" Dump a sigle curve to a dict
@ -129,26 +196,68 @@ def load_fcurve(fcurve_data, fcurve):
fcurve.update()
class BlAction(BlDatablock):
def dump_animation_data(datablock, data):
if has_action(datablock):
dumper = Dumper()
dumper.include_filter = ['action']
data['animation_data'] = dumper.dump(datablock.animation_data)
if has_driver(datablock):
dumped_drivers = {'animation_data': {'drivers': []}}
for driver in datablock.animation_data.drivers:
dumped_drivers['animation_data']['drivers'].append(
dump_driver(driver))
data.update(dumped_drivers)
def load_animation_data(data, datablock):
# Load animation data
if 'animation_data' in data.keys():
if datablock.animation_data is None:
datablock.animation_data_create()
for d in datablock.animation_data.drivers:
datablock.animation_data.drivers.remove(d)
if 'drivers' in data['animation_data']:
for driver in data['animation_data']['drivers']:
load_driver(datablock, driver)
if 'action' in data['animation_data']:
datablock.animation_data.action = bpy.data.actions[data['animation_data']['action']]
# Remove existing animation data if there is not more to load
elif hasattr(datablock, 'animation_data') and datablock.animation_data:
datablock.animation_data_clear()
def get_animation_dependencies(datablock):
if has_action(datablock):
return datablock.animation_data.action
class BlAction(ReplicatedDatablock):
bl_id = "actions"
bl_class = bpy.types.Action
bl_check_common = False
bl_icon = 'ACTION_TWEAK'
bl_reload_parent = False
def construct(self, data):
@staticmethod
def construct(data: dict) -> object:
return bpy.data.actions.new(data["name"])
def _load_implementation(self, data, target):
@staticmethod
def load(data: dict, datablock: object):
for dumped_fcurve in data["fcurves"]:
dumped_data_path = dumped_fcurve["data_path"]
dumped_array_index = dumped_fcurve["dumped_array_index"]
# create fcurve if needed
fcurve = target.fcurves.find(
fcurve = datablock.fcurves.find(
dumped_data_path, index=dumped_array_index)
if fcurve is None:
fcurve = target.fcurves.new(
fcurve = datablock.fcurves.new(
dumped_data_path, index=dumped_array_index)
load_fcurve(dumped_fcurve, fcurve)
@ -156,9 +265,12 @@ class BlAction(BlDatablock):
id_root = data.get('id_root')
if id_root:
target.id_root = id_root
datablock.id_root = id_root
@staticmethod
def dump(datablock: object) -> dict:
stamp_uuid(datablock)
def _dump_implementation(self, data, instance=None):
dumper = Dumper()
dumper.exclude_filter = [
'name_full',
@ -173,11 +285,11 @@ class BlAction(BlDatablock):
'users'
]
dumper.depth = 1
data = dumper.dump(instance)
data = dumper.dump(datablock)
data["fcurves"] = []
for fcurve in instance.fcurves:
for fcurve in datablock.fcurves:
data["fcurves"].append(dump_fcurve(fcurve, use_numpy=True))
return data

View File

@ -42,10 +42,10 @@ class BlArmature(BlDatablock):
bl_icon = 'ARMATURE_DATA'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.armatures.new(data["name"])
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
# Load parent object
parent_object = utils.find_from_attr(
'uuid',
@ -119,7 +119,7 @@ class BlArmature(BlDatablock):
if 'EDIT' in current_mode:
bpy.ops.object.mode_set(mode='EDIT')
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
dumper = Dumper()

View File

@ -30,11 +30,11 @@ class BlCamera(BlDatablock):
bl_icon = 'CAMERA_DATA'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.cameras.new(data["name"])
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
loader = Loader()
loader.load(target, data)
@ -56,7 +56,7 @@ class BlCamera(BlDatablock):
target_img.image = bpy.data.images[img_id]
loader.load(target_img, img_data)
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
# TODO: background image support
@ -106,7 +106,7 @@ class BlCamera(BlDatablock):
return dumper.dump(instance)
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
for background in datablock.background_images:
if background.image:

View File

@ -20,9 +20,9 @@ import bpy
import mathutils
from .. import utils
from .bl_datablock import BlDatablock
from .dump_anything import Loader, Dumper
from replication.protocol import ReplicatedDatablock
from replication.objects import Node
def dump_collection_children(collection):
collection_children = []
@ -81,42 +81,37 @@ def resolve_collection_dependencies(collection):
return deps
class BlCollection(BlDatablock):
class BlCollection(ReplicatedDatablock):
bl_id = "collections"
bl_icon = 'FILE_FOLDER'
bl_class = bpy.types.Collection
bl_check_common = True
bl_reload_parent = False
def construct(self, data):
if self.is_library:
with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData):
targetData.collections = [
name for name in sourceData.collections if name == self.data['name']]
@staticmethod
def construct(data: dict) -> object:
datablock = bpy.data.collections.new(node.data["name"])
return datablock
instance = bpy.data.collections[self.data['name']]
return instance
instance = bpy.data.collections.new(data["name"])
return instance
def _load_implementation(self, data, target):
@staticmethod
def load(data: dict, datablock: object):
data = node.data
loader = Loader()
loader.load(target, data)
loader.load(datablock, data)
# Objects
load_collection_objects(data['objects'], target)
load_collection_objects(data['objects'], datablock)
# Link childrens
load_collection_childrens(data['children'], target)
load_collection_childrens(data['children'], datablock)
# FIXME: Find a better way after the replication big refacotoring
# Keep other user from deleting collection object by flushing their history
utils.flush_history()
def _dump_implementation(self, data, instance=None):
assert(instance)
@staticmethod
def dump(datablock: object) -> dict:
assert(datablock)
dumper = Dumper()
dumper.depth = 1
@ -124,16 +119,16 @@ class BlCollection(BlDatablock):
"name",
"instance_offset"
]
data = dumper.dump(instance)
data = dumper.dump(datablock)
# dump objects
data['objects'] = dump_collection_objects(instance)
data['objects'] = dump_collection_objects(datablock)
# dump children collections
data['children'] = dump_collection_children(instance)
data['children'] = dump_collection_children(datablock)
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
return resolve_collection_dependencies(datablock)

View File

@ -141,10 +141,10 @@ class BlCurve(BlDatablock):
bl_icon = 'CURVE_DATA'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.curves.new(data["name"], data["type"])
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
loader = Loader()
loader.load(target, data)
@ -175,7 +175,7 @@ class BlCurve(BlDatablock):
if src_materials:
load_materials_slots(src_materials, target.materials)
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
dumper = Dumper()
# Conflicting attributes
@ -223,7 +223,7 @@ class BlCurve(BlDatablock):
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
curve = datablock

View File

@ -23,72 +23,14 @@ import bpy
import mathutils
from replication.constants import DIFF_BINARY, DIFF_JSON, UP
from replication.protocol import ReplicatedDatablock
from replication.objects import Node
from uuid import uuid4
from .. import utils
from .dump_anything import Dumper, Loader
def has_action(target):
""" Check if the target datablock has actions
"""
return (hasattr(target, 'animation_data')
and target.animation_data
and target.animation_data.action)
def has_driver(target):
""" Check if the target datablock is driven
"""
return (hasattr(target, 'animation_data')
and target.animation_data
and target.animation_data.drivers)
def dump_driver(driver):
dumper = Dumper()
dumper.depth = 6
data = dumper.dump(driver)
return data
def load_driver(target_datablock, src_driver):
loader = Loader()
drivers = target_datablock.animation_data.drivers
src_driver_data = src_driver['driver']
new_driver = drivers.new(src_driver['data_path'], index=src_driver['array_index'])
# Settings
new_driver.driver.type = src_driver_data['type']
new_driver.driver.expression = src_driver_data['expression']
loader.load(new_driver, src_driver)
# Variables
for src_variable in src_driver_data['variables']:
src_var_data = src_driver_data['variables'][src_variable]
new_var = new_driver.driver.variables.new()
new_var.name = src_var_data['name']
new_var.type = src_var_data['type']
for src_target in src_var_data['targets']:
src_target_data = src_var_data['targets'][src_target]
new_var.targets[src_target].id = utils.resolve_from_id(
src_target_data['id'], src_target_data['id_type'])
loader.load(
new_var.targets[src_target], src_target_data)
# Fcurve
new_fcurve = new_driver.keyframe_points
for p in reversed(new_fcurve):
new_fcurve.remove(p, fast=True)
new_fcurve.add(len(src_driver['keyframe_points']))
for index, src_point in enumerate(src_driver['keyframe_points']):
new_point = new_fcurve[index]
loader.load(new_point, src_driver['keyframe_points'][src_point])
def get_datablock_from_uuid(uuid, default, ignore=[]):
if not uuid:
return default
@ -101,118 +43,17 @@ def get_datablock_from_uuid(uuid, default, ignore=[]):
return default
class BlDatablock(ReplicatedDatablock):
"""BlDatablock
def resolve_datablock_from_root(node:Node, root)->object:
datablock_ref = utils.find_from_attr('uuid', node.uuid, root)
bl_id : blender internal storage identifier
bl_class : blender internal type
bl_icon : type icon (blender icon name)
bl_check_common: enable check even in common rights
bl_reload_parent: reload parent
"""
if not datablock_ref:
try:
datablock_ref = root[node.data['name']]
except Exception:
pass
return datablock_ref
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
self.preferences = utils.get_preferences()
# TODO: use is_library_indirect
self.is_library = (instance and hasattr(instance, 'library') and
instance.library) or \
(hasattr(self,'data') and self.data and 'library' in self.data)
if instance and hasattr(instance, 'uuid'):
instance.uuid = self.uuid
def resolve(self, construct = True):
datablock_root = getattr(bpy.data, self.bl_id)
datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root)
if not datablock_ref:
try:
datablock_ref = datablock_root[self.data['name']]
except Exception:
pass
if construct and not datablock_ref:
name = self.data.get('name')
logging.debug(f"Constructing {name}")
datablock_ref = self._construct(data=self.data)
if datablock_ref is not None:
setattr(datablock_ref, 'uuid', self.uuid)
self.instance = datablock_ref
return True
else:
return False
def dump(self, instance=None):
dumper = Dumper()
data = {}
# Dump animation data
if has_action(instance):
dumper = Dumper()
dumper.include_filter = ['action']
data['animation_data'] = dumper.dump(instance.animation_data)
if has_driver(instance):
dumped_drivers = {'animation_data': {'drivers': []}}
for driver in instance.animation_data.drivers:
dumped_drivers['animation_data']['drivers'].append(
dump_driver(driver))
data.update(dumped_drivers)
if self.is_library:
data.update(dumper.dump(instance))
else:
data.update(self._dump_implementation(data, instance=instance))
return data
def _dump_implementation(self, data, target):
raise NotImplementedError
def load(self, data, target):
# Load animation data
if 'animation_data' in data.keys():
if target.animation_data is None:
target.animation_data_create()
for d in target.animation_data.drivers:
target.animation_data.drivers.remove(d)
if 'drivers' in data['animation_data']:
for driver in data['animation_data']['drivers']:
load_driver(target, driver)
if 'action' in data['animation_data']:
target.animation_data.action = bpy.data.actions[data['animation_data']['action']]
# Remove existing animation data if there is not more to load
elif hasattr(target, 'animation_data') and target.animation_data:
target.animation_data_clear()
if self.is_library:
return
else:
self._load_implementation(data, target)
def _load_implementation(self, data, target):
raise NotImplementedError
def resolve_deps(self, datablock):
dependencies = []
if has_action(datablock):
dependencies.append(datablock.animation_data.action)
dependencies.extend(self._resolve_deps_implementation(datablock))
return dependencies
@staticmethod
def _resolve_deps_implementation(datablock):
return []
def stamp_uuid(datablock):
if not datablock.uuid:
datablock.uuid = str(uuid4())

View File

@ -34,7 +34,7 @@ class BlFont(BlDatablock):
bl_icon = 'FILE_FONT'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
filename = data.get('filename')
if filename == '<builtin>':
@ -63,7 +63,7 @@ class BlFont(BlDatablock):
return False
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
if datablock.filepath and datablock.filepath != '<builtin>':
ensure_unpacked(datablock)

View File

@ -235,10 +235,10 @@ class BlGpencil(BlDatablock):
bl_icon = 'GREASEPENCIL'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.grease_pencils.new(data["name"])
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
target.materials.clear()
if "materials" in data.keys():
for mat in data['materials']:
@ -267,7 +267,7 @@ class BlGpencil(BlDatablock):
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
dumper = Dumper()
dumper.depth = 2
@ -291,7 +291,7 @@ class BlGpencil(BlDatablock):
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
for material in datablock.materials:

View File

@ -55,7 +55,7 @@ class BlImage(BlDatablock):
bl_icon = 'IMAGE_DATA'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.images.new(
name=data['name'],
width=data['size'][0],
@ -102,7 +102,7 @@ class BlImage(BlDatablock):
return False
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
if datablock.packed_file:

View File

@ -33,10 +33,10 @@ class BlLattice(BlDatablock):
bl_icon = 'LATTICE_DATA'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.lattices.new(data["name"])
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
if target.is_editmode:
raise ContextError("lattice is in edit mode")
@ -45,7 +45,7 @@ class BlLattice(BlDatablock):
np_load_collection(data['points'], target.points, POINT)
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
if instance.is_editmode:
raise ContextError("lattice is in edit mode")

View File

@ -30,7 +30,7 @@ class BlLibrary(BlDatablock):
bl_icon = 'LIBRARY_DATA_DIRECT'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
with bpy.data.libraries.load(filepath=data["filepath"], link=True) as (sourceData, targetData):
targetData = sourceData
return sourceData

View File

@ -30,14 +30,14 @@ class BlLight(BlDatablock):
bl_icon = 'LIGHT_DATA'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.lights.new(data["name"], data["type"])
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
loader = Loader()
loader.load(target, data)
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
dumper = Dumper()
dumper.depth = 3

View File

@ -31,7 +31,7 @@ class BlLightprobe(BlDatablock):
bl_icon = 'LIGHTPROBE_GRID'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type']
# See https://developer.blender.org/D6396
if bpy.app.version[1] >= 83:
@ -39,11 +39,11 @@ class BlLightprobe(BlDatablock):
else:
logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
loader = Loader()
loader.load(target, data)
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
if bpy.app.version[1] < 83:
logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")

View File

@ -24,7 +24,9 @@ import re
from uuid import uuid4
from .dump_anything import Loader, Dumper
from .bl_datablock import BlDatablock, get_datablock_from_uuid
from .bl_datablock import get_datablock_from_uuid, stamp_uuid
from replication.protocol import ReplicatedDatablock
from replication.objects import Node
NODE_SOCKET_INDEX = re.compile('\[(\d*)\]')
IGNORED_SOCKETS = ['GEOMETRY', 'SHADER']
@ -34,7 +36,7 @@ def load_node(node_data: dict, node_tree: bpy.types.ShaderNodeTree):
:arg node_data: dumped node data
:type node_data: dict
:arg node_tree: target node_tree
:arg node_tree: datablock node_tree
:type node_tree: bpy.types.NodeTree
"""
loader = Loader()
@ -84,7 +86,7 @@ def load_node(node_data: dict, node_tree: bpy.types.ShaderNodeTree):
def dump_node(node: bpy.types.ShaderNode) -> dict:
""" Dump a single node to a dict
:arg node: target node
:arg node: datablock node
:type node: bpy.types.Node
:retrun: dict
"""
@ -241,7 +243,7 @@ def dump_node_tree(node_tree: bpy.types.ShaderNodeTree) -> dict:
def dump_node_tree_sockets(sockets: bpy.types.Collection) -> dict:
""" dump sockets of a shader_node_tree
:arg target_node_tree: target node_tree
:arg target_node_tree: datablock node_tree
:type target_node_tree: bpy.types.NodeTree
:arg socket_id: socket identifer
:type socket_id: str
@ -264,7 +266,7 @@ def load_node_tree_sockets(sockets: bpy.types.Collection,
sockets_data: dict):
""" load sockets of a shader_node_tree
:arg target_node_tree: target node_tree
:arg target_node_tree: datablock node_tree
:type target_node_tree: bpy.types.NodeTree
:arg socket_id: socket identifer
:type socket_id: str
@ -292,7 +294,7 @@ def load_node_tree(node_tree_data: dict, target_node_tree: bpy.types.ShaderNodeT
:arg node_tree_data: dumped node data
:type node_tree_data: dict
:arg target_node_tree: target node_tree
:arg target_node_tree: datablock node_tree
:type target_node_tree: bpy.types.NodeTree
"""
# TODO: load only required nodes
@ -353,7 +355,7 @@ def load_materials_slots(src_materials: list, dst_materials: bpy.types.bpy_prop_
:arg src_materials: dumped material collection (ex: object.materials)
:type src_materials: list of tuples (uuid, name)
:arg dst_materials: target material collection pointer
:arg dst_materials: datablock material collection pointer
:type dst_materials: bpy.types.bpy_prop_collection
"""
# MATERIAL SLOTS
@ -372,36 +374,41 @@ def load_materials_slots(src_materials: list, dst_materials: bpy.types.bpy_prop_
dst_materials.append(mat_ref)
class BlMaterial(BlDatablock):
class BlMaterial(ReplicatedDatablock):
bl_id = "materials"
bl_class = bpy.types.Material
bl_check_common = False
bl_icon = 'MATERIAL_DATA'
bl_reload_parent = False
def construct(self, data):
@staticmethod
def construct(data: dict) -> object:
return bpy.data.materials.new(data["name"])
def _load_implementation(self, data, target):
@staticmethod
def load(data: dict, datablock: object):
data = data
loader = Loader()
is_grease_pencil = data.get('is_grease_pencil')
use_nodes = data.get('use_nodes')
loader.load(target, data)
loader.load(datablock, data)
if is_grease_pencil:
if not target.is_grease_pencil:
bpy.data.materials.create_gpencil_data(target)
loader.load(target.grease_pencil, data['grease_pencil'])
if not datablock.is_grease_pencil:
bpy.data.materials.create_gpencil_data(datablock)
loader.load(datablock.grease_pencil, data['grease_pencil'])
elif use_nodes:
if target.node_tree is None:
target.use_nodes = True
if datablock.node_tree is None:
datablock.use_nodes = True
load_node_tree(data['node_tree'], target.node_tree)
load_node_tree(data['node_tree'], datablock.node_tree)
@staticmethod
def dump(datablock: object) -> dict:
stamp_uuid(datablock)
def _dump_implementation(self, data, instance=None):
assert(instance)
mat_dumper = Dumper()
mat_dumper.depth = 2
mat_dumper.include_filter = [
@ -427,9 +434,9 @@ class BlMaterial(BlDatablock):
'line_priority',
'is_grease_pencil'
]
data = mat_dumper.dump(instance)
data = mat_dumper.dump(datablock)
if instance.is_grease_pencil:
if datablock.is_grease_pencil:
gp_mat_dumper = Dumper()
gp_mat_dumper.depth = 3
@ -463,14 +470,14 @@ class BlMaterial(BlDatablock):
'use_overlap_strokes',
'use_fill_holdout',
]
data['grease_pencil'] = gp_mat_dumper.dump(instance.grease_pencil)
elif instance.use_nodes:
data['node_tree'] = dump_node_tree(instance.node_tree)
data['grease_pencil'] = gp_mat_dumper.dump(datablock.grease_pencil)
elif datablock.use_nodes:
data['node_tree'] = dump_node_tree(datablock.node_tree)
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
# TODO: resolve node group deps
deps = []

View File

@ -22,12 +22,21 @@ import mathutils
import logging
import numpy as np
from .dump_anything import Dumper, Loader, np_load_collection_primitives, np_dump_collection_primitive, np_load_collection, np_dump_collection
from .dump_anything import (Dumper,
Loader,
np_load_collection_primitives,
np_dump_collection_primitive,
np_load_collection, np_dump_collection)
from replication.constants import DIFF_BINARY
from replication.exception import ContextError
from .bl_datablock import BlDatablock, get_datablock_from_uuid
from replication.protocol import ReplicatedDatablock
from replication.objects import Node
from .bl_datablock import get_datablock_from_uuid, stamp_uuid
from .bl_material import dump_materials_slots, load_materials_slots
from ..preferences import get_preferences
VERTICE = ['co']
EDGE = [
@ -49,80 +58,87 @@ POLYGON = [
'material_index',
]
class BlMesh(BlDatablock):
class BlMesh(ReplicatedDatablock):
bl_id = "meshes"
bl_class = bpy.types.Mesh
bl_check_common = False
bl_icon = 'MESH_DATA'
bl_reload_parent = True
def construct(self, data):
instance = bpy.data.meshes.new(data["name"])
instance.uuid = self.uuid
return instance
@staticmethod
def construct(data: dict) -> object:
datablock = bpy.data.meshes.new(data["name"])
datablock.uuid = data['uuid']
return datablock
def _load_implementation(self, data, target):
if not target or target.is_editmode:
@staticmethod
def load(data: dict, datablock: object):
data = data
if not datablock or datablock.is_editmode:
raise ContextError
else:
loader = Loader()
loader.load(target, data)
loader.load(datablock, data)
# MATERIAL SLOTS
src_materials = data.get('materials', None)
if src_materials:
load_materials_slots(src_materials, target.materials)
load_materials_slots(src_materials, datablock.materials)
# CLEAR GEOMETRY
if target.vertices:
target.clear_geometry()
if datablock.vertices:
datablock.clear_geometry()
target.vertices.add(data["vertex_count"])
target.edges.add(data["egdes_count"])
target.loops.add(data["loop_count"])
target.polygons.add(data["poly_count"])
datablock.vertices.add(data["vertex_count"])
datablock.edges.add(data["egdes_count"])
datablock.loops.add(data["loop_count"])
datablock.polygons.add(data["poly_count"])
# LOADING
np_load_collection(data['vertices'], target.vertices, VERTICE)
np_load_collection(data['edges'], target.edges, EDGE)
np_load_collection(data['loops'], target.loops, LOOP)
np_load_collection(data["polygons"],target.polygons, POLYGON)
np_load_collection(data['vertices'], datablock.vertices, VERTICE)
np_load_collection(data['edges'], datablock.edges, EDGE)
np_load_collection(data['loops'], datablock.loops, LOOP)
np_load_collection(data["polygons"], datablock.polygons, POLYGON)
# UV Layers
if 'uv_layers' in data.keys():
for layer in data['uv_layers']:
if layer not in target.uv_layers:
target.uv_layers.new(name=layer)
if layer not in datablock.uv_layers:
datablock.uv_layers.new(name=layer)
np_load_collection_primitives(
target.uv_layers[layer].data,
'uv',
datablock.uv_layers[layer].data,
'uv',
data["uv_layers"][layer]['data'])
# Vertex color
if 'vertex_colors' in data.keys():
for color_layer in data['vertex_colors']:
if color_layer not in target.vertex_colors:
target.vertex_colors.new(name=color_layer)
if color_layer not in datablock.vertex_colors:
datablock.vertex_colors.new(name=color_layer)
np_load_collection_primitives(
target.vertex_colors[color_layer].data,
'color',
datablock.vertex_colors[color_layer].data,
'color',
data["vertex_colors"][color_layer]['data'])
target.validate()
target.update()
datablock.validate()
datablock.update()
def _dump_implementation(self, data, instance=None):
assert(instance)
@staticmethod
def dump(datablock: object) -> dict:
stamp_uuid(datablock)
if (instance.is_editmode or bpy.context.mode == "SCULPT") and not self.preferences.sync_flags.sync_during_editmode:
if (datablock.is_editmode or bpy.context.mode == "SCULPT") and not get_preferences().sync_flags.sync_during_editmode:
raise ContextError("Mesh is in edit mode")
mesh = instance
mesh = datablock
dumper = Dumper()
dumper.depth = 1
dumper.include_filter = [
'uuid'
'name',
'use_auto_smooth',
'auto_smooth_angle',
@ -153,21 +169,23 @@ class BlMesh(BlDatablock):
data['uv_layers'] = {}
for layer in mesh.uv_layers:
data['uv_layers'][layer.name] = {}
data['uv_layers'][layer.name]['data'] = np_dump_collection_primitive(layer.data, 'uv')
data['uv_layers'][layer.name]['data'] = np_dump_collection_primitive(
layer.data, 'uv')
# Vertex color
if mesh.vertex_colors:
data['vertex_colors'] = {}
for color_map in mesh.vertex_colors:
data['vertex_colors'][color_map.name] = {}
data['vertex_colors'][color_map.name]['data'] = np_dump_collection_primitive(color_map.data, 'color')
data['vertex_colors'][color_map.name]['data'] = np_dump_collection_primitive(
color_map.data, 'color')
# Materials
data['materials'] = dump_materials_slots(instance.materials)
data['materials'] = dump_materials_slots(datablock.materials)
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
for material in datablock.materials:
@ -178,7 +196,7 @@ class BlMesh(BlDatablock):
def diff(self):
if 'EDIT' in bpy.context.mode \
and not self.preferences.sync_flags.sync_during_editmode:
and not get_preferences().sync_flags.sync_during_editmode:
return False
else:
return super().diff()

View File

@ -69,10 +69,10 @@ class BlMetaball(BlDatablock):
bl_icon = 'META_BALL'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.metaballs.new(data["name"])
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
loader = Loader()
loader.load(target, data)
@ -83,7 +83,7 @@ class BlMetaball(BlDatablock):
load_metaball_elements(data['elements'], target.elements)
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
dumper = Dumper()
dumper.depth = 1

View File

@ -32,15 +32,15 @@ class BlNodeGroup(BlDatablock):
bl_icon = 'NODETREE'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.node_groups.new(data["name"], data["type"])
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
load_node_tree(data, target)
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
return dump_node_tree(instance)
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
return get_node_tree_dependencies(datablock)

View File

@ -21,8 +21,15 @@ import re
import bpy
import mathutils
from replication.exception import ContextError
from replication.objects import Node
from replication.protocol import ReplicatedDatablock
from .bl_datablock import BlDatablock, get_datablock_from_uuid
from .bl_datablock import get_datablock_from_uuid, stamp_uuid
from .bl_action import (load_animation_data,
dump_animation_data,
get_animation_dependencies)
from ..preferences import get_preferences
from .dump_anything import (
Dumper,
Loader,
@ -30,13 +37,13 @@ from .dump_anything import (
np_dump_collection)
SKIN_DATA = [
'radius',
'use_loose',
'use_root'
]
def get_input_index(e):
return int(re.findall('[0-9]+', e)[0])
@ -95,45 +102,40 @@ def load_pose(target_bone, data):
def find_data_from_name(name=None):
instance = None
datablock = None
if not name:
pass
elif name in bpy.data.meshes.keys():
instance = bpy.data.meshes[name]
datablock = bpy.data.meshes[name]
elif name in bpy.data.lights.keys():
instance = bpy.data.lights[name]
datablock = bpy.data.lights[name]
elif name in bpy.data.cameras.keys():
instance = bpy.data.cameras[name]
datablock = bpy.data.cameras[name]
elif name in bpy.data.curves.keys():
instance = bpy.data.curves[name]
datablock = bpy.data.curves[name]
elif name in bpy.data.metaballs.keys():
instance = bpy.data.metaballs[name]
datablock = bpy.data.metaballs[name]
elif name in bpy.data.armatures.keys():
instance = bpy.data.armatures[name]
datablock = bpy.data.armatures[name]
elif name in bpy.data.grease_pencils.keys():
instance = bpy.data.grease_pencils[name]
datablock = bpy.data.grease_pencils[name]
elif name in bpy.data.curves.keys():
instance = bpy.data.curves[name]
datablock = bpy.data.curves[name]
elif name in bpy.data.lattices.keys():
instance = bpy.data.lattices[name]
datablock = bpy.data.lattices[name]
elif name in bpy.data.speakers.keys():
instance = bpy.data.speakers[name]
datablock = bpy.data.speakers[name]
elif name in bpy.data.lightprobes.keys():
# Only supported since 2.83
if bpy.app.version[1] >= 83:
instance = bpy.data.lightprobes[name]
datablock = bpy.data.lightprobes[name]
else:
logging.warning(
"Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")
elif bpy.app.version[1] >= 91 and name in bpy.data.volumes.keys():
# Only supported since 2.91
instance = bpy.data.volumes[name]
return instance
def load_data(object, name):
logging.info("loading data")
pass
datablock = bpy.data.volumes[name]
return datablock
def _is_editmode(object: bpy.types.Object) -> bool:
@ -175,6 +177,7 @@ def find_geometry_nodes(modifiers: bpy.types.bpy_prop_collection) -> [bpy.types.
return nodes_groups
def dump_vertex_groups(src_object: bpy.types.Object) -> dict:
""" Dump object's vertex groups
@ -219,130 +222,127 @@ def load_vertex_groups(dumped_vertex_groups: dict, target_object: bpy.types.Obje
for index, weight in vg['vertices']:
vertex_group.add([index], weight, 'REPLACE')
class BlObject(BlDatablock):
class BlObject(ReplicatedDatablock):
bl_id = "objects"
bl_class = bpy.types.Object
bl_check_common = False
bl_icon = 'OBJECT_DATA'
bl_reload_parent = False
def construct(self, data):
instance = None
if self.is_library:
with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData):
targetData.objects = [
name for name in sourceData.objects if name == self.data['name']]
instance = bpy.data.objects[self.data['name']]
instance.uuid = self.uuid
return instance
@staticmethod
def construct(data: dict) -> bpy.types.Object:
datablock = None
# TODO: refactoring
object_name = data.get("name")
data_uuid = data.get("data_uuid")
data_id = data.get("data")
object_uuid = data.get('uuid')
object_data = get_datablock_from_uuid(
data_uuid,
find_data_from_name(data_id),
ignore=['images']) # TODO: use resolve_from_id
if object_data is None and data_uuid:
raise Exception(f"Fail to load object {data['name']}({self.uuid})")
raise Exception(f"Fail to load object {data['name']}({object_uuid})")
instance = bpy.data.objects.new(object_name, object_data)
instance.uuid = self.uuid
datablock = bpy.data.objects.new(object_name, object_data)
datablock.uuid = object_uuid
return instance
return datablock
@staticmethod
def load(data: dict, datablock: bpy.types.Object):
data = node.data
load_animation_data(data, datablock)
def _load_implementation(self, data, target):
loader = Loader()
data_uuid = data.get("data_uuid")
data_id = data.get("data")
if target.data and (target.data.name != data_id):
target.data = get_datablock_from_uuid(
if datablock.data and (datablock.data.name != data_id):
datablock.data = get_datablock_from_uuid(
data_uuid, find_data_from_name(data_id), ignore=['images'])
# vertex groups
vertex_groups = data.get('vertex_groups', None)
if vertex_groups:
load_vertex_groups(vertex_groups, target)
load_vertex_groups(vertex_groups, datablock)
object_data = target.data
object_data = datablock.data
# SHAPE KEYS
if 'shape_keys' in data:
target.shape_key_clear()
datablock.shape_key_clear()
# Create keys and load vertices coords
for key_block in data['shape_keys']['key_blocks']:
key_data = data['shape_keys']['key_blocks'][key_block]
target.shape_key_add(name=key_block)
datablock.shape_key_add(name=key_block)
loader.load(
target.data.shape_keys.key_blocks[key_block], key_data)
datablock.data.shape_keys.key_blocks[key_block], key_data)
for vert in key_data['data']:
target.data.shape_keys.key_blocks[key_block].data[vert].co = key_data['data'][vert]['co']
datablock.data.shape_keys.key_blocks[key_block].data[vert].co = key_data['data'][vert]['co']
# Load relative key after all
for key_block in data['shape_keys']['key_blocks']:
reference = data['shape_keys']['key_blocks'][key_block]['relative_key']
target.data.shape_keys.key_blocks[key_block].relative_key = target.data.shape_keys.key_blocks[reference]
datablock.data.shape_keys.key_blocks[key_block].relative_key = datablock.data.shape_keys.key_blocks[reference]
# Load transformation data
loader.load(target, data)
loader.load(datablock, data)
# Object display fields
if 'display' in data:
loader.load(target.display, data['display'])
loader.load(datablock.display, data['display'])
# Parenting
parent_id = data.get('parent_id')
if parent_id:
parent = bpy.data.objects[parent_id]
# Avoid reloading
if target.parent != parent and parent is not None:
target.parent = parent
elif target.parent:
target.parent = None
if datablock.parent != parent and parent is not None:
datablock.parent = parent
elif datablock.parent:
datablock.parent = None
# Pose
if 'pose' in data:
if not target.pose:
if not datablock.pose:
raise Exception('No pose data yet (Fixed in a near futur)')
# Bone groups
for bg_name in data['pose']['bone_groups']:
bg_data = data['pose']['bone_groups'].get(bg_name)
bg_target = target.pose.bone_groups.get(bg_name)
bg_datablock = datablock.pose.bone_groups.get(bg_name)
if not bg_target:
bg_target = target.pose.bone_groups.new(name=bg_name)
if not bg_datablock:
bg_datablock = datablock.pose.bone_groups.new(name=bg_name)
loader.load(bg_target, bg_data)
# target.pose.bone_groups.get
loader.load(bg_datablock, bg_data)
# datablock.pose.bone_groups.get
# Bones
for bone in data['pose']['bones']:
target_bone = target.pose.bones.get(bone)
datablock_bone = datablock.pose.bones.get(bone)
bone_data = data['pose']['bones'].get(bone)
if 'constraints' in bone_data.keys():
loader.load(target_bone, bone_data['constraints'])
loader.load(datablock_bone, bone_data['constraints'])
load_pose(target_bone, bone_data)
load_pose(datablock_bone, bone_data)
if 'bone_index' in bone_data.keys():
target_bone.bone_group = target.pose.bone_group[bone_data['bone_group_index']]
datablock_bone.bone_group = datablock.pose.bone_group[bone_data['bone_group_index']]
# TODO: find another way...
if target.empty_display_type == "IMAGE":
if datablock.empty_display_type == "IMAGE":
img_uuid = data.get('data_uuid')
if target.data is None and img_uuid:
target.data = get_datablock_from_uuid(img_uuid, None)
if datablock.data is None and img_uuid:
datablock.data = get_datablock_from_uuid(img_uuid, None)
if hasattr(object_data, 'skin_vertices') \
and object_data.skin_vertices\
@ -353,34 +353,43 @@ class BlObject(BlDatablock):
skin_data.data,
SKIN_DATA)
if hasattr(target, 'cycles_visibility') \
and 'cycles_visibility' in data:
loader.load(target.cycles_visibility, data['cycles_visibility'])
if hasattr(datablock, 'cycles_visibility') \
and 'cycles_visibility' in data:
loader.load(datablock.cycles_visibility, data['cycles_visibility'])
# TODO: handle geometry nodes input from dump_anything
if hasattr(target, 'modifiers'):
nodes_modifiers = [mod for mod in target.modifiers if mod.type == 'NODES']
if hasattr(datablock, 'modifiers'):
nodes_modifiers = [
mod for mod in datablock.modifiers if mod.type == 'NODES']
for modifier in nodes_modifiers:
load_modifier_geometry_node_inputs(data['modifiers'][modifier.name], modifier)
load_modifier_geometry_node_inputs(
data['modifiers'][modifier.name], modifier)
transform = data.get('transforms', None)
if transform:
target.matrix_parent_inverse = mathutils.Matrix(transform['matrix_parent_inverse'])
target.matrix_basis = mathutils.Matrix(transform['matrix_basis'])
target.matrix_local = mathutils.Matrix(transform['matrix_local'])
datablock.matrix_parent_inverse = mathutils.Matrix(
transform['matrix_parent_inverse'])
datablock.matrix_basis = mathutils.Matrix(
transform['matrix_basis'])
datablock.matrix_local = mathutils.Matrix(
transform['matrix_local'])
def _dump_implementation(self, data, instance=None):
assert(instance)
@staticmethod
def dump(datablock: bpy.types.Object) -> dict:
assert(datablock)
if _is_editmode(instance):
if self.preferences.sync_flags.sync_during_editmode:
instance.update_from_editmode()
stamp_uuid(datablock)
if _is_editmode(datablock):
if get_preferences().sync_flags.sync_during_editmode:
datablock.update_from_editmode()
else:
raise ContextError("Object is in edit-mode.")
dumper = Dumper()
dumper.depth = 1
dumper.include_filter = [
"uuid",
"name",
"rotation_mode",
"data",
@ -413,30 +422,28 @@ class BlObject(BlDatablock):
'type'
]
data = dumper.dump(instance)
data = dumper.dump(datablock)
dumper.include_filter = [
'matrix_parent_inverse',
'matrix_local',
'matrix_basis']
data['transforms'] = dumper.dump(instance)
data['transforms'] = dumper.dump(datablock)
dumper.include_filter = [
'show_shadows',
]
data['display'] = dumper.dump(instance.display)
data['display'] = dumper.dump(datablock.display)
data['data_uuid'] = getattr(instance.data, 'uuid', None)
if self.is_library:
return data
data['data_uuid'] = getattr(datablock.data, 'uuid', None)
# PARENTING
if instance.parent:
data['parent_id'] = instance.parent.name
if datablock.parent:
data['parent_id'] = datablock.parent.name
# MODIFIERS
if hasattr(instance, 'modifiers'):
if hasattr(datablock, 'modifiers'):
data["modifiers"] = {}
modifiers = getattr(instance, 'modifiers', None)
modifiers = getattr(datablock, 'modifiers', None)
if modifiers:
dumper.include_filter = None
dumper.depth = 1
@ -444,9 +451,10 @@ class BlObject(BlDatablock):
data["modifiers"][modifier.name] = dumper.dump(modifier)
# hack to dump geometry nodes inputs
if modifier.type == 'NODES':
dumped_inputs = dump_modifier_geometry_node_inputs(modifier)
dumped_inputs = dump_modifier_geometry_node_inputs(
modifier)
data["modifiers"][modifier.name]['inputs'] = dumped_inputs
gp_modifiers = getattr(instance, 'grease_pencil_modifiers', None)
gp_modifiers = getattr(datablock, 'grease_pencil_modifiers', None)
if gp_modifiers:
dumper.include_filter = None
@ -468,16 +476,16 @@ class BlObject(BlDatablock):
gp_mod_data['curve'] = curve_dumper.dump(modifier.curve)
# CONSTRAINTS
if hasattr(instance, 'constraints'):
if hasattr(datablock, 'constraints'):
dumper.include_filter = None
dumper.depth = 3
data["constraints"] = dumper.dump(instance.constraints)
data["constraints"] = dumper.dump(datablock.constraints)
# POSE
if hasattr(instance, 'pose') and instance.pose:
if hasattr(datablock, 'pose') and datablock.pose:
# BONES
bones = {}
for bone in instance.pose.bones:
for bone in datablock.pose.bones:
bones[bone.name] = {}
dumper.depth = 1
rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler'
@ -502,7 +510,7 @@ class BlObject(BlDatablock):
# GROUPS
bone_groups = {}
for group in instance.pose.bone_groups:
for group in datablock.pose.bone_groups:
dumper.depth = 3
dumper.include_filter = [
'name',
@ -511,13 +519,12 @@ class BlObject(BlDatablock):
bone_groups[group.name] = dumper.dump(group)
data['pose']['bone_groups'] = bone_groups
# VERTEx GROUP
if len(instance.vertex_groups) > 0:
data['vertex_groups'] = dump_vertex_groups(instance)
if len(datablock.vertex_groups) > 0:
data['vertex_groups'] = dump_vertex_groups(datablock)
# SHAPE KEYS
object_data = instance.data
object_data = datablock.data
if hasattr(object_data, 'shape_keys') and object_data.shape_keys:
dumper = Dumper()
dumper.depth = 2
@ -548,11 +555,12 @@ class BlObject(BlDatablock):
if hasattr(object_data, 'skin_vertices') and object_data.skin_vertices:
skin_vertices = list()
for skin_data in object_data.skin_vertices:
skin_vertices.append(np_dump_collection(skin_data.data, SKIN_DATA))
skin_vertices.append(
np_dump_collection(skin_data.data, SKIN_DATA))
data['skin_vertices'] = skin_vertices
# CYCLE SETTINGS
if hasattr(instance, 'cycles_visibility'):
if hasattr(datablock, 'cycles_visibility'):
dumper.include_filter = [
'camera',
'diffuse',
@ -561,25 +569,28 @@ class BlObject(BlDatablock):
'scatter',
'shadow',
]
data['cycles_visibility'] = dumper.dump(instance.cycles_visibility)
data['cycles_visibility'] = dumper.dump(
datablock.cycles_visibility)
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: bpy.types.Object) -> list:
deps = []
# Avoid Empty case
if datablock.data:
deps.append(datablock.data)
if datablock.parent :
if datablock.parent:
deps.append(datablock.parent)
if datablock.instance_type == 'COLLECTION':
# TODO: uuid based
deps.append(datablock.instance_collection)
deps.append(get_animation_dependencies(datablock))
if datablock.modifiers:
deps.extend(find_textures_dependencies(datablock.modifiers))
deps.extend(find_geometry_nodes(datablock.modifiers))

View File

@ -23,14 +23,21 @@ import bpy
import mathutils
from deepdiff import DeepDiff
from replication.constants import DIFF_JSON, MODIFIED
from replication.protocol import ReplicatedDatablock
from replication.objects import Node
from ..utils import flush_history
from .bl_collection import (dump_collection_children, dump_collection_objects,
load_collection_childrens, load_collection_objects,
resolve_collection_dependencies)
from .bl_datablock import BlDatablock
from .bl_action import (load_animation_data,
dump_animation_data,
get_animation_dependencies)
from .bl_datablock import stamp_uuid
from .bl_file import get_filepath
from .dump_anything import Dumper, Loader
from ..preferences import get_preferences
RENDER_SETTINGS = [
'dither_intensity',
@ -286,12 +293,10 @@ def dump_sequence(sequence: bpy.types.Sequence) -> dict:
dumper.depth = 1
data = dumper.dump(sequence)
# TODO: Support multiple images
if sequence.type == 'IMAGE':
data['filenames'] = [e.filename for e in sequence.elements]
# Effect strip inputs
input_count = getattr(sequence, 'input_count', None)
if input_count:
@ -321,53 +326,54 @@ def load_sequence(sequence_data: dict, sequence_editor: bpy.types.SequenceEditor
if strip_type == 'SCENE':
strip_scene = bpy.data.scenes.get(sequence_data.get('scene'))
sequence = sequence_editor.sequences.new_scene(strip_name,
strip_scene,
strip_channel,
strip_frame_start)
strip_scene,
strip_channel,
strip_frame_start)
elif strip_type == 'MOVIE':
filepath = get_filepath(Path(sequence_data['filepath']).name)
sequence = sequence_editor.sequences.new_movie(strip_name,
filepath,
strip_channel,
strip_frame_start)
filepath,
strip_channel,
strip_frame_start)
elif strip_type == 'SOUND':
filepath = bpy.data.sounds[sequence_data['sound']].filepath
sequence = sequence_editor.sequences.new_sound(strip_name,
filepath,
strip_channel,
strip_frame_start)
filepath,
strip_channel,
strip_frame_start)
elif strip_type == 'IMAGE':
images_name = sequence_data.get('filenames')
filepath = get_filepath(images_name[0])
sequence = sequence_editor.sequences.new_image(strip_name,
filepath,
strip_channel,
strip_frame_start)
filepath,
strip_channel,
strip_frame_start)
# load other images
if len(images_name)>1:
for img_idx in range(1,len(images_name)):
if len(images_name) > 1:
for img_idx in range(1, len(images_name)):
sequence.elements.append((images_name[img_idx]))
else:
seq = {}
for i in range(sequence_data['input_count']):
seq[f"seq{i+1}"] = sequence_editor.sequences_all.get(sequence_data.get(f"input_{i+1}", None))
seq[f"seq{i+1}"] = sequence_editor.sequences_all.get(
sequence_data.get(f"input_{i+1}", None))
sequence = sequence_editor.sequences.new_effect(name=strip_name,
type=strip_type,
channel=strip_channel,
frame_start=strip_frame_start,
frame_end=sequence_data['frame_final_end'],
**seq)
type=strip_type,
channel=strip_channel,
frame_start=strip_frame_start,
frame_end=sequence_data['frame_final_end'],
**seq)
loader = Loader()
# TODO: Support filepath updates
loader.exclure_filter = ['filepath', 'sound', 'filenames','fps']
# TODO: Support filepath updates
loader.exclure_filter = ['filepath', 'sound', 'filenames', 'fps']
loader.load(sequence, sequence_data)
sequence.select = False
class BlScene(BlDatablock):
class BlScene(ReplicatedDatablock):
is_root = True
bl_id = "scenes"
@ -376,58 +382,60 @@ class BlScene(BlDatablock):
bl_icon = 'SCENE_DATA'
bl_reload_parent = False
def construct(self, data):
instance = bpy.data.scenes.new(data["name"])
instance.uuid = self.uuid
@staticmethod
def construct(data: dict) -> object:
datablock = bpy.data.scenes.new(data["name"])
datablock.uuid = data.get("uuid")
return instance
return datablock
def _load_implementation(self, data, target):
@staticmethod
def load(data: dict, datablock: object):
# Load other meshes metadata
loader = Loader()
loader.load(target, data)
loader.load(datablock, data)
# Load master collection
load_collection_objects(
data['collection']['objects'], target.collection)
data['collection']['objects'], datablock.collection)
load_collection_childrens(
data['collection']['children'], target.collection)
data['collection']['children'], datablock.collection)
if 'world' in data.keys():
target.world = bpy.data.worlds[data['world']]
datablock.world = bpy.data.worlds[data['world']]
# Annotation
if 'grease_pencil' in data.keys():
target.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']]
datablock.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']]
if self.preferences.sync_flags.sync_render_settings:
if get_preferences().sync_flags.sync_render_settings:
if 'eevee' in data.keys():
loader.load(target.eevee, data['eevee'])
loader.load(datablock.eevee, data['eevee'])
if 'cycles' in data.keys():
loader.load(target.cycles, data['cycles'])
loader.load(datablock.cycles, data['cycles'])
if 'render' in data.keys():
loader.load(target.render, data['render'])
loader.load(datablock.render, data['render'])
if 'view_settings' in data.keys():
loader.load(target.view_settings, data['view_settings'])
if target.view_settings.use_curve_mapping and \
loader.load(datablock.view_settings, data['view_settings'])
if datablock.view_settings.use_curve_mapping and \
'curve_mapping' in data['view_settings']:
# TODO: change this ugly fix
target.view_settings.curve_mapping.white_level = data[
datablock.view_settings.curve_mapping.white_level = data[
'view_settings']['curve_mapping']['white_level']
target.view_settings.curve_mapping.black_level = data[
datablock.view_settings.curve_mapping.black_level = data[
'view_settings']['curve_mapping']['black_level']
target.view_settings.curve_mapping.update()
datablock.view_settings.curve_mapping.update()
# Sequencer
sequences = data.get('sequences')
if sequences:
# Create sequencer data
target.sequence_editor_create()
vse = target.sequence_editor
datablock.sequence_editor_create()
vse = datablock.sequence_editor
# Clear removed sequences
for seq in vse.sequences_all:
@ -437,15 +445,16 @@ class BlScene(BlDatablock):
for seq_name, seq_data in sequences.items():
load_sequence(seq_data, vse)
# If the sequence is no longer used, clear it
elif target.sequence_editor and not sequences:
target.sequence_editor_clear()
elif datablock.sequence_editor and not sequences:
datablock.sequence_editor_clear()
# FIXME: Find a better way after the replication big refacotoring
# Keep other user from deleting collection object by flushing their history
flush_history()
def _dump_implementation(self, data, instance=None):
assert(instance)
@staticmethod
def dump(datablock: object) -> dict:
stamp_uuid(datablock)
# Metadata
scene_dumper = Dumper()
@ -458,41 +467,43 @@ class BlScene(BlDatablock):
'frame_start',
'frame_end',
'frame_step',
'uuid'
]
if self.preferences.sync_flags.sync_active_camera:
if get_preferences().sync_flags.sync_active_camera:
scene_dumper.include_filter.append('camera')
data.update(scene_dumper.dump(instance))
data = scene_dumper.dump(datablock)
dump_animation_data(datablock, data)
# Master collection
data['collection'] = {}
data['collection']['children'] = dump_collection_children(
instance.collection)
datablock.collection)
data['collection']['objects'] = dump_collection_objects(
instance.collection)
datablock.collection)
scene_dumper.depth = 1
scene_dumper.include_filter = None
# Render settings
if self.preferences.sync_flags.sync_render_settings:
if get_preferences().sync_flags.sync_render_settings:
scene_dumper.include_filter = RENDER_SETTINGS
data['render'] = scene_dumper.dump(instance.render)
data['render'] = scene_dumper.dump(datablock.render)
if instance.render.engine == 'BLENDER_EEVEE':
if datablock.render.engine == 'BLENDER_EEVEE':
scene_dumper.include_filter = EVEE_SETTINGS
data['eevee'] = scene_dumper.dump(instance.eevee)
elif instance.render.engine == 'CYCLES':
data['eevee'] = scene_dumper.dump(datablock.eevee)
elif datablock.render.engine == 'CYCLES':
scene_dumper.include_filter = CYCLES_SETTINGS
data['cycles'] = scene_dumper.dump(instance.cycles)
data['cycles'] = scene_dumper.dump(datablock.cycles)
scene_dumper.include_filter = VIEW_SETTINGS
data['view_settings'] = scene_dumper.dump(instance.view_settings)
data['view_settings'] = scene_dumper.dump(datablock.view_settings)
if instance.view_settings.use_curve_mapping:
if datablock.view_settings.use_curve_mapping:
data['view_settings']['curve_mapping'] = scene_dumper.dump(
instance.view_settings.curve_mapping)
datablock.view_settings.curve_mapping)
scene_dumper.depth = 5
scene_dumper.include_filter = [
'curves',
@ -500,21 +511,20 @@ class BlScene(BlDatablock):
'location',
]
data['view_settings']['curve_mapping']['curves'] = scene_dumper.dump(
instance.view_settings.curve_mapping.curves)
datablock.view_settings.curve_mapping.curves)
# Sequence
vse = instance.sequence_editor
vse = datablock.sequence_editor
if vse:
dumped_sequences = {}
for seq in vse.sequences_all:
dumped_sequences[seq.name] = dump_sequence(seq)
data['sequences'] = dumped_sequences
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
# Master Collection
@ -540,20 +550,20 @@ class BlScene(BlDatablock):
for elem in sequence.elements:
sequence.append(
Path(bpy.path.abspath(sequence.directory),
elem.filename))
elem.filename))
return deps
def diff(self):
exclude_path = []
if not self.preferences.sync_flags.sync_render_settings:
if not get_preferences().sync_flags.sync_render_settings:
exclude_path.append("root['eevee']")
exclude_path.append("root['cycles']")
exclude_path.append("root['view_settings']")
exclude_path.append("root['render']")
if not self.preferences.sync_flags.sync_active_camera:
if not get_preferences().sync_flags.sync_active_camera:
exclude_path.append("root['camera']")
return DeepDiff(self.data, self._dump(instance=self.instance), exclude_paths=exclude_path)
return DeepDiff(self.data, self._dump(datablock=self.datablock), exclude_paths=exclude_path)

View File

@ -34,7 +34,7 @@ class BlSound(BlDatablock):
bl_icon = 'SOUND'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
filename = data.get('filename')
return bpy.data.sounds.load(get_filepath(filename))
@ -58,7 +58,7 @@ class BlSound(BlDatablock):
}
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
if datablock.filepath and datablock.filepath != '<builtin>':
ensure_unpacked(datablock)

View File

@ -30,14 +30,14 @@ class BlSpeaker(BlDatablock):
bl_icon = 'SPEAKER'
bl_reload_parent = False
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
loader = Loader()
loader.load(target, data)
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.speakers.new(data["name"])
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
dumper = Dumper()
@ -61,7 +61,7 @@ class BlSpeaker(BlDatablock):
return dumper.dump(instance)
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
sound = datablock.sound

View File

@ -30,14 +30,14 @@ class BlTexture(BlDatablock):
bl_icon = 'TEXTURE'
bl_reload_parent = False
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
loader = Loader()
loader.load(target, data)
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.textures.new(data["name"], data["type"])
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
dumper = Dumper()
@ -62,7 +62,7 @@ class BlTexture(BlDatablock):
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
image = getattr(datablock,"image", None)

View File

@ -31,7 +31,7 @@ class BlVolume(BlDatablock):
bl_icon = 'VOLUME_DATA'
bl_reload_parent = False
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
loader = Loader()
loader.load(target, data)
loader.load(target.display, data['display'])
@ -41,10 +41,10 @@ class BlVolume(BlDatablock):
if src_materials:
load_materials_slots(src_materials, target.materials)
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.volumes.new(data["name"])
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
dumper = Dumper()
@ -70,7 +70,7 @@ class BlVolume(BlDatablock):
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
external_vdb = Path(bpy.path.abspath(datablock.filepath))

View File

@ -33,10 +33,10 @@ class BlWorld(BlDatablock):
bl_icon = 'WORLD_DATA'
bl_reload_parent = False
def construct(self, data):
def construct(data: dict) -> object:
return bpy.data.worlds.new(data["name"])
def _load_implementation(self, data, target):
def load(data: dict, datablock: object):
loader = Loader()
loader.load(target, data)
@ -46,7 +46,7 @@ class BlWorld(BlDatablock):
load_node_tree(data['node_tree'], target.node_tree)
def _dump_implementation(self, data, instance=None):
def dump(datablock: object) -> dict:
assert(instance)
world_dumper = Dumper()
@ -63,7 +63,7 @@ class BlWorld(BlDatablock):
return data
@staticmethod
def _resolve_deps_implementation(datablock):
def resolve_deps(datablock: object) -> [object]:
deps = []
if datablock.use_nodes:

View File

@ -517,6 +517,8 @@ class SessionProps(bpy.types.PropertyGroup):
default=False
)
def get_preferences():
return bpy.context.preferences.addons[__package__].preferences
classes = (
SessionUser,