From 40ad96b0af4578b3a74c900bdd26238a048079b9 Mon Sep 17 00:00:00 2001 From: Swann Martinez Date: Wed, 11 Mar 2020 17:45:56 +0100 Subject: [PATCH 01/25] feat: initial particle system support Related to #24 --- multi_user/bl_types/__init__.py | 3 ++- multi_user/bl_types/bl_datablock.py | 4 ++-- multi_user/bl_types/bl_object.py | 26 ++++++++++++++++++++--- multi_user/bl_types/bl_particle.py | 32 +++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 multi_user/bl_types/bl_particle.py diff --git a/multi_user/bl_types/__init__.py b/multi_user/bl_types/__init__.py index c3e9605..81f744a 100644 --- a/multi_user/bl_types/__init__.py +++ b/multi_user/bl_types/__init__.py @@ -16,7 +16,8 @@ __all__ = [ 'bl_metaball', 'bl_lattice', 'bl_lightprobe', - 'bl_speaker' + 'bl_speaker', + 'bl_particle' ] # Order here defines execution order from . import * diff --git a/multi_user/bl_types/bl_datablock.py b/multi_user/bl_types/bl_datablock.py index be6c8ed..c18dfcd 100644 --- a/multi_user/bl_types/bl_datablock.py +++ b/multi_user/bl_types/bl_datablock.py @@ -3,7 +3,7 @@ import mathutils from .. import utils from ..libs.replication.replication.data import ReplicatedDatablock -from ..libs.replication.replication.constants import (UP, DIFF_BINARY) +from ..libs.replication.replication.constants import (UP, DIFF_BINARY,DIFF_JSON) from ..libs import dump_anything def dump_driver(driver): @@ -75,7 +75,7 @@ class BlDatablock(ReplicatedDatablock): if self.pointer and hasattr(self.pointer, 'uuid'): self.pointer.uuid = self.uuid - self.diff_method = DIFF_BINARY + self.diff_method = DIFF_JSON def library_apply(self): """Apply stored data diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index 26b5469..f9ef377 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -101,14 +101,19 @@ class BlObject(BlDatablock): for modifier in data['modifiers']: target_modifier = target.modifiers.get(modifier) - if not target_modifier: target_modifier = target.modifiers.new( data['modifiers'][modifier]['name'], data['modifiers'][modifier]['type']) + if target_modifier.type == 'PARTICLE_SYSTEM': + tmp_particle_system = target_modifier.particle_system.name + utils.dump_anything.load( target_modifier, data['modifiers'][modifier]) + if target_modifier.type == 'PARTICLE_SYSTEM': + target.particle_systems[data['modifiers'][modifier]['name']].settings = bpy.data.particles[data['modifiers'][modifier]['particle_system']] + # bpy.data.particles.remove(tmp_particle_system) # Load constraints # Object if hasattr(target, 'constraints') and 'constraints' in data: @@ -188,6 +193,7 @@ class BlObject(BlDatablock): target.data.shape_keys.key_blocks[key_block].relative_key = target.data.shape_keys.key_blocks[reference] + def dump_implementation(self, data, pointer=None): assert(pointer) dumper = utils.dump_anything.Dumper() @@ -219,9 +225,17 @@ class BlObject(BlDatablock): dumper.depth = 2 data["modifiers"] = {} for index, modifier in enumerate(pointer.modifiers): - data["modifiers"][modifier.name] = dumper.dump(modifier) - data["modifiers"][modifier.name]['m_index'] = index + modifier_data = {} + if modifier.type == 'PARTICLE_SYSTEM': + modifier_data['particle_system'] = modifier.particle_system.name + dumper.depth = 1 + + modifier_data.update(dumper.dump(modifier)) + + modifier_data['m_index'] = index + + data["modifiers"][modifier.name] = modifier_data # CONSTRAINTS # OBJECT if hasattr(pointer, 'constraints'): @@ -334,9 +348,15 @@ class BlObject(BlDatablock): # Avoid Empty case if self.pointer.data: deps.append(self.pointer.data) + + # Childred if len(self.pointer.children) > 0: deps.extend(list(self.pointer.children)) + # Particle systems + for particle_slot in self.pointer.particle_systems: + deps.append(bpy.data.particles[particle_slot.name]) + if self.is_library: deps.append(self.pointer.library) diff --git a/multi_user/bl_types/bl_particle.py b/multi_user/bl_types/bl_particle.py new file mode 100644 index 0000000..4d08f33 --- /dev/null +++ b/multi_user/bl_types/bl_particle.py @@ -0,0 +1,32 @@ +import bpy +import mathutils + +from .. import utils +from ..libs.replication.replication.constants import (DIFF_JSON) +from .bl_datablock import BlDatablock + + +class BlParticle(BlDatablock): + bl_id = "particles" + bl_class = bpy.types.ParticleSettings + bl_delay_refresh = 1 + bl_delay_apply = 1 + bl_automatic_push = True + bl_icon = 'PARTICLES' + + diff_method = DIFF_JSON + + def construct(self, data): + return bpy.data.particles.new(data["name"]) + + def load_implementation(self, data, target): + utils.dump_anything.load(target, data) + + def dump_implementation(self, data, pointer=None): + assert(pointer) + + dumper = utils.dump_anything.Dumper() + dumper.depth = 1 + data = dumper.dump(pointer) + + return data From 5f95eadc1dad680d1bdc8d0228f071d846a1a8d3 Mon Sep 17 00:00:00 2001 From: Swann Martinez Date: Wed, 11 Mar 2020 18:37:43 +0100 Subject: [PATCH 02/25] feat: test particle cache access --- multi_user/bl_types/bl_object.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index f9ef377..b7f356f 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -340,6 +340,9 @@ class BlObject(BlDatablock): key_blocks[key.name]['relative_key'] = key.relative_key.name data['shape_keys']['key_blocks'] = key_blocks + if pointer.particle_systems: + psys = pointer.evaluated_get(bpy.context.evaluated_depsgraph_get()).particle_systems + print(len(psys[0].particles)) return data def resolve_deps_implementation(self): From 07862f1cf02837acb195e2b60476875f4ac6be7a Mon Sep 17 00:00:00 2001 From: Swann Date: Fri, 19 Mar 2021 11:07:04 +0100 Subject: [PATCH 03/25] fix: missing hue_interpolation --- multi_user/bl_types/bl_material.py | 1 + 1 file changed, 1 insertion(+) diff --git a/multi_user/bl_types/bl_material.py b/multi_user/bl_types/bl_material.py index 12964ed..1d3b57f 100644 --- a/multi_user/bl_types/bl_material.py +++ b/multi_user/bl_types/bl_material.py @@ -155,6 +155,7 @@ def dump_node(node: bpy.types.ShaderNode) -> dict: 'color', 'position', 'interpolation', + 'hue_interpolation', 'color_mode' ] dumped_node['color_ramp'] = ramp_dumper.dump(node.color_ramp) From 2446df4fe33995ae54f5f971fce27e8d2c82c44e Mon Sep 17 00:00:00 2001 From: Swann Date: Sun, 21 Mar 2021 09:28:54 +0100 Subject: [PATCH 04/25] feat: raise the default timeout to 5 second --- multi_user/preferences.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multi_user/preferences.py b/multi_user/preferences.py index 1757c1b..790af67 100644 --- a/multi_user/preferences.py +++ b/multi_user/preferences.py @@ -195,7 +195,7 @@ class SessionPrefs(bpy.types.AddonPreferences): connection_timeout: bpy.props.IntProperty( name='connection timeout', description='connection timeout before disconnection', - default=1000 + default=5000 ) # Replication update settings depsgraph_update_rate: bpy.props.FloatProperty( From 9d0d684589215237bc08e814de5db43081708097 Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 31 Mar 2021 11:19:03 +0200 Subject: [PATCH 05/25] fix: geometry nodes str, float, int loading --- multi_user/bl_types/bl_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index fdb4ba7..b3c787e 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -79,7 +79,7 @@ def load_modifier_geometry_node_inputs(dumped_modifier: dict, target_modifier: b dumped_value = dumped_modifier['inputs'][input_index] input_value = target_modifier[input_name] if type(input_value) in [int, str, float]: - input_value = dumped_value + target_modifier[input_name] = dumped_value elif hasattr(input_value, 'to_list'): for index in range(len(input_value)): input_value[index] = dumped_value[index] From 67d18f08e2977ff66230a4eaa1854aaea70215f6 Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 31 Mar 2021 15:38:35 +0200 Subject: [PATCH 06/25] fix: Timer not unregistered error fix: handle correctly unsupported float parameter for geometry nodes fix: Material loading --- multi_user/bl_types/bl_material.py | 5 +---- multi_user/bl_types/bl_object.py | 8 ++++++-- multi_user/operators.py | 3 +-- multi_user/timers.py | 5 +++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/multi_user/bl_types/bl_material.py b/multi_user/bl_types/bl_material.py index 1d3b57f..3dc82ca 100644 --- a/multi_user/bl_types/bl_material.py +++ b/multi_user/bl_types/bl_material.py @@ -365,10 +365,7 @@ def load_materials_slots(src_materials: list, dst_materials: bpy.types.bpy_prop_ if mat_uuid is not None: mat_ref = get_datablock_from_uuid(mat_uuid, None) else: - mat_ref = bpy.data.materials.get(mat_name, None) - - if mat_ref is None: - raise Exception(f"Material {mat_name} doesn't exist") + mat_ref = bpy.data.materials[mat_name] dst_materials.append(mat_ref) diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index b3c787e..3f72d39 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -55,7 +55,9 @@ def dump_modifier_geometry_node_inputs(modifier: bpy.types.Modifier) -> list: dumped_input = None if isinstance(input_value, bpy.types.ID): dumped_input = input_value.uuid - elif type(input_value) in [int, str, float]: + elif isinstance(input_value, float): + logging.warning("Float parameter not supported in blender 2.92, skipping it") + elif isinstance(input_value,(int, str)): dumped_input = input_value elif hasattr(input_value, 'to_list'): dumped_input = input_value.to_list() @@ -78,7 +80,9 @@ def load_modifier_geometry_node_inputs(dumped_modifier: dict, target_modifier: b for input_index, input_name in enumerate(inputs_name): dumped_value = dumped_modifier['inputs'][input_index] input_value = target_modifier[input_name] - if type(input_value) in [int, str, float]: + if isinstance(input_value, float): + logging.warning("Float parameter not supported in blender 2.92, skipping it") + elif isinstance(input_value,(int, str)): target_modifier[input_name] = dumped_value elif hasattr(input_value, 'to_list'): for index in range(len(input_value)): diff --git a/multi_user/operators.py b/multi_user/operators.py index 8aa9a0f..981c7df 100644 --- a/multi_user/operators.py +++ b/multi_user/operators.py @@ -210,8 +210,6 @@ class SessionStartOperator(bpy.types.Operator): type_module_class, check_common=type_module_class.bl_check_common) - deleyables.append(timers.ApplyTimer(timeout=settings.depsgraph_update_rate)) - if bpy.app.version[1] >= 91: python_binary_path = sys.executable else: @@ -272,6 +270,7 @@ class SessionStartOperator(bpy.types.Operator): # Background client updates service deleyables.append(timers.ClientUpdate()) deleyables.append(timers.DynamicRightSelectTimer()) + deleyables.append(timers.ApplyTimer(timeout=settings.depsgraph_update_rate)) # deleyables.append(timers.PushTimer( # queue=stagging, # timeout=settings.depsgraph_update_rate diff --git a/multi_user/timers.py b/multi_user/timers.py index 9e1d2e6..1e6c64d 100644 --- a/multi_user/timers.py +++ b/multi_user/timers.py @@ -17,7 +17,7 @@ import logging import sys - +import traceback import bpy from replication.constants import (FETCHED, RP_COMMON, STATE_ACTIVE, STATE_INITIAL, STATE_LOBBY, STATE_QUITTING, @@ -112,7 +112,8 @@ class ApplyTimer(Timer): try: session.apply(node) except Exception as e: - logging.error(f"Fail to apply {node_ref.uuid}: {e}") + logging.error(f"Fail to apply {node_ref.uuid}") + traceback.print_exc() else: if node_ref.bl_reload_parent: for parent in session._graph.find_parents(node): From 9c633c35ec981123ac7da800ddaa625e8cbaa0d1 Mon Sep 17 00:00:00 2001 From: Swann Date: Fri, 2 Apr 2021 10:01:45 +0200 Subject: [PATCH 07/25] fix: geometry node socket for blender 2.93 --- multi_user/bl_types/bl_object.py | 43 +++++++++++++++++++------------- multi_user/libs/replication | 1 + 2 files changed, 27 insertions(+), 17 deletions(-) create mode 160000 multi_user/libs/replication diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index 3f72d39..80142c4 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -30,13 +30,20 @@ from .dump_anything import ( np_dump_collection) - SKIN_DATA = [ 'radius', 'use_loose', 'use_root' ] +if bpy.app.version[1] >= 93: + SUPPORTED_GEOMETRY_NODE_PARAMETERS = (int, str, float) +else: + SUPPORTED_GEOMETRY_NODE_PARAMETERS = (int, str) + logging.warning("Geometry node Float parameter not supported in \ + blender 2.92.") + + def get_input_index(e): return int(re.findall('[0-9]+', e)[0]) @@ -55,9 +62,7 @@ def dump_modifier_geometry_node_inputs(modifier: bpy.types.Modifier) -> list: dumped_input = None if isinstance(input_value, bpy.types.ID): dumped_input = input_value.uuid - elif isinstance(input_value, float): - logging.warning("Float parameter not supported in blender 2.92, skipping it") - elif isinstance(input_value,(int, str)): + elif isinstance(input_value, SUPPORTED_GEOMETRY_NODE_PARAMETERS): dumped_input = input_value elif hasattr(input_value, 'to_list'): dumped_input = input_value.to_list() @@ -80,14 +85,12 @@ def load_modifier_geometry_node_inputs(dumped_modifier: dict, target_modifier: b for input_index, input_name in enumerate(inputs_name): dumped_value = dumped_modifier['inputs'][input_index] input_value = target_modifier[input_name] - if isinstance(input_value, float): - logging.warning("Float parameter not supported in blender 2.92, skipping it") - elif isinstance(input_value,(int, str)): + if isinstance(input_value, SUPPORTED_GEOMETRY_NODE_PARAMETERS): target_modifier[input_name] = dumped_value elif hasattr(input_value, 'to_list'): for index in range(len(input_value)): input_value[index] = dumped_value[index] - else: + elif input_value and isinstance(input_value, bpy.types.ID): target_modifier[input_name] = get_datablock_from_uuid( dumped_value, None) @@ -179,6 +182,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 @@ -223,6 +227,7 @@ 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): bl_id = "objects" bl_class = bpy.types.Object @@ -358,18 +363,21 @@ class BlObject(BlDatablock): SKIN_DATA) if hasattr(target, 'cycles_visibility') \ - and 'cycles_visibility' in data: + and 'cycles_visibility' in data: loader.load(target.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'] + nodes_modifiers = [ + mod for mod in target.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_parent_inverse = mathutils.Matrix( + transform['matrix_parent_inverse']) target.matrix_basis = mathutils.Matrix(transform['matrix_basis']) target.matrix_local = mathutils.Matrix(transform['matrix_local']) @@ -435,7 +443,7 @@ class BlObject(BlDatablock): # PARENTING if instance.parent: - data['parent_id'] = instance.parent.name + data['parent_id'] = instance.parent.name # MODIFIERS if hasattr(instance, 'modifiers'): @@ -448,7 +456,8 @@ 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) @@ -515,7 +524,6 @@ 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) @@ -552,7 +560,8 @@ 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 @@ -579,7 +588,7 @@ class BlObject(BlDatablock): if self.is_library: deps.append(self.instance.library) - if self.instance.parent : + if self.instance.parent: deps.append(self.instance.parent) if self.instance.instance_type == 'COLLECTION': diff --git a/multi_user/libs/replication b/multi_user/libs/replication new file mode 160000 index 0000000..9a02e16 --- /dev/null +++ b/multi_user/libs/replication @@ -0,0 +1 @@ +Subproject commit 9a02e16d70b03bd3a49722f899ac19dd5d3f8019 From 4e19c169b2684d8d9080cdf0c77fb69881f994fe Mon Sep 17 00:00:00 2001 From: Swann Date: Fri, 2 Apr 2021 15:51:31 +0200 Subject: [PATCH 08/25] fix: node_groups unordered socket loading fix: geometry_node sample texture handling fix: geometry node dependencies --- multi_user/bl_types/bl_material.py | 20 +++++++---- multi_user/bl_types/bl_object.py | 55 +++++++++++++++++------------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/multi_user/bl_types/bl_material.py b/multi_user/bl_types/bl_material.py index 3dc82ca..51e4130 100644 --- a/multi_user/bl_types/bl_material.py +++ b/multi_user/bl_types/bl_material.py @@ -27,7 +27,7 @@ from .dump_anything import Loader, Dumper from .bl_datablock import BlDatablock, get_datablock_from_uuid NODE_SOCKET_INDEX = re.compile('\[(\d*)\]') -IGNORED_SOCKETS = ['GEOMETRY', 'SHADER'] +IGNORED_SOCKETS = ['GEOMETRY', 'SHADER', 'CUSTOM'] def load_node(node_data: dict, node_tree: bpy.types.ShaderNodeTree): """ Load a node into a node_tree from a dict @@ -54,8 +54,8 @@ def load_node(node_data: dict, node_tree: bpy.types.ShaderNodeTree): if inputs_data: inputs = [i for i in target_node.inputs if i.type not in IGNORED_SOCKETS] for idx, inpt in enumerate(inputs): - loaded_input = inputs_data[idx] if idx < len(inputs_data) and hasattr(inpt, "default_value"): + loaded_input = inputs_data[idx] try: if inpt.type in ['OBJECT', 'COLLECTION']: inpt.default_value = get_datablock_from_uuid(loaded_input, None) @@ -69,13 +69,17 @@ def load_node(node_data: dict, node_tree: bpy.types.ShaderNodeTree): outputs_data = node_data.get('outputs') if outputs_data: outputs = [o for o in target_node.outputs if o.type not in IGNORED_SOCKETS] - for idx, output in enumerate(outputs_data): - if idx < len(outputs) and hasattr(outputs[idx], "default_value"): + for idx, output in enumerate(outputs): + if idx < len(outputs_data) and hasattr(output, "default_value"): + loaded_output = outputs_data[idx] try: - outputs[idx].default_value = output + if output.type in ['OBJECT', 'COLLECTION']: + output.default_value = get_datablock_from_uuid(loaded_output, None) + else: + output.default_value = loaded_output except Exception as e: logging.warning( - f"Node {target_node.name} output {outputs[idx].name} parameter not supported, skipping ({e})") + f"Node {target_node.name} output {output.name} parameter not supported, skipping ({e})") else: logging.warning( f"Node {target_node.name} output length mismatch.") @@ -328,6 +332,8 @@ def get_node_tree_dependencies(node_tree: bpy.types.NodeTree) -> list: def has_node_group(node): return ( hasattr(node, 'node_tree') and node.node_tree) + def has_texture(node): return ( + node.type in ['ATTRIBUTE_SAMPLE_TEXTURE','TEXTURE'] and node.texture) deps = [] for node in node_tree.nodes: @@ -335,6 +341,8 @@ def get_node_tree_dependencies(node_tree: bpy.types.NodeTree) -> list: deps.append(node.image) elif has_node_group(node): deps.append(node.node_tree) + elif has_texture(node): + deps.append(node.texture) return deps diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index 80142c4..2141159 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -23,6 +23,7 @@ import mathutils from replication.exception import ContextError from .bl_datablock import BlDatablock, get_datablock_from_uuid +from .bl_material import IGNORED_SOCKETS from .dump_anything import ( Dumper, Loader, @@ -43,10 +44,15 @@ else: logging.warning("Geometry node Float parameter not supported in \ blender 2.92.") - -def get_input_index(e): - return int(re.findall('[0-9]+', e)[0]) - +def get_node_group_inputs(node_group): + inputs = [] + for inpt in node_group.inputs: + if inpt.type in IGNORED_SOCKETS: + continue + else: + inputs.append(inpt) + return inputs + # return [inpt.identifer for inpt in node_group.inputs if inpt.type not in IGNORED_SOCKETS] def dump_modifier_geometry_node_inputs(modifier: bpy.types.Modifier) -> list: """ Dump geometry node modifier input properties @@ -54,11 +60,10 @@ def dump_modifier_geometry_node_inputs(modifier: bpy.types.Modifier) -> list: :arg modifier: geometry node modifier to dump :type modifier: bpy.type.Modifier """ - inputs_name = [p for p in dir(modifier) if "Input_" in p] - inputs_name.sort(key=get_input_index) dumped_inputs = [] - for inputs_index, input_name in enumerate(inputs_name): - input_value = modifier[input_name] + for inpt in get_node_group_inputs(modifier.node_group): + input_value = modifier[inpt.identifier] + dumped_input = None if isinstance(input_value, bpy.types.ID): dumped_input = input_value.uuid @@ -80,18 +85,16 @@ def load_modifier_geometry_node_inputs(dumped_modifier: dict, target_modifier: b :type target_modifier: bpy.type.Modifier """ - inputs_name = [p for p in dir(target_modifier) if "Input_" in p] - inputs_name.sort(key=get_input_index) - for input_index, input_name in enumerate(inputs_name): + for input_index, inpt in enumerate(get_node_group_inputs(target_modifier.node_group)): dumped_value = dumped_modifier['inputs'][input_index] - input_value = target_modifier[input_name] + input_value = target_modifier[inpt.identifier] if isinstance(input_value, SUPPORTED_GEOMETRY_NODE_PARAMETERS): - target_modifier[input_name] = dumped_value + target_modifier[inpt.identifier] = dumped_value elif hasattr(input_value, 'to_list'): for index in range(len(input_value)): input_value[index] = dumped_value[index] - elif input_value and isinstance(input_value, bpy.types.ID): - target_modifier[input_name] = get_datablock_from_uuid( + elif inpt.type in ['COLLECTION', 'OBJECT']: + target_modifier[inpt.identifier] = get_datablock_from_uuid( dumped_value, None) @@ -168,19 +171,23 @@ def find_textures_dependencies(modifiers: bpy.types.bpy_prop_collection) -> [bpy return textures -def find_geometry_nodes(modifiers: bpy.types.bpy_prop_collection) -> [bpy.types.NodeTree]: - """ Find geometry nodes group from a modifier stack +def find_geometry_nodes_dependencies(modifiers: bpy.types.bpy_prop_collection) -> [bpy.types.NodeTree]: + """ Find geometry nodes dependencies from a modifier stack :arg modifiers: modifiers collection :type modifiers: bpy.types.bpy_prop_collection :return: list of bpy.types.NodeTree pointers """ - nodes_groups = [] - for item in modifiers: - if item.type == 'NODES' and item.node_group: - nodes_groups.append(item.node_group) - - return nodes_groups + dependencies = [] + for mod in modifiers: + if mod.type == 'NODES' and mod.node_group: + dependencies.append(mod.node_group) + for inpt in get_node_group_inputs(mod.node_group): + parameter = mod.get(inpt.identifier) + if parameter and isinstance(parameter, bpy.types.ID): + dependencies.append(parameter) + logging.info(dependencies) + return dependencies def dump_vertex_groups(src_object: bpy.types.Object) -> dict: @@ -597,6 +604,6 @@ class BlObject(BlDatablock): if self.instance.modifiers: deps.extend(find_textures_dependencies(self.instance.modifiers)) - deps.extend(find_geometry_nodes(self.instance.modifiers)) + deps.extend(find_geometry_nodes_dependencies(self.instance.modifiers)) return deps From 9f167256d01a85c32a181720c8b197fedc7b29c7 Mon Sep 17 00:00:00 2001 From: Swann Date: Fri, 2 Apr 2021 16:12:51 +0200 Subject: [PATCH 09/25] fix: node frame trasform --- multi_user/bl_types/bl_material.py | 9 +++++++++ multi_user/bl_types/bl_object.py | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/multi_user/bl_types/bl_material.py b/multi_user/bl_types/bl_material.py index 51e4130..1b71058 100644 --- a/multi_user/bl_types/bl_material.py +++ b/multi_user/bl_types/bl_material.py @@ -123,6 +123,9 @@ def dump_node(node: bpy.types.ShaderNode) -> dict: dumped_node = node_dumper.dump(node) + if node.parent: + dumped_node['parent'] = node.parent.name + dump_io_needed = (node.type not in ['REROUTE', 'OUTPUT_MATERIAL']) if dump_io_needed: @@ -318,6 +321,12 @@ def load_node_tree(node_tree_data: dict, target_node_tree: bpy.types.ShaderNodeT for node in node_tree_data["nodes"]: load_node(node_tree_data["nodes"][node], target_node_tree) + for node_id, node_data in node_tree_data["nodes"].items(): + target_node = target_node_tree.nodes[node_id] + if 'parent' in node_data: + target_node.parent = target_node_tree.nodes[node_data['parent']] + else: + target_node.parent = None # TODO: load only required nodes links # Load nodes links target_node_tree.links.clear() diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index 2141159..9a1df18 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -182,10 +182,10 @@ def find_geometry_nodes_dependencies(modifiers: bpy.types.bpy_prop_collection) - for mod in modifiers: if mod.type == 'NODES' and mod.node_group: dependencies.append(mod.node_group) - for inpt in get_node_group_inputs(mod.node_group): - parameter = mod.get(inpt.identifier) - if parameter and isinstance(parameter, bpy.types.ID): - dependencies.append(parameter) + # for inpt in get_node_group_inputs(mod.node_group): + # parameter = mod.get(inpt.identifier) + # if parameter and isinstance(parameter, bpy.types.ID): + # dependencies.append(parameter) logging.info(dependencies) return dependencies From 5e30e215ab21cfb65f9ce6daf0238fd54cf51327 Mon Sep 17 00:00:00 2001 From: Swann Date: Fri, 2 Apr 2021 16:37:47 +0200 Subject: [PATCH 10/25] fix: empty node --- multi_user/bl_types/bl_material.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/multi_user/bl_types/bl_material.py b/multi_user/bl_types/bl_material.py index 1b71058..f6fec50 100644 --- a/multi_user/bl_types/bl_material.py +++ b/multi_user/bl_types/bl_material.py @@ -322,8 +322,10 @@ def load_node_tree(node_tree_data: dict, target_node_tree: bpy.types.ShaderNodeT load_node(node_tree_data["nodes"][node], target_node_tree) for node_id, node_data in node_tree_data["nodes"].items(): - target_node = target_node_tree.nodes[node_id] - if 'parent' in node_data: + target_node = target_node_tree.nodes.get(node_id, None) + if target_node is None: + continue + elif 'parent' in node_data: target_node.parent = target_node_tree.nodes[node_data['parent']] else: target_node.parent = None From cb85a1db4c94401f2bd22cc58e7a9c2ddc52ba65 Mon Sep 17 00:00:00 2001 From: Swann Date: Tue, 13 Apr 2021 14:37:43 +0200 Subject: [PATCH 11/25] feat: dual identification for object parents --- multi_user/bl_types/bl_object.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index 9a1df18..a5e3bb6 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -317,9 +317,9 @@ class BlObject(BlDatablock): loader.load(target.display, data['display']) # Parenting - parent_id = data.get('parent_id') + parent_id = data.get('parent_uid') if parent_id: - parent = bpy.data.objects[parent_id] + parent = get_datablock_from_uuid(parent_id[0], bpy.data.objects[parent_id[1]]) # Avoid reloading if target.parent != parent and parent is not None: target.parent = parent @@ -450,7 +450,7 @@ class BlObject(BlDatablock): # PARENTING if instance.parent: - data['parent_id'] = instance.parent.name + data['parent_uid'] = (instance.parent.uuid, instance.parent.name) # MODIFIERS if hasattr(instance, 'modifiers'): From 826a59085edf7ac77d3d1478be4d27a2aada2115 Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 14 Apr 2021 09:45:18 +0200 Subject: [PATCH 12/25] feat: particle texture slot support --- multi_user/bl_types/bl_particle.py | 37 +++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/multi_user/bl_types/bl_particle.py b/multi_user/bl_types/bl_particle.py index db31562..b97efcb 100644 --- a/multi_user/bl_types/bl_particle.py +++ b/multi_user/bl_types/bl_particle.py @@ -4,7 +4,11 @@ import mathutils from . import dump_anything from .bl_datablock import BlDatablock, get_datablock_from_uuid -def dump_textures_slots(texture_slots): + +def dump_textures_slots(texture_slots: bpy.types.bpy_prop_collection) -> list: + """ Dump every texture slot collection as the form: + [(index, slot_texture_uuid, slot_texture_name), (), ...] + """ dumped_slots = [] for index, slot in enumerate(texture_slots): if slot and slot.texture: @@ -12,12 +16,19 @@ def dump_textures_slots(texture_slots): return dumped_slots -def load_texture_slots(dumped_slots, target_slots): + +def load_texture_slots(dumped_slots: list, target_slots: bpy.types.bpy_prop_collection): + """ + """ for index, slot in enumerate(target_slots): - target_slots.clear(index) + if slot: + target_slots.clear(index) for index, slot_uuid, slot_name in dumped_slots: - target_slots.create(index).texture = get_datablock_from_uuid(slot_uuid, slot_name) + target_slots.create(index).texture = get_datablock_from_uuid( + slot_uuid, slot_name + ) + class BlParticle(BlDatablock): bl_id = "particles" @@ -32,19 +43,19 @@ class BlParticle(BlDatablock): def _load_implementation(self, data, target): dump_anything.load(target, data) - dump_anything.load(target.effector_weights, data['effector_weights']) + dump_anything.load(target.effector_weights, data["effector_weights"]) # Force field - force_field_1 = data.get('force_field_1', None) + force_field_1 = data.get("force_field_1", None) if force_field_1: dump_anything.load(target.force_field_1, force_field_1) - force_field_2 = data.get('force_field_2', None) + force_field_2 = data.get("force_field_2", None) if force_field_2: dump_anything.load(target.force_field_2, force_field_2) # 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): assert instance @@ -54,16 +65,16 @@ class BlParticle(BlDatablock): data = dumper.dump(instance) # Particle effectors - data['effector_weights'] = dumper.dump(instance.effector_weights) + data["effector_weights"] = dumper.dump(instance.effector_weights) if instance.force_field_1: - data['force_field_1'] = dumper.dump(instance.force_field_1) + data["force_field_1"] = dumper.dump(instance.force_field_1) if instance.force_field_2: - data['force_field_2'] = dumper.dump(instance.force_field_2) + data["force_field_2"] = dumper.dump(instance.force_field_2) # Texture slots - # data['texture_slots'] = dump_textures_slots(instance.texture_slots) + data["texture_slots"] = dump_textures_slots(instance.texture_slots) return data def _resolve_deps_implementation(self): - return [ t.texture for t in self.instance.texture_slots if t] \ No newline at end of file + return [t.texture for t in self.instance.texture_slots if t and t.texture] From 12acd226606103ceefffeff92f99de29dee24406 Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 14 Apr 2021 09:54:34 +0200 Subject: [PATCH 13/25] feat: ignore some attributes --- multi_user/bl_types/bl_object.py | 5 +++++ multi_user/bl_types/bl_particle.py | 8 ++++++++ multi_user/libs/replication | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index 95e37b5..8ab4f82 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -479,6 +479,11 @@ class BlObject(BlDatablock): dumped_modifier['inputs'] = dumped_inputs if modifier.type == 'PARTICLE_SYSTEM': + dumper.exclude_filter = [ + "is_edited", + "is_editable", + "is_global_hair" + ] dumped_modifier['particle_system'] = dumper.dump(modifier.particle_system) data["modifiers"][modifier.name] = dumped_modifier diff --git a/multi_user/bl_types/bl_particle.py b/multi_user/bl_types/bl_particle.py index b97efcb..b7d7697 100644 --- a/multi_user/bl_types/bl_particle.py +++ b/multi_user/bl_types/bl_particle.py @@ -29,6 +29,13 @@ def load_texture_slots(dumped_slots: list, target_slots: bpy.types.bpy_prop_coll slot_uuid, slot_name ) +IGNORED_ATTR = [ + "is_embedded_data", + "is_evaluated", + "is_fluid", + "is_library_indirect", + "users" +] class BlParticle(BlDatablock): bl_id = "particles" @@ -62,6 +69,7 @@ class BlParticle(BlDatablock): dumper = dump_anything.Dumper() dumper.depth = 1 + dumper.exclude_filter = IGNORED_ATTR data = dumper.dump(instance) # Particle effectors diff --git a/multi_user/libs/replication b/multi_user/libs/replication index 9a02e16..0614a09 160000 --- a/multi_user/libs/replication +++ b/multi_user/libs/replication @@ -1 +1 @@ -Subproject commit 9a02e16d70b03bd3a49722f899ac19dd5d3f8019 +Subproject commit 0614a09e70cb52e57c4f3bb1b9e02876f8b6c6cd From d9d5a34653006d5fc644a6011ce95e1595e41c1c Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 14 Apr 2021 09:56:07 +0200 Subject: [PATCH 14/25] clean: remove libs --- multi_user/libs/replication | 1 - 1 file changed, 1 deletion(-) delete mode 160000 multi_user/libs/replication diff --git a/multi_user/libs/replication b/multi_user/libs/replication deleted file mode 160000 index 0614a09..0000000 --- a/multi_user/libs/replication +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0614a09e70cb52e57c4f3bb1b9e02876f8b6c6cd From 552c649d342571a56945f4ba466e3e8544a49db5 Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 14 Apr 2021 11:49:34 +0200 Subject: [PATCH 15/25] feat: physics forcefield and collision support --- multi_user/bl_types/bl_object.py | 93 ++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index 8ab4f82..dc84433 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -54,6 +54,93 @@ def get_node_group_inputs(node_group): return inputs # return [inpt.identifer for inpt in node_group.inputs if inpt.type not in IGNORED_SOCKETS] +def get_rigid_body_scenes(target: bpy.types.Object)->[str]: + """ Find the list of scene using the rigid body + """ + scenes = [] + for scene in bpy.data.scenes: + if scene.rigidbody_world: + for obj in scene.rigidbody_world.collection.objects: + if obj == target: + scenes.append((scene.name, scene.rigidbody_world.collection.name)) + break + return scenes + +def dump_physics(target: bpy.types.Object)->dict: + """ + Dump all physics settings from a given object excluding modifier + related physics settings (such as softbody, cloth, dynapaint and fluid) + """ + dumper = Dumper() + dumper.depth = 1 + physics_data = {} + + # Collisions (collision) + if target.collision and target.collision.use: + physics_data['collision'] = dumper.dump(target.collision) + + # Field (field) + if target.field and target.field.type != "None": + physics_data['field'] = dumper.dump(target.field) + + # Rigid Body (rigid_body) + if target.rigid_body: + logging.warning("Rigid body not synced yet. ") + # physics_data['rigid_body_scenes'] = get_rigid_body_scenes(target) + # physics_data['rigid_body'] = dumper.dump(target.rigid_body) + + # Rigid Body constraint (rigid_body_constraint) + if target.rigid_body_constraint: + logging.warning("Rigid body constraints not synced yet. ") + # physics_data['rigid_body_constraint'] = dumper.dump(target.rigid_body_constraint) + + return physics_data + +def load_physics(dumped_settings: dict, target: bpy.types.Object): + """ Load all physics settings from a given object excluding modifier + related physics settings (such as softbody, cloth, dynapaint and fluid) + """ + loader = Loader() + + if 'collision' in dumped_settings: + loader.load(target.collision, dumped_settings['collision']) + + if 'field' in dumped_settings: + loader.load(target.field, dumped_settings['field']) + + # if 'rigid_body' in dumped_settings: + # # ugly fix to link the rigid body to the scene + # for scene, physics_collection in dumped_settings['rigid_body_scenes']: + # scene = bpy.data.scenes[scene] + # phys_collection = bpy.data.collections.get(physics_collection) + + # # Create the scene physics settings + # if not scene.rigidbody_world: + # ctx_override = {'scene': scene} + # bpy.ops.rigidbody.world_add(ctx_override) + + # if not phys_collection: + # phys_collection = bpy.data.collections.new(physics_collection) + + # if not scene.rigidbody_world.collection \ + # or scene.rigidbody_world.collection != phys_collection : + # scene.rigidbody_world.collection = phys_collection + + # if target.name not in phys_collection.objects: + # phys_collection.objects.link(target) + + # loader.load(target.rigid_body, dumped_settings['rigid_body']) + # elif target.rigid_body: + # scenes = get_rigid_body_scenes(target) + # for scene, collection in scenes: + # phys_collection = bpy.data.collections.get(collection) + # if phys_collection and target.name in phys_collection.objects: + # phys_collection.objects.unlink(target) + + # if 'rigid_body_constraint' in dumped_settings: + # loader.load(target.rigid_body_constraint, dumped_settings['rigid_body_constraint']) + + def dump_modifier_geometry_node_inputs(modifier: bpy.types.Modifier) -> list: """ Dump geometry node modifier input properties @@ -399,6 +486,9 @@ class BlObject(BlDatablock): target.matrix_basis = mathutils.Matrix(transform['matrix_basis']) target.matrix_local = mathutils.Matrix(transform['matrix_local']) + # PHYSICS + load_physics(data, target) + def _dump_implementation(self, data, instance=None): assert(instance) @@ -606,6 +696,9 @@ class BlObject(BlDatablock): ] data['cycles_visibility'] = dumper.dump(instance.cycles_visibility) + # PHYSICS + data.update(dump_physics(instance)) + return data def _resolve_deps_implementation(self): From 70641435ccc501635d34696e717dab7bfc6e24d3 Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 14 Apr 2021 12:25:16 +0200 Subject: [PATCH 16/25] feat: initial rigid body supports --- multi_user/bl_types/bl_object.py | 67 +++++++++----------------------- 1 file changed, 18 insertions(+), 49 deletions(-) diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index dc84433..e625cac 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -54,17 +54,6 @@ def get_node_group_inputs(node_group): return inputs # return [inpt.identifer for inpt in node_group.inputs if inpt.type not in IGNORED_SOCKETS] -def get_rigid_body_scenes(target: bpy.types.Object)->[str]: - """ Find the list of scene using the rigid body - """ - scenes = [] - for scene in bpy.data.scenes: - if scene.rigidbody_world: - for obj in scene.rigidbody_world.collection.objects: - if obj == target: - scenes.append((scene.name, scene.rigidbody_world.collection.name)) - break - return scenes def dump_physics(target: bpy.types.Object)->dict: """ @@ -85,14 +74,11 @@ def dump_physics(target: bpy.types.Object)->dict: # Rigid Body (rigid_body) if target.rigid_body: - logging.warning("Rigid body not synced yet. ") - # physics_data['rigid_body_scenes'] = get_rigid_body_scenes(target) - # physics_data['rigid_body'] = dumper.dump(target.rigid_body) + physics_data['rigid_body'] = dumper.dump(target.rigid_body) # Rigid Body constraint (rigid_body_constraint) if target.rigid_body_constraint: - logging.warning("Rigid body constraints not synced yet. ") - # physics_data['rigid_body_constraint'] = dumper.dump(target.rigid_body_constraint) + physics_data['rigid_body_constraint'] = dumper.dump(target.rigid_body_constraint) return physics_data @@ -108,38 +94,19 @@ def load_physics(dumped_settings: dict, target: bpy.types.Object): if 'field' in dumped_settings: loader.load(target.field, dumped_settings['field']) - # if 'rigid_body' in dumped_settings: - # # ugly fix to link the rigid body to the scene - # for scene, physics_collection in dumped_settings['rigid_body_scenes']: - # scene = bpy.data.scenes[scene] - # phys_collection = bpy.data.collections.get(physics_collection) - - # # Create the scene physics settings - # if not scene.rigidbody_world: - # ctx_override = {'scene': scene} - # bpy.ops.rigidbody.world_add(ctx_override) - - # if not phys_collection: - # phys_collection = bpy.data.collections.new(physics_collection) - - # if not scene.rigidbody_world.collection \ - # or scene.rigidbody_world.collection != phys_collection : - # scene.rigidbody_world.collection = phys_collection - - # if target.name not in phys_collection.objects: - # phys_collection.objects.link(target) - - # loader.load(target.rigid_body, dumped_settings['rigid_body']) - # elif target.rigid_body: - # scenes = get_rigid_body_scenes(target) - # for scene, collection in scenes: - # phys_collection = bpy.data.collections.get(collection) - # if phys_collection and target.name in phys_collection.objects: - # phys_collection.objects.unlink(target) - - # if 'rigid_body_constraint' in dumped_settings: - # loader.load(target.rigid_body_constraint, dumped_settings['rigid_body_constraint']) + if 'rigid_body' in dumped_settings: + if not target.rigid_body: + bpy.ops.rigidbody.object_add({"object": target}) + loader.load(target.rigid_body, dumped_settings['rigid_body']) + elif target.rigid_body: + bpy.ops.rigidbody.object_remove({"object": target}) + if 'rigid_body_constraint' in dumped_settings: + if not target.rigid_body_constraint: + bpy.ops.rigidbody.constraint_add({"object": target}) + loader.load(target.rigid_body_constraint, dumped_settings['rigid_body_constraint']) + elif target.rigid_body_constraint: + bpy.ops.rigidbody.constraint_remove({"object": target}) def dump_modifier_geometry_node_inputs(modifier: bpy.types.Modifier) -> list: """ Dump geometry node modifier input properties @@ -479,6 +446,9 @@ class BlObject(BlDatablock): # Hack to remove the default generated particle settings # bpy.data.particles.remove(default_settings) + # PHYSICS + load_physics(data, target) + transform = data.get('transforms', None) if transform: target.matrix_parent_inverse = mathutils.Matrix( @@ -486,8 +456,7 @@ class BlObject(BlDatablock): target.matrix_basis = mathutils.Matrix(transform['matrix_basis']) target.matrix_local = mathutils.Matrix(transform['matrix_local']) - # PHYSICS - load_physics(data, target) + def _dump_implementation(self, data, instance=None): assert(instance) From eb631e2d4b553b0e523e8a2d16aa845d5ad55415 Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 14 Apr 2021 14:36:06 +0200 Subject: [PATCH 17/25] feat: update changelog 0.3.0 release --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 293e81d..49f0192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,4 +157,33 @@ All notable changes to this project will be documented in this file. - Empty and Light object selection highlights - Material renaming - Default material nodes input parameters -- blender 2.91 python api compatibility \ No newline at end of file +- blender 2.91 python api compatibility + +## [0.3.0] - 2021-04-14 + +### Added + +- Curve material support +- Cycle visibility settings +- Session save/load operator +- Add new scene support +- Physic initial support +- Geometry node initial support +- Blender 2.93 compatibility +### Changed + +- Host documentation on Gitlab Page +- Event driven update (from the blender deps graph) + +### Fixed + +- Vertex group assignation +- Parent relation can't be removed +- Separate object +- Delete animation +- Sync missing holdout option for grease pencil material +- Sync missing `skin_vertices` +- Exception access violation during Undo/Redo +- Sync missing armature bone Roll +- Sync missing driver data_path +- Constraint replication \ No newline at end of file From 8e606068f3e201acb711a1c3c806608d8a58eb97 Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 14 Apr 2021 15:29:02 +0200 Subject: [PATCH 18/25] fix: particle system duplication feat: update Readme --- README.md | 60 ++++++++++++++-------------- multi_user/bl_types/bl_object.py | 31 ++++++++++---- multi_user/bl_types/bl_particle.py | 4 +- multi_user/bl_types/dump_anything.py | 4 +- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index d2c8991..051ef32 100644 --- a/README.md +++ b/README.md @@ -29,35 +29,35 @@ See the [troubleshooting guide](https://slumber.gitlab.io/multi-user/getting_sta Currently, not all data-block are supported for replication over the wire. The following list summarizes the status for each ones. -| Name | Status | Comment | -| -------------- | :----: | :--------------------------------------------------------------------------: | -| action | ✔️ | | -| armature | ❗ | Not stable | -| camera | ✔️ | | -| collection | ✔️ | | -| curve | ❗ | Nurbs surfaces not supported | -| gpencil | ✔️ | [Airbrush not supported](https://gitlab.com/slumber/multi-user/-/issues/123) | -| image | ✔️ | | -| mesh | ✔️ | | -| material | ✔️ | | -| node_groups | ❗ | Material only | -| geometry nodes | ✔️ | | -| metaball | ✔️ | | -| object | ✔️ | | -| textures | ❗ | Supported for modifiers/materials only | -| texts | ✔️ | | -| scene | ✔️ | | -| world | ✔️ | | -| lightprobes | ✔️ | | -| compositing | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/46) | -| texts | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/81) | -| nla | ❌ | | -| volumes | ✔️ | | -| particles | ❌ | [On-going](https://gitlab.com/slumber/multi-user/-/issues/24) | -| speakers | ❗ | [Partial](https://gitlab.com/slumber/multi-user/-/issues/65) | -| vse | ❗ | Mask and Clip not supported yet | -| physics | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/45) | -| libraries | ❗ | Partial | +| Name | Status | Comment | +| -------------- | :----: | :----------------------------------------------------------: | +| action | ✔️ | | +| armature | ❗ | Not stable | +| camera | ✔️ | | +| collection | ✔️ | | +| curve | ❗ | Nurbs surfaces not supported | +| gpencil | ✔️ | | +| image | ✔️ | | +| mesh | ✔️ | | +| material | ✔️ | | +| node_groups | ❗ | Material & Geometry only | +| geometry nodes | ✔️ | | +| metaball | ✔️ | | +| object | ✔️ | | +| textures | ❗ | Supported for modifiers/materials/geo nodes only | +| texts | ✔️ | | +| scene | ✔️ | | +| world | ✔️ | | +| lightprobes | ✔️ | | +| compositing | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/46) | +| texts | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/81) | +| nla | ❌ | | +| volumes | ✔️ | | +| particles | ❗ | The cache isn't syncing. | +| speakers | ❗ | [Partial](https://gitlab.com/slumber/multi-user/-/issues/65) | +| vse | ❗ | Mask and Clip not supported yet | +| physics | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/45) | +| libraries | ❗ | Partial | @@ -70,7 +70,7 @@ I'm working on it. | Dependencies | Version | Needed | | ------------ | :-----: | -----: | -| Replication | latest | yes | +| Replication | latest | yes | diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index e625cac..fd9bbd5 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -439,12 +439,24 @@ class BlObject(BlDatablock): mod for mod in target.modifiers if mod.type == 'PARTICLE_SYSTEM'] for mod in particles_modifiers: - loader.load(mod.particle_system, data['modifiers'][mod.name]['particle_system']) - # default_settings = mod.particle_system.settings - # mod.particle_system.settings = get_datablock_from_uuid(data['modifiers'][mod.name]['particle_system']['settings'], None) + default = mod.particle_system.settings.name + dumped_particles = data['modifiers'][mod.name]['particle_system'] + loader.load(mod.particle_system, dumped_particles) - # Hack to remove the default generated particle settings - # bpy.data.particles.remove(default_settings) + settings = get_datablock_from_uuid(dumped_particles['settings_uuid'], None) + if settings: + mod.particle_system.settings = settings + + # Hack to remove the default generated particle settings + for settings in bpy.data.particles: + if settings.users == 0: + bpy.data.particles.remove(settings) + + phys_modifiers = [ + mod for mod in target.modifiers if mod.type in ['SOFT_BODY', 'CLOTH']] + + for mod in phys_modifiers: + loader.load(mod.settings, data['modifiers'][mod.name]['settings']) # PHYSICS load_physics(data, target) @@ -456,7 +468,6 @@ class BlObject(BlDatablock): target.matrix_basis = mathutils.Matrix(transform['matrix_basis']) target.matrix_local = mathutils.Matrix(transform['matrix_local']) - def _dump_implementation(self, data, instance=None): assert(instance) @@ -537,13 +548,17 @@ class BlObject(BlDatablock): modifier) dumped_modifier['inputs'] = dumped_inputs - if modifier.type == 'PARTICLE_SYSTEM': + elif modifier.type == 'PARTICLE_SYSTEM': dumper.exclude_filter = [ "is_edited", "is_editable", "is_global_hair" ] dumped_modifier['particle_system'] = dumper.dump(modifier.particle_system) + dumped_modifier['particle_system']['settings_uuid'] = modifier.particle_system.settings.uuid + + elif modifier.type in ['SOFT_BODY', 'CLOTH']: + dumped_modifier['settings'] = dumper.dump(modifier.settings) data["modifiers"][modifier.name] = dumped_modifier @@ -679,7 +694,7 @@ class BlObject(BlDatablock): # Particle systems for particle_slot in self.instance.particle_systems: - deps.append(bpy.data.particles[particle_slot.name]) + deps.append(particle_slot.settings) if self.is_library: deps.append(self.instance.library) diff --git a/multi_user/bl_types/bl_particle.py b/multi_user/bl_types/bl_particle.py index b7d7697..2ec6fac 100644 --- a/multi_user/bl_types/bl_particle.py +++ b/multi_user/bl_types/bl_particle.py @@ -45,7 +45,9 @@ class BlParticle(BlDatablock): bl_reload_parent = False def _construct(self, data): - return bpy.data.particles.new(data["name"]) + instance = bpy.data.particles.new(data["name"]) + instance.uuid = self.uuid + return instance def _load_implementation(self, data, target): dump_anything.load(target, data) diff --git a/multi_user/bl_types/dump_anything.py b/multi_user/bl_types/dump_anything.py index 10e1293..2b97ecb 100644 --- a/multi_user/bl_types/dump_anything.py +++ b/multi_user/bl_types/dump_anything.py @@ -610,8 +610,8 @@ class Loader: instance.write(bpy.data.fonts.get(dump)) elif isinstance(rna_property_type, T.Sound): instance.write(bpy.data.sounds.get(dump)) - elif isinstance(rna_property_type, T.ParticleSettings): - instance.write(bpy.data.particles.get(dump)) + # elif isinstance(rna_property_type, T.ParticleSettings): + # instance.write(bpy.data.particles.get(dump)) def _load_matrix(self, matrix, dump): matrix.write(mathutils.Matrix(dump)) From 736c3df7c4a452d5ec2b6b7486dbdbb74431336d Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 14 Apr 2021 15:50:53 +0200 Subject: [PATCH 19/25] feat: remove new particle systems clean: remove logs --- multi_user/bl_types/bl_object.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index fd9bbd5..18945cb 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -240,7 +240,7 @@ def find_geometry_nodes_dependencies(modifiers: bpy.types.bpy_prop_collection) - # parameter = mod.get(inpt.identifier) # if parameter and isinstance(parameter, bpy.types.ID): # dependencies.append(parameter) - logging.info(dependencies) + return dependencies @@ -439,18 +439,16 @@ class BlObject(BlDatablock): mod for mod in target.modifiers if mod.type == 'PARTICLE_SYSTEM'] for mod in particles_modifiers: - default = mod.particle_system.settings.name + default = mod.particle_system.settings dumped_particles = data['modifiers'][mod.name]['particle_system'] loader.load(mod.particle_system, dumped_particles) settings = get_datablock_from_uuid(dumped_particles['settings_uuid'], None) if settings: mod.particle_system.settings = settings - - # Hack to remove the default generated particle settings - for settings in bpy.data.particles: - if settings.users == 0: - bpy.data.particles.remove(settings) + # Hack to remove the default generated particle settings + if not default.uuid: + bpy.data.particles.remove(default) phys_modifiers = [ mod for mod in target.modifiers if mod.type in ['SOFT_BODY', 'CLOTH']] From 570909a7c471d6c33cf063e3b1dec5a4ba9a85e3 Mon Sep 17 00:00:00 2001 From: Swann Date: Wed, 14 Apr 2021 16:25:21 +0200 Subject: [PATCH 20/25] fix: prevent field from being dumped if unused fix: bl_object tests --- multi_user/bl_types/bl_object.py | 3 ++- scripts/test_addon.py | 2 +- tests/test_bl_types/test_object.py | 7 +++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py index 18945cb..0220646 100644 --- a/multi_user/bl_types/bl_object.py +++ b/multi_user/bl_types/bl_object.py @@ -69,7 +69,7 @@ def dump_physics(target: bpy.types.Object)->dict: physics_data['collision'] = dumper.dump(target.collision) # Field (field) - if target.field and target.field.type != "None": + if target.field and target.field.type != "NONE": physics_data['field'] = dumper.dump(target.field) # Rigid Body (rigid_body) @@ -538,6 +538,7 @@ class BlObject(BlDatablock): if modifiers: dumper.include_filter = None dumper.depth = 1 + dumper.exclude_filter = ['is_active'] for index, modifier in enumerate(modifiers): dumped_modifier = dumper.dump(modifier) # hack to dump geometry nodes inputs diff --git a/scripts/test_addon.py b/scripts/test_addon.py index 2f524dd..96575a9 100644 --- a/scripts/test_addon.py +++ b/scripts/test_addon.py @@ -13,7 +13,7 @@ def main(): if len(sys.argv) > 2: blender_rev = sys.argv[2] else: - blender_rev = "2.91.0" + blender_rev = "2.92.0" try: exit_val = BAT.test_blender_addon(addon_path=addon, blender_revision=blender_rev) diff --git a/tests/test_bl_types/test_object.py b/tests/test_bl_types/test_object.py index f73b848..db63981 100644 --- a/tests/test_bl_types/test_object.py +++ b/tests/test_bl_types/test_object.py @@ -7,7 +7,7 @@ import bpy import random from multi_user.bl_types.bl_object import BlObject -# Removed 'BUILD' modifier because the seed doesn't seems to be +# Removed 'BUILD', 'SOFT_BODY' modifier because the seed doesn't seems to be # correctly initialized (#TODO: report the bug) MOFIFIERS_TYPES = [ 'DATA_TRANSFER', 'MESH_CACHE', 'MESH_SEQUENCE_CACHE', @@ -22,8 +22,7 @@ MOFIFIERS_TYPES = [ 'MESH_DEFORM', 'SHRINKWRAP', 'SIMPLE_DEFORM', 'SMOOTH', 'CORRECTIVE_SMOOTH', 'LAPLACIANSMOOTH', 'SURFACE_DEFORM', 'WARP', 'WAVE', 'CLOTH', 'COLLISION', 'DYNAMIC_PAINT', - 'EXPLODE', 'FLUID', 'OCEAN', 'PARTICLE_INSTANCE', - 'SOFT_BODY', 'SURFACE'] + 'EXPLODE', 'FLUID', 'OCEAN', 'PARTICLE_INSTANCE', 'SURFACE'] GP_MODIFIERS_TYPE = [ 'GP_ARRAY', 'GP_BUILD', 'GP_MIRROR', 'GP_MULTIPLY', @@ -72,5 +71,5 @@ def test_object(clear_blend): test = implementation._construct(expected) implementation._load(expected, test) result = implementation._dump(test) - + print(DeepDiff(expected, result)) assert not DeepDiff(expected, result) From 53eaaa2fcd768b236c800c82976e237ebcf15b98 Mon Sep 17 00:00:00 2001 From: Swann Date: Thu, 15 Apr 2021 15:28:59 +0200 Subject: [PATCH 21/25] fix: auto-updater operator registration for blender 2.93 compatibility --- multi_user/addon_updater_ops.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/multi_user/addon_updater_ops.py b/multi_user/addon_updater_ops.py index 9dd3960..efe7641 100644 --- a/multi_user/addon_updater_ops.py +++ b/multi_user/addon_updater_ops.py @@ -122,13 +122,13 @@ class addon_updater_install_popup(bpy.types.Operator): # if true, run clean install - ie remove all files before adding new # equivalent to deleting the addon and reinstalling, except the # updater folder/backup folder remains - clean_install = bpy.props.BoolProperty( + clean_install: bpy.props.BoolProperty( name="Clean install", description="If enabled, completely clear the addon's folder before installing new update, creating a fresh install", default=False, options={'HIDDEN'} ) - ignore_enum = bpy.props.EnumProperty( + ignore_enum: bpy.props.EnumProperty( name="Process update", description="Decide to install, ignore, or defer new addon update", items=[ @@ -264,7 +264,7 @@ class addon_updater_update_now(bpy.types.Operator): # if true, run clean install - ie remove all files before adding new # equivalent to deleting the addon and reinstalling, except the # updater folder/backup folder remains - clean_install = bpy.props.BoolProperty( + clean_install: bpy.props.BoolProperty( name="Clean install", description="If enabled, completely clear the addon's folder before installing new update, creating a fresh install", default=False, @@ -332,7 +332,7 @@ class addon_updater_update_target(bpy.types.Operator): i+=1 return ret - target = bpy.props.EnumProperty( + target: bpy.props.EnumProperty( name="Target version to install", description="Select the version to install", items=target_version @@ -341,7 +341,7 @@ class addon_updater_update_target(bpy.types.Operator): # if true, run clean install - ie remove all files before adding new # equivalent to deleting the addon and reinstalling, except the # updater folder/backup folder remains - clean_install = bpy.props.BoolProperty( + clean_install: bpy.props.BoolProperty( name="Clean install", description="If enabled, completely clear the addon's folder before installing new update, creating a fresh install", default=False, @@ -399,7 +399,7 @@ class addon_updater_install_manually(bpy.types.Operator): bl_description = "Proceed to manually install update" bl_options = {'REGISTER', 'INTERNAL'} - error = bpy.props.StringProperty( + error: bpy.props.StringProperty( name="Error Occurred", default="", options={'HIDDEN'} @@ -461,7 +461,7 @@ class addon_updater_updated_successful(bpy.types.Operator): bl_description = "Update installation response" bl_options = {'REGISTER', 'INTERNAL', 'UNDO'} - error = bpy.props.StringProperty( + error: bpy.props.StringProperty( name="Error Occurred", default="", options={'HIDDEN'} From e5651151d9a70352be74d1220b8488faf91b314b Mon Sep 17 00:00:00 2001 From: Swann Date: Thu, 22 Apr 2021 14:00:26 +0200 Subject: [PATCH 22/25] fix: having both animation and drivers on the same object --- multi_user/bl_types/bl_datablock.py | 17 +++++++++-------- multi_user/libs/replication | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) create mode 160000 multi_user/libs/replication diff --git a/multi_user/bl_types/bl_datablock.py b/multi_user/bl_types/bl_datablock.py index c3ab5a7..348a961 100644 --- a/multi_user/bl_types/bl_datablock.py +++ b/multi_user/bl_types/bl_datablock.py @@ -161,19 +161,17 @@ class BlDatablock(ReplicatedDatablock): def _dump(self, instance=None): dumper = Dumper() data = {} + animation_data = {} # Dump animation data if has_action(instance): - dumper = Dumper() - dumper.include_filter = ['action'] - data['animation_data'] = dumper.dump(instance.animation_data) - + animation_data['action'] = instance.animation_data.action.name if has_driver(instance): - dumped_drivers = {'animation_data': {'drivers': []}} + animation_data['drivers'] = [] for driver in instance.animation_data.drivers: - dumped_drivers['animation_data']['drivers'].append( - dump_driver(driver)) + animation_data['drivers'].append(dump_driver(driver)) - data.update(dumped_drivers) + if animation_data: + data['animation_data'] = animation_data if self.is_library: data.update(dumper.dump(instance)) @@ -200,6 +198,9 @@ class BlDatablock(ReplicatedDatablock): 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() diff --git a/multi_user/libs/replication b/multi_user/libs/replication new file mode 160000 index 0000000..001fbdc --- /dev/null +++ b/multi_user/libs/replication @@ -0,0 +1 @@ +Subproject commit 001fbdc60da58a5e3b7006f1d782d6f472c12809 From d2108facaba2a82dfa3d35d425b19caf8794ff37 Mon Sep 17 00:00:00 2001 From: Swann Date: Thu, 22 Apr 2021 14:52:43 +0200 Subject: [PATCH 23/25] feat: fcurve modifiers support --- multi_user/bl_types/bl_action.py | 30 ++++++++++++++++++++++++++++-- tests/test_bl_types/test_action.py | 4 ++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/multi_user/bl_types/bl_action.py b/multi_user/bl_types/bl_action.py index 8672fb6..893fe30 100644 --- a/multi_user/bl_types/bl_action.py +++ b/multi_user/bl_types/bl_action.py @@ -61,7 +61,6 @@ def dump_fcurve(fcurve: bpy.types.FCurve, use_numpy: bool = True) -> dict: points = fcurve.keyframe_points fcurve_data['keyframes_count'] = len(fcurve.keyframe_points) fcurve_data['keyframe_points'] = np_dump_collection(points, KEYFRAME) - else: # Legacy method dumper = Dumper() fcurve_data["keyframe_points"] = [] @@ -71,6 +70,18 @@ def dump_fcurve(fcurve: bpy.types.FCurve, use_numpy: bool = True) -> dict: dumper.dump(k) ) + if fcurve.modifiers: + dumper = Dumper() + dumper.exclude_filter = [ + 'is_valid', + 'active' + ] + dumped_modifiers = [] + for modfifier in fcurve.modifiers: + dumped_modifiers.append(dumper.dump(modfifier)) + + fcurve_data['modifiers'] = dumped_modifiers + return fcurve_data @@ -83,7 +94,7 @@ def load_fcurve(fcurve_data, fcurve): :type fcurve: bpy.types.FCurve """ use_numpy = fcurve_data.get('use_numpy') - + loader = Loader() keyframe_points = fcurve.keyframe_points # Remove all keyframe points @@ -128,6 +139,21 @@ def load_fcurve(fcurve_data, fcurve): fcurve.update() + dumped_fcurve_modifiers = fcurve_data.get('modifiers', None) + + if dumped_fcurve_modifiers: + # clear modifiers + for fmod in fcurve.modifiers: + fcurve.modifiers.remove(fmod) + + # Load each modifiers in order + for modifier_data in dumped_fcurve_modifiers: + modifier = fcurve.modifiers.new(modifier_data['type']) + + loader.load(modifier, modifier_data) + elif fcurve.modifiers: + for fmod in fcurve.modifiers: + fcurve.modifiers.remove(fmod) class BlAction(BlDatablock): bl_id = "actions" diff --git a/tests/test_bl_types/test_action.py b/tests/test_bl_types/test_action.py index 3659777..0c95b8c 100644 --- a/tests/test_bl_types/test_action.py +++ b/tests/test_bl_types/test_action.py @@ -8,6 +8,7 @@ import random from multi_user.bl_types.bl_action import BlAction INTERPOLATION = ['CONSTANT', 'LINEAR', 'BEZIER', 'SINE', 'QUAD', 'CUBIC', 'QUART', 'QUINT', 'EXPO', 'CIRC', 'BACK', 'BOUNCE', 'ELASTIC'] +FMODIFIERS = ['GENERATOR', 'FNGENERATOR', 'ENVELOPE', 'CYCLES', 'NOISE', 'LIMITS', 'STEPPED'] # @pytest.mark.parametrize('blendname', ['test_action.blend']) def test_action(clear_blend): @@ -22,6 +23,9 @@ def test_action(clear_blend): point.co[1] = random.randint(-10,10) point.interpolation = INTERPOLATION[random.randint(0, len(INTERPOLATION)-1)] + for mod_type in FMODIFIERS: + fcurve_sample.modifiers.new(mod_type) + bpy.ops.mesh.primitive_plane_add() bpy.data.objects[0].animation_data_create() bpy.data.objects[0].animation_data.action = datablock From a36c3740cc789ea6f26ee69b36e4d1d9a96d5605 Mon Sep 17 00:00:00 2001 From: Swann Date: Thu, 22 Apr 2021 15:00:08 +0200 Subject: [PATCH 24/25] fix: load driver variable without id --- multi_user/bl_types/bl_datablock.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/multi_user/bl_types/bl_datablock.py b/multi_user/bl_types/bl_datablock.py index 348a961..7cdb2d1 100644 --- a/multi_user/bl_types/bl_datablock.py +++ b/multi_user/bl_types/bl_datablock.py @@ -72,10 +72,10 @@ def load_driver(target_datablock, src_driver): 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) + 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 From 14779be1ed858f10ebab3e49ab876f5e147d34d1 Mon Sep 17 00:00:00 2001 From: Swann Date: Thu, 22 Apr 2021 15:52:06 +0200 Subject: [PATCH 25/25] feat: support video file as camera background images --- multi_user/bl_types/bl_camera.py | 20 +++++++++++++++++--- multi_user/bl_types/bl_image.py | 8 ++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/multi_user/bl_types/bl_camera.py b/multi_user/bl_types/bl_camera.py index 486726c..244ad77 100644 --- a/multi_user/bl_types/bl_camera.py +++ b/multi_user/bl_types/bl_camera.py @@ -56,6 +56,11 @@ class BlCamera(BlDatablock): target_img.image = bpy.data.images[img_id] loader.load(target_img, img_data) + img_user = img_data.get('image_user') + if img_user: + loader.load(target_img.image_user, img_user) + + def _dump_implementation(self, data, instance=None): assert(instance) @@ -101,10 +106,19 @@ class BlCamera(BlDatablock): 'scale', 'use_flip_x', 'use_flip_y', - 'image' + 'image_user', + 'image', + 'frame_duration', + 'frame_start', + 'frame_offset', + 'use_cyclic', + 'use_auto_refresh' ] - return dumper.dump(instance) - + data = dumper.dump(instance) + for index, image in enumerate(instance.background_images): + if image.image_user: + data['background_images'][index]['image_user'] = dumper.dump(image.image_user) + return data def _resolve_deps_implementation(self): deps = [] for background in self.instance.background_images: diff --git a/multi_user/bl_types/bl_image.py b/multi_user/bl_types/bl_image.py index c559938..3a248c6 100644 --- a/multi_user/bl_types/bl_image.py +++ b/multi_user/bl_types/bl_image.py @@ -66,9 +66,12 @@ class BlImage(BlDatablock): loader = Loader() loader.load(data, target) - target.source = 'FILE' + target.source = data['source'] target.filepath_raw = get_filepath(data['filename']) - target.colorspace_settings.name = data["colorspace_settings"]["name"] + color_space_name = data["colorspace_settings"]["name"] + + if color_space_name: + target.colorspace_settings.name = color_space_name def _dump(self, instance=None): assert(instance) @@ -83,6 +86,7 @@ class BlImage(BlDatablock): dumper.depth = 2 dumper.include_filter = [ "name", + 'source', 'size', 'height', 'alpha',