Compare commits

..

20 Commits

Author SHA1 Message Date
ab350ca7bc fix: late update logging error
Related to #208
2021-06-24 17:24:08 +02:00
de73f022e6 merge 2021-06-24 14:52:07 +02:00
f517205647 fix: doc authors 2021-06-24 14:51:00 +02:00
f33c3d8481 fix: doc version 2021-06-24 14:50:12 +02:00
71c69000ec Merge branch '207-repository-panel-filtering-is-boken' into 'develop'
Resolve "Repository panel filtering is boken"

See merge request slumber/multi-user!132
2021-06-24 12:49:06 +00:00
de1e684b3c fix: name filtering 2021-06-24 14:35:59 +02:00
d87730cffb Merge branch '197-user-selection-bounding-box-glitches-for-non-mesh-objects' into 'develop'
User selection bounding box glitches for non-mesh objects

See merge request slumber/multi-user!129
2021-06-23 16:02:50 +00:00
3f005b86ab fix : add enumerate / remove nb_object 2021-06-23 17:45:01 +02:00
5098e5135d fix: bbox work for non-mesh objects+ins.collection 2021-06-23 17:00:05 +02:00
37cfed489c Merge branch '204-animation-doesn-t-sync-for-gpencil-materials' into 'develop'
Resolve "Animation doesn't sync for materials"

See merge request slumber/multi-user!128
2021-06-22 12:10:23 +00:00
9003abcd18 feat: notes for furtur improvements 2021-06-22 14:06:19 +02:00
a199e0df00 feat: apply bl_apply_child member to force dependencies reloading
fix: node_tree animation dependencies
2021-06-22 11:36:51 +02:00
3774419b7e fix: force push is now pushing the whole node data instead of delta 2021-06-22 10:41:36 +02:00
3e552cb406 feat: gpencil materials animation support 2021-06-22 10:39:40 +02:00
9f381b44c8 fix: material animation support 2021-06-21 18:58:16 +02:00
ad795caed5 fix: only apply repository heads on connection 2021-06-21 18:38:43 +02:00
504dd77405 fix: scene cleaning 2021-06-21 17:10:05 +02:00
82022c9e4d clean: only log ignored update in debug logging level 2021-06-18 15:45:51 +02:00
d81b4dc014 feat: enable delta back for all datablocks execpt gpencil, files and images 2021-06-18 15:30:39 +02:00
63affa079f Merge branch '199-filter-correctly-distant-updates-in-the-depsgraph-handler' into 'develop'
Resolve "Filter correctly distant updates in the depsgraph handler"

See merge request slumber/multi-user!126
2021-06-18 13:12:15 +00:00
25 changed files with 150 additions and 71 deletions

View File

@ -19,10 +19,10 @@ import sys
project = 'multi-user'
copyright = '2020, Swann Martinez'
author = 'Swann Martinez, with contributions from Poochy'
author = 'Swann Martinez, Poochy, Fabian'
# The full version, including alpha/beta/rc tags
release = '0.2.0'
release = '0.5.0-develop'
# -- General configuration ---------------------------------------------------

View File

@ -219,7 +219,7 @@ def load_fcurve(fcurve_data, fcurve):
def dump_animation_data(datablock):
animation_data = {}
if has_action(datablock):
animation_data['action'] = datablock.animation_data.action.name
animation_data['action'] = datablock.animation_data.action.uuid
if has_driver(datablock):
animation_data['drivers'] = []
for driver in datablock.animation_data.drivers:
@ -241,8 +241,10 @@ def load_animation_data(animation_data, datablock):
for driver in animation_data['drivers']:
load_driver(datablock, driver)
if 'action' in animation_data:
datablock.animation_data.action = bpy.data.actions[animation_data['action']]
action = animation_data.get('action')
if action:
action = resolve_datablock_from_uuid(action, bpy.data.actions)
datablock.animation_data.action = action
elif datablock.animation_data.action:
datablock.animation_data.action = None
@ -259,6 +261,8 @@ def resolve_animation_dependencies(datablock):
class BlAction(ReplicatedDatablock):
use_delta = True
bl_id = "actions"
bl_class = bpy.types.Action
bl_check_common = False

View File

@ -37,6 +37,8 @@ def get_roll(bone: bpy.types.Bone) -> float:
class BlArmature(ReplicatedDatablock):
use_delta = True
bl_id = "armatures"
bl_class = bpy.types.Armature
bl_check_common = False

View File

@ -26,6 +26,8 @@ from .bl_action import dump_animation_data, load_animation_data, resolve_animati
class BlCamera(ReplicatedDatablock):
use_delta = True
bl_id = "cameras"
bl_class = bpy.types.Camera
bl_check_common = False

View File

@ -137,6 +137,8 @@ SPLINE_METADATA = [
class BlCurve(ReplicatedDatablock):
use_delta = True
bl_id = "curves"
bl_class = bpy.types.Curve
bl_check_common = False

View File

@ -29,6 +29,8 @@ POINT = ['co', 'weight_softbody', 'co_deform']
class BlLattice(ReplicatedDatablock):
use_delta = True
bl_id = "lattices"
bl_class = bpy.types.Lattice
bl_check_common = False

View File

@ -26,6 +26,8 @@ from .bl_action import dump_animation_data, load_animation_data, resolve_animati
class BlLight(ReplicatedDatablock):
use_delta = True
bl_id = "lights"
bl_class = bpy.types.Light
bl_check_common = False

View File

@ -25,6 +25,8 @@ from replication.protocol import ReplicatedDatablock
from .bl_datablock import resolve_datablock_from_uuid
class BlLightprobe(ReplicatedDatablock):
use_delta = True
bl_id = "lightprobes"
bl_class = bpy.types.LightProbe
bl_check_common = False

View File

@ -397,11 +397,14 @@ def load_materials_slots(src_materials: list, dst_materials: bpy.types.bpy_prop_
class BlMaterial(ReplicatedDatablock):
use_delta = True
bl_id = "materials"
bl_class = bpy.types.Material
bl_check_common = False
bl_icon = 'MATERIAL_DATA'
bl_reload_parent = False
bl_reload_child = True
@staticmethod
def construct(data: dict) -> object:
@ -409,8 +412,6 @@ class BlMaterial(ReplicatedDatablock):
@staticmethod
def load(data: dict, datablock: object):
load_animation_data(data.get('animation_data'), datablock)
loader = Loader()
is_grease_pencil = data.get('is_grease_pencil')
@ -427,6 +428,8 @@ class BlMaterial(ReplicatedDatablock):
datablock.use_nodes = True
load_node_tree(data['node_tree'], datablock.node_tree)
load_animation_data(data.get('nodes_animation_data'), datablock.node_tree)
load_animation_data(data.get('animation_data'), datablock)
@staticmethod
def dump(datablock: object) -> dict:
@ -494,8 +497,10 @@ class BlMaterial(ReplicatedDatablock):
data['grease_pencil'] = gp_mat_dumper.dump(datablock.grease_pencil)
elif datablock.use_nodes:
data['node_tree'] = dump_node_tree(datablock.node_tree)
data['nodes_animation_data'] = dump_animation_data(datablock.node_tree)
data['animation_data'] = dump_animation_data(datablock)
return data
@staticmethod
@ -509,7 +514,7 @@ class BlMaterial(ReplicatedDatablock):
if datablock.use_nodes:
deps.extend(get_node_tree_dependencies(datablock.node_tree))
deps.extend(resolve_animation_dependencies(datablock.node_tree))
deps.extend(resolve_animation_dependencies(datablock))
return deps

View File

@ -55,6 +55,8 @@ POLYGON = [
]
class BlMesh(ReplicatedDatablock):
use_delta = True
bl_id = "meshes"
bl_class = bpy.types.Mesh
bl_check_common = False

View File

@ -65,6 +65,8 @@ def load_metaball_elements(elements_data, elements):
class BlMetaball(ReplicatedDatablock):
use_delta = True
bl_id = "metaballs"
bl_class = bpy.types.MetaBall
bl_check_common = False

View File

@ -28,6 +28,8 @@ from .bl_datablock import resolve_datablock_from_uuid
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
class BlNodeGroup(ReplicatedDatablock):
use_delta = True
bl_id = "node_groups"
bl_class = bpy.types.NodeTree
bl_check_common = False

View File

@ -493,6 +493,8 @@ def load_modifiers_custom_data(dumped_modifiers: dict, modifiers: bpy.types.bpy_
class BlObject(ReplicatedDatablock):
use_delta = True
bl_id = "objects"
bl_class = bpy.types.Object
bl_check_common = False

View File

@ -41,6 +41,8 @@ IGNORED_ATTR = [
]
class BlParticle(ReplicatedDatablock):
use_delta = True
bl_id = "particles"
bl_class = bpy.types.ParticleSettings
bl_icon = "PARTICLES"

View File

@ -25,6 +25,8 @@ from .bl_datablock import resolve_datablock_from_uuid
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
class BlSpeaker(ReplicatedDatablock):
use_delta = True
bl_id = "speakers"
bl_class = bpy.types.Speaker
bl_check_common = False

View File

@ -26,6 +26,8 @@ from .bl_action import dump_animation_data, load_animation_data, resolve_animati
import bpy.types as T
class BlTexture(ReplicatedDatablock):
use_delta = True
bl_id = "textures"
bl_class = bpy.types.Texture
bl_check_common = False

View File

@ -27,6 +27,8 @@ from .bl_material import dump_materials_slots, load_materials_slots
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
class BlVolume(ReplicatedDatablock):
use_delta = True
bl_id = "volumes"
bl_class = bpy.types.Volume
bl_check_common = False

View File

@ -30,6 +30,8 @@ from .bl_action import dump_animation_data, load_animation_data, resolve_animati
class BlWorld(ReplicatedDatablock):
use_delta = True
bl_id = "worlds"
bl_class = bpy.types.World
bl_check_common = True

View File

@ -75,7 +75,7 @@ def on_scene_update(scene):
if distant_update:
for u in distant_update:
shared_data.session.applied_updates.remove(u)
logging.info(f"Ignoring distant update of {dependency_updates[0].id.name}")
logging.debug(f"Ignoring distant update of {dependency_updates[0].id.name}")
return
update_external_dependencies()

View File

@ -100,7 +100,7 @@ def initialize_session():
# Step 2: Load nodes
logging.info("Applying nodes")
for node in session.repository.index_sorted:
for node in session.repository.heads:
porcelain.apply(session.repository, node)
logging.info("Registering timers")
@ -604,9 +604,9 @@ class SessionApply(bpy.types.Operator):
node_ref = session.repository.graph.get(self.target)
porcelain.apply(session.repository,
self.target,
force=True,
force_dependencies=self.reset_dependencies)
force=True)
impl = session.repository.rdp.get_implementation(node_ref.instance)
# NOTE: find another way to handle child and parent automatic reloading
if impl.bl_reload_parent:
for parent in session.repository.graph.get_parents(self.target):
logging.debug(f"Refresh parent {parent}")
@ -614,6 +614,11 @@ class SessionApply(bpy.types.Operator):
porcelain.apply(session.repository,
parent.uuid,
force=True)
if hasattr(impl, 'bl_reload_child') and impl.bl_reload_child:
for dep in node_ref.dependencies:
porcelain.apply(session.repository,
dep,
force=True)
except Exception as e:
self.report({'ERROR'}, repr(e))
traceback.print_exc()
@ -637,7 +642,7 @@ class SessionCommit(bpy.types.Operator):
def execute(self, context):
try:
porcelain.commit(session.repository, self.target)
porcelain.push(session.repository, 'origin', self.target)
porcelain.push(session.repository, 'origin', self.target, force=True)
return {"FINISHED"}
except Exception as e:
self.report({'ERROR'}, repr(e))

View File

@ -94,15 +94,35 @@ def project_to_viewport(region: bpy.types.Region, rv3d: bpy.types.RegionView3D,
return [target.x, target.y, target.z]
def bbox_from_obj(obj: bpy.types.Object, radius: float) -> list:
def bbox_from_obj(obj: bpy.types.Object) -> list:
""" Generate a bounding box for a given object by using its world matrix
:param obj: target object
:type obj: bpy.types.Object
:param radius: bounding box radius
:type radius: float
:return: list of 8 points [(x,y,z),...]
:return: list of 8 points [(x,y,z),...], list of 12 link between these points [(1,2),...]
"""
radius = 1.0 # Radius of the bounding box
vertex_indices = (
(0, 1), (0, 2), (1, 3), (2, 3),
(4, 5), (4, 6), (5, 7), (6, 7),
(0, 4), (1, 5), (2, 6), (3, 7))
if obj.type == 'EMPTY':
radius = obj.empty_display_size
elif obj.type == 'LIGHT':
radius = obj.data.shadow_soft_size
elif obj.type == 'LIGHT_PROBE':
radius = obj.data.influence_distance
elif obj.type == 'CAMERA':
radius = obj.data.display_size
elif hasattr(obj, 'bound_box'):
vertex_indices = (
(0, 1), (1, 2), (2, 3), (0, 3),
(4, 5), (5, 6), (6, 7), (4, 7),
(0, 4), (1, 5), (2, 6), (3, 7))
vertex_pos = get_bb_coords_from_obj(obj)
return vertex_pos, vertex_indices
coords = [
(-radius, -radius, -radius), (+radius, -radius, -radius),
(-radius, +radius, -radius), (+radius, +radius, -radius),
@ -112,9 +132,37 @@ def bbox_from_obj(obj: bpy.types.Object, radius: float) -> list:
base = obj.matrix_world
bbox_corners = [base @ mathutils.Vector(corner) for corner in coords]
return [(point.x, point.y, point.z)
for point in bbox_corners]
vertex_pos = [(point.x, point.y, point.z) for point in bbox_corners]
return vertex_pos, vertex_indices
def bbox_from_instance_collection(ic: bpy.types.Object) -> list:
""" Generate a bounding box for a given instance collection by using its objects
:param ic: target instance collection
:type ic: bpy.types.Object
:param radius: bounding box radius
:type radius: float
:return: list of 8*objs points [(x,y,z),...], tuple of 12*objs link between these points [(1,2),...]
"""
vertex_pos = []
vertex_indices = ()
for obj_index, obj in enumerate(ic.instance_collection.objects):
vertex_pos_temp, vertex_indices_temp = bbox_from_obj(obj)
vertex_pos += vertex_pos_temp
vertex_indices_list_temp = list(list(indice) for indice in vertex_indices_temp)
for indice in vertex_indices_list_temp:
indice[0] += 8*obj_index
indice[1] += 8*obj_index
vertex_indices_temp = tuple(tuple(indice) for indice in vertex_indices_list_temp)
vertex_indices += vertex_indices_temp
bbox_corners = [ic.matrix_world @ mathutils.Vector(vertex) for vertex in vertex_pos]
vertex_pos = [(point.x, point.y, point.z) for point in bbox_corners]
return vertex_pos, vertex_indices
def generate_user_camera() -> list:
""" Generate a basic camera represention of the user point of view
@ -296,36 +344,14 @@ class UserSelectionWidget(Widget):
def draw(self):
user_selection = self.data.get('selected_objects')
for select_ob in user_selection:
ob = find_from_attr("uuid", select_ob, bpy.data.objects)
if not ob:
for select_obj in user_selection:
obj = find_from_attr("uuid", select_obj, bpy.data.objects)
if not obj:
return
vertex_pos = bbox_from_obj(ob, 1.0)
vertex_indices = (
(0, 1), (1, 2), (2, 3), (0, 3),
(4, 5), (5, 6), (6, 7), (4, 7),
(0, 4), (1, 5), (2, 6), (3, 7))
if ob.instance_collection:
for obj in ob.instance_collection.objects:
if obj.type == 'MESH' and hasattr(obj, 'bound_box'):
vertex_pos = get_bb_coords_from_obj(obj, instance=ob)
break
elif ob.type == 'EMPTY':
vertex_pos = bbox_from_obj(ob, ob.empty_display_size)
elif ob.type == 'LIGHT':
vertex_pos = bbox_from_obj(ob, ob.data.shadow_soft_size)
elif ob.type == 'LIGHT_PROBE':
vertex_pos = bbox_from_obj(ob, ob.data.influence_distance)
elif ob.type == 'CAMERA':
vertex_pos = bbox_from_obj(ob, ob.data.display_size)
elif hasattr(ob, 'bound_box'):
vertex_indices = (
(0, 1), (1, 2), (2, 3), (0, 3),
(4, 5), (5, 6), (6, 7), (4, 7),
(0, 4), (1, 5), (2, 6), (3, 7))
vertex_pos = get_bb_coords_from_obj(ob)
if obj.instance_collection:
vertex_pos, vertex_indices = bbox_from_instance_collection(obj)
else :
vertex_pos, vertex_indices = bbox_from_obj(obj)
shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
batch = batch_for_shader(
@ -338,7 +364,6 @@ class UserSelectionWidget(Widget):
shader.uniform_float("color", self.data.get('color'))
batch.draw(shader)
class UserNameWidget(Widget):
draw_type = 'POST_PIXEL'

View File

@ -129,6 +129,11 @@ class ApplyTimer(Timer):
porcelain.apply(session.repository,
parent.uuid,
force=True)
if hasattr(impl, 'bl_reload_child') and impl.bl_reload_child:
for dep in node_ref.dependencies:
porcelain.apply(session.repository,
dep,
force=True)
class DynamicRightSelectTimer(Timer):

View File

@ -555,20 +555,15 @@ class SESSION_PT_repository(bpy.types.Panel):
# Properties
owned_nodes = [k for k, v in session.repository.graph.items() if v.owner==settings.username]
filtered_node = owned_nodes if runtime_settings.filter_owned else session.repository.graph.keys()
filtered_node = owned_nodes if runtime_settings.filter_owned else list(session.repository.graph.keys())
if runtime_settings.filter_name:
for node_id in filtered_node:
node_instance = session.repository.graph.get(node_id)
name = node_instance.data.get('name')
if runtime_settings.filter_name not in name:
filtered_node.remove(node_id)
filtered_node = [n for n in filtered_node if runtime_settings.filter_name.lower() in session.repository.graph.get(n).data.get('name').lower()]
if filtered_node:
col = layout.column(align=True)
for key in filtered_node:
draw_property(context, col, key)
else:
layout.row().label(text="Empty")

View File

@ -38,6 +38,14 @@ from replication.constants import (STATE_ACTIVE, STATE_AUTH,
STATE_LOBBY,
CONNECTING)
CLEARED_DATABLOCKS = ['actions', 'armatures', 'cache_files', 'cameras',
'collections', 'curves', 'filepath', 'fonts',
'grease_pencils', 'images', 'lattices', 'libraries',
'lightprobes', 'lights', 'linestyles', 'masks',
'materials', 'meshes', 'metaballs', 'movieclips',
'node_groups', 'objects', 'paint_curves', 'particles',
'scenes', 'shape_keys', 'sounds', 'speakers', 'texts',
'textures', 'volumes', 'worlds']
def find_from_attr(attr_name, attr_value, list):
for item in list:
@ -101,23 +109,25 @@ def get_state_str(state):
def clean_scene():
to_delete = [f for f in dir(bpy.data) if f not in ['brushes', 'palettes']]
for type_name in to_delete:
try:
sub_collection_to_avoid = [bpy.data.linestyles['LineStyle'], bpy.data.materials['Dots Stroke']]
type_collection = getattr(bpy.data, type_name)
items_to_remove = [i for i in type_collection if i not in sub_collection_to_avoid]
for item in items_to_remove:
try:
type_collection.remove(item)
except:
continue
except:
continue
for type_name in CLEARED_DATABLOCKS:
sub_collection_to_avoid = [
bpy.data.linestyles.get('LineStyle'),
bpy.data.materials.get('Dots Stroke')
]
type_collection = getattr(bpy.data, type_name)
items_to_remove = [i for i in type_collection if i not in sub_collection_to_avoid]
for item in items_to_remove:
try:
type_collection.remove(item)
logging.info(item.name)
except:
continue
# Clear sequencer
bpy.context.scene.sequence_editor_clear()
def get_selected_objects(scene, active_view_layer):
return [obj.uuid for obj in scene.objects if obj.select_get(view_layer=active_view_layer)]