Compare commits
49 Commits
96-add-a-k
...
v0.0.3
Author | SHA1 | Date | |
---|---|---|---|
09ee1cf826 | |||
61bcec98c3 | |||
1c85d436fd | |||
03318026d4 | |||
7a0b142d69 | |||
eb874110f8 | |||
6e0c7bc332 | |||
ee83e61b09 | |||
99b2dc0539 | |||
53f1118181 | |||
2791264a92 | |||
6c2ee0cad3 | |||
20f8c25f55 | |||
0224f55104 | |||
644702ebdf | |||
9377b2be9b | |||
29cbf23142 | |||
a645f71d19 | |||
909d92a7a1 | |||
7ee9089087 | |||
6201c82392 | |||
0faf7d9436 | |||
e69e61117a | |||
25e988d423 | |||
8a3ab895e0 | |||
06a8e3c0ab | |||
c1c1628a38 | |||
022e3354d9 | |||
211cb848b9 | |||
25e233f328 | |||
9bc3d9b29d | |||
15debf339d | |||
56df7d182d | |||
26e1579e35 | |||
a0e290ad6d | |||
092384b2e4 | |||
2dc3654e6c | |||
f37a9efc60 | |||
0c5d323063 | |||
b9f1b8a871 | |||
2f6d8e1701 | |||
9e64584f2d | |||
154aaf71c8 | |||
ac24ab69ff | |||
ad431378f8 | |||
784506cd95 | |||
eb7542b1dd | |||
a0676f4e37 | |||
61a05dc347 |
@ -4,5 +4,4 @@ stages:
|
||||
|
||||
include:
|
||||
- local: .gitlab/ci/test.gitlab-ci.yml
|
||||
- local: .gitlab/ci/build.gitlab-ci.yml
|
||||
|
||||
- local: .gitlab/ci/build.gitlab-ci.yml
|
@ -1,10 +1,7 @@
|
||||
build:
|
||||
stage: build
|
||||
image: python:latest
|
||||
image: debian:stable-slim
|
||||
script:
|
||||
- git submodule init
|
||||
- git submodule update
|
||||
- cd multi_user/libs/replication
|
||||
- rm -rf tests .git .gitignore script
|
||||
|
||||
artifacts:
|
||||
@ -12,3 +9,4 @@ build:
|
||||
paths:
|
||||
- multi_user
|
||||
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
test:
|
||||
stage: test
|
||||
image: python:latest
|
||||
image: python:3.7
|
||||
script:
|
||||
- git submodule init
|
||||
- git submodule update
|
||||
- apt update
|
||||
- apt-get update
|
||||
# install blender to get all required dependencies
|
||||
# TODO: indtall only dependencies
|
||||
- apt install -f -y gcc python-dev python3.7-dev
|
||||
- apt install -f -y blender
|
||||
- python3 -m pip install blender-addon-tester
|
||||
- python3 scripts/test_addon.py
|
||||
- python -m pip install blender-addon-tester
|
||||
- python scripts/test_addon.py
|
||||
|
||||
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "multi_user/libs/replication"]
|
||||
path = multi_user/libs/replication
|
||||
url = https://gitlab.com/slumber/replication.git
|
||||
|
@ -11,7 +11,7 @@ This tool aims to allow multiple users to work on the same scene over the networ
|
||||
|
||||
## Quick installation
|
||||
|
||||
1. Download latest release [multi_user.zip](/uploads/8aef79c7cf5b1d9606dc58307fd9ad8b/multi_user.zip).
|
||||
1. Download latest release [multi_user.zip](https://gitlab.com/slumber/multi-user/-/jobs/artifacts/master/download?job=build).
|
||||
2. Run blender as administrator (dependencies installation).
|
||||
3. Install last_version.zip from your addon preferences.
|
||||
|
||||
|
@ -8,5 +8,4 @@ Getting started
|
||||
|
||||
install
|
||||
quickstart
|
||||
known_problems
|
||||
glossary
|
||||
|
@ -1,46 +0,0 @@
|
||||
.. _known-problems:
|
||||
|
||||
==============
|
||||
Known problems
|
||||
==============
|
||||
|
||||
.. rubric:: What do you need to do in order to use Multi-User through internet?
|
||||
|
||||
1. Use Hamachi or ZeroTier (I prefer Hamachi) and create a network.
|
||||
2. All participants need to join this network.
|
||||
3. Go to Blender and install Multi-User in the preferneces.
|
||||
4. Setup and start the session:
|
||||
|
||||
* **Host**: After activating Multi-User as an Add-On, press N and go on Multi-User.
|
||||
Then, put the IP of your network where IP is asked for.
|
||||
Leave Port and IPC Port on default(5555 and 5561). Increase the Timeout(ms) if the connection is not stable.
|
||||
Then press on "host".
|
||||
|
||||
* **Guest**: After activating Multi-User as an Add-On, press N and go to Multi-User
|
||||
Then, put the IP of your network where IP is asked for.
|
||||
Leave Port and IPC Port on default(5555 and 5561)(Simpler, put the same information that the host is using.
|
||||
BUT,it needs 4 ports for communication. Therefore, you need to put 5555+count of guests [up to 4]. ).
|
||||
Increase the Timeout(ms) if the connection is not stable. Then press on "connexion".
|
||||
|
||||
.. rubric:: What do you need to check if you can't host?
|
||||
|
||||
You need to check, if the IP and all ports are correct. If it's not loading, because you laoded a project before hosting, it's not your fault.
|
||||
Then the version is not sable yet (the project contains data, that is not made stable yet).
|
||||
|
||||
.. rubric:: What do you need to check if you can't connect?
|
||||
|
||||
Check, if you are connected to the network (VPN) of the host. Also, check if you have all of the information like the host has.
|
||||
Maybe you have different versions (which shouldn't be the case after Auto-Updater is introduced).
|
||||
|
||||
.. rubric:: You are connected, but you dont see anything?
|
||||
|
||||
After pressing N, go presence overlay and check the box.
|
||||
Also, go down and uncheck the box "Show only owned"(unless you need privacy ( ͡° ͜ʖ ͡°) ).
|
||||
|
||||
If it's still not working, hit the support channel on the discord channel "multi-user". This little helping text is produced by my own experience
|
||||
(Ultr-X).
|
||||
In order to bring attention to other problems, please @ me on the support channel. Every problem brought to me will be documentated to optimize and update this text.
|
||||
Thank you and have fun with Multi-User, brought to you by "swann".
|
||||
|
||||
Here the discord server: https://discord.gg/v5eKgm
|
||||
|
@ -48,7 +48,6 @@ Documentation is organized into the following sections:
|
||||
|
||||
getting_started/install
|
||||
getting_started/quickstart
|
||||
getting_started/known_problems
|
||||
getting_started/glossary
|
||||
|
||||
.. toctree::
|
||||
|
@ -186,25 +186,24 @@ Using a regular command line
|
||||
You can run the dedicated server on any platform by following those steps:
|
||||
|
||||
1. Firstly, download and intall python 3 (3.6 or above).
|
||||
2. Download and extract the dedicated server from `here <https://gitlab.com/slumber/replication/-/archive/develop/replication-develop.zip>`_
|
||||
3. Open a terminal in the extracted folder and install python dependencies by running:
|
||||
2. Install the replication library:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python -m pip install -r requirements.txt
|
||||
python -m pip install replication
|
||||
|
||||
4. Launch the server from the same terminal with:
|
||||
4. Launch the server with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python scripts/server.py
|
||||
replication.serve
|
||||
|
||||
.. hint::
|
||||
You can also specify a custom **port** (-p), **timeout** (-t) and **admin password** (-pwd) with the following optionnal argument
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python scripts/server.py -p 5555 -pwd toto -t 1000
|
||||
replication.serve -p 5555 -pwd toto -t 1000
|
||||
|
||||
As soon as the dedicated server is running, you can connect to it from blender (follow :ref:`how-to-join`).
|
||||
|
||||
|
@ -21,7 +21,7 @@ bl_info = {
|
||||
"author": "Swann Martinez",
|
||||
"version": (0, 0, 3),
|
||||
"description": "Enable real-time collaborative workflow inside blender",
|
||||
"blender": (2, 80, 0),
|
||||
"blender": (2, 82, 0),
|
||||
"location": "3D View > Sidebar > Multi-User tab",
|
||||
"warning": "Unstable addon, use it at your own risks",
|
||||
"category": "Collaboration",
|
||||
@ -45,22 +45,15 @@ from . import environment, utils
|
||||
|
||||
# TODO: remove dependency as soon as replication will be installed as a module
|
||||
DEPENDENCIES = {
|
||||
("zmq","zmq"),
|
||||
("jsondiff","jsondiff"),
|
||||
("deepdiff", "deepdiff"),
|
||||
("psutil","psutil")
|
||||
("replication", '0.0.20'),
|
||||
("deepdiff", '5.0.1'),
|
||||
}
|
||||
|
||||
|
||||
libs = os.path.dirname(os.path.abspath(__file__))+"\\libs\\replication\\replication"
|
||||
|
||||
def register():
|
||||
# Setup logging policy
|
||||
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
|
||||
|
||||
if libs not in sys.path:
|
||||
sys.path.append(libs)
|
||||
|
||||
|
||||
try:
|
||||
environment.setup(DEPENDENCIES, bpy.app.binary_path_python)
|
||||
except ModuleNotFoundError:
|
||||
|
@ -722,21 +722,22 @@ class Singleton_updater(object):
|
||||
|
||||
self._source_zip = os.path.join(local,"source.zip")
|
||||
|
||||
if self._verbose: print("Starting download update zip")
|
||||
if self._verbose: print(f"Starting download update zip to {self._source_zip}")
|
||||
try:
|
||||
request = urllib.request.Request(url)
|
||||
context = ssl._create_unverified_context()
|
||||
import urllib3
|
||||
http = urllib3.PoolManager()
|
||||
r = http.request('GET', url, preload_content=False)
|
||||
chunk_size = 1024*8
|
||||
with open(self._source_zip, 'wb') as out:
|
||||
while True:
|
||||
data = r.read(chunk_size)
|
||||
if not data:
|
||||
break
|
||||
out.write(data)
|
||||
|
||||
# setup private token if appropriate
|
||||
if self._engine.token != None:
|
||||
if self._engine.name == "gitlab":
|
||||
request.add_header('PRIVATE-TOKEN',self._engine.token)
|
||||
else:
|
||||
if self._verbose: print("Tokens not setup for selected engine yet")
|
||||
self.urlretrieve(urllib.request.urlopen(request,context=context), self._source_zip)
|
||||
# add additional checks on file size being non-zero
|
||||
r.release_conn()
|
||||
if self._verbose: print("Successfully downloaded update zip")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
self._error = "Error retrieving download, bad link?"
|
||||
self._error_msg = "Error: {}".format(e)
|
||||
|
@ -38,7 +38,7 @@ __all__ = [
|
||||
] # Order here defines execution order
|
||||
|
||||
from . import *
|
||||
from ..libs.replication.replication.data import ReplicatedDataFactory
|
||||
from replication.data import ReplicatedDataFactory
|
||||
|
||||
def types_to_register():
|
||||
return __all__
|
||||
|
@ -92,6 +92,7 @@ class BlArmature(BlDatablock):
|
||||
new_bone.head = bone_data['head_local']
|
||||
new_bone.tail_radius = bone_data['tail_radius']
|
||||
new_bone.head_radius = bone_data['head_radius']
|
||||
# new_bone.roll = bone_data['roll']
|
||||
|
||||
if 'parent' in bone_data:
|
||||
new_bone.parent = target.edit_bones[data['bones']
|
||||
@ -123,7 +124,8 @@ class BlArmature(BlDatablock):
|
||||
'use_connect',
|
||||
'parent',
|
||||
'name',
|
||||
'layers'
|
||||
'layers',
|
||||
# 'roll',
|
||||
|
||||
]
|
||||
data = dumper.dump(instance)
|
||||
|
@ -36,7 +36,7 @@ class BlCamera(BlDatablock):
|
||||
|
||||
|
||||
def _load_implementation(self, data, target):
|
||||
loader = Loader()
|
||||
loader = Loader()
|
||||
loader.load(target, data)
|
||||
|
||||
dof_settings = data.get('dof')
|
||||
@ -45,13 +45,22 @@ class BlCamera(BlDatablock):
|
||||
if dof_settings:
|
||||
loader.load(target.dof, dof_settings)
|
||||
|
||||
background_images = data.get('background_images')
|
||||
|
||||
if background_images:
|
||||
target.background_images.clear()
|
||||
for img_name, img_data in background_images.items():
|
||||
target_img = target.background_images.new()
|
||||
target_img.image = bpy.data.images[img_name]
|
||||
loader.load(target_img, img_data)
|
||||
|
||||
def _dump_implementation(self, data, instance=None):
|
||||
assert(instance)
|
||||
|
||||
# TODO: background image support
|
||||
|
||||
dumper = Dumper()
|
||||
dumper.depth = 2
|
||||
dumper.depth = 3
|
||||
dumper.include_filter = [
|
||||
"name",
|
||||
'type',
|
||||
@ -79,7 +88,24 @@ class BlCamera(BlDatablock):
|
||||
'sensor_fit',
|
||||
'sensor_height',
|
||||
'sensor_width',
|
||||
'show_background_images',
|
||||
'background_images',
|
||||
'alpha',
|
||||
'display_depth',
|
||||
'frame_method',
|
||||
'offset',
|
||||
'rotation',
|
||||
'scale',
|
||||
'use_flip_x',
|
||||
'use_flip_y',
|
||||
'image'
|
||||
]
|
||||
return dumper.dump(instance)
|
||||
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
deps = []
|
||||
for background in self.instance.background_images:
|
||||
if background.image:
|
||||
deps.append(background.image)
|
||||
|
||||
return deps
|
||||
|
@ -21,8 +21,8 @@ import mathutils
|
||||
|
||||
from .. import utils
|
||||
from .dump_anything import Loader, Dumper
|
||||
from ..libs.replication.replication.data import ReplicatedDatablock
|
||||
from ..libs.replication.replication.constants import (UP, DIFF_BINARY)
|
||||
from replication.data import ReplicatedDatablock
|
||||
from replication.constants import (UP, DIFF_BINARY)
|
||||
|
||||
|
||||
def has_action(target):
|
||||
@ -117,9 +117,10 @@ class BlDatablock(ReplicatedDatablock):
|
||||
datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root)
|
||||
|
||||
if not datablock_ref:
|
||||
datablock_ref = datablock_root.get(
|
||||
self.data['name'], # Resolve by name
|
||||
self._construct(data=self.data)) # If it doesn't exist create it
|
||||
try:
|
||||
datablock_ref = datablock_root[self.data['name']]
|
||||
except Exception:
|
||||
datablock_ref = self._construct(data=self.data)
|
||||
|
||||
if datablock_ref:
|
||||
setattr(datablock_ref, 'uuid', self.uuid)
|
||||
|
@ -21,7 +21,7 @@ import mathutils
|
||||
|
||||
from .dump_anything import Dumper, Loader, np_dump_collection, np_load_collection
|
||||
from .bl_datablock import BlDatablock
|
||||
from ..libs.replication.replication.exception import ContextError
|
||||
from replication.exception import ContextError
|
||||
|
||||
POINT = ['co', 'weight_softbody', 'co_deform']
|
||||
|
||||
|
@ -19,11 +19,13 @@
|
||||
import bpy
|
||||
import mathutils
|
||||
import logging
|
||||
import re
|
||||
|
||||
from .. import utils
|
||||
from .dump_anything import Loader, Dumper
|
||||
from .bl_datablock import BlDatablock
|
||||
|
||||
NODE_SOCKET_INDEX = re.compile('\[(\d*)\]')
|
||||
|
||||
def load_node(node_data, node_tree):
|
||||
""" Load a node into a node_tree from a dict
|
||||
@ -36,21 +38,20 @@ def load_node(node_data, node_tree):
|
||||
loader = Loader()
|
||||
target_node = node_tree.nodes.new(type=node_data["bl_idname"])
|
||||
|
||||
loader.load(target_node, node_data)
|
||||
|
||||
|
||||
loader.load(target_node, node_data)
|
||||
|
||||
for input in node_data["inputs"]:
|
||||
if hasattr(target_node.inputs[input], "default_value"):
|
||||
try:
|
||||
target_node.inputs[input].default_value = node_data["inputs"][input]["default_value"]
|
||||
except:
|
||||
logging.error(f"Material {input} parameter not supported, skipping")
|
||||
logging.error(
|
||||
f"Material {input} parameter not supported, skipping")
|
||||
|
||||
|
||||
def load_links(links_data, node_tree):
|
||||
""" Load node_tree links from a list
|
||||
|
||||
|
||||
:arg links_data: dumped node links
|
||||
:type links_data: list
|
||||
:arg node_tree: node links collection
|
||||
@ -60,7 +61,6 @@ def load_links(links_data, node_tree):
|
||||
for link in links_data:
|
||||
input_socket = node_tree.nodes[link['to_node']].inputs[int(link['to_socket'])]
|
||||
output_socket = node_tree.nodes[link['from_node']].outputs[int(link['from_socket'])]
|
||||
|
||||
node_tree.links.new(input_socket, output_socket)
|
||||
|
||||
|
||||
@ -75,11 +75,13 @@ def dump_links(links):
|
||||
links_data = []
|
||||
|
||||
for link in links:
|
||||
to_socket = NODE_SOCKET_INDEX.search(link.to_socket.path_from_id()).group(1)
|
||||
from_socket = NODE_SOCKET_INDEX.search(link.from_socket.path_from_id()).group(1)
|
||||
links_data.append({
|
||||
'to_node':link.to_node.name,
|
||||
'to_socket':link.to_socket.path_from_id()[-2:-1],
|
||||
'from_node':link.from_node.name,
|
||||
'from_socket':link.from_socket.path_from_id()[-2:-1],
|
||||
'to_node': link.to_node.name,
|
||||
'to_socket': to_socket,
|
||||
'from_node': link.from_node.name,
|
||||
'from_socket': from_socket,
|
||||
})
|
||||
|
||||
return links_data
|
||||
@ -118,7 +120,7 @@ def dump_node(node):
|
||||
"outputs",
|
||||
"width_hidden"
|
||||
]
|
||||
|
||||
|
||||
dumped_node = node_dumper.dump(node)
|
||||
|
||||
if hasattr(node, 'inputs'):
|
||||
@ -151,7 +153,7 @@ def dump_node(node):
|
||||
'location'
|
||||
]
|
||||
dumped_node['mapping'] = curve_dumper.dump(node.mapping)
|
||||
|
||||
|
||||
return dumped_node
|
||||
|
||||
|
||||
@ -176,15 +178,14 @@ class BlMaterial(BlDatablock):
|
||||
loader.load(
|
||||
target.grease_pencil, data['grease_pencil'])
|
||||
|
||||
|
||||
if data["use_nodes"]:
|
||||
if target.node_tree is None:
|
||||
target.use_nodes = True
|
||||
|
||||
target.node_tree.nodes.clear()
|
||||
|
||||
loader.load(target,data)
|
||||
|
||||
loader.load(target, data)
|
||||
|
||||
# Load nodes
|
||||
for node in data["node_tree"]["nodes"]:
|
||||
load_node(data["node_tree"]["nodes"][node], target.node_tree)
|
||||
@ -221,9 +222,9 @@ class BlMaterial(BlDatablock):
|
||||
for node in instance.node_tree.nodes:
|
||||
nodes[node.name] = dump_node(node)
|
||||
data["node_tree"]['nodes'] = nodes
|
||||
|
||||
|
||||
data["node_tree"]["links"] = dump_links(instance.node_tree.links)
|
||||
|
||||
|
||||
if instance.is_grease_pencil:
|
||||
gp_mat_dumper = Dumper()
|
||||
gp_mat_dumper.depth = 3
|
||||
@ -248,7 +249,7 @@ class BlMaterial(BlDatablock):
|
||||
'texture_clamp',
|
||||
'gradient_type',
|
||||
'mix_color',
|
||||
'flip'
|
||||
'flip'
|
||||
]
|
||||
data['grease_pencil'] = gp_mat_dumper.dump(instance.grease_pencil)
|
||||
return data
|
||||
@ -265,4 +266,3 @@ class BlMaterial(BlDatablock):
|
||||
deps.append(self.instance.library)
|
||||
|
||||
return deps
|
||||
|
||||
|
@ -23,8 +23,8 @@ import logging
|
||||
import numpy as np
|
||||
|
||||
from .dump_anything import Dumper, Loader, np_load_collection_primitives, np_dump_collection_primitive, np_load_collection, np_dump_collection
|
||||
from ..libs.replication.replication.constants import DIFF_BINARY
|
||||
from ..libs.replication.replication.exception import ContextError
|
||||
from replication.constants import DIFF_BINARY
|
||||
from replication.exception import ContextError
|
||||
from .bl_datablock import BlDatablock
|
||||
|
||||
|
||||
|
@ -22,7 +22,7 @@ import logging
|
||||
|
||||
from .dump_anything import Loader, Dumper
|
||||
from .bl_datablock import BlDatablock
|
||||
from ..libs.replication.replication.exception import ContextError
|
||||
from replication.exception import ContextError
|
||||
|
||||
|
||||
def load_pose(target_bone, data):
|
||||
@ -152,6 +152,13 @@ class BlObject(BlDatablock):
|
||||
|
||||
target.data.shape_keys.key_blocks[key_block].relative_key = target.data.shape_keys.key_blocks[reference]
|
||||
|
||||
# TODO: find another way...
|
||||
if target.type == 'EMPTY':
|
||||
img_key = data.get('data')
|
||||
|
||||
if target.data is None and img_key:
|
||||
target.data = bpy.data.images.get(img_key, None)
|
||||
|
||||
def _dump_implementation(self, data, instance=None):
|
||||
assert(instance)
|
||||
|
||||
@ -171,6 +178,14 @@ class BlObject(BlDatablock):
|
||||
"library",
|
||||
"empty_display_type",
|
||||
"empty_display_size",
|
||||
"empty_image_offset",
|
||||
"empty_image_depth",
|
||||
"empty_image_side",
|
||||
"show_empty_image_orthographic",
|
||||
"show_empty_image_perspective",
|
||||
"show_empty_image_only_axis_aligned",
|
||||
"use_empty_image_alpha",
|
||||
"color"
|
||||
"instance_collection",
|
||||
"instance_type",
|
||||
"location",
|
||||
|
@ -20,7 +20,7 @@ import logging
|
||||
import bpy
|
||||
|
||||
from . import operators, presence, utils
|
||||
from .libs.replication.replication.constants import (FETCHED,
|
||||
from replication.constants import (FETCHED,
|
||||
RP_COMMON,
|
||||
STATE_INITIAL,
|
||||
STATE_QUITTING,
|
||||
@ -239,7 +239,7 @@ class DrawClient(Draw):
|
||||
|
||||
|
||||
class ClientUpdate(Timer):
|
||||
def __init__(self, timout=.016):
|
||||
def __init__(self, timout=.032):
|
||||
super().__init__(timout)
|
||||
self.handle_quit = False
|
||||
self.users_metadata = {}
|
||||
@ -298,31 +298,36 @@ class ClientUpdate(Timer):
|
||||
local_user_metadata['view_corners'] = current_view_corners
|
||||
local_user_metadata['view_matrix'] = presence.get_view_matrix()
|
||||
session.update_user_metadata(local_user_metadata)
|
||||
# sync online users
|
||||
session_users = operators.client.online_users
|
||||
ui_users = bpy.context.window_manager.online_users
|
||||
|
||||
for index, user in enumerate(ui_users):
|
||||
if user.username not in session_users.keys():
|
||||
ui_users.remove(index)
|
||||
renderer.flush_selection()
|
||||
renderer.flush_users()
|
||||
break
|
||||
class SessionStatusUpdate(Timer):
|
||||
def __init__(self, timout=1):
|
||||
super().__init__(timout)
|
||||
|
||||
for user in session_users:
|
||||
if user not in ui_users:
|
||||
new_key = ui_users.add()
|
||||
new_key.name = user
|
||||
new_key.username = user
|
||||
elif session.state['STATE'] == STATE_QUITTING:
|
||||
presence.refresh_sidebar_view()
|
||||
self.handle_quit = True
|
||||
elif session.state['STATE'] == STATE_INITIAL and self.handle_quit:
|
||||
self.handle_quit = False
|
||||
presence.refresh_sidebar_view()
|
||||
def execute(self):
|
||||
presence.refresh_sidebar_view()
|
||||
|
||||
operators.unregister_delayables()
|
||||
class SessionUserSync(Timer):
|
||||
def __init__(self, timout=1):
|
||||
super().__init__(timout)
|
||||
|
||||
presence.renderer.stop()
|
||||
def execute(self):
|
||||
session = getattr(operators, 'client', None)
|
||||
renderer = getattr(presence, 'renderer', None)
|
||||
|
||||
presence.refresh_sidebar_view()
|
||||
if session and renderer:
|
||||
# sync online users
|
||||
session_users = operators.client.online_users
|
||||
ui_users = bpy.context.window_manager.online_users
|
||||
|
||||
for index, user in enumerate(ui_users):
|
||||
if user.username not in session_users.keys():
|
||||
ui_users.remove(index)
|
||||
renderer.flush_selection()
|
||||
renderer.flush_users()
|
||||
break
|
||||
|
||||
for user in session_users:
|
||||
if user not in ui_users:
|
||||
new_key = ui_users.add()
|
||||
new_key.name = user
|
||||
new_key.username = user
|
@ -23,6 +23,9 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import re
|
||||
|
||||
VERSION_EXPR = re.compile('\d+\.\d+\.\d+')
|
||||
|
||||
THIRD_PARTY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "libs")
|
||||
DEFAULT_CACHE_DIR = os.path.join(
|
||||
@ -47,10 +50,22 @@ def install_pip():
|
||||
subprocess.run([str(PYTHON_PATH), "-m", "ensurepip"])
|
||||
|
||||
|
||||
def install_package(name):
|
||||
logging.debug(f"Using {PYTHON_PATH} for installation")
|
||||
subprocess.run([str(PYTHON_PATH), "-m", "pip", "install", name])
|
||||
def install_package(name, version):
|
||||
logging.info(f"installing {name} version...")
|
||||
subprocess.run([str(PYTHON_PATH), "-m", "pip", "install", f"{name}=={version}"])
|
||||
|
||||
def check_package_version(name, required_version):
|
||||
logging.info(f"Checking {name} version...")
|
||||
out = subprocess.run(f"{str(PYTHON_PATH)} -m pip show {name}", capture_output=True)
|
||||
|
||||
version = VERSION_EXPR.search(out.stdout.decode())
|
||||
|
||||
if version and version.group() == required_version:
|
||||
logging.info(f"{name} is up to date")
|
||||
return True
|
||||
else:
|
||||
logging.info(f"{name} need an update")
|
||||
return False
|
||||
|
||||
def get_ip():
|
||||
"""
|
||||
@ -78,7 +93,9 @@ def setup(dependencies, python_path):
|
||||
if not module_can_be_imported("pip"):
|
||||
install_pip()
|
||||
|
||||
for module_name, package_name in dependencies:
|
||||
if not module_can_be_imported(module_name):
|
||||
install_package(package_name)
|
||||
for package_name, package_version in dependencies:
|
||||
if not module_can_be_imported(package_name):
|
||||
install_package(package_name, package_version)
|
||||
module_can_be_imported(package_name)
|
||||
elif not check_package_version(package_name, package_version):
|
||||
install_package(package_name, package_version)
|
||||
|
Submodule multi_user/libs/replication deleted from 4b04cf7474
@ -33,31 +33,19 @@ import mathutils
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
from . import bl_types, delayable, environment, presence, ui, utils
|
||||
from .libs.replication.replication.constants import (FETCHED, STATE_ACTIVE,
|
||||
from replication.constants import (FETCHED, STATE_ACTIVE,
|
||||
STATE_INITIAL,
|
||||
STATE_SYNCING)
|
||||
from .libs.replication.replication.data import ReplicatedDataFactory
|
||||
from .libs.replication.replication.exception import NonAuthorizedOperationError
|
||||
from .libs.replication.replication.interface import Session
|
||||
from replication.data import ReplicatedDataFactory
|
||||
from replication.exception import NonAuthorizedOperationError
|
||||
from replication.interface import Session
|
||||
|
||||
|
||||
client = None
|
||||
delayables = []
|
||||
stop_modal_executor = False
|
||||
modal_executor_queue = None
|
||||
|
||||
|
||||
def unregister_delayables():
|
||||
global delayables, stop_modal_executor
|
||||
|
||||
for d in delayables:
|
||||
try:
|
||||
d.unregister()
|
||||
except:
|
||||
continue
|
||||
|
||||
stop_modal_executor = True
|
||||
|
||||
# OPERATORS
|
||||
|
||||
|
||||
@ -80,7 +68,6 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
users = bpy.data.window_managers['WinMan'].online_users
|
||||
admin_pass = runtime_settings.password
|
||||
|
||||
unregister_delayables()
|
||||
users.clear()
|
||||
delayables.clear()
|
||||
|
||||
@ -163,16 +150,44 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
delayables.append(delayable.DrawClient())
|
||||
delayables.append(delayable.DynamicRightSelectTimer())
|
||||
|
||||
# Launch drawing module
|
||||
if runtime_settings.enable_presence:
|
||||
presence.renderer.run()
|
||||
session_update = delayable.SessionStatusUpdate()
|
||||
session_user_sync = delayable.SessionUserSync()
|
||||
session_update.register()
|
||||
session_user_sync.register()
|
||||
|
||||
# Register blender main thread tools
|
||||
for d in delayables:
|
||||
d.register()
|
||||
delayables.append(session_update)
|
||||
delayables.append(session_user_sync)
|
||||
|
||||
|
||||
@client.register('on_connection')
|
||||
def initialize_session():
|
||||
for node in client._graph.list_ordered():
|
||||
node_ref = client.get(node)
|
||||
if node_ref.state == FETCHED:
|
||||
node_ref.resolve()
|
||||
node_ref.apply()
|
||||
|
||||
# Launch drawing module
|
||||
if runtime_settings.enable_presence:
|
||||
presence.renderer.run()
|
||||
|
||||
# Register blender main thread tools
|
||||
for d in delayables:
|
||||
d.register()
|
||||
|
||||
@client.register('on_exit')
|
||||
def desinitialize_session():
|
||||
global delayables, stop_modal_executor
|
||||
|
||||
for d in delayables:
|
||||
try:
|
||||
d.unregister()
|
||||
except:
|
||||
continue
|
||||
|
||||
stop_modal_executor = True
|
||||
presence.renderer.stop()
|
||||
|
||||
global modal_executor_queue
|
||||
modal_executor_queue = queue.Queue()
|
||||
bpy.ops.session.apply_armature_operator()
|
||||
|
||||
self.report(
|
||||
@ -527,7 +542,7 @@ class ApplyArmatureOperator(bpy.types.Operator):
|
||||
try:
|
||||
client.apply(node)
|
||||
except Exception as e:
|
||||
logging.error("Dail to apply armature: {e}")
|
||||
logging.error("Fail to apply armature: {e}")
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
|
@ -22,7 +22,7 @@ import string
|
||||
import re
|
||||
|
||||
from . import utils, bl_types, environment, addon_updater_ops, presence, ui
|
||||
from .libs.replication.replication.constants import RP_COMMON
|
||||
from replication.constants import RP_COMMON
|
||||
|
||||
IP_EXPR = re.compile('\d+\.\d+\.\d+\.\d+')
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
import copy
|
||||
import logging
|
||||
import math
|
||||
import traceback
|
||||
|
||||
import bgl
|
||||
import blf
|
||||
@ -311,10 +312,10 @@ class DrawFactory(object):
|
||||
self.d2d_items[client_id] = (position[1], client_id, color)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Draw client exception: {e}")
|
||||
logging.debug(f"Draw client exception: {e} \n {traceback.format_exc()}\n pos:{position},ind:{indices}")
|
||||
|
||||
def draw3d_callback(self):
|
||||
bgl.glLineWidth(1.5)
|
||||
bgl.glLineWidth(2.)
|
||||
bgl.glEnable(bgl.GL_DEPTH_TEST)
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
|
@ -19,7 +19,7 @@
|
||||
import bpy
|
||||
|
||||
from . import operators, utils
|
||||
from .libs.replication.replication.constants import (ADDED, ERROR, FETCHED,
|
||||
from replication.constants import (ADDED, ERROR, FETCHED,
|
||||
MODIFIED, RP_COMMON, UP,
|
||||
STATE_ACTIVE, STATE_AUTH,
|
||||
STATE_CONFIG, STATE_SYNCING,
|
||||
@ -50,6 +50,8 @@ def printProgressBar(iteration, total, prefix='', suffix='', decimals=1, length=
|
||||
From here:
|
||||
https://gist.github.com/greenstick/b23e475d2bfdc3a82e34eaa1f6781ee4
|
||||
"""
|
||||
if total == 0:
|
||||
return ""
|
||||
filledLength = int(length * iteration // total)
|
||||
bar = fill * filledLength + fill_empty * (length - filledLength)
|
||||
return f"{prefix} |{bar}| {iteration}/{total}{suffix}"
|
||||
@ -84,7 +86,7 @@ def get_state_str(state):
|
||||
class SESSION_PT_settings(bpy.types.Panel):
|
||||
"""Settings panel"""
|
||||
bl_idname = "MULTIUSER_SETTINGS_PT_panel"
|
||||
bl_label = ""
|
||||
bl_label = " "
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Multiuser"
|
||||
@ -126,14 +128,18 @@ class SESSION_PT_settings(bpy.types.Panel):
|
||||
current_state = cli_state['STATE']
|
||||
|
||||
# STATE ACTIVE
|
||||
if current_state in [STATE_ACTIVE, STATE_LOBBY]:
|
||||
if current_state in [STATE_ACTIVE]:
|
||||
row.operator("session.stop", icon='QUIT', text="Exit")
|
||||
row = layout.row()
|
||||
if runtime_settings.is_host:
|
||||
row = row.box()
|
||||
row.label(text=f"{runtime_settings.internet_ip}:{settings.port}", icon='INFO')
|
||||
row.label(text=f"LAN: {runtime_settings.internet_ip}", icon='INFO')
|
||||
row = layout.row()
|
||||
|
||||
if current_state == STATE_LOBBY:
|
||||
row = row.box()
|
||||
row.label(text=f"Waiting the session to start", icon='INFO')
|
||||
row = layout.row()
|
||||
row.operator("session.stop", icon='QUIT', text="Exit")
|
||||
# CONNECTION STATE
|
||||
elif current_state in [STATE_SRV_SYNC,
|
||||
STATE_SYNCING,
|
||||
@ -356,17 +362,19 @@ class SESSION_PT_user(bpy.types.Panel):
|
||||
if active_user != 0 and active_user.username != settings.username:
|
||||
row = layout.row()
|
||||
user_operations = row.split()
|
||||
user_operations.alert = context.window_manager.session.time_snap_running
|
||||
user_operations.operator(
|
||||
"session.snapview",
|
||||
text="",
|
||||
icon='VIEW_CAMERA').target_client = active_user.username
|
||||
if operators.client.state['STATE'] == STATE_ACTIVE:
|
||||
|
||||
user_operations.alert = context.window_manager.session.time_snap_running
|
||||
user_operations.operator(
|
||||
"session.snapview",
|
||||
text="",
|
||||
icon='VIEW_CAMERA').target_client = active_user.username
|
||||
|
||||
user_operations.alert = context.window_manager.session.user_snap_running
|
||||
user_operations.operator(
|
||||
"session.snaptime",
|
||||
text="",
|
||||
icon='TIME').target_client = active_user.username
|
||||
user_operations.alert = context.window_manager.session.user_snap_running
|
||||
user_operations.operator(
|
||||
"session.snaptime",
|
||||
text="",
|
||||
icon='TIME').target_client = active_user.username
|
||||
|
||||
if operators.client.online_users[settings.username]['admin']:
|
||||
user_operations.operator(
|
||||
@ -532,9 +540,18 @@ class SESSION_PT_repository(bpy.types.Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
session = operators.client
|
||||
settings = utils.get_preferences()
|
||||
admin = False
|
||||
|
||||
if session and hasattr(session,'online_users'):
|
||||
usr = session.online_users.get(settings.username)
|
||||
if usr:
|
||||
admin = usr['admin']
|
||||
return hasattr(context.window_manager, 'session') and \
|
||||
operators.client and \
|
||||
operators.client.state['STATE'] in [STATE_ACTIVE, STATE_LOBBY]
|
||||
(operators.client.state['STATE'] == STATE_ACTIVE or \
|
||||
operators.client.state['STATE'] == STATE_LOBBY and admin)
|
||||
|
||||
def draw_header(self, context):
|
||||
self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE')
|
||||
|
@ -30,9 +30,11 @@ CONSTRAINTS_TYPES = [
|
||||
'COPY_ROTATION', 'COPY_SCALE', 'COPY_TRANSFORMS', 'LIMIT_DISTANCE',
|
||||
'LIMIT_LOCATION', 'LIMIT_ROTATION', 'LIMIT_SCALE', 'MAINTAIN_VOLUME',
|
||||
'TRANSFORM', 'TRANSFORM_CACHE', 'CLAMP_TO', 'DAMPED_TRACK', 'IK',
|
||||
'LOCKED_TRACK', 'SPLINE_IK', 'STRETCH_TO', 'TRACK_TO', 'ACTION',
|
||||
'LOCKED_TRACK', 'STRETCH_TO', 'TRACK_TO', 'ACTION',
|
||||
'ARMATURE', 'CHILD_OF', 'FLOOR', 'FOLLOW_PATH', 'PIVOT', 'SHRINKWRAP']
|
||||
|
||||
#temporary disabled 'SPLINE_IK' until its fixed
|
||||
|
||||
def test_object(clear_blend):
|
||||
bpy.ops.mesh.primitive_cube_add(
|
||||
enter_editmode=False, align='WORLD', location=(0, 0, 0))
|
||||
|
Reference in New Issue
Block a user