Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ad93cf304 | |||
| 771d76a98b | |||
| 1e83241494 | |||
| 1bcbff3ed4 | |||
| 9a45fe7125 | |||
| 207901afdd | |||
| c6eb1ba22f | |||
| ba4168d0fd | |||
| 00e7adf022 | |||
| d9d8ca7ca0 | |||
| e8cd271bd8 | |||
| e71af6402c | |||
| dd1c6a4fc7 | |||
| 7fe1ae83b1 | |||
| a7ad9d30c3 | |||
| 14779be1ed | |||
| a36c3740cc | |||
| d2108facab | |||
| e5651151d9 | |||
| fb61b380b6 | |||
| 3a02711baa | |||
| c7e8002fed | |||
| f4e7ec6be8 | |||
| 235db712fd | |||
| 647ac46c01 | |||
| 8e3c86561f | |||
| dba19e831d | |||
| 93df5ca5fa | |||
| b17104c67e | |||
| 875b9ce934 | |||
| 2d638ef76f |
@@ -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.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
bl_info = {
|
||||
"name": "Multi-User",
|
||||
"author": "Swann Martinez",
|
||||
"version": (0, 3, 0),
|
||||
"version": (0, 4, 0),
|
||||
"description": "Enable real-time collaborative workflow inside blender",
|
||||
"blender": (2, 82, 0),
|
||||
"location": "3D View > Sidebar > Multi-User tab",
|
||||
@@ -44,7 +44,7 @@ from . import environment
|
||||
|
||||
|
||||
DEPENDENCIES = {
|
||||
("replication", '0.1.26'),
|
||||
("replication", '0.1.33'),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ if bpy.app.version[1] >= 91:
|
||||
__all__.append('bl_volume')
|
||||
|
||||
from . import *
|
||||
from replication.data import ReplicatedDataFactory
|
||||
from replication.data import DataTranslationProtocol
|
||||
|
||||
def types_to_register():
|
||||
return __all__
|
||||
|
||||
@@ -25,7 +25,7 @@ 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
|
||||
from .bl_datablock import BlDatablock, has_action, has_driver, dump_driver, load_driver
|
||||
|
||||
|
||||
KEYFRAME = [
|
||||
@@ -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,64 @@ 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)
|
||||
|
||||
|
||||
def dump_animation_data(datablock):
|
||||
animation_data = {}
|
||||
if has_action(datablock):
|
||||
animation_data['action'] = datablock.animation_data.action.name
|
||||
if has_driver(datablock):
|
||||
animation_data['drivers'] = []
|
||||
for driver in datablock.animation_data.drivers:
|
||||
animation_data['drivers'].append(dump_driver(driver))
|
||||
|
||||
return animation_data
|
||||
|
||||
|
||||
def load_animation_data(animation_data, datablock):
|
||||
# Load animation data
|
||||
if animation_data:
|
||||
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 animation_data:
|
||||
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']]
|
||||
elif datablock.animation_data.action:
|
||||
datablock.animation_data.action = None
|
||||
|
||||
# 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(BlDatablock):
|
||||
bl_id = "actions"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -134,6 +134,8 @@ class BlFile(ReplicatedDatablock):
|
||||
if self.preferences.clear_memory_filecache:
|
||||
return False
|
||||
else:
|
||||
if not self.instance:
|
||||
return False
|
||||
memory_size = sys.getsizeof(self.data['file'])-33
|
||||
disk_size = self.instance.stat().st_size
|
||||
return memory_size != disk_size
|
||||
|
||||
@@ -68,7 +68,10 @@ class BlImage(BlDatablock):
|
||||
|
||||
target.source = 'FILE'
|
||||
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',
|
||||
|
||||
@@ -24,6 +24,7 @@ from replication.exception import ContextError
|
||||
|
||||
from .bl_datablock import BlDatablock, get_datablock_from_uuid
|
||||
from .bl_material import IGNORED_SOCKETS
|
||||
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
|
||||
from .dump_anything import (
|
||||
Dumper,
|
||||
Loader,
|
||||
@@ -37,6 +38,12 @@ SKIN_DATA = [
|
||||
'use_root'
|
||||
]
|
||||
|
||||
SHAPEKEY_BLOCK_ATTR = [
|
||||
'mute',
|
||||
'value',
|
||||
'slider_min',
|
||||
'slider_max',
|
||||
]
|
||||
if bpy.app.version[1] >= 93:
|
||||
SUPPORTED_GEOMETRY_NODE_PARAMETERS = (int, str, float)
|
||||
else:
|
||||
@@ -288,7 +295,147 @@ 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')
|
||||
|
||||
def dump_shape_keys(target_key: bpy.types.Key)->dict:
|
||||
""" Dump the target shape_keys datablock to a dict using numpy
|
||||
|
||||
:param dumped_key: target key datablock
|
||||
:type dumped_key: bpy.types.Key
|
||||
:return: dict
|
||||
"""
|
||||
|
||||
dumped_key_blocks = []
|
||||
dumper = Dumper()
|
||||
dumper.include_filter = [
|
||||
'name',
|
||||
'mute',
|
||||
'value',
|
||||
'slider_min',
|
||||
'slider_max',
|
||||
]
|
||||
for key in target_key.key_blocks:
|
||||
dumped_key_block = dumper.dump(key)
|
||||
dumped_key_block['data'] = np_dump_collection(key.data, ['co'])
|
||||
dumped_key_block['relative_key'] = key.relative_key.name
|
||||
dumped_key_blocks.append(dumped_key_block)
|
||||
|
||||
return {
|
||||
'reference_key': target_key.reference_key.name,
|
||||
'use_relative': target_key.use_relative,
|
||||
'key_blocks': dumped_key_blocks,
|
||||
'animation_data': dump_animation_data(target_key)
|
||||
}
|
||||
|
||||
|
||||
def load_shape_keys(dumped_shape_keys: dict, target_object: bpy.types.Object):
|
||||
""" Load the target shape_keys datablock to a dict using numpy
|
||||
|
||||
:param dumped_key: src key data
|
||||
:type dumped_key: bpy.types.Key
|
||||
:param target_object: object used to load the shapekeys data onto
|
||||
:type target_object: bpy.types.Object
|
||||
"""
|
||||
loader = Loader()
|
||||
# Remove existing ones
|
||||
target_object.shape_key_clear()
|
||||
|
||||
# Create keys and load vertices coords
|
||||
dumped_key_blocks = dumped_shape_keys.get('key_blocks')
|
||||
for dumped_key_block in dumped_key_blocks:
|
||||
key_block = target_object.shape_key_add(name=dumped_key_block['name'])
|
||||
|
||||
loader.load(key_block, dumped_key_block)
|
||||
np_load_collection(dumped_key_block['data'], key_block.data, ['co'])
|
||||
|
||||
# Load relative key after all
|
||||
for dumped_key_block in dumped_key_blocks:
|
||||
relative_key_name = dumped_key_block.get('relative_key')
|
||||
key_name = dumped_key_block.get('name')
|
||||
|
||||
target_keyblock = target_object.data.shape_keys.key_blocks[key_name]
|
||||
relative_key = target_object.data.shape_keys.key_blocks[relative_key_name]
|
||||
|
||||
target_keyblock.relative_key = relative_key
|
||||
|
||||
# Shape keys animation data
|
||||
anim_data = dumped_shape_keys.get('animation_data')
|
||||
|
||||
if anim_data:
|
||||
load_animation_data(anim_data, target_object.data.shape_keys)
|
||||
|
||||
|
||||
def dump_modifiers(modifiers: bpy.types.bpy_prop_collection)->dict:
|
||||
""" Dump all modifiers of a modifier collection into a dict
|
||||
|
||||
:param modifiers: modifiers
|
||||
:type modifiers: bpy.types.bpy_prop_collection
|
||||
:return: dict
|
||||
"""
|
||||
dumped_modifiers = {}
|
||||
dumper = Dumper()
|
||||
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
|
||||
if modifier.type == 'NODES':
|
||||
dumped_inputs = dump_modifier_geometry_node_inputs(
|
||||
modifier)
|
||||
dumped_modifier['inputs'] = dumped_inputs
|
||||
|
||||
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)
|
||||
elif modifier.type == 'UV_PROJECT':
|
||||
dumped_modifier['projectors'] =[p.object.name for p in modifier.projectors if p and p.object]
|
||||
|
||||
dumped_modifiers[modifier.name] = dumped_modifier
|
||||
return dumped_modifiers
|
||||
|
||||
|
||||
def load_modifiers_custom_data(dumped_modifiers: dict, modifiers: bpy.types.bpy_prop_collection):
|
||||
""" Load modifiers custom data not managed by the dump_anything loader
|
||||
|
||||
:param dumped_modifiers: modifiers to load
|
||||
:type dumped_modifiers: dict
|
||||
:param modifiers: target modifiers collection
|
||||
:type modifiers: bpy.types.bpy_prop_collection
|
||||
"""
|
||||
loader = Loader()
|
||||
|
||||
for modifier in modifiers:
|
||||
dumped_modifier = dumped_modifiers.get(modifier.name)
|
||||
if modifier.type == 'NODES':
|
||||
load_modifier_geometry_node_inputs(dumped_modifier, modifier)
|
||||
elif modifier.type == 'PARTICLE_SYSTEM':
|
||||
default = modifier.particle_system.settings
|
||||
dumped_particles = dumped_modifier['particle_system']
|
||||
loader.load(modifier.particle_system, dumped_particles)
|
||||
|
||||
settings = get_datablock_from_uuid(dumped_particles['settings_uuid'], None)
|
||||
if settings:
|
||||
modifier.particle_system.settings = settings
|
||||
# Hack to remove the default generated particle settings
|
||||
if not default.uuid:
|
||||
bpy.data.particles.remove(default)
|
||||
elif modifier.type in ['SOFT_BODY', 'CLOTH']:
|
||||
loader.load(modifier.settings, dumped_modifier['settings'])
|
||||
elif modifier.type == 'UV_PROJECT':
|
||||
for projector_index, projector_object in enumerate(dumped_modifier['projectors']):
|
||||
target_object = bpy.data.objects.get(projector_object)
|
||||
if target_object:
|
||||
modifier.projectors[projector_index].object = target_object
|
||||
else:
|
||||
logging.error("Could't load projector target object {projector_object}")
|
||||
|
||||
class BlObject(BlDatablock):
|
||||
bl_id = "objects"
|
||||
bl_class = bpy.types.Object
|
||||
@@ -312,13 +459,14 @@ class BlObject(BlDatablock):
|
||||
object_name = data.get("name")
|
||||
data_uuid = data.get("data_uuid")
|
||||
data_id = data.get("data")
|
||||
data_type = data.get("type")
|
||||
|
||||
object_data = get_datablock_from_uuid(
|
||||
data_uuid,
|
||||
find_data_from_name(data_id),
|
||||
ignore=['images']) # TODO: use resolve_from_id
|
||||
|
||||
if object_data is None and data_uuid:
|
||||
if data_type != 'EMPTY' and object_data is None:
|
||||
raise Exception(f"Fail to load object {data['name']}({self.uuid})")
|
||||
|
||||
instance = bpy.data.objects.new(object_name, object_data)
|
||||
@@ -344,24 +492,9 @@ class BlObject(BlDatablock):
|
||||
object_data = target.data
|
||||
|
||||
# SHAPE KEYS
|
||||
if 'shape_keys' in data:
|
||||
target.shape_key_clear()
|
||||
|
||||
# Create keys and load vertices coords
|
||||
for key_block in data['shape_keys']['key_blocks']:
|
||||
key_data = data['shape_keys']['key_blocks'][key_block]
|
||||
target.shape_key_add(name=key_block)
|
||||
|
||||
loader.load(
|
||||
target.data.shape_keys.key_blocks[key_block], key_data)
|
||||
for vert in key_data['data']:
|
||||
target.data.shape_keys.key_blocks[key_block].data[vert].co = key_data['data'][vert]['co']
|
||||
|
||||
# Load relative key after all
|
||||
for key_block in data['shape_keys']['key_blocks']:
|
||||
reference = data['shape_keys']['key_blocks'][key_block]['relative_key']
|
||||
|
||||
target.data.shape_keys.key_blocks[key_block].relative_key = target.data.shape_keys.key_blocks[reference]
|
||||
shape_keys = data.get('shape_keys')
|
||||
if shape_keys:
|
||||
load_shape_keys(shape_keys, target)
|
||||
|
||||
# Load transformation data
|
||||
loader.load(target, data)
|
||||
@@ -427,34 +560,8 @@ class BlObject(BlDatablock):
|
||||
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']
|
||||
for modifier in nodes_modifiers:
|
||||
load_modifier_geometry_node_inputs(
|
||||
data['modifiers'][modifier.name], modifier)
|
||||
|
||||
particles_modifiers = [
|
||||
mod for mod in target.modifiers if mod.type == 'PARTICLE_SYSTEM']
|
||||
|
||||
for mod in particles_modifiers:
|
||||
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
|
||||
if not default.uuid:
|
||||
bpy.data.particles.remove(default)
|
||||
|
||||
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'])
|
||||
load_modifiers_custom_data(data['modifiers'], target.modifiers)
|
||||
|
||||
# PHYSICS
|
||||
load_physics(data, target)
|
||||
@@ -532,34 +639,9 @@ class BlObject(BlDatablock):
|
||||
data['parent_uid'] = (instance.parent.uuid, instance.parent.name)
|
||||
|
||||
# MODIFIERS
|
||||
modifiers = getattr(instance, 'modifiers', None)
|
||||
if hasattr(instance, 'modifiers'):
|
||||
data["modifiers"] = {}
|
||||
modifiers = getattr(instance, 'modifiers', None)
|
||||
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
|
||||
if modifier.type == 'NODES':
|
||||
dumped_inputs = dump_modifier_geometry_node_inputs(
|
||||
modifier)
|
||||
dumped_modifier['inputs'] = dumped_inputs
|
||||
|
||||
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
|
||||
data['modifiers'] = dump_modifiers(modifiers)
|
||||
|
||||
gp_modifiers = getattr(instance, 'grease_pencil_modifiers', None)
|
||||
|
||||
@@ -634,30 +716,7 @@ class BlObject(BlDatablock):
|
||||
# SHAPE KEYS
|
||||
object_data = instance.data
|
||||
if hasattr(object_data, 'shape_keys') and object_data.shape_keys:
|
||||
dumper = Dumper()
|
||||
dumper.depth = 2
|
||||
dumper.include_filter = [
|
||||
'reference_key',
|
||||
'use_relative'
|
||||
]
|
||||
data['shape_keys'] = dumper.dump(object_data.shape_keys)
|
||||
data['shape_keys']['reference_key'] = object_data.shape_keys.reference_key.name
|
||||
key_blocks = {}
|
||||
for key in object_data.shape_keys.key_blocks:
|
||||
dumper.depth = 3
|
||||
dumper.include_filter = [
|
||||
'name',
|
||||
'data',
|
||||
'mute',
|
||||
'value',
|
||||
'slider_min',
|
||||
'slider_max',
|
||||
'data',
|
||||
'co'
|
||||
]
|
||||
key_blocks[key.name] = dumper.dump(key)
|
||||
key_blocks[key.name]['relative_key'] = key.relative_key.name
|
||||
data['shape_keys']['key_blocks'] = key_blocks
|
||||
data['shape_keys'] = dump_shape_keys(object_data.shape_keys)
|
||||
|
||||
# SKIN VERTICES
|
||||
if hasattr(object_data, 'skin_vertices') and object_data.skin_vertices:
|
||||
@@ -709,4 +768,6 @@ class BlObject(BlDatablock):
|
||||
deps.extend(find_textures_dependencies(self.instance.modifiers))
|
||||
deps.extend(find_geometry_nodes_dependencies(self.instance.modifiers))
|
||||
|
||||
if hasattr(self.instance.data, 'shape_keys') and self.instance.data.shape_keys:
|
||||
deps.extend(resolve_animation_dependencies(self.instance.data.shape_keys))
|
||||
return deps
|
||||
|
||||
@@ -368,6 +368,8 @@ def load_sequence(sequence_data: dict, sequence_editor: bpy.types.SequenceEditor
|
||||
|
||||
|
||||
class BlScene(BlDatablock):
|
||||
is_root = True
|
||||
|
||||
bl_id = "scenes"
|
||||
bl_class = bpy.types.Scene
|
||||
bl_check_common = True
|
||||
|
||||
Submodule
+1
Submodule multi_user/libs/replication added at 001fbdc60d
+56
-407
@@ -32,7 +32,7 @@ from operator import itemgetter
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from time import gmtime, strftime
|
||||
from numpy import interp
|
||||
import traceback
|
||||
|
||||
try:
|
||||
import _pickle as pickle
|
||||
@@ -40,15 +40,16 @@ except ImportError:
|
||||
import pickle
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import mathutils
|
||||
from bpy.app.handlers import persistent
|
||||
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
||||
from replication.constants import (COMMITED, FETCHED, RP_COMMON, STATE_ACTIVE,
|
||||
STATE_INITIAL, STATE_SYNCING, UP)
|
||||
from replication.data import ReplicatedDataFactory
|
||||
from replication.exception import NonAuthorizedOperationError, ContextError
|
||||
from replication.data import DataTranslationProtocol
|
||||
from replication.exception import ContextError, NonAuthorizedOperationError
|
||||
from replication.interface import session
|
||||
from replication.porcelain import add, apply
|
||||
from replication.repository import Repository
|
||||
|
||||
from . import bl_types, environment, timers, ui, utils
|
||||
from .presence import SessionStatusWidget, renderer, view3d_find
|
||||
@@ -58,226 +59,6 @@ background_execution_queue = Queue()
|
||||
deleyables = []
|
||||
stop_modal_executor = False
|
||||
|
||||
CLEARED_DATABLOCKS = ['actions', 'armatures', 'cache_files', 'cameras',
|
||||
'collections', 'curves', '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']
|
||||
|
||||
PERSISTENT_DATABLOCKS = ['LineStyle', 'Dots Stroke', 'replay_action']
|
||||
|
||||
def clean_scene(ignored_datablocks: list = None):
|
||||
"""
|
||||
Delete all datablock of the scene except PERSISTENT_DATABLOCKS and ignored
|
||||
ones in ignored_datablocks.
|
||||
"""
|
||||
PERSISTENT_DATABLOCKS.extend(ignored_datablocks)
|
||||
# Avoid to trigger a runtime error by keeping the last scene
|
||||
PERSISTENT_DATABLOCKS.append(bpy.data.scenes[0].name)
|
||||
|
||||
for type_name in CLEARED_DATABLOCKS:
|
||||
type_collection = getattr(bpy.data, type_name)
|
||||
for datablock in type_collection:
|
||||
if datablock.name in PERSISTENT_DATABLOCKS:
|
||||
logging.debug(f"Skipping {datablock.name}")
|
||||
continue
|
||||
else:
|
||||
logging.debug(f"Removing {datablock.name}")
|
||||
type_collection.remove(datablock)
|
||||
|
||||
# Clear sequencer
|
||||
bpy.context.scene.sequence_editor_clear()
|
||||
|
||||
def get_bb_coords_from_obj(object: bpy.types.Object, instance: bpy.types.Object = None) -> list:
|
||||
""" Generate bounding box in world coordinate from object bound box
|
||||
|
||||
:param object: target object
|
||||
:type object: bpy.types.Object
|
||||
:param instance: optionnal instance
|
||||
:type instance: bpy.types.Object
|
||||
:return: list of 8 points [(x,y,z),...]
|
||||
"""
|
||||
base = object.matrix_world
|
||||
|
||||
if instance:
|
||||
scale = mathutils.Matrix.Diagonal(object.matrix_world.to_scale())
|
||||
base = instance.matrix_world @ scale.to_4x4()
|
||||
|
||||
bbox_corners = [base @ mathutils.Vector(
|
||||
corner) for corner in object.bound_box]
|
||||
|
||||
|
||||
return [(point.x, point.y, point.z) for point in bbox_corners]
|
||||
|
||||
|
||||
def bbox_from_obj(obj: bpy.types.Object, index: int = 1) -> list:
|
||||
""" Generate a bounding box for a given object by using its world matrix
|
||||
|
||||
:param obj: target object
|
||||
:type obj: bpy.types.Object
|
||||
:param index: indice offset
|
||||
:type index: int
|
||||
: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
|
||||
index = 8*index
|
||||
vertex_indices = (
|
||||
(0+index, 1+index), (0+index, 2+index), (1+index, 3+index), (2+index, 3+index),
|
||||
(4+index, 5+index), (4+index, 6+index), (5+index, 7+index), (6+index, 7+index),
|
||||
(0+index, 4+index), (1+index, 5+index), (2+index, 6+index), (3+index, 7+index))
|
||||
|
||||
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+index, 1+index), (1+index, 2+index),
|
||||
(2+index, 3+index), (0+index, 3+index),
|
||||
(4+index, 5+index), (5+index, 6+index),
|
||||
(6+index, 7+index), (4+index, 7+index),
|
||||
(0+index, 4+index), (1+index, 5+index),
|
||||
(2+index, 6+index), (3+index, 7+index))
|
||||
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),
|
||||
(-radius, -radius, +radius), (+radius, -radius, +radius),
|
||||
(-radius, +radius, +radius), (+radius, +radius, +radius)]
|
||||
|
||||
base = obj.matrix_world
|
||||
bbox_corners = [base @ mathutils.Vector(corner) for corner in coords]
|
||||
|
||||
vertex_pos = [(point.x, point.y, point.z) for point in bbox_corners]
|
||||
|
||||
return vertex_pos, vertex_indices
|
||||
|
||||
def draw_user(username, metadata, radius=0.01, intensity=10.0):
|
||||
"""
|
||||
Generate a mesh representation of a given user frustum and
|
||||
sight of view.
|
||||
"""
|
||||
view_corners = metadata.get('view_corners')
|
||||
color = metadata.get('color', (1,1,1,0))
|
||||
objects = metadata.get('selected_objects', None)
|
||||
scene = metadata.get('scene_current', bpy.context.scene.name)
|
||||
|
||||
user_collection = bpy.data.collections.new(username)
|
||||
|
||||
# User Color
|
||||
user_mat = bpy.data.materials.new(username)
|
||||
user_mat.use_nodes = True
|
||||
nodes = user_mat.node_tree.nodes
|
||||
nodes.remove(nodes['Principled BSDF'])
|
||||
emission_node = nodes.new('ShaderNodeEmission')
|
||||
emission_node.inputs['Color'].default_value = color
|
||||
emission_node.inputs['Strength'].default_value = intensity
|
||||
|
||||
output_node = nodes['Material Output']
|
||||
user_mat.node_tree.links.new(
|
||||
emission_node.outputs['Emission'], output_node.inputs['Surface'])
|
||||
|
||||
# Generate camera mesh
|
||||
camera_vertices = view_corners[:4]
|
||||
camera_vertices.append(view_corners[6])
|
||||
camera_mesh = bpy.data.meshes.new(f"{username}_camera")
|
||||
camera_obj = bpy.data.objects.new(f"{username}_camera", camera_mesh)
|
||||
frustum_bm = bmesh.new()
|
||||
frustum_bm.from_mesh(camera_mesh)
|
||||
|
||||
for p in camera_vertices:
|
||||
frustum_bm.verts.new(p)
|
||||
frustum_bm.verts.ensure_lookup_table()
|
||||
|
||||
frustum_bm.edges.new((frustum_bm.verts[0], frustum_bm.verts[2]))
|
||||
frustum_bm.edges.new((frustum_bm.verts[2], frustum_bm.verts[1]))
|
||||
frustum_bm.edges.new((frustum_bm.verts[1], frustum_bm.verts[3]))
|
||||
frustum_bm.edges.new((frustum_bm.verts[3], frustum_bm.verts[0]))
|
||||
|
||||
frustum_bm.edges.new((frustum_bm.verts[0], frustum_bm.verts[4]))
|
||||
frustum_bm.edges.new((frustum_bm.verts[2], frustum_bm.verts[4]))
|
||||
frustum_bm.edges.new((frustum_bm.verts[1], frustum_bm.verts[4]))
|
||||
frustum_bm.edges.new((frustum_bm.verts[3], frustum_bm.verts[4]))
|
||||
frustum_bm.edges.ensure_lookup_table()
|
||||
|
||||
frustum_bm.to_mesh(camera_mesh)
|
||||
frustum_bm.free() # free and prevent further access
|
||||
|
||||
camera_obj.modifiers.new("wireframe", "SKIN")
|
||||
camera_obj.data.skin_vertices[0].data[0].use_root = True
|
||||
for v in camera_mesh.skin_vertices[0].data:
|
||||
v.radius = [radius, radius]
|
||||
|
||||
camera_mesh.materials.append(user_mat)
|
||||
user_collection.objects.link(camera_obj)
|
||||
|
||||
# Generate sight mesh
|
||||
sight_mesh = bpy.data.meshes.new(f"{username}_sight")
|
||||
sight_obj = bpy.data.objects.new(f"{username}_sight", sight_mesh)
|
||||
sight_verts = view_corners[4:6]
|
||||
sight_bm = bmesh.new()
|
||||
sight_bm.from_mesh(sight_mesh)
|
||||
|
||||
for p in sight_verts:
|
||||
sight_bm.verts.new(p)
|
||||
sight_bm.verts.ensure_lookup_table()
|
||||
|
||||
sight_bm.edges.new((sight_bm.verts[0], sight_bm.verts[1]))
|
||||
sight_bm.edges.ensure_lookup_table()
|
||||
sight_bm.to_mesh(sight_mesh)
|
||||
sight_bm.free()
|
||||
|
||||
sight_obj.modifiers.new("wireframe", "SKIN")
|
||||
sight_obj.data.skin_vertices[0].data[0].use_root = True
|
||||
for v in sight_mesh.skin_vertices[0].data:
|
||||
v.radius = [radius, radius]
|
||||
|
||||
sight_mesh.materials.append(user_mat)
|
||||
user_collection.objects.link(sight_obj)
|
||||
|
||||
# Draw selected objects
|
||||
if objects:
|
||||
for o in list(objects):
|
||||
instance = bl_types.bl_datablock.get_datablock_from_uuid(o, None)
|
||||
if instance:
|
||||
bbox_mesh = bpy.data.meshes.new(f"{instance.name}_bbox")
|
||||
bbox_obj = bpy.data.objects.new(
|
||||
f"{instance.name}_bbox", bbox_mesh)
|
||||
bbox_verts, bbox_ind = bbox_from_obj(instance, index=0)
|
||||
bbox_bm = bmesh.new()
|
||||
bbox_bm.from_mesh(bbox_mesh)
|
||||
|
||||
for p in bbox_verts:
|
||||
bbox_bm.verts.new(p)
|
||||
bbox_bm.verts.ensure_lookup_table()
|
||||
|
||||
for e in bbox_ind:
|
||||
bbox_bm.edges.new(
|
||||
(bbox_bm.verts[e[0]], bbox_bm.verts[e[1]]))
|
||||
|
||||
bbox_bm.to_mesh(bbox_mesh)
|
||||
bbox_bm.free()
|
||||
bpy.data.collections[username].objects.link(bbox_obj)
|
||||
|
||||
bbox_obj.modifiers.new("wireframe", "SKIN")
|
||||
bbox_obj.data.skin_vertices[0].data[0].use_root = True
|
||||
for v in bbox_mesh.skin_vertices[0].data:
|
||||
v.radius = [radius, radius]
|
||||
|
||||
bbox_mesh.materials.append(user_mat)
|
||||
|
||||
bpy.data.scenes[scene].collection.children.link(user_collection)
|
||||
|
||||
|
||||
def session_callback(name):
|
||||
""" Session callback wrapper
|
||||
|
||||
@@ -302,8 +83,8 @@ def initialize_session():
|
||||
|
||||
# Step 1: Constrect nodes
|
||||
logging.info("Constructing nodes")
|
||||
for node in session._graph.list_ordered():
|
||||
node_ref = session.get(uuid=node)
|
||||
for node in session.repository.list_ordered():
|
||||
node_ref = session.repository.get_node(node)
|
||||
if node_ref is None:
|
||||
logging.error(f"Can't construct node {node}")
|
||||
elif node_ref.state == FETCHED:
|
||||
@@ -311,8 +92,8 @@ def initialize_session():
|
||||
|
||||
# Step 2: Load nodes
|
||||
logging.info("Loading nodes")
|
||||
for node in session._graph.list_ordered():
|
||||
node_ref = session.get(uuid=node)
|
||||
for node in session.repository.list_ordered():
|
||||
node_ref = session.repository.get_node(node)
|
||||
|
||||
if node_ref is None:
|
||||
logging.error(f"Can't load node {node}")
|
||||
@@ -408,7 +189,7 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
bpy_factory = ReplicatedDataFactory()
|
||||
bpy_protocol = DataTranslationProtocol()
|
||||
supported_bl_types = []
|
||||
|
||||
# init the factory with supported types
|
||||
@@ -427,7 +208,7 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
|
||||
type_local_config = settings.supported_datablocks[type_impl_name]
|
||||
|
||||
bpy_factory.register_type(
|
||||
bpy_protocol.register_type(
|
||||
type_module_class.bl_class,
|
||||
type_module_class,
|
||||
check_common=type_module_class.bl_check_common)
|
||||
@@ -437,10 +218,7 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
else:
|
||||
python_binary_path = bpy.app.binary_path_python
|
||||
|
||||
session.configure(
|
||||
factory=bpy_factory,
|
||||
python_path=python_binary_path,
|
||||
external_update_handling=True)
|
||||
repo = Repository(data_protocol=bpy_protocol)
|
||||
|
||||
# Host a session
|
||||
if self.host:
|
||||
@@ -451,13 +229,14 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
runtime_settings.internet_ip = environment.get_ip()
|
||||
|
||||
try:
|
||||
# Init repository
|
||||
for scene in bpy.data.scenes:
|
||||
session.add(scene)
|
||||
add(repo, scene)
|
||||
|
||||
session.host(
|
||||
repository= repo,
|
||||
id=settings.username,
|
||||
port=settings.port,
|
||||
ipc_port=settings.ipc_port,
|
||||
timeout=settings.connection_timeout,
|
||||
password=admin_pass,
|
||||
cache_directory=settings.cache_directory,
|
||||
@@ -467,7 +246,6 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
except Exception as e:
|
||||
self.report({'ERROR'}, repr(e))
|
||||
logging.error(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
# Join a session
|
||||
else:
|
||||
@@ -478,10 +256,10 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
|
||||
try:
|
||||
session.connect(
|
||||
repository= repo,
|
||||
id=settings.username,
|
||||
address=settings.ip,
|
||||
port=settings.port,
|
||||
ipc_port=settings.ipc_port,
|
||||
timeout=settings.connection_timeout,
|
||||
password=admin_pass
|
||||
)
|
||||
@@ -501,7 +279,9 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
session_user_sync = timers.SessionUserSync()
|
||||
session_background_executor = timers.MainThreadExecutor(
|
||||
execution_queue=background_execution_queue)
|
||||
session_listen = timers.SessionListenTimer(timeout=0.001)
|
||||
|
||||
session_listen.register()
|
||||
session_update.register()
|
||||
session_user_sync.register()
|
||||
session_background_executor.register()
|
||||
@@ -509,7 +289,7 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
deleyables.append(session_background_executor)
|
||||
deleyables.append(session_update)
|
||||
deleyables.append(session_user_sync)
|
||||
|
||||
deleyables.append(session_listen)
|
||||
|
||||
|
||||
self.report(
|
||||
@@ -550,7 +330,7 @@ class SessionInitOperator(bpy.types.Operator):
|
||||
utils.clean_scene()
|
||||
|
||||
for scene in bpy.data.scenes:
|
||||
session.add(scene)
|
||||
add(session.repository, scene)
|
||||
|
||||
session.init()
|
||||
|
||||
@@ -572,7 +352,7 @@ class SessionStopOperator(bpy.types.Operator):
|
||||
|
||||
if session:
|
||||
try:
|
||||
session.disconnect()
|
||||
session.disconnect(reason='user')
|
||||
|
||||
except Exception as e:
|
||||
self.report({'ERROR'}, repr(e))
|
||||
@@ -821,17 +601,22 @@ class SessionApply(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
logging.debug(f"Running apply on {self.target}")
|
||||
try:
|
||||
node_ref = session.get(uuid=self.target)
|
||||
session.apply(self.target,
|
||||
force=True,
|
||||
force_dependencies=self.reset_dependencies)
|
||||
node_ref = session.repository.get_node(self.target)
|
||||
apply(session.repository,
|
||||
self.target,
|
||||
force=True,
|
||||
force_dependencies=self.reset_dependencies)
|
||||
if node_ref.bl_reload_parent:
|
||||
for parent in session._graph.find_parents(self.target):
|
||||
for parent in session.repository.get_parents(self.target):
|
||||
logging.debug(f"Refresh parent {parent}")
|
||||
session.apply(parent, force=True)
|
||||
|
||||
apply(session.repository,
|
||||
parent.uuid,
|
||||
force=True)
|
||||
except Exception as e:
|
||||
self.report({'ERROR'}, repr(e))
|
||||
return {"CANCELED"}
|
||||
traceback.print_exc()
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -871,15 +656,15 @@ class ApplyArmatureOperator(bpy.types.Operator):
|
||||
return {'CANCELLED'}
|
||||
|
||||
if event.type == 'TIMER':
|
||||
if session and session.state['STATE'] == STATE_ACTIVE:
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
nodes = session.list(filter=bl_types.bl_armature.BlArmature)
|
||||
|
||||
for node in nodes:
|
||||
node_ref = session.get(uuid=node)
|
||||
node_ref = session.repository.get_node(node)
|
||||
|
||||
if node_ref.state == FETCHED:
|
||||
try:
|
||||
session.apply(node)
|
||||
apply(session.repository, node)
|
||||
except Exception as e:
|
||||
logging.error("Fail to apply armature: {e}")
|
||||
|
||||
@@ -1016,7 +801,7 @@ class SessionSaveBackupOperator(bpy.types.Operator, ExportHelper):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return session.state['STATE'] == STATE_ACTIVE
|
||||
return session.state == STATE_ACTIVE
|
||||
|
||||
class SessionStopAutoSaveOperator(bpy.types.Operator):
|
||||
bl_idname = "session.cancel_autosave"
|
||||
@@ -1025,7 +810,7 @@ class SessionStopAutoSaveOperator(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (session.state['STATE'] == STATE_ACTIVE and 'SessionBackupTimer' in registry)
|
||||
return (session.state == STATE_ACTIVE and 'SessionBackupTimer' in registry)
|
||||
|
||||
def execute(self, context):
|
||||
autosave_timer = registry.get('SessionBackupTimer')
|
||||
@@ -1033,26 +818,6 @@ class SessionStopAutoSaveOperator(bpy.types.Operator):
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class SessionRenderReplay(bpy.types.Operator):
|
||||
bl_idname = "session.render_replay"
|
||||
bl_label = "Render Replay"
|
||||
bl_description = "Render Replay"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.window_manager.session.replay_files
|
||||
|
||||
def execute(self, context):
|
||||
base_path = str(context.scene.render.filepath)
|
||||
for frame in range(0,context.scene.frame_end):
|
||||
logging.info(f"Rendering frame {frame} to {base_path}_{frame}.png")
|
||||
context.scene.frame_current = frame
|
||||
filename = Path(bpy.context.window_manager.session.replay_files[context.scene.active_replay_file].name)
|
||||
context.scene.render.filepath = f"{base_path}{frame}_{filename.stem}"
|
||||
bpy.ops.render.render(write_still=True)
|
||||
|
||||
context.scene.render.filepath = base_path
|
||||
return {'FINISHED'}
|
||||
|
||||
class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "session.load"
|
||||
@@ -1069,41 +834,8 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||
maxlen=255, # Max internal buffer length, longer would be clamped.
|
||||
)
|
||||
|
||||
draw_users: bpy.props.BoolProperty(
|
||||
name="Load users",
|
||||
description="Draw users in the scene",
|
||||
default=False,
|
||||
)
|
||||
|
||||
replay: bpy.props.BoolProperty(
|
||||
name="Replay mode",
|
||||
description="Enable replay functions",
|
||||
default=False,
|
||||
)
|
||||
|
||||
user_skin_radius: bpy.props.FloatProperty(
|
||||
name="Wireframe radius",
|
||||
description="Wireframe radius",
|
||||
default=0.005,
|
||||
)
|
||||
user_color_intensity: bpy.props.FloatProperty(
|
||||
name="Shading intensity",
|
||||
description="Shading intensity",
|
||||
default=1.0,
|
||||
)
|
||||
|
||||
files: bpy.props.CollectionProperty(
|
||||
name='File paths',
|
||||
type=bpy.types.OperatorFileListElement
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
def execute(self, context):
|
||||
from replication.graph import ReplicationGraph
|
||||
|
||||
runtime_settings = context.window_manager.session
|
||||
from replication.repository import Repository
|
||||
|
||||
# TODO: add filechecks
|
||||
|
||||
@@ -1123,7 +855,7 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
# init the factory with supported types
|
||||
bpy_factory = ReplicatedDataFactory()
|
||||
bpy_protocol = DataTranslationProtocol()
|
||||
for type in bl_types.types_to_register():
|
||||
type_module = getattr(bl_types, type)
|
||||
name = [e.capitalize() for e in type.split('_')[1:]]
|
||||
@@ -1131,16 +863,16 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||
type_module_class = getattr(type_module, type_impl_name)
|
||||
|
||||
|
||||
bpy_factory.register_type(
|
||||
bpy_protocol.register_type(
|
||||
type_module_class.bl_class,
|
||||
type_module_class)
|
||||
|
||||
graph = ReplicationGraph()
|
||||
graph = Repository()
|
||||
|
||||
for node, node_data in nodes:
|
||||
node_type = node_data.get('str_type')
|
||||
|
||||
impl = bpy_factory.get_implementation_from_net(node_type)
|
||||
impl = bpy_protocol.get_implementation_from_net(node_type)
|
||||
|
||||
if impl:
|
||||
logging.info(f"Loading {node}")
|
||||
@@ -1148,21 +880,12 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||
uuid=node,
|
||||
dependencies=node_data['dependencies'],
|
||||
data=node_data['data'])
|
||||
instance.store(graph)
|
||||
graph.do_commit(instance)
|
||||
instance.state = FETCHED
|
||||
|
||||
logging.info("Graph succefully loaded")
|
||||
|
||||
# Persitstent collection
|
||||
ignored_datablocks = []
|
||||
|
||||
persistent_collection = bpy.data.collections.get("multiuser_timelapse")
|
||||
if self.replay and \
|
||||
runtime_settings.replay_persistent_collection and \
|
||||
persistent_collection:
|
||||
ignored_datablocks = ['multiuser_timelapse','multiuser_timelapse_cam','multiuser_timelapse_cam_obj','multiuser_timelapse_path','multiuser_timelapse_path_obj', 'multiuser_timelapse_pathAction']
|
||||
|
||||
clean_scene(ignored_datablocks=ignored_datablocks)
|
||||
utils.clean_scene()
|
||||
|
||||
# Step 1: Construct nodes
|
||||
for node in graph.list_ordered():
|
||||
@@ -1172,69 +895,6 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||
for node in graph.list_ordered():
|
||||
graph[node].apply()
|
||||
|
||||
if len(self.files) > 1:
|
||||
runtime_settings.replay_files.clear()
|
||||
context.scene.active_replay_file = len(self.files)-1
|
||||
directory = Path(self.filepath).parent
|
||||
file_list = [f['name'] for f in self.files]
|
||||
file_list.sort()
|
||||
for f in file_list:
|
||||
snap = runtime_settings.replay_files.add()
|
||||
snap.name = str(Path(directory, f))
|
||||
print(f)
|
||||
|
||||
if runtime_settings.replay_mode == 'TIMELINE':
|
||||
replay_action = bpy.data.actions.get('replay_action', bpy.data.actions.new('replay_action'))
|
||||
|
||||
bpy.context.scene.animation_data_create()
|
||||
bpy.context.scene.animation_data.action = replay_action
|
||||
if len(replay_action.fcurves) > 0 and replay_action.fcurves[0].data_path == 'active_replay_file':
|
||||
replay_fcurve = replay_action.fcurves[0]
|
||||
else:
|
||||
replay_fcurve = replay_action.fcurves.new('active_replay_file')
|
||||
|
||||
for p in reversed(replay_fcurve.keyframe_points):
|
||||
replay_fcurve.keyframe_points.remove(p, fast=True)
|
||||
|
||||
duration = runtime_settings.replay_duration
|
||||
file_count = len(self.files)-1
|
||||
for index in range(0, file_count):
|
||||
frame = interp(index, [0, file_count], [bpy.context.scene.frame_start, duration])
|
||||
replay_fcurve.keyframe_points.insert(frame, index)
|
||||
|
||||
if self.draw_users:
|
||||
f = gzip.open(self.filepath, "rb")
|
||||
db = pickle.load(f)
|
||||
|
||||
users = db.get("users")
|
||||
|
||||
for username, user_data in users.items():
|
||||
metadata = user_data['metadata']
|
||||
|
||||
if metadata:
|
||||
draw_user(username, metadata, radius=self.user_skin_radius, intensity=self.user_color_intensity)
|
||||
|
||||
|
||||
# Relink the persistent collection
|
||||
if self.replay and persistent_collection:
|
||||
logging.info(f"Relinking {persistent_collection.name}")
|
||||
bpy.context.scene.collection.children.link(persistent_collection)
|
||||
|
||||
# Reasign scene action
|
||||
if self.replay and \
|
||||
runtime_settings.replay_mode == 'TIMELINE' and \
|
||||
not bpy.context.scene.animation_data :
|
||||
bpy.context.scene.animation_data_create()
|
||||
bpy.context.scene.animation_data.action = bpy.data.actions.get('replay_action')
|
||||
bpy.context.scene.frame_end = runtime_settings.replay_duration
|
||||
|
||||
# Reasign the scene camera
|
||||
if self.replay and \
|
||||
runtime_settings.replay_persistent_collection and \
|
||||
runtime_settings.replay_camera:
|
||||
bpy.context.scene.camera = runtime_settings.replay_camera
|
||||
|
||||
bpy.context.scene.eevee.use_bloom = False
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
@@ -1264,13 +924,12 @@ classes = (
|
||||
SessionLoadSaveOperator,
|
||||
SessionStopAutoSaveOperator,
|
||||
SessionPurgeOperator,
|
||||
SessionRenderReplay
|
||||
)
|
||||
|
||||
def update_external_dependencies():
|
||||
nodes_ids = session.list(filter=bl_types.bl_file.BlFile)
|
||||
for node_id in nodes_ids:
|
||||
node = session.get(node_id)
|
||||
node = session.repository.get_node(node_id)
|
||||
if node and node.owner in [session.id, RP_COMMON] \
|
||||
and node.has_changed():
|
||||
session.commit(node_id)
|
||||
@@ -1279,11 +938,11 @@ def update_external_dependencies():
|
||||
def sanitize_deps_graph(remove_nodes: bool = False):
|
||||
""" Cleanup the replication graph
|
||||
"""
|
||||
if session and session.state['STATE'] == STATE_ACTIVE:
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
start = utils.current_milli_time()
|
||||
rm_cpt = 0
|
||||
for node_key in session.list():
|
||||
node = session.get(node_key)
|
||||
node = session.repository.get_node(node_key)
|
||||
if node is None \
|
||||
or (node.state == UP and not node.resolve(construct=False)):
|
||||
if remove_nodes:
|
||||
@@ -1304,28 +963,18 @@ def resolve_deps_graph(dummy):
|
||||
A future solution should be to avoid storing dataclock reference...
|
||||
|
||||
"""
|
||||
if session and session.state['STATE'] == STATE_ACTIVE:
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
sanitize_deps_graph(remove_nodes=True)
|
||||
|
||||
@persistent
|
||||
def load_pre_handler(dummy):
|
||||
if session and session.state['STATE'] in [STATE_ACTIVE, STATE_SYNCING]:
|
||||
if session and session.state in [STATE_ACTIVE, STATE_SYNCING]:
|
||||
bpy.ops.session.stop()
|
||||
|
||||
|
||||
@persistent
|
||||
def update_client_frame(scene):
|
||||
setting = bpy.context.window_manager.session
|
||||
if setting.replay_mode == 'TIMELINE' and \
|
||||
setting.replay_files and \
|
||||
scene.active_replay_file != setting.replay_frame_current :
|
||||
index = bpy.context.scene.active_replay_file
|
||||
bpy.ops.session.load(filepath=bpy.context.window_manager.session.replay_files[index].name,
|
||||
draw_users=True,
|
||||
replay=True)
|
||||
setting.replay_frame_current = index
|
||||
|
||||
if session and session.state['STATE'] == STATE_ACTIVE:
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
session.update_user_metadata({
|
||||
'frame_current': scene.frame_current
|
||||
})
|
||||
@@ -1333,7 +982,7 @@ def update_client_frame(scene):
|
||||
|
||||
@persistent
|
||||
def depsgraph_evaluation(scene):
|
||||
if session and session.state['STATE'] == STATE_ACTIVE:
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
context = bpy.context
|
||||
blender_depsgraph = bpy.context.view_layer.depsgraph
|
||||
dependency_updates = [u for u in blender_depsgraph.updates]
|
||||
@@ -1346,7 +995,7 @@ def depsgraph_evaluation(scene):
|
||||
# Is the object tracked ?
|
||||
if update.id.uuid:
|
||||
# Retrieve local version
|
||||
node = session.get(uuid=update.id.uuid)
|
||||
node = session.repository.get_node(update.id.uuid)
|
||||
|
||||
# Check our right on this update:
|
||||
# - if its ours or ( under common and diff), launch the
|
||||
@@ -1370,11 +1019,11 @@ def depsgraph_evaluation(scene):
|
||||
continue
|
||||
# A new scene is created
|
||||
elif isinstance(update.id, bpy.types.Scene):
|
||||
ref = session.get(reference=update.id)
|
||||
ref = session.repository.get_node_by_datablock(update.id)
|
||||
if ref:
|
||||
ref.resolve()
|
||||
else:
|
||||
scn_uuid = session.add(update.id)
|
||||
scn_uuid = add(session.repository, update.id)
|
||||
session.commit(scn_uuid)
|
||||
session.push(scn_uuid, check_data=False)
|
||||
def register():
|
||||
@@ -1392,7 +1041,7 @@ def register():
|
||||
|
||||
|
||||
def unregister():
|
||||
if session and session.state['STATE'] == STATE_ACTIVE:
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
session.disconnect()
|
||||
|
||||
from bpy.utils import unregister_class
|
||||
|
||||
+1
-139
@@ -28,7 +28,7 @@ from . import bl_types, environment, addon_updater_ops, presence, ui
|
||||
from .utils import get_preferences, get_expanded_icon
|
||||
from replication.constants import RP_COMMON
|
||||
from replication.interface import session
|
||||
from numpy import interp
|
||||
|
||||
# From https://stackoverflow.com/a/106223
|
||||
IP_REGEX = re.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")
|
||||
HOSTNAME_REGEX = re.compile("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$")
|
||||
@@ -66,14 +66,6 @@ def update_ip(self, context):
|
||||
self['ip'] = "127.0.0.1"
|
||||
|
||||
|
||||
def update_port(self, context):
|
||||
max_port = self.port + 3
|
||||
|
||||
if self.ipc_port < max_port and \
|
||||
self['ipc_port'] >= self.port:
|
||||
logging.error(
|
||||
"IPC Port in conflict with the port, assigning a random value")
|
||||
self['ipc_port'] = random.randrange(self.port+4, 10000)
|
||||
|
||||
|
||||
def update_directory(self, context):
|
||||
@@ -93,88 +85,6 @@ def set_log_level(self, value):
|
||||
def get_log_level(self):
|
||||
return logging.getLogger().level
|
||||
|
||||
def set_active_replay(self, value):
|
||||
files_count = len(bpy.context.window_manager.session.replay_files)
|
||||
|
||||
if files_count == 0:
|
||||
return
|
||||
|
||||
max_index = files_count-1
|
||||
|
||||
if value > max_index:
|
||||
value = max_index
|
||||
|
||||
if hasattr(self, 'active_replay_file'):
|
||||
self["active_replay_file"] = value
|
||||
else:
|
||||
self.active_replay_file = value
|
||||
|
||||
if bpy.context.window_manager.session.replay_mode == 'MANUAL':
|
||||
bpy.ops.session.load(
|
||||
filepath=bpy.context.window_manager.session.replay_files[value].name,
|
||||
draw_users=True,
|
||||
replay=True)
|
||||
|
||||
def get_active_replay(self):
|
||||
return self.get('active_replay_file', 0)
|
||||
|
||||
|
||||
def set_replay_persistent_collection(self, value):
|
||||
if hasattr(self, 'replay_persistent_collection'):
|
||||
self["replay_persistent_collection"] = value
|
||||
else:
|
||||
self.replay_persistent_collection = value
|
||||
|
||||
collection = bpy.data.collections.get("multiuser_timelapse", None)
|
||||
|
||||
if collection is None and value:
|
||||
collection = bpy.data.collections.new('multiuser_timelapse')
|
||||
cam = bpy.data.cameras.get('multiuser_timelapse_cam', bpy.data.cameras.new('multiuser_timelapse_cam'))
|
||||
cam_obj = bpy.data.objects.get('multiuser_timelapse_cam_obj', bpy.data.objects.new('multiuser_timelapse_cam_obj', cam))
|
||||
curve = bpy.data.curves.get('multiuser_timelapse_path', bpy.data.curves.new('multiuser_timelapse_path', 'CURVE'))
|
||||
curve_obj = bpy.data.objects.get('multiuser_timelapse_path_obj', bpy.data.objects.new('multiuser_timelapse_path_obj', curve))
|
||||
|
||||
if cam_obj.name not in collection.objects:
|
||||
collection.objects.link(cam_obj)
|
||||
if curve_obj.name not in collection.objects:
|
||||
collection.objects.link(curve_obj)
|
||||
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
elif collection and not value:
|
||||
for o in collection.objects:
|
||||
bpy.data.objects.remove(o)
|
||||
bpy.data.collections.remove(collection)
|
||||
|
||||
def get_replay_persistent_collection(self):
|
||||
return self.get('replay_persistent_collection', False)
|
||||
|
||||
def set_replay_duration(self, value):
|
||||
if hasattr(self, 'replay_duration'):
|
||||
self["replay_duration"] = value
|
||||
else:
|
||||
self.replay_duration = value
|
||||
|
||||
# Update the animation fcurve
|
||||
replay_action = bpy.data.actions.get('replay_action')
|
||||
replay_fcurve = None
|
||||
|
||||
for fcurve in replay_action.fcurves:
|
||||
if fcurve.data_path == 'active_replay_file':
|
||||
replay_fcurve = fcurve
|
||||
|
||||
if replay_fcurve:
|
||||
for p in reversed(replay_fcurve.keyframe_points):
|
||||
replay_fcurve.keyframe_points.remove(p, fast=True)
|
||||
|
||||
bpy.context.scene.frame_end = value
|
||||
files_count = len(bpy.context.window_manager.session.replay_files)-1
|
||||
for index in range(0, files_count):
|
||||
frame = interp(index,[0, files_count],[bpy.context.scene.frame_start, value])
|
||||
replay_fcurve.keyframe_points.insert(frame, index)
|
||||
|
||||
def get_replay_duration(self):
|
||||
return self.get('replay_duration', 10)
|
||||
|
||||
|
||||
class ReplicatedDatablock(bpy.types.PropertyGroup):
|
||||
type_name: bpy.props.StringProperty()
|
||||
@@ -256,12 +166,6 @@ class SessionPrefs(bpy.types.AddonPreferences):
|
||||
supported_datablocks: bpy.props.CollectionProperty(
|
||||
type=ReplicatedDatablock,
|
||||
)
|
||||
ipc_port: bpy.props.IntProperty(
|
||||
name="ipc_port",
|
||||
description='internal ttl port(only useful for multiple local instances)',
|
||||
default=random.randrange(5570, 70000),
|
||||
update=update_port,
|
||||
)
|
||||
init_method: bpy.props.EnumProperty(
|
||||
name='init_method',
|
||||
description='Init repo',
|
||||
@@ -612,37 +516,6 @@ class SessionProps(bpy.types.PropertyGroup):
|
||||
is_host: bpy.props.BoolProperty(
|
||||
default=False
|
||||
)
|
||||
replay_files: bpy.props.CollectionProperty(
|
||||
name='File paths',
|
||||
type=bpy.types.OperatorFileListElement
|
||||
)
|
||||
replay_persistent_collection: bpy.props.BoolProperty(
|
||||
name="replay_persistent_collection",
|
||||
description='Enable a collection that persist accross frames loading',
|
||||
get=get_replay_persistent_collection,
|
||||
set=set_replay_persistent_collection,
|
||||
)
|
||||
replay_mode: bpy.props.EnumProperty(
|
||||
name='replay method',
|
||||
description='Replay in keyframe (timeline) or manually',
|
||||
items={
|
||||
('TIMELINE', 'TIMELINE', 'Replay from the timeline.'),
|
||||
('MANUAL', 'MANUAL', 'Replay manually, from the replay frame widget.')},
|
||||
default='TIMELINE')
|
||||
replay_duration: bpy.props.IntProperty(
|
||||
name='replay interval',
|
||||
default=250,
|
||||
min=10,
|
||||
set=set_replay_duration,
|
||||
get=get_replay_duration,
|
||||
)
|
||||
replay_frame_current: bpy.props.IntProperty(
|
||||
name='replay_frame_current',
|
||||
)
|
||||
replay_camera: bpy.props.PointerProperty(
|
||||
name='Replay camera',
|
||||
type=bpy.types.Object
|
||||
)
|
||||
|
||||
|
||||
classes = (
|
||||
@@ -665,20 +538,9 @@ def register():
|
||||
logging.debug('Generating bl_types preferences')
|
||||
prefs.generate_supported_types()
|
||||
|
||||
bpy.types.Scene.active_replay_file = bpy.props.IntProperty(
|
||||
name="active_replay_file",
|
||||
default=0,
|
||||
min=0,
|
||||
description='Active snapshot',
|
||||
set=set_active_replay,
|
||||
get=get_active_replay,
|
||||
options={'ANIMATABLE'}
|
||||
)
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
|
||||
for cls in reversed(classes):
|
||||
unregister_class(cls)
|
||||
|
||||
del bpy.types.Scene.active_replay_file
|
||||
|
||||
@@ -30,7 +30,7 @@ import mathutils
|
||||
from bpy_extras import view3d_utils
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from replication.constants import (STATE_ACTIVE, STATE_AUTH, STATE_CONFIG,
|
||||
STATE_INITIAL, STATE_LAUNCHING_SERVICES,
|
||||
STATE_INITIAL, CONNECTING,
|
||||
STATE_LOBBY, STATE_QUITTING, STATE_SRV_SYNC,
|
||||
STATE_SYNCING, STATE_WAITING)
|
||||
from replication.interface import session
|
||||
@@ -399,7 +399,7 @@ class SessionStatusWidget(Widget):
|
||||
text_scale = self.preferences.presence_hud_scale
|
||||
ui_scale = bpy.context.preferences.view.ui_scale
|
||||
color = [1, 1, 0, 1]
|
||||
state = session.state.get('STATE')
|
||||
state = session.state
|
||||
state_str = f"{get_state_str(state)}"
|
||||
|
||||
if state == STATE_ACTIVE:
|
||||
|
||||
+20
-13
@@ -24,6 +24,7 @@ from replication.constants import (FETCHED, RP_COMMON, STATE_ACTIVE,
|
||||
STATE_SRV_SYNC, STATE_SYNCING, UP)
|
||||
from replication.exception import NonAuthorizedOperationError, ContextError
|
||||
from replication.interface import session
|
||||
from replication.porcelain import apply, add
|
||||
|
||||
from . import operators, utils
|
||||
from .presence import (UserFrustumWidget, UserNameWidget, UserSelectionWidget,
|
||||
@@ -71,7 +72,7 @@ class Timer(object):
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
self.unregister()
|
||||
session.disconnect()
|
||||
session.disconnect(reason=f"Error during timer {self.id} execution")
|
||||
else:
|
||||
if self.is_running:
|
||||
return self._timeout
|
||||
@@ -100,25 +101,31 @@ class SessionBackupTimer(Timer):
|
||||
def execute(self):
|
||||
session.save(self._filepath)
|
||||
|
||||
class SessionListenTimer(Timer):
|
||||
def execute(self):
|
||||
session.listen()
|
||||
|
||||
class ApplyTimer(Timer):
|
||||
def execute(self):
|
||||
if session and session.state['STATE'] == STATE_ACTIVE:
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
nodes = session.list()
|
||||
|
||||
for node in nodes:
|
||||
node_ref = session.get(uuid=node)
|
||||
node_ref = session.repository.get_node(node)
|
||||
|
||||
if node_ref.state == FETCHED:
|
||||
try:
|
||||
session.apply(node)
|
||||
apply(session.repository, node)
|
||||
except Exception as 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):
|
||||
for parent in session.repository.get_parents(node):
|
||||
logging.debug("Refresh parent {node}")
|
||||
session.apply(parent, force=True)
|
||||
apply(session.repository,
|
||||
parent.uuid,
|
||||
force=True)
|
||||
|
||||
|
||||
class DynamicRightSelectTimer(Timer):
|
||||
@@ -131,7 +138,7 @@ class DynamicRightSelectTimer(Timer):
|
||||
def execute(self):
|
||||
settings = utils.get_preferences()
|
||||
|
||||
if session and session.state['STATE'] == STATE_ACTIVE:
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
# Find user
|
||||
if self._user is None:
|
||||
self._user = session.online_users.get(settings.username)
|
||||
@@ -145,7 +152,7 @@ class DynamicRightSelectTimer(Timer):
|
||||
|
||||
# if an annotation exist and is tracked
|
||||
if annotation_gp and annotation_gp.uuid:
|
||||
registered_gp = session.get(uuid=annotation_gp.uuid)
|
||||
registered_gp = session.repository.get_node(annotation_gp.uuid)
|
||||
if is_annotating(bpy.context):
|
||||
# try to get the right on it
|
||||
if registered_gp.owner == RP_COMMON:
|
||||
@@ -159,7 +166,7 @@ class DynamicRightSelectTimer(Timer):
|
||||
affect_dependencies=False)
|
||||
|
||||
if registered_gp.owner == settings.username:
|
||||
gp_node = session.get(uuid=annotation_gp.uuid)
|
||||
gp_node = session.repository.get_node(annotation_gp.uuid)
|
||||
if gp_node.has_changed():
|
||||
session.commit(gp_node.uuid)
|
||||
session.push(gp_node.uuid, check_data=False)
|
||||
@@ -183,7 +190,7 @@ class DynamicRightSelectTimer(Timer):
|
||||
|
||||
# change old selection right to common
|
||||
for obj in obj_common:
|
||||
node = session.get(uuid=obj)
|
||||
node = session.repository.get_node(obj)
|
||||
|
||||
if node and (node.owner == settings.username or node.owner == RP_COMMON):
|
||||
recursive = True
|
||||
@@ -201,7 +208,7 @@ class DynamicRightSelectTimer(Timer):
|
||||
|
||||
# change new selection to our
|
||||
for obj in obj_ours:
|
||||
node = session.get(uuid=obj)
|
||||
node = session.repository.get_node(obj)
|
||||
|
||||
if node and node.owner == RP_COMMON:
|
||||
recursive = True
|
||||
@@ -234,7 +241,7 @@ class DynamicRightSelectTimer(Timer):
|
||||
owned_keys = session.list(
|
||||
filter_owner=settings.username)
|
||||
for key in owned_keys:
|
||||
node = session.get(uuid=key)
|
||||
node = session.repository.get_node(key)
|
||||
try:
|
||||
session.change_owner(
|
||||
key,
|
||||
@@ -263,7 +270,7 @@ class ClientUpdate(Timer):
|
||||
settings = utils.get_preferences()
|
||||
|
||||
if session and renderer:
|
||||
if session.state['STATE'] in [STATE_ACTIVE, STATE_LOBBY]:
|
||||
if session.state in [STATE_ACTIVE, STATE_LOBBY]:
|
||||
local_user = session.online_users.get(
|
||||
settings.username)
|
||||
|
||||
|
||||
+21
-58
@@ -26,7 +26,7 @@ from replication.constants import (ADDED, ERROR, FETCHED,
|
||||
STATE_INITIAL, STATE_SRV_SYNC,
|
||||
STATE_WAITING, STATE_QUITTING,
|
||||
STATE_LOBBY,
|
||||
STATE_LAUNCHING_SERVICES)
|
||||
CONNECTING)
|
||||
from replication import __version__
|
||||
from replication.interface import session
|
||||
from .timers import registry
|
||||
@@ -71,9 +71,9 @@ class SESSION_PT_settings(bpy.types.Panel):
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
if session and session.state['STATE'] != STATE_INITIAL:
|
||||
if session and session.state != STATE_INITIAL:
|
||||
cli_state = session.state
|
||||
state = session.state.get('STATE')
|
||||
state = session.state
|
||||
connection_icon = "KEYTYPE_MOVING_HOLD_VEC"
|
||||
|
||||
if state == STATE_ACTIVE:
|
||||
@@ -81,7 +81,7 @@ class SESSION_PT_settings(bpy.types.Panel):
|
||||
else:
|
||||
connection_icon = 'PROP_CON'
|
||||
|
||||
layout.label(text=f"Session - {get_state_str(cli_state['STATE'])}", icon=connection_icon)
|
||||
layout.label(text=f"Session - {get_state_str(cli_state)}", icon=connection_icon)
|
||||
else:
|
||||
layout.label(text=f"Session - v{__version__}",icon="PROP_OFF")
|
||||
|
||||
@@ -94,13 +94,13 @@ class SESSION_PT_settings(bpy.types.Panel):
|
||||
if hasattr(context.window_manager, 'session'):
|
||||
# STATE INITIAL
|
||||
if not session \
|
||||
or (session and session.state['STATE'] == STATE_INITIAL):
|
||||
or (session and session.state == STATE_INITIAL):
|
||||
pass
|
||||
else:
|
||||
cli_state = session.state
|
||||
progress = session.state_progress
|
||||
row = layout.row()
|
||||
|
||||
current_state = cli_state['STATE']
|
||||
current_state = session.state
|
||||
info_msg = None
|
||||
|
||||
if current_state in [STATE_ACTIVE]:
|
||||
@@ -124,8 +124,8 @@ class SESSION_PT_settings(bpy.types.Panel):
|
||||
if current_state in [STATE_SYNCING, STATE_SRV_SYNC, STATE_WAITING]:
|
||||
info_box = row.box()
|
||||
info_box.row().label(text=printProgressBar(
|
||||
cli_state['CURRENT'],
|
||||
cli_state['TOTAL'],
|
||||
progress['current'],
|
||||
progress['total'],
|
||||
length=16
|
||||
))
|
||||
|
||||
@@ -141,7 +141,7 @@ class SESSION_PT_settings_network(bpy.types.Panel):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not session \
|
||||
or (session and session.state['STATE'] == 0)
|
||||
or (session and session.state == 0)
|
||||
|
||||
def draw_header(self, context):
|
||||
self.layout.label(text="", icon='URL')
|
||||
@@ -199,7 +199,7 @@ class SESSION_PT_settings_user(bpy.types.Panel):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not session \
|
||||
or (session and session.state['STATE'] == 0)
|
||||
or (session and session.state == 0)
|
||||
|
||||
def draw_header(self, context):
|
||||
self.layout.label(text="", icon='USER')
|
||||
@@ -230,7 +230,7 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not session \
|
||||
or (session and session.state['STATE'] == 0)
|
||||
or (session and session.state == 0)
|
||||
|
||||
def draw_header(self, context):
|
||||
self.layout.label(text="", icon='PREFERENCES')
|
||||
@@ -251,9 +251,6 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
|
||||
emboss=False)
|
||||
|
||||
if settings.sidebar_advanced_net_expanded:
|
||||
net_section_row = net_section.row()
|
||||
net_section_row.label(text="IPC Port:")
|
||||
net_section_row.prop(settings, "ipc_port", text="")
|
||||
net_section_row = net_section.row()
|
||||
net_section_row.label(text="Timeout (ms):")
|
||||
net_section_row.prop(settings, "connection_timeout", text="")
|
||||
@@ -322,7 +319,7 @@ class SESSION_PT_user(bpy.types.Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return session and session.state['STATE'] in [STATE_ACTIVE, STATE_LOBBY]
|
||||
return session and session.state in [STATE_ACTIVE, STATE_LOBBY]
|
||||
|
||||
def draw_header(self, context):
|
||||
self.layout.label(text="", icon='USER')
|
||||
@@ -353,7 +350,7 @@ class SESSION_PT_user(bpy.types.Panel):
|
||||
if active_user != 0 and active_user.username != settings.username:
|
||||
row = layout.row()
|
||||
user_operations = row.split()
|
||||
if session.state['STATE'] == STATE_ACTIVE:
|
||||
if session.state == STATE_ACTIVE:
|
||||
|
||||
user_operations.alert = context.window_manager.session.time_snap_running
|
||||
user_operations.operator(
|
||||
@@ -411,7 +408,7 @@ class SESSION_PT_presence(bpy.types.Panel):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return not session \
|
||||
or (session and session.state['STATE'] in [STATE_INITIAL, STATE_ACTIVE])
|
||||
or (session and session.state in [STATE_INITIAL, STATE_ACTIVE])
|
||||
|
||||
def draw_header(self, context):
|
||||
self.layout.prop(context.window_manager.session,
|
||||
@@ -441,7 +438,7 @@ class SESSION_PT_presence(bpy.types.Panel):
|
||||
def draw_property(context, parent, property_uuid, level=0):
|
||||
settings = get_preferences()
|
||||
runtime_settings = context.window_manager.session
|
||||
item = session.get(uuid=property_uuid)
|
||||
item = session.repository.get_node(property_uuid)
|
||||
|
||||
area_msg = parent.row(align=True)
|
||||
|
||||
@@ -519,8 +516,8 @@ class SESSION_PT_repository(bpy.types.Panel):
|
||||
admin = usr['admin']
|
||||
return hasattr(context.window_manager, 'session') and \
|
||||
session and \
|
||||
(session.state['STATE'] == STATE_ACTIVE or \
|
||||
session.state['STATE'] == STATE_LOBBY and admin)
|
||||
(session.state == STATE_ACTIVE or \
|
||||
session.state == STATE_LOBBY and admin)
|
||||
|
||||
def draw_header(self, context):
|
||||
self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE')
|
||||
@@ -536,7 +533,7 @@ class SESSION_PT_repository(bpy.types.Panel):
|
||||
|
||||
row = layout.row()
|
||||
|
||||
if session.state['STATE'] == STATE_ACTIVE:
|
||||
if session.state == STATE_ACTIVE:
|
||||
if 'SessionBackupTimer' in registry:
|
||||
row.alert = True
|
||||
row.operator('session.cancel_autosave', icon="CANCEL")
|
||||
@@ -568,7 +565,7 @@ class SESSION_PT_repository(bpy.types.Panel):
|
||||
filter_owner=settings.username) if runtime_settings.filter_owned else session.list()
|
||||
|
||||
client_keys = [key for key in key_to_filter
|
||||
if session.get(uuid=key).str_type
|
||||
if session.repository.get_node(key).str_type
|
||||
in types_filter]
|
||||
|
||||
if client_keys:
|
||||
@@ -579,7 +576,7 @@ class SESSION_PT_repository(bpy.types.Panel):
|
||||
else:
|
||||
row.label(text="Empty")
|
||||
|
||||
elif session.state['STATE'] == STATE_LOBBY and usr and usr['admin']:
|
||||
elif session.state == STATE_LOBBY and usr and usr['admin']:
|
||||
row.operator("session.init", icon='TOOL_SETTINGS', text="Init")
|
||||
else:
|
||||
row.label(text="Waiting to start")
|
||||
@@ -615,39 +612,6 @@ class VIEW3D_PT_overlay_session(bpy.types.Panel):
|
||||
row.active = settings.presence_show_user
|
||||
row.prop(settings, "presence_show_far_user")
|
||||
|
||||
class SESSION_PT_replay(bpy.types.Panel):
|
||||
bl_idname = "MULTIUSER_REPLAY_PT_panel"
|
||||
bl_label = "Replay"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel'
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.window_manager.session.replay_files
|
||||
|
||||
def draw_header(self, context):
|
||||
self.layout.label(text="", icon='RECOVER_LAST')
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
settings = context.window_manager.session
|
||||
row= layout.row()
|
||||
row.prop(settings,'replay_mode', toggle=True, expand=True)
|
||||
row= layout.row()
|
||||
if settings.replay_mode == 'MANUAL':
|
||||
row.prop(bpy.context.scene, 'active_replay_file', text="Snapshot index")
|
||||
else:
|
||||
row.prop(settings, 'replay_duration', text="Replay Duration")
|
||||
row= layout.row()
|
||||
row.prop(settings, 'replay_persistent_collection', text="persistent collection", toggle=True, icon='OUTLINER_COLLECTION')
|
||||
|
||||
if settings.replay_persistent_collection:
|
||||
row= layout.row()
|
||||
row.prop(settings, 'replay_camera', text="", icon='VIEW_CAMERA')
|
||||
|
||||
|
||||
classes = (
|
||||
SESSION_UL_users,
|
||||
SESSION_PT_settings,
|
||||
@@ -657,7 +621,6 @@ classes = (
|
||||
SESSION_PT_advanced_settings,
|
||||
SESSION_PT_user,
|
||||
SESSION_PT_repository,
|
||||
SESSION_PT_replay,
|
||||
VIEW3D_PT_overlay_session,
|
||||
)
|
||||
|
||||
|
||||
+14
-2
@@ -36,7 +36,7 @@ from replication.constants import (STATE_ACTIVE, STATE_AUTH,
|
||||
STATE_INITIAL, STATE_SRV_SYNC,
|
||||
STATE_WAITING, STATE_QUITTING,
|
||||
STATE_LOBBY,
|
||||
STATE_LAUNCHING_SERVICES)
|
||||
CONNECTING)
|
||||
|
||||
|
||||
def find_from_attr(attr_name, attr_value, list):
|
||||
@@ -92,7 +92,7 @@ def get_state_str(state):
|
||||
state_str = 'OFFLINE'
|
||||
elif state == STATE_QUITTING:
|
||||
state_str = 'QUITTING'
|
||||
elif state == STATE_LAUNCHING_SERVICES:
|
||||
elif state == CONNECTING:
|
||||
state_str = 'LAUNCHING SERVICES'
|
||||
elif state == STATE_LOBBY:
|
||||
state_str = 'LOBBY'
|
||||
@@ -100,6 +100,18 @@ def get_state_str(state):
|
||||
return state_str
|
||||
|
||||
|
||||
def clean_scene():
|
||||
for type_name in dir(bpy.data):
|
||||
try:
|
||||
type_collection = getattr(bpy.data, type_name)
|
||||
for item in type_collection:
|
||||
type_collection.remove(item)
|
||||
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)]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user