Compare commits
2 Commits
v0.0.3
...
96-add-a-k
Author | SHA1 | Date | |
---|---|---|---|
48866b74d3 | |||
d9f1031107 |
@ -5,3 +5,4 @@ stages:
|
|||||||
include:
|
include:
|
||||||
- local: .gitlab/ci/test.gitlab-ci.yml
|
- local: .gitlab/ci/test.gitlab-ci.yml
|
||||||
- local: .gitlab/ci/build.gitlab-ci.yml
|
- local: .gitlab/ci/build.gitlab-ci.yml
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
image: debian:stable-slim
|
image: python:latest
|
||||||
script:
|
script:
|
||||||
|
- git submodule init
|
||||||
|
- git submodule update
|
||||||
|
- cd multi_user/libs/replication
|
||||||
- rm -rf tests .git .gitignore script
|
- rm -rf tests .git .gitignore script
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
@ -9,4 +12,3 @@ build:
|
|||||||
paths:
|
paths:
|
||||||
- multi_user
|
- multi_user
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
image: python:3.7
|
image: python:latest
|
||||||
script:
|
script:
|
||||||
- git submodule init
|
- git submodule init
|
||||||
- git submodule update
|
- git submodule update
|
||||||
- apt-get update
|
- apt update
|
||||||
# install blender to get all required dependencies
|
# install blender to get all required dependencies
|
||||||
# TODO: indtall only dependencies
|
# TODO: indtall only dependencies
|
||||||
|
- apt install -f -y gcc python-dev python3.7-dev
|
||||||
- apt install -f -y blender
|
- apt install -f -y blender
|
||||||
- python -m pip install blender-addon-tester
|
- python3 -m pip install blender-addon-tester
|
||||||
- python scripts/test_addon.py
|
- python3 scripts/test_addon.py
|
||||||
|
|
||||||
|
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -0,0 +1,3 @@
|
|||||||
|
[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
|
## Quick installation
|
||||||
|
|
||||||
1. Download latest release [multi_user.zip](https://gitlab.com/slumber/multi-user/-/jobs/artifacts/master/download?job=build).
|
1. Download latest release [multi_user.zip](/uploads/8aef79c7cf5b1d9606dc58307fd9ad8b/multi_user.zip).
|
||||||
2. Run blender as administrator (dependencies installation).
|
2. Run blender as administrator (dependencies installation).
|
||||||
3. Install last_version.zip from your addon preferences.
|
3. Install last_version.zip from your addon preferences.
|
||||||
|
|
||||||
|
@ -8,4 +8,5 @@ Getting started
|
|||||||
|
|
||||||
install
|
install
|
||||||
quickstart
|
quickstart
|
||||||
|
known_problems
|
||||||
glossary
|
glossary
|
||||||
|
46
docs/getting_started/known_problems.rst
Normal file
46
docs/getting_started/known_problems.rst
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
.. _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,6 +48,7 @@ Documentation is organized into the following sections:
|
|||||||
|
|
||||||
getting_started/install
|
getting_started/install
|
||||||
getting_started/quickstart
|
getting_started/quickstart
|
||||||
|
getting_started/known_problems
|
||||||
getting_started/glossary
|
getting_started/glossary
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
@ -186,24 +186,25 @@ Using a regular command line
|
|||||||
You can run the dedicated server on any platform by following those steps:
|
You can run the dedicated server on any platform by following those steps:
|
||||||
|
|
||||||
1. Firstly, download and intall python 3 (3.6 or above).
|
1. Firstly, download and intall python 3 (3.6 or above).
|
||||||
2. Install the replication library:
|
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:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
python -m pip install replication
|
python -m pip install -r requirements.txt
|
||||||
|
|
||||||
4. Launch the server with:
|
4. Launch the server from the same terminal with:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
replication.serve
|
python scripts/server.py
|
||||||
|
|
||||||
.. hint::
|
.. hint::
|
||||||
You can also specify a custom **port** (-p), **timeout** (-t) and **admin password** (-pwd) with the following optionnal argument
|
You can also specify a custom **port** (-p), **timeout** (-t) and **admin password** (-pwd) with the following optionnal argument
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
replication.serve -p 5555 -pwd toto -t 1000
|
python scripts/server.py -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`).
|
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",
|
"author": "Swann Martinez",
|
||||||
"version": (0, 0, 3),
|
"version": (0, 0, 3),
|
||||||
"description": "Enable real-time collaborative workflow inside blender",
|
"description": "Enable real-time collaborative workflow inside blender",
|
||||||
"blender": (2, 82, 0),
|
"blender": (2, 80, 0),
|
||||||
"location": "3D View > Sidebar > Multi-User tab",
|
"location": "3D View > Sidebar > Multi-User tab",
|
||||||
"warning": "Unstable addon, use it at your own risks",
|
"warning": "Unstable addon, use it at your own risks",
|
||||||
"category": "Collaboration",
|
"category": "Collaboration",
|
||||||
@ -45,15 +45,22 @@ from . import environment, utils
|
|||||||
|
|
||||||
# TODO: remove dependency as soon as replication will be installed as a module
|
# TODO: remove dependency as soon as replication will be installed as a module
|
||||||
DEPENDENCIES = {
|
DEPENDENCIES = {
|
||||||
("replication", '0.0.20'),
|
("zmq","zmq"),
|
||||||
("deepdiff", '5.0.1'),
|
("jsondiff","jsondiff"),
|
||||||
|
("deepdiff", "deepdiff"),
|
||||||
|
("psutil","psutil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
libs = os.path.dirname(os.path.abspath(__file__))+"\\libs\\replication\\replication"
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
# Setup logging policy
|
# Setup logging policy
|
||||||
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
|
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
|
||||||
|
|
||||||
|
if libs not in sys.path:
|
||||||
|
sys.path.append(libs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
environment.setup(DEPENDENCIES, bpy.app.binary_path_python)
|
environment.setup(DEPENDENCIES, bpy.app.binary_path_python)
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
|
@ -722,22 +722,21 @@ class Singleton_updater(object):
|
|||||||
|
|
||||||
self._source_zip = os.path.join(local,"source.zip")
|
self._source_zip = os.path.join(local,"source.zip")
|
||||||
|
|
||||||
if self._verbose: print(f"Starting download update zip to {self._source_zip}")
|
if self._verbose: print("Starting download update zip")
|
||||||
try:
|
try:
|
||||||
import urllib3
|
request = urllib.request.Request(url)
|
||||||
http = urllib3.PoolManager()
|
context = ssl._create_unverified_context()
|
||||||
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)
|
|
||||||
|
|
||||||
r.release_conn()
|
# 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
|
||||||
if self._verbose: print("Successfully downloaded update zip")
|
if self._verbose: print("Successfully downloaded update zip")
|
||||||
return False
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._error = "Error retrieving download, bad link?"
|
self._error = "Error retrieving download, bad link?"
|
||||||
self._error_msg = "Error: {}".format(e)
|
self._error_msg = "Error: {}".format(e)
|
||||||
|
@ -38,7 +38,7 @@ __all__ = [
|
|||||||
] # Order here defines execution order
|
] # Order here defines execution order
|
||||||
|
|
||||||
from . import *
|
from . import *
|
||||||
from replication.data import ReplicatedDataFactory
|
from ..libs.replication.replication.data import ReplicatedDataFactory
|
||||||
|
|
||||||
def types_to_register():
|
def types_to_register():
|
||||||
return __all__
|
return __all__
|
||||||
|
@ -92,7 +92,6 @@ class BlArmature(BlDatablock):
|
|||||||
new_bone.head = bone_data['head_local']
|
new_bone.head = bone_data['head_local']
|
||||||
new_bone.tail_radius = bone_data['tail_radius']
|
new_bone.tail_radius = bone_data['tail_radius']
|
||||||
new_bone.head_radius = bone_data['head_radius']
|
new_bone.head_radius = bone_data['head_radius']
|
||||||
# new_bone.roll = bone_data['roll']
|
|
||||||
|
|
||||||
if 'parent' in bone_data:
|
if 'parent' in bone_data:
|
||||||
new_bone.parent = target.edit_bones[data['bones']
|
new_bone.parent = target.edit_bones[data['bones']
|
||||||
@ -124,8 +123,7 @@ class BlArmature(BlDatablock):
|
|||||||
'use_connect',
|
'use_connect',
|
||||||
'parent',
|
'parent',
|
||||||
'name',
|
'name',
|
||||||
'layers',
|
'layers'
|
||||||
# 'roll',
|
|
||||||
|
|
||||||
]
|
]
|
||||||
data = dumper.dump(instance)
|
data = dumper.dump(instance)
|
||||||
|
@ -45,22 +45,13 @@ class BlCamera(BlDatablock):
|
|||||||
if dof_settings:
|
if dof_settings:
|
||||||
loader.load(target.dof, 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):
|
def _dump_implementation(self, data, instance=None):
|
||||||
assert(instance)
|
assert(instance)
|
||||||
|
|
||||||
# TODO: background image support
|
# TODO: background image support
|
||||||
|
|
||||||
dumper = Dumper()
|
dumper = Dumper()
|
||||||
dumper.depth = 3
|
dumper.depth = 2
|
||||||
dumper.include_filter = [
|
dumper.include_filter = [
|
||||||
"name",
|
"name",
|
||||||
'type',
|
'type',
|
||||||
@ -88,24 +79,7 @@ class BlCamera(BlDatablock):
|
|||||||
'sensor_fit',
|
'sensor_fit',
|
||||||
'sensor_height',
|
'sensor_height',
|
||||||
'sensor_width',
|
'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)
|
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 .. import utils
|
||||||
from .dump_anything import Loader, Dumper
|
from .dump_anything import Loader, Dumper
|
||||||
from replication.data import ReplicatedDatablock
|
from ..libs.replication.replication.data import ReplicatedDatablock
|
||||||
from replication.constants import (UP, DIFF_BINARY)
|
from ..libs.replication.replication.constants import (UP, DIFF_BINARY)
|
||||||
|
|
||||||
|
|
||||||
def has_action(target):
|
def has_action(target):
|
||||||
@ -117,10 +117,9 @@ class BlDatablock(ReplicatedDatablock):
|
|||||||
datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root)
|
datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root)
|
||||||
|
|
||||||
if not datablock_ref:
|
if not datablock_ref:
|
||||||
try:
|
datablock_ref = datablock_root.get(
|
||||||
datablock_ref = datablock_root[self.data['name']]
|
self.data['name'], # Resolve by name
|
||||||
except Exception:
|
self._construct(data=self.data)) # If it doesn't exist create it
|
||||||
datablock_ref = self._construct(data=self.data)
|
|
||||||
|
|
||||||
if datablock_ref:
|
if datablock_ref:
|
||||||
setattr(datablock_ref, 'uuid', self.uuid)
|
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 .dump_anything import Dumper, Loader, np_dump_collection, np_load_collection
|
||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
from replication.exception import ContextError
|
from ..libs.replication.replication.exception import ContextError
|
||||||
|
|
||||||
POINT = ['co', 'weight_softbody', 'co_deform']
|
POINT = ['co', 'weight_softbody', 'co_deform']
|
||||||
|
|
||||||
|
@ -19,13 +19,11 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
|
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from .dump_anything import Loader, Dumper
|
from .dump_anything import Loader, Dumper
|
||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
|
|
||||||
NODE_SOCKET_INDEX = re.compile('\[(\d*)\]')
|
|
||||||
|
|
||||||
def load_node(node_data, node_tree):
|
def load_node(node_data, node_tree):
|
||||||
""" Load a node into a node_tree from a dict
|
""" Load a node into a node_tree from a dict
|
||||||
@ -40,13 +38,14 @@ def load_node(node_data, node_tree):
|
|||||||
|
|
||||||
loader.load(target_node, node_data)
|
loader.load(target_node, node_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for input in node_data["inputs"]:
|
for input in node_data["inputs"]:
|
||||||
if hasattr(target_node.inputs[input], "default_value"):
|
if hasattr(target_node.inputs[input], "default_value"):
|
||||||
try:
|
try:
|
||||||
target_node.inputs[input].default_value = node_data["inputs"][input]["default_value"]
|
target_node.inputs[input].default_value = node_data["inputs"][input]["default_value"]
|
||||||
except:
|
except:
|
||||||
logging.error(
|
logging.error(f"Material {input} parameter not supported, skipping")
|
||||||
f"Material {input} parameter not supported, skipping")
|
|
||||||
|
|
||||||
|
|
||||||
def load_links(links_data, node_tree):
|
def load_links(links_data, node_tree):
|
||||||
@ -61,6 +60,7 @@ def load_links(links_data, node_tree):
|
|||||||
for link in links_data:
|
for link in links_data:
|
||||||
input_socket = node_tree.nodes[link['to_node']].inputs[int(link['to_socket'])]
|
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'])]
|
output_socket = node_tree.nodes[link['from_node']].outputs[int(link['from_socket'])]
|
||||||
|
|
||||||
node_tree.links.new(input_socket, output_socket)
|
node_tree.links.new(input_socket, output_socket)
|
||||||
|
|
||||||
|
|
||||||
@ -75,13 +75,11 @@ def dump_links(links):
|
|||||||
links_data = []
|
links_data = []
|
||||||
|
|
||||||
for link in links:
|
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({
|
links_data.append({
|
||||||
'to_node':link.to_node.name,
|
'to_node':link.to_node.name,
|
||||||
'to_socket': to_socket,
|
'to_socket':link.to_socket.path_from_id()[-2:-1],
|
||||||
'from_node':link.from_node.name,
|
'from_node':link.from_node.name,
|
||||||
'from_socket': from_socket,
|
'from_socket':link.from_socket.path_from_id()[-2:-1],
|
||||||
})
|
})
|
||||||
|
|
||||||
return links_data
|
return links_data
|
||||||
@ -178,6 +176,7 @@ class BlMaterial(BlDatablock):
|
|||||||
loader.load(
|
loader.load(
|
||||||
target.grease_pencil, data['grease_pencil'])
|
target.grease_pencil, data['grease_pencil'])
|
||||||
|
|
||||||
|
|
||||||
if data["use_nodes"]:
|
if data["use_nodes"]:
|
||||||
if target.node_tree is None:
|
if target.node_tree is None:
|
||||||
target.use_nodes = True
|
target.use_nodes = True
|
||||||
@ -266,3 +265,4 @@ class BlMaterial(BlDatablock):
|
|||||||
deps.append(self.instance.library)
|
deps.append(self.instance.library)
|
||||||
|
|
||||||
return deps
|
return deps
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ import logging
|
|||||||
import numpy as np
|
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 .dump_anything import Dumper, Loader, np_load_collection_primitives, np_dump_collection_primitive, np_load_collection, np_dump_collection
|
||||||
from replication.constants import DIFF_BINARY
|
from ..libs.replication.replication.constants import DIFF_BINARY
|
||||||
from replication.exception import ContextError
|
from ..libs.replication.replication.exception import ContextError
|
||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import logging
|
|||||||
|
|
||||||
from .dump_anything import Loader, Dumper
|
from .dump_anything import Loader, Dumper
|
||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
from replication.exception import ContextError
|
from ..libs.replication.replication.exception import ContextError
|
||||||
|
|
||||||
|
|
||||||
def load_pose(target_bone, data):
|
def load_pose(target_bone, data):
|
||||||
@ -152,13 +152,6 @@ class BlObject(BlDatablock):
|
|||||||
|
|
||||||
target.data.shape_keys.key_blocks[key_block].relative_key = target.data.shape_keys.key_blocks[reference]
|
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):
|
def _dump_implementation(self, data, instance=None):
|
||||||
assert(instance)
|
assert(instance)
|
||||||
|
|
||||||
@ -178,14 +171,6 @@ class BlObject(BlDatablock):
|
|||||||
"library",
|
"library",
|
||||||
"empty_display_type",
|
"empty_display_type",
|
||||||
"empty_display_size",
|
"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_collection",
|
||||||
"instance_type",
|
"instance_type",
|
||||||
"location",
|
"location",
|
||||||
|
@ -20,7 +20,7 @@ import logging
|
|||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from . import operators, presence, utils
|
from . import operators, presence, utils
|
||||||
from replication.constants import (FETCHED,
|
from .libs.replication.replication.constants import (FETCHED,
|
||||||
RP_COMMON,
|
RP_COMMON,
|
||||||
STATE_INITIAL,
|
STATE_INITIAL,
|
||||||
STATE_QUITTING,
|
STATE_QUITTING,
|
||||||
@ -239,7 +239,7 @@ class DrawClient(Draw):
|
|||||||
|
|
||||||
|
|
||||||
class ClientUpdate(Timer):
|
class ClientUpdate(Timer):
|
||||||
def __init__(self, timout=.032):
|
def __init__(self, timout=.016):
|
||||||
super().__init__(timout)
|
super().__init__(timout)
|
||||||
self.handle_quit = False
|
self.handle_quit = False
|
||||||
self.users_metadata = {}
|
self.users_metadata = {}
|
||||||
@ -298,23 +298,6 @@ class ClientUpdate(Timer):
|
|||||||
local_user_metadata['view_corners'] = current_view_corners
|
local_user_metadata['view_corners'] = current_view_corners
|
||||||
local_user_metadata['view_matrix'] = presence.get_view_matrix()
|
local_user_metadata['view_matrix'] = presence.get_view_matrix()
|
||||||
session.update_user_metadata(local_user_metadata)
|
session.update_user_metadata(local_user_metadata)
|
||||||
|
|
||||||
class SessionStatusUpdate(Timer):
|
|
||||||
def __init__(self, timout=1):
|
|
||||||
super().__init__(timout)
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
presence.refresh_sidebar_view()
|
|
||||||
|
|
||||||
class SessionUserSync(Timer):
|
|
||||||
def __init__(self, timout=1):
|
|
||||||
super().__init__(timout)
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
session = getattr(operators, 'client', None)
|
|
||||||
renderer = getattr(presence, 'renderer', None)
|
|
||||||
|
|
||||||
if session and renderer:
|
|
||||||
# sync online users
|
# sync online users
|
||||||
session_users = operators.client.online_users
|
session_users = operators.client.online_users
|
||||||
ui_users = bpy.context.window_manager.online_users
|
ui_users = bpy.context.window_manager.online_users
|
||||||
@ -331,3 +314,15 @@ class SessionUserSync(Timer):
|
|||||||
new_key = ui_users.add()
|
new_key = ui_users.add()
|
||||||
new_key.name = user
|
new_key.name = user
|
||||||
new_key.username = 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()
|
||||||
|
|
||||||
|
operators.unregister_delayables()
|
||||||
|
|
||||||
|
presence.renderer.stop()
|
||||||
|
|
||||||
|
presence.refresh_sidebar_view()
|
||||||
|
@ -23,9 +23,6 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import socket
|
import socket
|
||||||
import re
|
|
||||||
|
|
||||||
VERSION_EXPR = re.compile('\d+\.\d+\.\d+')
|
|
||||||
|
|
||||||
THIRD_PARTY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "libs")
|
THIRD_PARTY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "libs")
|
||||||
DEFAULT_CACHE_DIR = os.path.join(
|
DEFAULT_CACHE_DIR = os.path.join(
|
||||||
@ -50,22 +47,10 @@ def install_pip():
|
|||||||
subprocess.run([str(PYTHON_PATH), "-m", "ensurepip"])
|
subprocess.run([str(PYTHON_PATH), "-m", "ensurepip"])
|
||||||
|
|
||||||
|
|
||||||
def install_package(name, version):
|
def install_package(name):
|
||||||
logging.info(f"installing {name} version...")
|
logging.debug(f"Using {PYTHON_PATH} for installation")
|
||||||
subprocess.run([str(PYTHON_PATH), "-m", "pip", "install", f"{name}=={version}"])
|
subprocess.run([str(PYTHON_PATH), "-m", "pip", "install", name])
|
||||||
|
|
||||||
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():
|
def get_ip():
|
||||||
"""
|
"""
|
||||||
@ -93,9 +78,7 @@ def setup(dependencies, python_path):
|
|||||||
if not module_can_be_imported("pip"):
|
if not module_can_be_imported("pip"):
|
||||||
install_pip()
|
install_pip()
|
||||||
|
|
||||||
for package_name, package_version in dependencies:
|
for module_name, package_name in dependencies:
|
||||||
if not module_can_be_imported(package_name):
|
if not module_can_be_imported(module_name):
|
||||||
install_package(package_name, package_version)
|
install_package(package_name)
|
||||||
module_can_be_imported(package_name)
|
module_can_be_imported(package_name)
|
||||||
elif not check_package_version(package_name, package_version):
|
|
||||||
install_package(package_name, package_version)
|
|
||||||
|
0
multi_user/libs/__init__.py
Normal file
0
multi_user/libs/__init__.py
Normal file
1
multi_user/libs/replication
Submodule
1
multi_user/libs/replication
Submodule
Submodule multi_user/libs/replication added at 4b04cf7474
@ -33,19 +33,31 @@ import mathutils
|
|||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
|
|
||||||
from . import bl_types, delayable, environment, presence, ui, utils
|
from . import bl_types, delayable, environment, presence, ui, utils
|
||||||
from replication.constants import (FETCHED, STATE_ACTIVE,
|
from .libs.replication.replication.constants import (FETCHED, STATE_ACTIVE,
|
||||||
STATE_INITIAL,
|
STATE_INITIAL,
|
||||||
STATE_SYNCING)
|
STATE_SYNCING)
|
||||||
from replication.data import ReplicatedDataFactory
|
from .libs.replication.replication.data import ReplicatedDataFactory
|
||||||
from replication.exception import NonAuthorizedOperationError
|
from .libs.replication.replication.exception import NonAuthorizedOperationError
|
||||||
from replication.interface import Session
|
from .libs.replication.replication.interface import Session
|
||||||
|
|
||||||
|
|
||||||
client = None
|
client = None
|
||||||
delayables = []
|
delayables = []
|
||||||
stop_modal_executor = False
|
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
|
# OPERATORS
|
||||||
|
|
||||||
|
|
||||||
@ -68,6 +80,7 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
users = bpy.data.window_managers['WinMan'].online_users
|
users = bpy.data.window_managers['WinMan'].online_users
|
||||||
admin_pass = runtime_settings.password
|
admin_pass = runtime_settings.password
|
||||||
|
|
||||||
|
unregister_delayables()
|
||||||
users.clear()
|
users.clear()
|
||||||
delayables.clear()
|
delayables.clear()
|
||||||
|
|
||||||
@ -150,23 +163,6 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
delayables.append(delayable.DrawClient())
|
delayables.append(delayable.DrawClient())
|
||||||
delayables.append(delayable.DynamicRightSelectTimer())
|
delayables.append(delayable.DynamicRightSelectTimer())
|
||||||
|
|
||||||
session_update = delayable.SessionStatusUpdate()
|
|
||||||
session_user_sync = delayable.SessionUserSync()
|
|
||||||
session_update.register()
|
|
||||||
session_user_sync.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
|
# Launch drawing module
|
||||||
if runtime_settings.enable_presence:
|
if runtime_settings.enable_presence:
|
||||||
presence.renderer.run()
|
presence.renderer.run()
|
||||||
@ -175,19 +171,8 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
for d in delayables:
|
for d in delayables:
|
||||||
d.register()
|
d.register()
|
||||||
|
|
||||||
@client.register('on_exit')
|
global modal_executor_queue
|
||||||
def desinitialize_session():
|
modal_executor_queue = queue.Queue()
|
||||||
global delayables, stop_modal_executor
|
|
||||||
|
|
||||||
for d in delayables:
|
|
||||||
try:
|
|
||||||
d.unregister()
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
stop_modal_executor = True
|
|
||||||
presence.renderer.stop()
|
|
||||||
|
|
||||||
bpy.ops.session.apply_armature_operator()
|
bpy.ops.session.apply_armature_operator()
|
||||||
|
|
||||||
self.report(
|
self.report(
|
||||||
@ -542,7 +527,7 @@ class ApplyArmatureOperator(bpy.types.Operator):
|
|||||||
try:
|
try:
|
||||||
client.apply(node)
|
client.apply(node)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Fail to apply armature: {e}")
|
logging.error("Dail to apply armature: {e}")
|
||||||
|
|
||||||
return {'PASS_THROUGH'}
|
return {'PASS_THROUGH'}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import string
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from . import utils, bl_types, environment, addon_updater_ops, presence, ui
|
from . import utils, bl_types, environment, addon_updater_ops, presence, ui
|
||||||
from replication.constants import RP_COMMON
|
from .libs.replication.replication.constants import RP_COMMON
|
||||||
|
|
||||||
IP_EXPR = re.compile('\d+\.\d+\.\d+\.\d+')
|
IP_EXPR = re.compile('\d+\.\d+\.\d+\.\d+')
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import traceback
|
|
||||||
|
|
||||||
import bgl
|
import bgl
|
||||||
import blf
|
import blf
|
||||||
@ -312,10 +311,10 @@ class DrawFactory(object):
|
|||||||
self.d2d_items[client_id] = (position[1], client_id, color)
|
self.d2d_items[client_id] = (position[1], client_id, color)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.debug(f"Draw client exception: {e} \n {traceback.format_exc()}\n pos:{position},ind:{indices}")
|
logging.error(f"Draw client exception: {e}")
|
||||||
|
|
||||||
def draw3d_callback(self):
|
def draw3d_callback(self):
|
||||||
bgl.glLineWidth(2.)
|
bgl.glLineWidth(1.5)
|
||||||
bgl.glEnable(bgl.GL_DEPTH_TEST)
|
bgl.glEnable(bgl.GL_DEPTH_TEST)
|
||||||
bgl.glEnable(bgl.GL_BLEND)
|
bgl.glEnable(bgl.GL_BLEND)
|
||||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from . import operators, utils
|
from . import operators, utils
|
||||||
from replication.constants import (ADDED, ERROR, FETCHED,
|
from .libs.replication.replication.constants import (ADDED, ERROR, FETCHED,
|
||||||
MODIFIED, RP_COMMON, UP,
|
MODIFIED, RP_COMMON, UP,
|
||||||
STATE_ACTIVE, STATE_AUTH,
|
STATE_ACTIVE, STATE_AUTH,
|
||||||
STATE_CONFIG, STATE_SYNCING,
|
STATE_CONFIG, STATE_SYNCING,
|
||||||
@ -50,8 +50,6 @@ def printProgressBar(iteration, total, prefix='', suffix='', decimals=1, length=
|
|||||||
From here:
|
From here:
|
||||||
https://gist.github.com/greenstick/b23e475d2bfdc3a82e34eaa1f6781ee4
|
https://gist.github.com/greenstick/b23e475d2bfdc3a82e34eaa1f6781ee4
|
||||||
"""
|
"""
|
||||||
if total == 0:
|
|
||||||
return ""
|
|
||||||
filledLength = int(length * iteration // total)
|
filledLength = int(length * iteration // total)
|
||||||
bar = fill * filledLength + fill_empty * (length - filledLength)
|
bar = fill * filledLength + fill_empty * (length - filledLength)
|
||||||
return f"{prefix} |{bar}| {iteration}/{total}{suffix}"
|
return f"{prefix} |{bar}| {iteration}/{total}{suffix}"
|
||||||
@ -128,18 +126,14 @@ class SESSION_PT_settings(bpy.types.Panel):
|
|||||||
current_state = cli_state['STATE']
|
current_state = cli_state['STATE']
|
||||||
|
|
||||||
# STATE ACTIVE
|
# STATE ACTIVE
|
||||||
if current_state in [STATE_ACTIVE]:
|
if current_state in [STATE_ACTIVE, STATE_LOBBY]:
|
||||||
row.operator("session.stop", icon='QUIT', text="Exit")
|
row.operator("session.stop", icon='QUIT', text="Exit")
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
if runtime_settings.is_host:
|
if runtime_settings.is_host:
|
||||||
row = row.box()
|
row = row.box()
|
||||||
row.label(text=f"LAN: {runtime_settings.internet_ip}", icon='INFO')
|
row.label(text=f"{runtime_settings.internet_ip}:{settings.port}", icon='INFO')
|
||||||
row = layout.row()
|
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
|
# CONNECTION STATE
|
||||||
elif current_state in [STATE_SRV_SYNC,
|
elif current_state in [STATE_SRV_SYNC,
|
||||||
STATE_SYNCING,
|
STATE_SYNCING,
|
||||||
@ -362,8 +356,6 @@ class SESSION_PT_user(bpy.types.Panel):
|
|||||||
if active_user != 0 and active_user.username != settings.username:
|
if active_user != 0 and active_user.username != settings.username:
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
user_operations = row.split()
|
user_operations = row.split()
|
||||||
if operators.client.state['STATE'] == STATE_ACTIVE:
|
|
||||||
|
|
||||||
user_operations.alert = context.window_manager.session.time_snap_running
|
user_operations.alert = context.window_manager.session.time_snap_running
|
||||||
user_operations.operator(
|
user_operations.operator(
|
||||||
"session.snapview",
|
"session.snapview",
|
||||||
@ -540,18 +532,9 @@ class SESSION_PT_repository(bpy.types.Panel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
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 \
|
return hasattr(context.window_manager, 'session') and \
|
||||||
operators.client and \
|
operators.client and \
|
||||||
(operators.client.state['STATE'] == STATE_ACTIVE or \
|
operators.client.state['STATE'] in [STATE_ACTIVE, STATE_LOBBY]
|
||||||
operators.client.state['STATE'] == STATE_LOBBY and admin)
|
|
||||||
|
|
||||||
def draw_header(self, context):
|
def draw_header(self, context):
|
||||||
self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE')
|
self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE')
|
||||||
|
@ -30,11 +30,9 @@ CONSTRAINTS_TYPES = [
|
|||||||
'COPY_ROTATION', 'COPY_SCALE', 'COPY_TRANSFORMS', 'LIMIT_DISTANCE',
|
'COPY_ROTATION', 'COPY_SCALE', 'COPY_TRANSFORMS', 'LIMIT_DISTANCE',
|
||||||
'LIMIT_LOCATION', 'LIMIT_ROTATION', 'LIMIT_SCALE', 'MAINTAIN_VOLUME',
|
'LIMIT_LOCATION', 'LIMIT_ROTATION', 'LIMIT_SCALE', 'MAINTAIN_VOLUME',
|
||||||
'TRANSFORM', 'TRANSFORM_CACHE', 'CLAMP_TO', 'DAMPED_TRACK', 'IK',
|
'TRANSFORM', 'TRANSFORM_CACHE', 'CLAMP_TO', 'DAMPED_TRACK', 'IK',
|
||||||
'LOCKED_TRACK', 'STRETCH_TO', 'TRACK_TO', 'ACTION',
|
'LOCKED_TRACK', 'SPLINE_IK', 'STRETCH_TO', 'TRACK_TO', 'ACTION',
|
||||||
'ARMATURE', 'CHILD_OF', 'FLOOR', 'FOLLOW_PATH', 'PIVOT', 'SHRINKWRAP']
|
'ARMATURE', 'CHILD_OF', 'FLOOR', 'FOLLOW_PATH', 'PIVOT', 'SHRINKWRAP']
|
||||||
|
|
||||||
#temporary disabled 'SPLINE_IK' until its fixed
|
|
||||||
|
|
||||||
def test_object(clear_blend):
|
def test_object(clear_blend):
|
||||||
bpy.ops.mesh.primitive_cube_add(
|
bpy.ops.mesh.primitive_cube_add(
|
||||||
enter_editmode=False, align='WORLD', location=(0, 0, 0))
|
enter_editmode=False, align='WORLD', location=(0, 0, 0))
|
||||||
|
Reference in New Issue
Block a user