Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93d50ac56b | |||
| 0e73af4d49 | |||
| 00d5e622af | |||
| b358d3b305 | |||
| 203c10ccd3 | |||
| da349dd4a9 | |||
| 845bb11b8e | |||
| 9312d6a8c5 | |||
| ca78a42076 | |||
| ee886e00c8 | |||
| 9c7043e84c | |||
| e659b7da94 | |||
| e3af69a9c8 | |||
| 328c651cea | |||
| d4224c789a | |||
| 3a02711baa | |||
| c7e8002fed | |||
| f4e7ec6be8 | |||
| 235db712fd | |||
| 647ac46c01 | |||
| 8e3c86561f | |||
| dba19e831d | |||
| 93df5ca5fa | |||
| b17104c67e | |||
| 875b9ce934 | |||
| 2d638ef76f |
@@ -1,13 +0,0 @@
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
- doc
|
||||
|
||||
|
||||
|
||||
include:
|
||||
- local: .gitlab/ci/test.gitlab-ci.yml
|
||||
- local: .gitlab/ci/build.gitlab-ci.yml
|
||||
- local: .gitlab/ci/deploy.gitlab-ci.yml
|
||||
- local: .gitlab/ci/doc.gitlab-ci.yml
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "multi_user/libs/replication"]
|
||||
path = multi_user/libs/replication
|
||||
url = https://gitlab.com/slumber/replication
|
||||
|
||||
@@ -374,15 +374,6 @@ Network
|
||||
|
||||
Advanced network settings
|
||||
|
||||
**IPC Port** is the port used for Inter Process Communication. This port is used
|
||||
by the multi-user subprocesses to communicate with each other. If different instances
|
||||
of multi-user are using the same IPC port, this will create conflict !
|
||||
|
||||
.. note::
|
||||
You only need to modify this setting if you need to launch multiple clients from the same
|
||||
computer (or if you try to host and join from the same computer). To resolve this, you simply need to enter a different
|
||||
**IPC port** for each blender instance.
|
||||
|
||||
**Timeout (in milliseconds)** is the maximum ping authorized before auto-disconnecting.
|
||||
You should only increase it if you have a bad connection.
|
||||
|
||||
|
||||
+11
-14
@@ -41,12 +41,8 @@ import bpy
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
from . import environment
|
||||
|
||||
|
||||
DEPENDENCIES = {
|
||||
("replication", '0.1.26'),
|
||||
}
|
||||
|
||||
from uuid import uuid4
|
||||
LIBS = os.path.dirname(os.path.abspath(__file__))+"/libs/replication"
|
||||
|
||||
module_error_msg = "Insufficient rights to install the multi-user \
|
||||
dependencies, aunch blender with administrator rights."
|
||||
@@ -57,14 +53,15 @@ 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 not in sys.path:
|
||||
logging.info('Adding local modules dir to the path')
|
||||
sys.path.insert(0, LIBS)
|
||||
|
||||
try:
|
||||
if bpy.app.version[1] >= 91:
|
||||
python_binary_path = sys.executable
|
||||
else:
|
||||
python_binary_path = bpy.app.binary_path_python
|
||||
|
||||
environment.setup(DEPENDENCIES, python_binary_path)
|
||||
|
||||
from . import presence
|
||||
from . import operators
|
||||
from . import ui
|
||||
@@ -78,7 +75,7 @@ def register():
|
||||
ui.register()
|
||||
except ModuleNotFoundError as e:
|
||||
raise Exception(module_error_msg)
|
||||
logging.error(module_error_msg)
|
||||
logging.error(e)
|
||||
|
||||
bpy.types.WindowManager.session = bpy.props.PointerProperty(
|
||||
type=preferences.SessionProps)
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
|
||||
__all__ = [
|
||||
'bl_object',
|
||||
'bl_mesh',
|
||||
'bl_camera',
|
||||
'bl_collection',
|
||||
'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_particle",
|
||||
] # Order here defines execution order
|
||||
|
||||
if bpy.app.version[1] >= 91:
|
||||
__all__.append('bl_volume')
|
||||
|
||||
from . import *
|
||||
from replication.data import ReplicatedDataFactory
|
||||
|
||||
def types_to_register():
|
||||
return __all__
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
from replication.protocol import ReplicatedDatablock
|
||||
|
||||
__all__ = [
|
||||
'bl_object',
|
||||
'bl_mesh',
|
||||
# 'bl_camera',
|
||||
'bl_collection',
|
||||
# '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_particle",
|
||||
] # Order here defines execution order
|
||||
|
||||
# if bpy.app.version[1] >= 91:
|
||||
# __all__.append('bl_volume')
|
||||
|
||||
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
|
||||
@@ -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,70 @@ 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 resolve_animation_dependencies(datablock):
|
||||
if has_action(datablock):
|
||||
return [datablock.animation_data.action]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
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 +267,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 +287,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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -105,9 +105,10 @@ class BlCamera(BlDatablock):
|
||||
]
|
||||
return dumper.dump(instance)
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
@staticmethod
|
||||
def resolve_deps(datablock: object) -> [object]:
|
||||
deps = []
|
||||
for background in self.instance.background_images:
|
||||
for background in datablock.background_images:
|
||||
if background.image:
|
||||
deps.append(background.image)
|
||||
|
||||
@@ -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,15 +119,19 @@ 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
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
return resolve_collection_dependencies(self.instance)
|
||||
@staticmethod
|
||||
def resolve_deps(datablock: object) -> [object]:
|
||||
return resolve_collection_dependencies(datablock)
|
||||
|
||||
_type = bpy.types.Collection
|
||||
_class = BlCollection
|
||||
@@ -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
|
||||
@@ -222,10 +222,10 @@ class BlCurve(BlDatablock):
|
||||
|
||||
return data
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
# TODO: resolve material
|
||||
@staticmethod
|
||||
def resolve_deps(datablock: object) -> [object]:
|
||||
deps = []
|
||||
curve = self.instance
|
||||
curve = datablock
|
||||
|
||||
if isinstance(curve, T.TextCurve):
|
||||
deps.extend([
|
||||
@@ -234,7 +234,7 @@ class BlCurve(BlDatablock):
|
||||
curve.font_bold_italic,
|
||||
curve.font_italic])
|
||||
|
||||
for material in self.instance.materials:
|
||||
for material in curve.materials:
|
||||
if material:
|
||||
deps.append(material)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
|
||||
import logging
|
||||
from collections.abc import Iterable
|
||||
|
||||
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 get_datablock_from_uuid(uuid, default, ignore=[]):
|
||||
if not uuid:
|
||||
return default
|
||||
for category in dir(bpy.data):
|
||||
root = getattr(bpy.data, category)
|
||||
if isinstance(root, Iterable) and category not in ignore:
|
||||
for item in root:
|
||||
if getattr(item, 'uuid', None) == uuid:
|
||||
return item
|
||||
return default
|
||||
|
||||
|
||||
def resolve_datablock_from_root(node:Node, root)->object:
|
||||
datablock_ref = utils.find_from_attr('uuid', node.uuid, root)
|
||||
|
||||
if not datablock_ref:
|
||||
try:
|
||||
datablock_ref = root[node.data['name']]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return datablock_ref
|
||||
|
||||
def stamp_uuid(datablock):
|
||||
if not datablock.uuid:
|
||||
datablock.uuid = str(uuid4())
|
||||
@@ -24,7 +24,7 @@ from pathlib import Path
|
||||
import bpy
|
||||
import mathutils
|
||||
from replication.constants import DIFF_BINARY, UP
|
||||
from replication.data import ReplicatedDatablock
|
||||
from replication.protocol import ReplicatedDatablock
|
||||
|
||||
from .. import utils
|
||||
from .dump_anything import Dumper, Loader
|
||||
@@ -84,7 +84,7 @@ class BlFile(ReplicatedDatablock):
|
||||
if self.preferences.clear_memory_filecache:
|
||||
del self.data['file']
|
||||
|
||||
def _dump(self, instance=None):
|
||||
def dump(self, instance=None):
|
||||
"""
|
||||
Read the file and return a dict as:
|
||||
{
|
||||
@@ -114,7 +114,7 @@ class BlFile(ReplicatedDatablock):
|
||||
|
||||
return data
|
||||
|
||||
def _load(self, data, target):
|
||||
def load(self, data, target):
|
||||
"""
|
||||
Writing the 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>':
|
||||
@@ -42,10 +42,10 @@ class BlFont(BlDatablock):
|
||||
else:
|
||||
return bpy.data.fonts.load(get_filepath(filename))
|
||||
|
||||
def _load(self, data, target):
|
||||
def load(self, data, target):
|
||||
pass
|
||||
|
||||
def _dump(self, instance=None):
|
||||
def dump(self, instance=None):
|
||||
if instance.filepath == '<builtin>':
|
||||
filename = '<builtin>'
|
||||
else:
|
||||
@@ -62,11 +62,12 @@ class BlFont(BlDatablock):
|
||||
def diff(self):
|
||||
return False
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
@staticmethod
|
||||
def resolve_deps(datablock: object) -> [object]:
|
||||
deps = []
|
||||
if self.instance.filepath and self.instance.filepath != '<builtin>':
|
||||
ensure_unpacked(self.instance)
|
||||
if datablock.filepath and datablock.filepath != '<builtin>':
|
||||
ensure_unpacked(datablock)
|
||||
|
||||
deps.append(Path(bpy.path.abspath(self.instance.filepath)))
|
||||
deps.append(Path(bpy.path.abspath(datablock.filepath)))
|
||||
|
||||
return deps
|
||||
@@ -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
|
||||
@@ -290,10 +290,11 @@ class BlGpencil(BlDatablock):
|
||||
data["eval_frame"] = bpy.context.scene.frame_current
|
||||
return data
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
@staticmethod
|
||||
def resolve_deps(datablock: object) -> [object]:
|
||||
deps = []
|
||||
|
||||
for material in self.instance.materials:
|
||||
for material in datablock.materials:
|
||||
deps.append(material)
|
||||
|
||||
return deps
|
||||
@@ -55,14 +55,14 @@ 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],
|
||||
height=data['size'][1]
|
||||
)
|
||||
|
||||
def _load(self, data, target):
|
||||
def load(self, data, target):
|
||||
loader = Loader()
|
||||
loader.load(data, target)
|
||||
|
||||
@@ -70,7 +70,7 @@ class BlImage(BlDatablock):
|
||||
target.filepath_raw = get_filepath(data['filename'])
|
||||
target.colorspace_settings.name = data["colorspace_settings"]["name"]
|
||||
|
||||
def _dump(self, instance=None):
|
||||
def dump(self, instance=None):
|
||||
assert(instance)
|
||||
|
||||
filename = Path(instance.filepath).name
|
||||
@@ -101,23 +101,24 @@ class BlImage(BlDatablock):
|
||||
else:
|
||||
return False
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
@staticmethod
|
||||
def resolve_deps(datablock: object) -> [object]:
|
||||
deps = []
|
||||
|
||||
if self.instance.packed_file:
|
||||
filename = Path(bpy.path.abspath(self.instance.filepath)).name
|
||||
self.instance.filepath_raw = get_filepath(filename)
|
||||
self.instance.save()
|
||||
if datablock.packed_file:
|
||||
filename = Path(bpy.path.abspath(datablock.filepath)).name
|
||||
datablock.filepath_raw = get_filepath(filename)
|
||||
datablock.save()
|
||||
# An image can't be unpacked to the modified path
|
||||
# TODO: make a bug report
|
||||
self.instance.unpack(method="REMOVE")
|
||||
datablock.unpack(method="REMOVE")
|
||||
|
||||
elif self.instance.source == "GENERATED":
|
||||
filename = f"{self.instance.name}.png"
|
||||
self.instance.filepath = get_filepath(filename)
|
||||
self.instance.save()
|
||||
elif datablock.source == "GENERATED":
|
||||
filename = f"{datablock.name}.png"
|
||||
datablock.filepath = get_filepath(filename)
|
||||
datablock.save()
|
||||
|
||||
if self.instance.filepath:
|
||||
deps.append(Path(bpy.path.abspath(self.instance.filepath)))
|
||||
if datablock.filepath:
|
||||
deps.append(Path(bpy.path.abspath(datablock.filepath)))
|
||||
|
||||
return deps
|
||||
@@ -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")
|
||||
|
||||
@@ -30,14 +30,14 @@ 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
|
||||
def _load(self, data, target):
|
||||
def load(self, data, target):
|
||||
pass
|
||||
|
||||
def _dump(self, instance=None):
|
||||
def dump(self, instance=None):
|
||||
assert(instance)
|
||||
dumper = Dumper()
|
||||
return dumper.dump(instance)
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user