2020-03-20 21:56:50 +08:00
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####
2019-05-02 23:58:37 +08:00
import asyncio
2020-12-16 18:05:58 +08:00
import copy
import gzip
2019-02-12 00:02:25 +08:00
import logging
2021-07-21 17:12:17 +08:00
from multi_user . preferences import ServerPreset
2019-05-02 23:58:37 +08:00
import os
2019-08-08 17:43:12 +08:00
import queue
2019-02-22 23:46:35 +08:00
import random
2020-10-06 05:38:52 +08:00
import shutil
2019-02-22 23:46:35 +08:00
import string
2020-12-02 21:21:49 +08:00
import sys
2019-03-25 21:56:09 +08:00
import time
2021-06-18 20:59:56 +08:00
import traceback
2021-07-19 22:03:12 +08:00
from uuid import uuid4
2020-12-12 06:02:20 +08:00
from datetime import datetime
2019-04-03 21:07:35 +08:00
from operator import itemgetter
2019-08-08 17:43:12 +08:00
from pathlib import Path
2020-10-01 16:58:30 +08:00
from queue import Queue
2020-12-16 18:05:58 +08:00
from time import gmtime , strftime
2021-06-08 23:03:43 +08:00
from bpy . props import FloatProperty
2021-11-18 18:40:56 +08:00
import bmesh
2021-06-08 23:03:43 +08:00
2020-12-16 18:05:58 +08:00
try :
import _pickle as pickle
except ImportError :
import pickle
2019-09-30 19:35:50 +08:00
2019-03-25 21:56:09 +08:00
import bpy
import mathutils
2019-09-16 17:42:53 +08:00
from bpy . app . handlers import persistent
2020-12-16 18:05:58 +08:00
from bpy_extras . io_utils import ExportHelper , ImportHelper
2021-06-18 20:59:56 +08:00
from replication import porcelain
2020-12-22 23:04:50 +08:00
from replication . constants import ( COMMITED , FETCHED , RP_COMMON , STATE_ACTIVE ,
STATE_INITIAL , STATE_SYNCING , UP )
2021-03-09 17:19:51 +08:00
from replication . exception import ContextError , NonAuthorizedOperationError
from replication . interface import session
2021-05-19 05:14:09 +08:00
from replication . objects import Node
2021-06-18 20:59:56 +08:00
from replication . protocol import DataTranslationProtocol
from replication . repository import Repository
2019-03-25 21:56:09 +08:00
2021-06-18 20:59:56 +08:00
from . import bl_types , environment , shared_data , timers , ui , utils
from . handlers import on_scene_update , sanitize_deps_graph
2021-07-21 17:12:17 +08:00
from . presence import SessionStatusWidget , renderer , view3d_find , refresh_sidebar_view
2020-12-22 23:04:50 +08:00
from . timers import registry
2019-02-08 22:44:02 +08:00
2020-10-02 18:11:53 +08:00
background_execution_queue = Queue ( )
2020-12-10 01:35:29 +08:00
deleyables = [ ]
2019-11-05 21:22:57 +08:00
stop_modal_executor = False
2020-02-26 19:03:48 +08:00
2021-11-18 18:40:56 +08:00
def loard_users ( username , view_corners , radius = 0.01 , color = ( 1 , 1 , 1 , 0 ) ) :
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 = 10
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 )
bpy . context . scene . collection . children . link ( user_collection )
2020-10-02 18:11:53 +08:00
def session_callback ( name ) :
""" Session callback wrapper
This allow to encapsulate session callbacks to background_execution_queue .
By doing this way callback are executed from the main thread .
"""
def func_wrapper ( func ) :
@session.register ( name )
2020-11-26 18:37:51 +08:00
def add_background_task ( * * kwargs ) :
background_execution_queue . put ( ( func , kwargs ) )
2020-10-02 18:11:53 +08:00
return add_background_task
return func_wrapper
@session_callback ( ' on_connection ' )
def initialize_session ( ) :
2020-10-01 16:58:30 +08:00
""" Session connection init hander
"""
runtime_settings = bpy . context . window_manager . session
2021-06-03 17:41:25 +08:00
if not runtime_settings . is_host :
logging . info ( " Intializing the scene " )
# Step 1: Constrect nodes
logging . info ( " Instantiating nodes " )
2021-06-04 20:02:09 +08:00
for node in session . repository . index_sorted :
node_ref = session . repository . graph . get ( node )
2021-06-03 17:41:25 +08:00
if node_ref is None :
logging . error ( f " Can ' t construct node { node } " )
elif node_ref . state == FETCHED :
node_ref . instance = session . repository . rdp . resolve ( node_ref . data )
if node_ref . instance is None :
node_ref . instance = session . repository . rdp . construct ( node_ref . data )
node_ref . instance . uuid = node_ref . uuid
# Step 2: Load nodes
logging . info ( " Applying nodes " )
2021-06-22 00:38:43 +08:00
for node in session . repository . heads :
2021-06-03 17:41:25 +08:00
porcelain . apply ( session . repository , node )
2020-10-01 16:58:30 +08:00
2021-01-13 22:36:41 +08:00
logging . info ( " Registering timers " )
2020-10-01 16:58:30 +08:00
# Step 4: Register blender timers
2020-12-10 01:35:29 +08:00
for d in deleyables :
2020-10-01 16:58:30 +08:00
d . register ( )
2021-01-13 21:45:23 +08:00
# Step 5: Clearing history
2020-12-24 01:46:29 +08:00
utils . flush_history ( )
2020-12-24 00:27:43 +08:00
2021-01-13 21:45:23 +08:00
# Step 6: Launch deps graph update handling
2021-06-18 20:59:56 +08:00
bpy . app . handlers . depsgraph_update_post . append ( on_scene_update )
2021-01-13 21:45:23 +08:00
2020-10-02 18:11:53 +08:00
@session_callback ( ' on_exit ' )
2020-11-26 18:37:51 +08:00
def on_connection_end ( reason = " none " ) :
2020-10-01 16:58:30 +08:00
""" Session connection finished handler
"""
2020-12-10 01:35:29 +08:00
global deleyables , stop_modal_executor
2020-10-01 16:58:30 +08:00
settings = utils . get_preferences ( )
# Step 1: Unregister blender timers
2020-12-10 01:35:29 +08:00
for d in deleyables :
2020-10-01 16:58:30 +08:00
try :
d . unregister ( )
except :
continue
2020-12-10 01:35:29 +08:00
deleyables . clear ( )
2020-10-02 18:11:53 +08:00
2020-10-01 16:58:30 +08:00
stop_modal_executor = True
2021-06-18 20:59:56 +08:00
if on_scene_update in bpy . app . handlers . depsgraph_update_post :
bpy . app . handlers . depsgraph_update_post . remove ( on_scene_update )
2020-10-02 18:11:53 +08:00
2020-10-01 16:58:30 +08:00
# Step 3: remove file handled
logger = logging . getLogger ( )
for handler in logger . handlers :
if isinstance ( handler , logging . FileHandler ) :
logger . removeHandler ( handler )
2020-11-26 18:37:51 +08:00
if reason != " user " :
2021-07-23 18:51:16 +08:00
bpy . ops . session . notify ( ' INVOKE_DEFAULT ' , message = f " Disconnected from session. Reason: { reason } . " ) #TODO: change op session.notify to add ui + change reason (in replication->interface)
2020-11-26 05:53:38 +08:00
2021-07-26 23:30:56 +08:00
def setup_logging ( ) :
""" Session setup logging (host/connect)
"""
settings = utils . get_preferences ( )
logger = logging . getLogger ( )
if len ( logger . handlers ) == 1 :
formatter = logging . Formatter (
fmt = ' %(asctime)s CLIENT %(levelname)-8s %(message)s ' ,
datefmt = ' % H: % M: % S '
)
start_time = datetime . now ( ) . strftime ( ' % Y_ % m_ %d _ % H- % M- % S ' )
log_directory = os . path . join (
settings . cache_directory ,
f " multiuser_ { start_time } .log " )
os . makedirs ( settings . cache_directory , exist_ok = True )
handler = logging . FileHandler ( log_directory , mode = ' w ' )
logger . addHandler ( handler )
for handler in logger . handlers :
if isinstance ( handler , logging . NullHandler ) :
continue
handler . setFormatter ( formatter )
def setup_timer ( ) :
""" Session setup timer (host/connect)
"""
settings = utils . get_preferences ( )
deleyables . append ( timers . ClientUpdate ( ) )
deleyables . append ( timers . DynamicRightSelectTimer ( ) )
deleyables . append ( timers . ApplyTimer ( timeout = settings . depsgraph_update_rate ) )
session_update = timers . SessionStatusUpdate ( )
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 ( )
deleyables . append ( session_background_executor )
deleyables . append ( session_update )
deleyables . append ( session_user_sync )
deleyables . append ( session_listen )
deleyables . append ( timers . AnnotationUpdates ( ) )
def get_active_server_preset ( context ) :
active_index = context . window_manager . server_index
server_presets = utils . get_preferences ( ) . server_preset
active_index = active_index if active_index < = len ( server_presets ) - 1 else 0
return server_presets [ active_index ]
2020-01-22 23:17:48 +08:00
2020-10-01 16:58:30 +08:00
# OPERATORS
2021-07-19 22:03:12 +08:00
class SessionConnectOperator ( bpy . types . Operator ) :
bl_idname = " session.connect "
bl_label = " connect "
2019-02-11 22:48:07 +08:00
bl_description = " connect to a net server "
2021-07-19 22:03:12 +08:00
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
global deleyables
settings = utils . get_preferences ( )
users = bpy . data . window_managers [ ' WinMan ' ] . online_users
2021-07-27 23:03:44 +08:00
active_server = get_active_server_preset ( context )
2021-07-23 18:51:16 +08:00
admin_pass = active_server . admin_password if active_server . use_admin_password else None
2021-07-28 23:33:07 +08:00
server_pass = active_server . server_password if active_server . use_server_password else ' '
2021-07-19 22:03:12 +08:00
users . clear ( )
deleyables . clear ( )
2021-07-26 23:30:56 +08:00
setup_logging ( )
2021-07-19 22:03:12 +08:00
bpy_protocol = bl_types . get_data_translation_protocol ( )
# Check if supported_datablocks are up to date before starting the
# the session
for dcc_type_id in bpy_protocol . implementations . keys ( ) :
if dcc_type_id not in settings . supported_datablocks :
logging . info ( f " { dcc_type_id } not found, \
regenerate type settings . . . " )
settings . generate_supported_types ( )
2021-11-05 22:39:46 +08:00
if bpy . app . version > = ( 2 , 91 , 0 ) :
2021-07-19 22:03:12 +08:00
python_binary_path = sys . executable
else :
python_binary_path = bpy . app . binary_path_python
repo = Repository (
rdp = bpy_protocol ,
username = settings . username )
# Join a session
2021-07-23 18:51:16 +08:00
if not active_server . use_admin_password :
2021-07-19 22:03:12 +08:00
utils . clean_scene ( )
try :
porcelain . remote_add (
repo ,
' origin ' ,
2021-07-23 18:51:16 +08:00
active_server . ip ,
active_server . port ,
2021-07-19 22:03:12 +08:00
server_password = server_pass ,
admin_password = admin_pass )
session . connect (
repository = repo ,
timeout = settings . connection_timeout ,
server_password = server_pass ,
admin_password = admin_pass
)
except Exception as e :
self . report ( { ' ERROR ' } , str ( e ) )
logging . error ( str ( e ) )
# Background client updates service
2021-07-26 23:30:56 +08:00
setup_timer ( )
2021-07-19 22:03:12 +08:00
return { " FINISHED " }
class SessionHostOperator ( bpy . types . Operator ) :
bl_idname = " session.host "
bl_label = " host "
bl_description = " host server "
2019-08-05 23:58:56 +08:00
2019-02-11 22:48:07 +08:00
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
2020-12-10 01:35:29 +08:00
global deleyables
2020-06-22 23:25:44 +08:00
2020-03-02 18:09:45 +08:00
settings = utils . get_preferences ( )
2020-02-29 00:34:30 +08:00
runtime_settings = context . window_manager . session
2020-01-18 01:15:37 +08:00
users = bpy . data . window_managers [ ' WinMan ' ] . online_users
2021-07-23 18:51:16 +08:00
admin_pass = settings . host_admin_password if settings . host_use_admin_password else None
2021-07-28 23:33:07 +08:00
server_pass = settings . host_server_password if settings . host_use_server_password else ' '
2021-01-10 05:36:00 +08:00
2020-01-18 01:15:37 +08:00
users . clear ( )
2020-12-10 01:35:29 +08:00
deleyables . clear ( )
2020-09-23 23:37:21 +08:00
2021-07-26 23:30:56 +08:00
setup_logging ( )
2020-09-15 18:31:46 +08:00
2021-05-19 05:14:09 +08:00
bpy_protocol = bl_types . get_data_translation_protocol ( )
2019-09-18 23:54:02 +08:00
2021-05-19 05:14:09 +08:00
# Check if supported_datablocks are up to date before starting the
# the session
2021-05-19 16:52:04 +08:00
for dcc_type_id in bpy_protocol . implementations . keys ( ) :
if dcc_type_id not in settings . supported_datablocks :
logging . info ( f " { dcc_type_id } not found, \
2020-09-23 23:37:21 +08:00
regenerate type settings . . . " )
settings . generate_supported_types ( )
2019-08-23 18:28:57 +08:00
2021-11-05 22:39:46 +08:00
if bpy . app . version > = ( 2 , 91 , 0 ) :
2020-12-02 21:21:49 +08:00
python_binary_path = sys . executable
else :
python_binary_path = bpy . app . binary_path_python
2021-05-18 22:54:07 +08:00
repo = Repository (
2021-05-19 05:14:09 +08:00
rdp = bpy_protocol ,
2021-05-18 22:54:07 +08:00
username = settings . username )
2020-02-07 21:11:23 +08:00
# Host a session
2021-07-19 22:03:12 +08:00
if settings . init_method == ' EMPTY ' :
utils . clean_scene ( )
2020-09-23 23:37:21 +08:00
2021-07-19 22:03:12 +08:00
try :
# Init repository
for scene in bpy . data . scenes :
porcelain . add ( repo , scene )
porcelain . remote_add (
repo ,
' origin ' ,
' 127.0.0.1 ' ,
2021-07-23 18:51:16 +08:00
settings . host_port ,
2021-07-19 22:03:12 +08:00
server_password = server_pass ,
admin_password = admin_pass )
session . host (
repository = repo ,
remote = ' origin ' ,
timeout = settings . connection_timeout ,
server_password = server_pass ,
admin_password = admin_pass ,
cache_directory = settings . cache_directory ,
server_log_level = logging . getLevelName (
logging . getLogger ( ) . level ) ,
)
except Exception as e :
self . report ( { ' ERROR ' } , repr ( e ) )
logging . error ( f " Error: { e } " )
traceback . print_exc ( )
2020-02-07 21:11:23 +08:00
2020-02-08 00:08:36 +08:00
# Background client updates service
2021-07-26 23:30:56 +08:00
setup_timer ( )
2019-09-27 04:42:42 +08:00
2019-02-11 22:48:07 +08:00
return { " FINISHED " }
2019-02-26 01:05:46 +08:00
2020-06-11 00:43:21 +08:00
class SessionInitOperator ( bpy . types . Operator ) :
bl_idname = " session.init "
bl_label = " Init session repostitory from "
bl_description = " Init the current session "
bl_options = { " REGISTER " }
init_method : bpy . props . EnumProperty (
name = ' init_method ' ,
description = ' Init repo ' ,
items = {
( ' EMPTY ' , ' an empty scene ' , ' start empty ' ) ,
( ' BLEND ' , ' current scenes ' , ' use current scenes ' ) } ,
default = ' BLEND ' )
@classmethod
def poll ( cls , context ) :
return True
2020-07-07 16:58:34 +08:00
2020-06-11 00:43:21 +08:00
def draw ( self , context ) :
layout = self . layout
col = layout . column ( )
col . prop ( self , ' init_method ' , text = " " )
def invoke ( self , context , event ) :
wm = context . window_manager
return wm . invoke_props_dialog ( self )
def execute ( self , context ) :
if self . init_method == ' EMPTY ' :
utils . clean_scene ( )
for scene in bpy . data . scenes :
2021-05-17 17:12:18 +08:00
porcelain . add ( session . repository , scene )
2020-06-16 21:19:38 +08:00
2020-10-02 06:05:33 +08:00
session . init ( )
2021-06-03 17:41:25 +08:00
context . window_manager . session . is_host = True
2020-06-11 00:43:21 +08:00
return { " FINISHED " }
2020-07-07 16:58:34 +08:00
2019-08-06 17:34:39 +08:00
class SessionStopOperator ( bpy . types . Operator ) :
bl_idname = " session.stop "
bl_label = " close "
2019-09-18 23:54:02 +08:00
bl_description = " Exit current session "
2019-02-12 00:02:25 +08:00
bl_options = { " REGISTER " }
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
2020-12-10 01:35:29 +08:00
global deleyables , stop_modal_executor
2020-02-21 19:00:34 +08:00
2020-10-02 06:05:33 +08:00
if session :
2020-07-07 16:58:34 +08:00
try :
2021-03-06 17:20:57 +08:00
session . disconnect ( reason = ' user ' )
2020-09-28 16:32:41 +08:00
2020-07-07 16:58:34 +08:00
except Exception as e :
self . report ( { ' ERROR ' } , repr ( e ) )
else :
self . report ( { ' WARNING ' } , " No session to quit. " )
return { " FINISHED " }
2019-04-13 00:53:38 +08:00
return { " FINISHED " }
2020-05-28 18:59:05 +08:00
2020-04-03 20:59:33 +08:00
class SessionKickOperator ( bpy . types . Operator ) :
bl_idname = " session.kick "
bl_label = " Kick "
2020-10-16 16:57:45 +08:00
bl_description = " Kick the target user "
2020-04-03 20:59:33 +08:00
bl_options = { " REGISTER " }
user : bpy . props . StringProperty ( )
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
2020-12-10 01:35:29 +08:00
global deleyables , stop_modal_executor
2020-10-02 06:05:33 +08:00
assert ( session )
2020-04-03 20:59:33 +08:00
try :
2021-06-02 17:31:23 +08:00
porcelain . kick ( session . repository , self . user )
2020-04-03 20:59:33 +08:00
except Exception as e :
self . report ( { ' ERROR ' } , repr ( e ) )
return { " FINISHED " }
def invoke ( self , context , event ) :
return context . window_manager . invoke_props_dialog ( self )
def draw ( self , context ) :
row = self . layout
2020-05-28 18:59:05 +08:00
row . label ( text = f " Do you really want to kick { self . user } ? " )
2019-04-13 00:53:38 +08:00
2019-05-15 20:52:45 +08:00
class SessionPropertyRemoveOperator ( bpy . types . Operator ) :
2019-02-23 01:22:25 +08:00
bl_idname = " session.remove_prop "
2020-10-16 16:57:45 +08:00
bl_label = " Delete cache "
bl_description = " Stop tracking modification on the target datablock. " + \
" The datablock will no longer be updated for others client. "
2019-02-23 01:22:25 +08:00
bl_options = { " REGISTER " }
property_path : bpy . props . StringProperty ( default = " None " )
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
try :
2021-06-02 17:47:41 +08:00
porcelain . rm ( session . repository , self . property_path )
2019-02-23 01:22:25 +08:00
return { " FINISHED " }
2019-09-17 05:34:12 +08:00
except : # NonAuthorizedOperationError:
2019-08-28 21:02:57 +08:00
self . report (
{ ' ERROR ' } ,
" Non authorized operation " )
2019-08-23 22:56:59 +08:00
return { " CANCELLED " }
2019-02-23 01:22:25 +08:00
2019-02-26 01:05:46 +08:00
2019-05-15 20:52:45 +08:00
class SessionPropertyRightOperator ( bpy . types . Operator ) :
2019-04-24 23:42:23 +08:00
bl_idname = " session.right "
2020-10-16 16:57:45 +08:00
bl_label = " Change modification rights "
bl_description = " Modify the owner of the target datablock "
2019-04-24 23:42:23 +08:00
bl_options = { " REGISTER " }
key : bpy . props . StringProperty ( default = " None " )
2020-10-15 23:48:04 +08:00
recursive : bpy . props . BoolProperty ( default = True )
2019-04-24 23:42:23 +08:00
@classmethod
def poll ( cls , context ) :
return True
def invoke ( self , context , event ) :
wm = context . window_manager
return wm . invoke_props_dialog ( self )
def draw ( self , context ) :
layout = self . layout
2020-02-29 00:34:30 +08:00
runtime_settings = context . window_manager . session
2019-04-24 23:42:23 +08:00
2020-10-16 16:57:45 +08:00
row = layout . row ( )
row . label ( text = " Give the owning rights to: " )
row . prop ( runtime_settings , " clients " , text = " " )
row = layout . row ( )
2020-10-15 23:48:04 +08:00
row . label ( text = " Affect dependencies " )
row . prop ( self , " recursive " , text = " " )
2019-04-24 23:42:23 +08:00
def execute ( self , context ) :
2020-02-29 00:34:30 +08:00
runtime_settings = context . window_manager . session
2019-04-24 23:42:23 +08:00
2020-10-02 06:05:33 +08:00
if session :
2021-06-02 16:22:37 +08:00
if runtime_settings . clients == RP_COMMON :
2021-06-02 23:49:22 +08:00
porcelain . unlock ( session . repository ,
self . key ,
2020-10-21 20:40:15 +08:00
ignore_warnings = True ,
affect_dependencies = self . recursive )
2021-06-02 16:22:37 +08:00
else :
2021-06-02 23:49:22 +08:00
porcelain . lock ( session . repository ,
self . key ,
2021-06-02 16:22:37 +08:00
runtime_settings . clients ,
ignore_warnings = True ,
affect_dependencies = self . recursive )
2019-04-24 23:42:23 +08:00
return { " FINISHED " }
2019-05-15 20:52:45 +08:00
class SessionSnapUserOperator ( bpy . types . Operator ) :
2019-03-14 18:04:41 +08:00
bl_idname = " session.snapview "
2019-09-18 23:54:02 +08:00
bl_label = " snap to user "
bl_description = " Snap 3d view to selected user "
2019-03-14 18:04:41 +08:00
bl_options = { " REGISTER " }
2019-10-15 21:04:34 +08:00
_timer = None
2019-07-10 23:16:44 +08:00
target_client : bpy . props . StringProperty ( default = " None " )
2019-03-14 18:04:41 +08:00
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
2019-10-15 21:04:34 +08:00
wm = context . window_manager
2020-02-29 00:34:30 +08:00
runtime_settings = context . window_manager . session
2020-01-22 23:17:48 +08:00
2020-02-29 00:34:30 +08:00
if runtime_settings . time_snap_running :
runtime_settings . time_snap_running = False
2020-01-22 23:17:48 +08:00
return { ' CANCELLED ' }
else :
2020-02-29 00:34:30 +08:00
runtime_settings . time_snap_running = True
2020-01-22 23:17:48 +08:00
2019-10-15 21:04:34 +08:00
self . _timer = wm . event_timer_add ( 0.1 , window = context . window )
wm . modal_handler_add ( self )
return { ' RUNNING_MODAL ' }
2019-03-14 18:04:41 +08:00
2019-10-15 21:04:34 +08:00
def cancel ( self , context ) :
wm = context . window_manager
wm . event_timer_remove ( self . _timer )
2019-05-02 23:58:37 +08:00
2019-10-15 21:04:34 +08:00
def modal ( self , context , event ) :
2020-07-02 20:14:16 +08:00
session_sessings = context . window_manager . session
is_running = session_sessings . time_snap_running
2020-01-22 23:17:48 +08:00
if event . type in { ' RIGHTMOUSE ' , ' ESC ' } or not is_running :
2019-10-15 21:04:34 +08:00
self . cancel ( context )
return { ' CANCELLED ' }
if event . type == ' TIMER ' :
2020-10-06 05:38:52 +08:00
area , region , rv3d = view3d_find ( )
2019-10-15 21:04:34 +08:00
2020-10-02 06:05:33 +08:00
if session :
target_ref = session . online_users . get ( self . target_client )
2020-01-22 22:15:44 +08:00
if target_ref :
2020-03-06 00:31:12 +08:00
target_scene = target_ref [ ' metadata ' ] [ ' scene_current ' ]
2020-07-02 20:14:16 +08:00
# Handle client on other scenes
if target_scene != context . scene . name :
blender_scene = bpy . data . scenes . get ( target_scene , None )
if blender_scene is None :
2020-09-15 18:31:46 +08:00
self . report (
{ ' ERROR ' } , f " Scene { target_scene } doesn ' t exist on the local client. " )
2020-07-02 20:14:16 +08:00
session_sessings . time_snap_running = False
return { " CANCELLED " }
bpy . context . window . scene = blender_scene
# Update client viewmatrix
2020-09-15 18:31:46 +08:00
client_vmatrix = target_ref [ ' metadata ' ] . get (
' view_matrix ' , None )
2020-07-02 20:14:16 +08:00
if client_vmatrix :
rv3d . view_matrix = mathutils . Matrix ( client_vmatrix )
else :
self . report ( { ' ERROR ' } , f " Client viewport not ready. " )
session_sessings . time_snap_running = False
return { " CANCELLED " }
2019-10-15 21:04:34 +08:00
else :
return { " CANCELLED " }
2019-03-14 18:04:41 +08:00
2019-10-15 21:04:34 +08:00
return { ' PASS_THROUGH ' }
2019-03-14 18:04:41 +08:00
2019-03-25 21:56:09 +08:00
2020-01-22 23:17:48 +08:00
class SessionSnapTimeOperator ( bpy . types . Operator ) :
bl_idname = " session.snaptime "
bl_label = " snap to user time "
bl_description = " Snap time to selected user time ' s "
bl_options = { " REGISTER " }
_timer = None
target_client : bpy . props . StringProperty ( default = " None " )
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
2020-02-29 00:34:30 +08:00
runtime_settings = context . window_manager . session
2020-01-22 23:17:48 +08:00
2020-02-29 00:34:30 +08:00
if runtime_settings . user_snap_running :
runtime_settings . user_snap_running = False
2020-01-22 23:17:48 +08:00
return { ' CANCELLED ' }
else :
2020-02-29 00:34:30 +08:00
runtime_settings . user_snap_running = True
2020-01-22 23:17:48 +08:00
wm = context . window_manager
2020-02-14 23:44:53 +08:00
self . _timer = wm . event_timer_add ( 0.05 , window = context . window )
2020-01-22 23:17:48 +08:00
wm . modal_handler_add ( self )
return { ' RUNNING_MODAL ' }
def cancel ( self , context ) :
wm = context . window_manager
wm . event_timer_remove ( self . _timer )
def modal ( self , context , event ) :
is_running = context . window_manager . session . user_snap_running
2021-05-22 05:02:42 +08:00
if not is_running :
2020-01-22 23:17:48 +08:00
self . cancel ( context )
return { ' CANCELLED ' }
if event . type == ' TIMER ' :
2020-10-02 06:05:33 +08:00
if session :
target_ref = session . online_users . get ( self . target_client )
2020-01-22 23:17:48 +08:00
if target_ref :
context . scene . frame_current = target_ref [ ' metadata ' ] [ ' frame_current ' ]
else :
return { " CANCELLED " }
return { ' PASS_THROUGH ' }
2019-08-10 00:14:32 +08:00
class SessionApply ( bpy . types . Operator ) :
bl_idname = " session.apply "
2020-10-16 16:57:45 +08:00
bl_label = " Revert "
bl_description = " Revert the selected datablock from his cached " + \
" version. "
2019-06-14 00:09:16 +08:00
bl_options = { " REGISTER " }
2019-10-28 21:31:03 +08:00
target : bpy . props . StringProperty ( )
2020-10-15 18:11:28 +08:00
reset_dependencies : bpy . props . BoolProperty ( default = False )
2019-06-14 00:09:16 +08:00
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
2020-10-15 01:12:28 +08:00
logging . debug ( f " Running apply on { self . target } " )
2020-11-13 21:15:11 +08:00
try :
2021-06-04 20:02:09 +08:00
node_ref = session . repository . graph . get ( self . target )
2021-05-17 17:12:18 +08:00
porcelain . apply ( session . repository ,
self . target ,
2021-06-22 17:36:51 +08:00
force = True )
2021-05-21 23:14:28 +08:00
impl = session . repository . rdp . get_implementation ( node_ref . instance )
2021-06-22 20:06:19 +08:00
# NOTE: find another way to handle child and parent automatic reloading
2021-05-21 23:14:28 +08:00
if impl . bl_reload_parent :
2021-06-04 20:02:09 +08:00
for parent in session . repository . graph . get_parents ( self . target ) :
2021-02-17 04:58:26 +08:00
logging . debug ( f " Refresh parent { parent } " )
2021-03-15 01:32:04 +08:00
2021-05-17 17:12:18 +08:00
porcelain . apply ( session . repository ,
parent . uuid ,
force = True )
2021-06-22 17:36:51 +08:00
if hasattr ( impl , ' bl_reload_child ' ) and impl . bl_reload_child :
for dep in node_ref . dependencies :
porcelain . apply ( session . repository ,
dep ,
force = True )
2020-11-13 21:15:11 +08:00
except Exception as e :
self . report ( { ' ERROR ' } , repr ( e ) )
2021-03-15 01:32:04 +08:00
traceback . print_exc ( )
return { " CANCELLED " }
2019-06-14 00:09:16 +08:00
2019-08-10 00:14:32 +08:00
return { " FINISHED " }
2019-07-02 00:04:35 +08:00
2019-09-17 05:34:12 +08:00
2019-08-28 19:13:32 +08:00
class SessionCommit ( bpy . types . Operator ) :
bl_idname = " session.commit "
2020-10-16 16:57:45 +08:00
bl_label = " Force server update "
bl_description = " Commit and push the target datablock to server "
2019-08-28 19:13:32 +08:00
bl_options = { " REGISTER " }
2019-10-28 21:31:03 +08:00
target : bpy . props . StringProperty ( )
2019-08-28 19:13:32 +08:00
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
2020-11-13 21:15:11 +08:00
try :
2021-05-17 23:18:17 +08:00
porcelain . commit ( session . repository , self . target )
2021-06-22 16:41:36 +08:00
porcelain . push ( session . repository , ' origin ' , self . target , force = True )
2020-11-13 21:15:11 +08:00
return { " FINISHED " }
except Exception as e :
self . report ( { ' ERROR ' } , repr ( e ) )
2021-05-17 23:18:17 +08:00
return { " CANCELLED " }
2019-12-03 00:34:08 +08:00
2019-09-17 05:34:12 +08:00
2020-11-26 05:53:38 +08:00
class SessionClearCache ( bpy . types . Operator ) :
2020-09-21 22:47:49 +08:00
" Clear local session cache "
bl_idname = " session.clear_cache "
bl_label = " Modal Executor Operator "
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
2020-09-23 23:37:21 +08:00
cache_dir = utils . get_preferences ( ) . cache_directory
2020-09-21 22:47:49 +08:00
try :
for root , dirs , files in os . walk ( cache_dir ) :
for name in files :
Path ( root , name ) . unlink ( )
except Exception as e :
self . report ( { ' ERROR ' } , repr ( e ) )
return { " FINISHED " }
def invoke ( self , context , event ) :
return context . window_manager . invoke_props_dialog ( self )
def draw ( self , context ) :
row = self . layout
row . label ( text = f " Do you really want to remove local cache ? " )
2021-06-04 18:07:54 +08:00
2021-01-16 06:43:35 +08:00
class SessionPurgeOperator ( bpy . types . Operator ) :
" Remove node with lost references "
bl_idname = " session.purge "
bl_label = " Purge session data "
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
try :
2021-06-18 20:59:56 +08:00
porcelain . purge_orphan_nodes ( session . repository )
2021-01-16 06:43:35 +08:00
except Exception as e :
self . report ( { ' ERROR ' } , repr ( e ) )
return { " FINISHED " }
def invoke ( self , context , event ) :
return context . window_manager . invoke_props_dialog ( self )
def draw ( self , context ) :
row = self . layout
row . label ( text = f " Do you really want to remove local cache ? " )
2020-11-26 05:53:38 +08:00
class SessionNotifyOperator ( bpy . types . Operator ) :
""" Dialog only operator """
bl_idname = " session.notify "
2020-12-03 20:28:45 +08:00
bl_label = " Multi-user "
bl_description = " multiuser notification "
2020-11-26 05:53:38 +08:00
message : bpy . props . StringProperty ( )
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
return { ' FINISHED ' }
def draw ( self , context ) :
layout = self . layout
2020-12-03 20:30:54 +08:00
layout . row ( ) . label ( text = self . message )
2020-11-26 05:53:38 +08:00
def invoke ( self , context , event ) :
2020-12-03 20:28:45 +08:00
return context . window_manager . invoke_props_dialog ( self )
2020-11-26 05:53:38 +08:00
2020-09-23 23:37:21 +08:00
2020-12-22 23:04:50 +08:00
class SessionSaveBackupOperator ( bpy . types . Operator , ExportHelper ) :
bl_idname = " session.save "
2020-12-22 23:16:06 +08:00
bl_label = " Save session data "
2020-12-22 23:04:50 +08:00
bl_description = " Save a snapshot of the collaborative session "
2020-12-10 20:31:43 +08:00
# ExportHelper mixin class uses this
filename_ext = " .db "
2020-12-22 23:58:00 +08:00
filter_glob : bpy . props . StringProperty (
default = " *.db " ,
options = { ' HIDDEN ' } ,
maxlen = 255 , # Max internal buffer length, longer would be clamped.
)
2020-12-22 23:04:50 +08:00
enable_autosave : bpy . props . BoolProperty (
name = " Auto-save " ,
description = " Enable session auto-save " ,
default = True ,
)
save_interval : bpy . props . FloatProperty (
name = " Auto save interval " ,
description = " auto-save interval (seconds) " ,
default = 10 ,
)
2020-12-10 20:31:43 +08:00
def execute ( self , context ) :
2020-12-22 23:04:50 +08:00
if self . enable_autosave :
recorder = timers . SessionBackupTimer (
filepath = self . filepath ,
timeout = self . save_interval )
recorder . register ( )
deleyables . append ( recorder )
else :
2021-05-31 17:39:54 +08:00
session . repository . dumps ( self . filepath )
2020-12-10 20:31:43 +08:00
return { ' FINISHED ' }
2020-12-10 22:50:43 +08:00
@classmethod
def poll ( cls , context ) :
2021-03-04 21:22:54 +08:00
return session . state == STATE_ACTIVE
2020-12-10 22:50:43 +08:00
2021-06-04 18:07:54 +08:00
2020-12-22 23:04:50 +08:00
class SessionStopAutoSaveOperator ( bpy . types . Operator ) :
bl_idname = " session.cancel_autosave "
bl_label = " Cancel auto-save "
bl_description = " Cancel session auto-save "
@classmethod
def poll ( cls , context ) :
2021-03-04 21:22:54 +08:00
return ( session . state == STATE_ACTIVE and ' SessionBackupTimer ' in registry )
2020-12-22 23:04:50 +08:00
def execute ( self , context ) :
autosave_timer = registry . get ( ' SessionBackupTimer ' )
autosave_timer . unregister ( )
return { ' FINISHED ' }
2020-12-22 23:58:00 +08:00
class SessionLoadSaveOperator ( bpy . types . Operator , ImportHelper ) :
2020-12-16 18:05:58 +08:00
bl_idname = " session.load "
2020-12-22 23:58:00 +08:00
bl_label = " Load session save "
2020-12-22 23:04:50 +08:00
bl_description = " Load a Multi-user session save "
2020-12-16 18:05:58 +08:00
bl_options = { ' REGISTER ' , ' UNDO ' }
# ExportHelper mixin class uses this
filename_ext = " .db "
2020-12-22 23:58:00 +08:00
filter_glob : bpy . props . StringProperty (
default = " *.db " ,
options = { ' HIDDEN ' } ,
maxlen = 255 , # Max internal buffer length, longer would be clamped.
)
2021-11-16 16:55:13 +08:00
draw_users : bpy . props . BoolProperty (
default = False ,
)
2020-12-16 18:05:58 +08:00
def execute ( self , context ) :
2021-03-04 21:22:54 +08:00
from replication . repository import Repository
2021-11-18 18:40:56 +08:00
2021-05-31 17:39:54 +08:00
# init the factory with supported types
bpy_protocol = bl_types . get_data_translation_protocol ( )
repo = Repository ( bpy_protocol )
repo . loads ( self . filepath )
utils . clean_scene ( )
2020-12-16 18:05:58 +08:00
2021-06-04 20:02:09 +08:00
nodes = [ repo . graph . get ( n ) for n in repo . index_sorted ]
2020-12-16 18:05:58 +08:00
2021-05-31 17:39:54 +08:00
# Step 1: Construct nodes
for node in nodes :
node . instance = bpy_protocol . resolve ( node . data )
if node . instance is None :
node . instance = bpy_protocol . construct ( node . data )
node . instance . uuid = node . uuid
2020-12-16 18:05:58 +08:00
2021-05-31 17:39:54 +08:00
# Step 2: Load nodes
for node in nodes :
porcelain . apply ( repo , node . uuid )
2021-11-16 16:55:13 +08:00
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 ( ) :
2021-11-18 18:40:56 +08:00
metadata = user_data [ ' metadata ' ]
2021-11-16 16:55:13 +08:00
2021-11-18 18:40:56 +08:00
points = metadata . get ( ' view_corners ' )
color = metadata . get ( ' color ' , ( 1 , 1 , 1 , 0 ) )
2020-12-16 18:05:58 +08:00
2021-11-18 18:43:01 +08:00
loard_users ( username , points , color = color )
2020-12-16 18:05:58 +08:00
return { ' FINISHED ' }
@classmethod
def poll ( cls , context ) :
return True
2021-06-08 23:03:43 +08:00
class SessionPresetServerAdd ( bpy . types . Operator ) :
""" Add a server to the server list preset """
bl_idname = " session.preset_server_add "
2021-07-19 22:03:12 +08:00
bl_label = " Add server preset "
bl_description = " add a server to the server preset list "
2021-06-08 23:03:43 +08:00
bl_options = { " REGISTER " }
2021-07-21 17:12:17 +08:00
server_name : bpy . props . StringProperty ( default = " " )
2021-07-23 18:51:16 +08:00
ip : bpy . props . StringProperty ( default = " 127.0.0.1 " )
port : bpy . props . IntProperty ( default = 5555 )
2021-07-21 17:12:17 +08:00
use_server_password : bpy . props . BoolProperty ( default = False )
2021-07-23 18:51:16 +08:00
server_password : bpy . props . StringProperty ( default = " " , subtype = " PASSWORD " )
2021-07-21 17:12:17 +08:00
use_admin_password : bpy . props . BoolProperty ( default = False )
2021-07-23 18:51:16 +08:00
admin_password : bpy . props . StringProperty ( default = " " , subtype = " PASSWORD " )
2021-07-19 22:03:12 +08:00
2021-06-10 21:39:12 +08:00
@classmethod
def poll ( cls , context ) :
return True
2021-06-11 22:57:02 +08:00
def invoke ( self , context , event ) :
2021-07-21 17:12:17 +08:00
self . server_name = " "
2021-07-23 18:51:16 +08:00
self . ip = " 127.0.0.1 "
self . port = 5555
2021-07-21 17:12:17 +08:00
self . use_server_password = False
2021-07-23 18:51:16 +08:00
self . server_password = " "
2021-07-21 17:12:17 +08:00
self . use_admin_password = False
2021-07-23 18:51:16 +08:00
self . admin_password = " "
2021-07-19 22:03:12 +08:00
2021-06-10 21:39:12 +08:00
assert ( context )
2021-06-11 22:57:02 +08:00
return context . window_manager . invoke_props_dialog ( self )
def draw ( self , context ) :
layout = self . layout
2021-07-19 22:03:12 +08:00
row = layout . row ( )
2021-07-21 17:12:17 +08:00
row . prop ( self , " server_name " , text = " Server name " )
2021-07-19 22:03:12 +08:00
row = layout . row ( align = True )
2021-07-23 18:51:16 +08:00
row . prop ( self , " ip " , text = " IP+port " )
row . prop ( self , " port " , text = " " )
2021-07-19 22:03:12 +08:00
row = layout . row ( )
2021-07-21 17:12:17 +08:00
col = row . column ( )
col . prop ( self , " use_server_password " , text = " Server password: " )
col = row . column ( )
col . enabled = True if self . use_server_password else False
2021-07-23 18:51:16 +08:00
col . prop ( self , " server_password " , text = " " )
2021-07-19 22:03:12 +08:00
row = layout . row ( )
2021-07-21 17:12:17 +08:00
col = row . column ( )
col . prop ( self , " use_admin_password " , text = " Admin password: " )
col = row . column ( )
col . enabled = True if self . use_admin_password else False
2021-07-23 18:51:16 +08:00
col . prop ( self , " admin_password " , text = " " )
2021-06-11 22:57:02 +08:00
def execute ( self , context ) :
assert ( context )
2021-06-10 21:39:12 +08:00
2021-06-11 18:13:23 +08:00
settings = utils . get_preferences ( )
2021-07-21 17:12:17 +08:00
existing_preset = settings . get_server_preset ( self . server_name )
2021-06-14 21:17:07 +08:00
2021-06-11 22:57:02 +08:00
new_server = existing_preset if existing_preset else settings . server_preset . add ( )
2021-07-19 22:03:12 +08:00
new_server . name = str ( uuid4 ( ) )
2021-07-21 17:12:17 +08:00
new_server . server_name = self . server_name
2021-07-23 18:51:16 +08:00
new_server . ip = self . ip
new_server . port = self . port
2021-07-21 17:12:17 +08:00
new_server . use_server_password = self . use_server_password
2021-07-23 18:51:16 +08:00
new_server . server_password = self . server_password
2021-07-21 17:12:17 +08:00
new_server . use_admin_password = self . use_admin_password
2021-07-23 18:51:16 +08:00
new_server . admin_password = self . admin_password
2021-07-21 17:12:17 +08:00
refresh_sidebar_view ( )
2021-06-11 18:13:23 +08:00
2021-06-14 21:17:07 +08:00
if new_server == existing_preset :
2021-07-26 23:30:56 +08:00
self . report ( { ' INFO ' } , " Server ' " + self . server_name + " ' edited " )
2021-06-14 21:17:07 +08:00
else :
2021-07-21 17:12:17 +08:00
self . report ( { ' INFO ' } , " New ' " + self . server_name + " ' server preset " )
2021-06-11 22:57:02 +08:00
2021-06-14 21:17:07 +08:00
return { ' FINISHED ' }
2021-06-11 22:57:02 +08:00
2021-07-23 18:51:16 +08:00
class SessionPresetServerEdit ( bpy . types . Operator ) : # TODO : use preset, not settings
2021-07-19 22:03:12 +08:00
""" Edit a server to the server list preset """
bl_idname = " session.preset_server_edit "
bl_label = " Edit server preset "
bl_description = " Edit a server from the server preset list "
bl_options = { " REGISTER " }
target_server_name : bpy . props . StringProperty ( default = " None " )
@classmethod
def poll ( cls , context ) :
return True
def invoke ( self , context , event ) :
assert ( context )
return context . window_manager . invoke_props_dialog ( self )
def draw ( self , context ) :
layout = self . layout
settings = utils . get_preferences ( )
2021-07-23 18:51:16 +08:00
settings_active_server = settings . server_preset . get ( self . target_server_name )
2021-07-19 22:03:12 +08:00
row = layout . row ( )
2021-07-23 18:51:16 +08:00
row . prop ( settings_active_server , " server_name " , text = " Server name " )
2021-07-19 22:03:12 +08:00
row = layout . row ( align = True )
2021-07-23 18:51:16 +08:00
row . prop ( settings_active_server , " ip " , text = " IP+port " )
row . prop ( settings_active_server , " port " , text = " " )
2021-07-19 22:03:12 +08:00
row = layout . row ( )
2021-07-21 17:12:17 +08:00
col = row . column ( )
2021-07-23 18:51:16 +08:00
col . prop ( settings_active_server , " use_server_password " , text = " Server password: " )
2021-07-21 17:12:17 +08:00
col = row . column ( )
2021-07-23 18:51:16 +08:00
col . enabled = True if settings_active_server . use_server_password else False
col . prop ( settings_active_server , " server_password " , text = " " )
2021-07-19 22:03:12 +08:00
row = layout . row ( )
2021-07-21 17:12:17 +08:00
col = row . column ( )
2021-07-23 18:51:16 +08:00
col . prop ( settings_active_server , " use_admin_password " , text = " Admin password: " )
2021-07-21 17:12:17 +08:00
col = row . column ( )
2021-07-23 18:51:16 +08:00
col . enabled = True if settings_active_server . use_admin_password else False
col . prop ( settings_active_server , " admin_password " , text = " " )
2021-07-19 22:03:12 +08:00
def execute ( self , context ) :
assert ( context )
settings = utils . get_preferences ( )
settings_active_server = settings . server_preset . get ( self . target_server_name )
2021-07-21 17:12:17 +08:00
refresh_sidebar_view ( )
2021-07-26 23:30:56 +08:00
self . report ( { ' INFO ' } , " Server ' " + settings_active_server . server_name + " ' edited " )
2021-07-19 22:03:12 +08:00
return { ' FINISHED ' }
2021-06-10 21:39:12 +08:00
class SessionPresetServerRemove ( bpy . types . Operator ) :
""" Remove a server to the server list preset """
bl_idname = " session.preset_server_remove "
bl_label = " remove server preset "
bl_description = " remove the current server from the server preset list "
bl_options = { " REGISTER " }
2021-07-19 22:03:12 +08:00
target_server_name : bpy . props . StringProperty ( default = " None " )
2021-06-10 21:39:12 +08:00
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
assert ( context )
settings = utils . get_preferences ( )
2021-07-19 22:03:12 +08:00
settings . server_preset . remove ( settings . server_preset . find ( self . target_server_name ) )
2021-06-10 21:39:12 +08:00
return { ' FINISHED ' }
2021-06-11 18:13:23 +08:00
2021-07-23 18:51:16 +08:00
class RefreshServerStatus ( bpy . types . Operator ) :
2021-07-22 16:55:18 +08:00
bl_idname = " session.get_info "
bl_label = " Get session info "
bl_description = " Get session info "
target_server : bpy . props . StringProperty ( default = " 127.0.0.1:5555 " )
@classmethod
def poll ( cls , context ) :
return ( session . state != STATE_ACTIVE )
def execute ( self , context ) :
2021-07-23 18:51:16 +08:00
settings = utils . get_preferences ( )
for server in settings . server_preset :
2021-07-26 23:42:13 +08:00
infos = porcelain . request_session_info ( f " { server . ip } : { server . port } " , timeout = settings . ping_timeout )
2021-07-23 18:51:16 +08:00
server . is_online = True if infos else False
if server . is_online :
server . is_private = infos . get ( " private " )
2021-07-22 16:55:18 +08:00
return { ' FINISHED ' }
2021-06-08 23:03:43 +08:00
2021-07-21 17:12:17 +08:00
class GetDoc ( bpy . types . Operator ) :
""" Get the documentation of the addon """
bl_idname = " doc.get "
bl_label = " Multi-user ' s doc "
bl_description = " Go to the doc of the addon "
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
assert ( context )
2021-07-26 23:30:56 +08:00
bpy . ops . wm . url_open ( url = " https://slumber.gitlab.io/multi-user/index.html " )
2021-07-21 17:12:17 +08:00
return { ' FINISHED ' }
class FirstLaunch ( bpy . types . Operator ) :
""" First time lauching the addon """
bl_idname = " firstlaunch.verify "
bl_label = " First launch "
bl_description = " First time lauching the addon "
@classmethod
def poll ( cls , context ) :
return True
def execute ( self , context ) :
assert ( context )
settings = utils . get_preferences ( )
settings . is_first_launch = False
2021-07-23 23:10:10 +08:00
settings . server_preset . clear ( )
2021-07-23 18:51:16 +08:00
prefs = bpy . context . preferences . addons [ __package__ ] . preferences
prefs . generate_default_presets ( )
2021-07-21 17:12:17 +08:00
return { ' FINISHED ' }
2021-06-08 23:03:43 +08:00
2020-12-16 18:05:58 +08:00
def menu_func_import ( self , context ) :
2020-12-22 23:58:00 +08:00
self . layout . operator ( SessionLoadSaveOperator . bl_idname , text = ' Multi-user session snapshot (.db) ' )
2020-12-16 18:05:58 +08:00
2021-07-22 15:38:01 +08:00
def menu_func_export ( self , context ) :
self . layout . operator ( SessionSaveBackupOperator . bl_idname , text = ' Multi-user session snapshot (.db) ' )
2020-12-10 20:31:43 +08:00
2019-02-08 22:44:02 +08:00
classes = (
2021-07-19 22:03:12 +08:00
SessionConnectOperator ,
SessionHostOperator ,
2019-05-15 20:52:45 +08:00
SessionStopOperator ,
SessionPropertyRemoveOperator ,
SessionSnapUserOperator ,
2020-01-22 23:17:48 +08:00
SessionSnapTimeOperator ,
2019-05-15 20:52:45 +08:00
SessionPropertyRightOperator ,
2019-08-10 00:14:32 +08:00
SessionApply ,
2019-08-28 19:13:32 +08:00
SessionCommit ,
2020-04-03 20:59:33 +08:00
SessionKickOperator ,
2020-06-11 00:43:21 +08:00
SessionInitOperator ,
2020-11-26 05:53:38 +08:00
SessionClearCache ,
2021-06-10 21:39:12 +08:00
SessionNotifyOperator ,
2020-12-22 23:04:50 +08:00
SessionSaveBackupOperator ,
2020-12-22 23:58:00 +08:00
SessionLoadSaveOperator ,
2020-12-22 23:04:50 +08:00
SessionStopAutoSaveOperator ,
2021-01-16 06:43:35 +08:00
SessionPurgeOperator ,
2021-06-10 21:39:12 +08:00
SessionPresetServerAdd ,
2021-07-19 22:03:12 +08:00
SessionPresetServerEdit ,
2021-06-10 21:39:12 +08:00
SessionPresetServerRemove ,
2021-07-23 18:51:16 +08:00
RefreshServerStatus ,
2021-07-21 17:12:17 +08:00
GetDoc ,
FirstLaunch ,
2019-02-08 22:44:02 +08:00
)
2020-01-14 18:49:36 +08:00
2021-06-04 18:07:54 +08:00
2019-02-09 01:34:10 +08:00
def register ( ) :
from bpy . utils import register_class
2020-10-06 21:46:35 +08:00
2020-12-17 21:04:00 +08:00
for cls in classes :
2019-02-09 01:34:10 +08:00
register_class ( cls )
2019-07-10 23:16:44 +08:00
2021-01-12 18:33:48 +08:00
2019-02-14 20:13:28 +08:00
def unregister ( ) :
2021-03-04 21:22:54 +08:00
if session and session . state == STATE_ACTIVE :
2020-10-02 06:05:33 +08:00
session . disconnect ( )
2019-03-12 00:59:49 +08:00
2019-02-09 01:34:10 +08:00
from bpy . utils import unregister_class
for cls in reversed ( classes ) :
unregister_class ( cls )