diff --git a/.gitlab/ci/build.gitlab-ci.yml b/.gitlab/ci/build.gitlab-ci.yml index 6c1c29b..7518270 100644 --- a/.gitlab/ci/build.gitlab-ci.yml +++ b/.gitlab/ci/build.gitlab-ci.yml @@ -7,4 +7,7 @@ build: name: multi_user paths: - multi_user - + only: + refs: + - master + - develop diff --git a/.gitlab/ci/deploy.gitlab-ci.yml b/.gitlab/ci/deploy.gitlab-ci.yml index 6cbc1ff..f81aac5 100644 --- a/.gitlab/ci/deploy.gitlab-ci.yml +++ b/.gitlab/ci/deploy.gitlab-ci.yml @@ -16,3 +16,7 @@ deploy: - echo "Pushing to gitlab registry ${VERSION}" - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker push registry.gitlab.com/slumber/multi-user/multi-user-server:${VERSION} + only: + refs: + - master + - develop \ No newline at end of file diff --git a/multi_user/delayable.py b/multi_user/delayable.py index c71806e..65b03de 100644 --- a/multi_user/delayable.py +++ b/multi_user/delayable.py @@ -211,30 +211,6 @@ class DynamicRightSelectTimer(Timer): obj.hide_select = True -class Draw(Delayable): - def __init__(self): - super().__init__() - self._handler = None - - def register(self): - if not self.is_registered: - self._handler = bpy.types.SpaceView3D.draw_handler_add( - self.execute, (), 'WINDOW', 'POST_VIEW') - logging.debug(f"Register {self.__class__.__name__}") - else: - logging.debug(f"Drow {self.__class__.__name__} already registered") - - def execute(self): - raise NotImplementedError() - - def unregister(self): - try: - bpy.types.SpaceView3D.draw_handler_remove( - self._handler, "WINDOW") - except: - pass - - class ClientUpdate(Timer): def __init__(self, timout=.1): super().__init__(timout) @@ -271,7 +247,7 @@ class ClientUpdate(Timer): local_user_metadata = local_user.get('metadata') scene_current = bpy.context.scene.name local_user = session.online_users.get(settings.username) - current_view_corners = presence.get_view_corners() + current_view_corners = presence.generate_user_camera() # Init client metadata if not local_user_metadata or 'color' not in local_user_metadata.keys(): diff --git a/multi_user/operators.py b/multi_user/operators.py index 8228340..de7eddb 100644 --- a/multi_user/operators.py +++ b/multi_user/operators.py @@ -80,10 +80,6 @@ def initialize_session(): if node_ref.state == FETCHED: node_ref.apply() - # Step 3: Launch presence overlay - # if runtime_settings.enable_presence: - # presence.renderer.run() - # Step 4: Register blender timers for d in delayables: d.register() @@ -108,9 +104,6 @@ def on_connection_end(): stop_modal_executor = True - # Step 2: Unregister presence renderer - presence.renderer.stop() - if settings.update_method == 'DEPSGRAPH': bpy.app.handlers.depsgraph_update_post.remove( depsgraph_evaluation) diff --git a/multi_user/preferences.py b/multi_user/preferences.py index ce444a7..fee33b2 100644 --- a/multi_user/preferences.py +++ b/multi_user/preferences.py @@ -476,25 +476,21 @@ class SessionProps(bpy.types.PropertyGroup): name="Presence overlay", description='Enable overlay drawing module', default=True, - # update=presence.update_presence ) presence_show_selected: bpy.props.BoolProperty( name="Show selected objects", description='Enable selection overlay ', default=True, - # update=presence.update_overlay_settings ) presence_show_user: bpy.props.BoolProperty( name="Show users", description='Enable user overlay ', default=True, - # update=presence.update_overlay_settings ) presence_show_far_user: bpy.props.BoolProperty( name="Show users on different scenes", description="Show user on different scenes", default=False, - # update=presence.update_overlay_settings ) filter_owned: bpy.props.BoolProperty( name="filter_owned", diff --git a/multi_user/presence.py b/multi_user/presence.py index fd8e801..97123e6 100644 --- a/multi_user/presence.py +++ b/multi_user/presence.py @@ -34,7 +34,10 @@ from gpu_extras.batch import batch_for_shader from . import utils from replication.interface import session -def view3d_find(): + +# Helper functions + +def view3d_find() -> tuple: """ Find the first 'VIEW_3D' windows found in areas :return: tuple(Area, Region, RegionView3D) @@ -58,7 +61,7 @@ def refresh_3d_view(): def refresh_sidebar_view(): - """ Refresh the blender sidebar + """ Refresh the blender viewport sidebar """ area, region, rv3d = view3d_find() @@ -66,29 +69,38 @@ def refresh_sidebar_view(): area.regions[3].tag_redraw() -def get_target(region, rv3d, coord): +def project_to_viewport(region: bpy.types.Region, rv3d: bpy.types.RegionView3D, coords: list, distance: float = 1.0) -> list: + """ Compute a projection from 2D to 3D viewport coordinate + + :param region: target windows region + :type region: bpy.types.Region + :param rv3d: view 3D + :type rv3d: bpy.types.RegionView3D + :param coords: coordinate to project + :type coords: list + :param distance: distance offset into viewport + :type distance: float + :return: list of coordinates [x,y,z] + """ target = [0, 0, 0] - if coord and region and rv3d: - view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord) - ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord) - target = ray_origin + view_vector - - return [target.x, target.y, target.z] - - -def get_target_far(region, rv3d, coord, distance): - target = [0, 0, 0] - - if coord and region and rv3d: - view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord) - ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord) + if coords and region and rv3d: + view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coords) + ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coords) target = ray_origin + view_vector * distance return [target.x, target.y, target.z] -def get_default_bbox(obj, radius): +def bbox_from_obj(obj: bpy.types.Object, radius: float) -> list: + """ Generate a bounding box for a given object by using its world matrix + + :param obj: target object + :type obj: bpy.types.Object + :param radius: bounding box radius + :type radius: float + :return: list of 8 points [(x,y,z),...] + """ coords = [ (-radius, -radius, -radius), (+radius, -radius, -radius), (-radius, +radius, -radius), (+radius, +radius, -radius), @@ -102,36 +114,41 @@ def get_default_bbox(obj, radius): for point in bbox_corners] -def get_view_corners(): +def generate_user_camera() -> list: + """ Generate a basic camera represention of the user point of view + + :return: list of 7 points + """ area, region, rv3d = view3d_find() - v1 = [0, 0, 0] - v2 = [0, 0, 0] - v3 = [0, 0, 0] - v4 = [0, 0, 0] - v5 = [0, 0, 0] - v6 = [0, 0, 0] - v7 = [0, 0, 0] + v1 = v2 = v3 = v4 = v5 = v6 = v7 = [0, 0, 0] if area and region and rv3d: width = region.width height = region.height - v1 = get_target(region, rv3d, (0, 0)) - v3 = get_target(region, rv3d, (0, height)) - v2 = get_target(region, rv3d, (width, height)) - v4 = get_target(region, rv3d, (width, 0)) + v1 = project_to_viewport(region, rv3d, (0, 0)) + v3 = project_to_viewport(region, rv3d, (0, height)) + v2 = project_to_viewport(region, rv3d, (width, height)) + v4 = project_to_viewport(region, rv3d, (width, 0)) - v5 = get_target(region, rv3d, (width/2, height/2)) + v5 = project_to_viewport(region, rv3d, (width/2, height/2)) v6 = list(rv3d.view_location) - v7 = get_target_far(region, rv3d, (width/2, height/2), -.8) + v7 = project_to_viewport( + region, rv3d, (width/2, height/2), distance=-.8) coords = [v1, v2, v3, v4, v5, v6, v7] return coords -def get_client_2d(coords): +def project_to_screen(coords: list) -> list: + """ Project 3D coordinate to 2D screen coordinates + + :param coords: 3D coordinates (x,y,z) + :type coords: list + :return: list of 2D coordinates [x,y] + """ area, region, rv3d = view3d_find() if area and region and rv3d: return view3d_utils.location_3d_to_region_2d(region, rv3d, coords) @@ -139,7 +156,15 @@ def get_client_2d(coords): return (0, 0) -def get_bb_coords_from_obj(object, parent=None): +def get_bb_coords_from_obj(object: bpy.types.Object, parent: bpy.types.Object = None) -> list: + """ Generate bounding box in world coordinate from object bound box + + :param object: target object + :type object: bpy.types.Object + :param parent: optionnal parent + :type parent: bpy.types.Object + :return: list of 8 points [(x,y,z),...] + """ base = object.matrix_world if parent is None else parent.matrix_world bbox_corners = [base @ mathutils.Vector( corner) for corner in object.bound_box] @@ -148,21 +173,17 @@ def get_bb_coords_from_obj(object, parent=None): for point in bbox_corners] -def get_view_matrix(): +def get_view_matrix() -> list: + """ Return the 3d viewport view matrix + + :return: view matrix as a 4x4 list + """ area, region, rv3d = view3d_find() if area and region and rv3d: return [list(v) for v in rv3d.view_matrix] -def update_presence(self, context): - if 'renderer' in globals() and hasattr(renderer, 'run'): - if self.enable_presence: - renderer.run() - else: - renderer.stop() - - class Widget(object): def poll(self) -> bool: return True @@ -171,6 +192,10 @@ class Widget(object): raise NotImplementedError() +class ViewportWidget(Widget): + pass + + class UserWidget(Widget): # Camera widget indices indices = ((1, 3), (2, 1), (3, 0), @@ -200,9 +225,9 @@ class UserWidget(Widget): return (scene_current == bpy.context.scene.name or self.settings.presence_show_far_user) and \ - view_corners and \ - self.settings.presence_show_user and \ - self.settings.enable_presence + view_corners and \ + self.settings.presence_show_user and \ + self.settings.enable_presence def draw(self): location = self.data.get('view_corners') @@ -252,9 +277,9 @@ class UserSelectionWidget(Widget): return (scene_current == bpy.context.scene.name or self.settings.presence_show_far_user) and \ - user_selection and \ - self.settings.presence_show_selected and \ - self.settings.enable_presence + user_selection and \ + self.settings.presence_show_selected and \ + self.settings.enable_presence def draw(self): user_selection = self.data.get('selected_objects') @@ -273,7 +298,7 @@ class UserSelectionWidget(Widget): if ob.instance_collection: for obj in ob.instance_collection.objects: if obj.type == 'MESH': - positions = get_bb_coords_from_obj(obj, parent=ob) + positions = get_bb_coords_from_obj(obj, parent=ob) if hasattr(ob, 'bound_box'): indices = ( @@ -286,9 +311,9 @@ class UserSelectionWidget(Widget): (0, 1), (0, 2), (1, 3), (2, 3), (4, 5), (4, 6), (5, 7), (6, 7), (0, 4), (1, 5), (2, 6), (3, 7)) - + positions = get_default_bbox(ob, ob.scale.x) - + shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') batch = batch_for_shader( shader, @@ -300,6 +325,7 @@ class UserSelectionWidget(Widget): shader.uniform_float("color", self.data.get('color')) batch.draw(shader) + class DrawFactory(object): def __init__(self): self.d3d_items = {} @@ -311,17 +337,7 @@ class DrawFactory(object): self.active_object = None self.widgets = [] - def run(self): - self.register_handlers() - - def stop(self): - self.flush_users() - self.flush_selection() - self.unregister_handlers() - - refresh_3d_view() - - def register(self, widget): + def register(self, widget: Widget): self.widgets.append(widget) def unregister(self, widget): @@ -347,28 +363,6 @@ class DrawFactory(object): self.d3d_items.clear() self.d2d_items.clear() - def flush_selection(self, user=None): - key_to_remove = [] - select_key = f"{user}_select" if user else "select" - for k in self.d3d_items.keys(): - - if select_key in k: - key_to_remove.append(k) - - for k in key_to_remove: - del self.d3d_items[k] - - def flush_users(self): - key_to_remove = [] - for k in self.d3d_items.keys(): - if "select" not in k: - key_to_remove.append(k) - - for k in key_to_remove: - del self.d3d_items[k] - - self.d2d_items.clear() - def post_view_callback(self): bgl.glLineWidth(2.) bgl.glEnable(bgl.GL_DEPTH_TEST) @@ -376,11 +370,6 @@ class DrawFactory(object): bgl.glEnable(bgl.GL_LINE_SMOOTH) try: - for shader, batch, color in self.d3d_items.values(): - shader.bind() - shader.uniform_float("color", color) - batch.draw(shader) - for widget in self.widgets: if widget.poll(): widget.draw() @@ -390,7 +379,7 @@ class DrawFactory(object): def post_pixel_callback(self): for position, font, color in self.d2d_items.values(): try: - coords = get_client_2d(position) + coords = project_to_screen(position) if coords: blf.position(0, coords[0], coords[1]+10, 0) @@ -401,12 +390,14 @@ class DrawFactory(object): except Exception: logging.error(f"2D Exception: {e} \n {traceback.print_exc()}") + this = sys.modules[__name__] this.renderer = DrawFactory() + def register(): - renderer.run() + renderer.register_handlers() def unregister(): - renderer.stop() + renderer.unregister_handlers()