5
$\begingroup$

I'm drawing some lines using a 2D_UNIFORM_COLOR shader based on object location. When I rotate my view, the lines update only when the rotation is done: I thought that I can just put a function that updates my coordinates in modal() but this didn't work. So I wonder if there's a way to update the regions while the view is rotated?

enter image description here

P.S. I realise that a 3d shader would work better for drawing axes, but axes here is just an example

My code:

import bpy
from mathutils import Vector
import bgl
import gpu
from gpu_extras.batch import batch_for_shader
from bpy_extras.view3d_utils import location_3d_to_region_2d

def draw_callback_px(self, context):

    # do I really have to create a separate shader for each line? Ugh

    shader_X = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    shader_Y = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    shader_Z = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glLineWidth(2)
    
    v_x = (self.axes[0] - self.ob_location).normalized()
    v_y = (self.axes[1] - self.ob_location).normalized()
    v_z = (self.axes[2] - self.ob_location).normalized()
    batch_X = batch_for_shader(shader_X, 'LINE_STRIP', {"pos": [self.ob_location, self.ob_location + v_x*100]})
    batch_Y = batch_for_shader(shader_Y, 'LINE_STRIP', {"pos": [self.ob_location, self.ob_location + v_y*100]})
    batch_Z = batch_for_shader(shader_Z, 'LINE_STRIP', {"pos": [self.ob_location, self.ob_location + v_z*100]})

    shader_X.bind()
    shader_X.uniform_float("color", (1, 0, 0, 0.2))
    batch_X.draw(shader_X)

    shader_Y.bind()
    shader_Y.uniform_float("color", (0, 1, 0, 0.2))
    batch_Y.draw(shader_Y)

    shader_Z.bind()
    shader_Z.uniform_float("color", (0, 0, 1, 0.2))
    batch_Z.draw(shader_Z)

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)

class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator_test"
    bl_label = "Simple Modal Operator Test"

    def modal(self, context, event):
        context.area.tag_redraw()
        
        self.update_region(context, event)
        
        # Navigation
        if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'} or event.alt:        
            return {'PASS_THROUGH'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.ob = bpy.context.view_layer.objects.active
        if not self.ob:
            return {'CANCELLED'}
        self.update_region(context, event)

        args = (self, context)
        self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}
    
    def update_region(self, context, event):
        self.region = context.region
        self.rv3d = context.space_data.region_3d
        self.ob_location = location_3d_to_region_2d(self.region, self.rv3d, self.ob.location, default=None)
        self.axes = [location_3d_to_region_2d(self.region, self.rv3d, self.ob.location + Vector((1,0,0)), default=None),
                     location_3d_to_region_2d(self.region, self.rv3d, self.ob.location + Vector((0,1,0)), default=None),
                     location_3d_to_region_2d(self.region, self.rv3d, self.ob.location + Vector((0,0,1)), default=None)]
        
def register():
    bpy.utils.register_class(ModalOperator)


def unregister():
    bpy.utils.unregister_class(ModalOperator)

if __name__ == "__main__":
    register()
$\endgroup$
4
$\begingroup$

Edit: as I wrote all that is below, I just realized the question was explicitly about 2D shader...

You just missed to call

self.update_region(context, None)

from inside the callback, which is itself called at each drawing.

Though, as it is the calculation is not correct. You should use object world matrix (see the previous answer).


Previous answer (I don't delete it as that could be useful):

As you want to move during the operator, you are in 3d, use:

gpu.shader.from_builtin('3D_UNIFORM_COLOR')

instead of:

gpu.shader.from_builtin('2D_UNIFORM_COLOR')

Also, in this case, use POST_VIEW, as indicated in the documentation.

draw_type (str) – Usually POST_PIXEL for 2D drawing and POST_VIEW for 3D drawing. In some cases PRE_VIEW can be used. BACKDROP can be used for backdrops in the node editor.

self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_VIEW')

One shader is needed.

You can start from object axis and convert them in world space:

def draw_callback_px(self, context):

    obj = context.active_object
    mat = obj.matrix_world

    shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
    shader.bind()

    bgl.glEnable(bgl.GL_BLEND)
    bgl.glLineWidth(2)

    origin = mat @ Vector((0, 0, 0)) #Convert coordinates in world space
    x = mat @ Vector((2,0,0))
    y = mat @ Vector((0,2,0))
    z = mat @ Vector((0,0,2))
    batch_X = batch_for_shader(shader, 'LINES', {"pos": [origin, x]}, indices = ((0, 1),) )
    batch_Y = batch_for_shader(shader, 'LINES', {"pos": [origin, y]}, indices = ((0, 1),) )
    batch_Z = batch_for_shader(shader, 'LINES', {"pos": [origin, z]}, indices = ((0, 1),) )

    shader.uniform_float("color", (1, 0, 0, 0.2))
    batch_X.draw(shader)

    shader.uniform_float("color", (0, 1, 0, 0.2))
    batch_Y.draw(shader)

    shader.uniform_float("color", (0, 0, 1, 0.2))
    batch_Z.draw(shader)

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)

enter image description here

$\endgroup$
5
  • $\begingroup$ Why POST_VIEW? $\endgroup$
    – brockmann
    Dec 7 '20 at 13:07
  • $\begingroup$ @brockmann, just edited about that. I don't know why, but it is in the docs... $\endgroup$
    – lemon
    Dec 7 '20 at 13:11
  • $\begingroup$ Cool! Thanks @lemon. $\endgroup$
    – brockmann
    Dec 7 '20 at 13:11
  • $\begingroup$ @brockmann... fyi (if needed), the issue was simply... see edited answer... : )) $\endgroup$
    – lemon
    Dec 7 '20 at 13:37
  • 1
    $\begingroup$ Thank you for the answer! Yes, I knew how to make it work for 3d shader so I was looking for a solution for 2d shader. Putting my region_update to draw callback fixed the issue I had, thank you! $\endgroup$ Dec 7 '20 at 18:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.