3
$\begingroup$

I want to draw a diagram using shapes such as triangles and squares in the node editor. How can I do this?

$\endgroup$
8
$\begingroup$

Here is a basic bgl and blf example based on this answer:

enter image description here

import bpy
import bgl
import blf
import math

# based on http://slabode.exofire.net/circle_draw.shtml
def draw_circle_2d(color, cx, cy, r, num_segments):
    theta = 2 * 3.1415926 / num_segments
    c = math.cos(theta) #precalculate the sine and cosine
    s = math.sin(theta)
    x = r # we start at angle = 0 
    y = 0
    bgl.glColor4f(*color)
    bgl.glBegin(bgl.GL_LINE_LOOP)
    for i in range (num_segments):
        bgl.glVertex2f(x + cx, y + cy) # output vertex 
        # apply the rotation matrix
        t = x
        x = c * x - s * y
        y = s * t + c * y
    bgl.glEnd()

def draw_line_2d(color, start, end):
    bgl.glColor4f(*color)
    bgl.glBegin(bgl.GL_LINES)
    bgl.glVertex2f(*start)
    bgl.glVertex2f(*end)
    bgl.glEnd()

def draw_typo_2d(color, text):
    font_id = 0  # XXX, need to find out how best to get this.
    # draw some text
    bgl.glColor4f(*color)
    blf.position(font_id, 20, 70, 0)
    blf.size(font_id, 20, 72)
    blf.draw(font_id, text)

def draw_callback_px(self, context):

    bgl.glPushAttrib(bgl.GL_ENABLE_BIT)
    # glPushAttrib is done to return everything to normal after drawing

    bgl.glLineStipple(10, 0x9999)
    bgl.glEnable(bgl.GL_LINE_STIPPLE)

    # 50% alpha, 2 pixel width line
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor4f(1.0, 1.0, 1.0, 0.8)
    bgl.glLineWidth(5)

    bgl.glBegin(bgl.GL_LINE_STRIP)
    for x, y in self.mouse_path:
        bgl.glVertex2i(x, y)

    bgl.glEnd()
    bgl.glPopAttrib()

    bgl.glEnable(bgl.GL_BLEND)

    # ...api_current/bpy.types.Area.html?highlight=bpy.types.area
    header_height = context.area.regions[0].height # 26px
    width = context.area.width
    height = context.area.height - header_height

    p1_2d = (0,0)
    p2_2d = (width, height)
    p3_2d = (width, 0)
    p4_2d = (0, height)

    # green line
    bgl.glLineWidth(3)

    draw_line_2d((0.0, 1.0, 0.0, 0.8), p1_2d, p2_2d)

    # yellow line
    bgl.glLineWidth(5)
    draw_line_2d((1.0, 1.0, 0.0, 0.8), p3_2d, p4_2d) 

    # white circle
    bgl.glLineWidth(4)
    draw_circle_2d((1.0, 1.0, 1.0, 0.8), width/2, height/2, 70, 360)

    # red circle
    bgl.glLineWidth(5)
    draw_circle_2d((1.0, 0.0, 0.0, 0.4), width/2, height/2, 230, 5)

    # draw text
    draw_typo_2d((1.0, 1.0, 1.0, 1), "Hello Word " + str(len(self.mouse_path)))

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)


class ModalDrawOperator(bpy.types.Operator):
    """Draw a line with the mouse"""
    bl_idname = "node.modal_operator"
    bl_label = "Simple Modal Node Editor Operator"

    def modal(self, context, event):
        context.area.tag_redraw()

        if event.type == 'MOUSEMOVE':
            self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))

        elif event.type == 'LEFTMOUSE':
            bpy.types.SpaceNodeEditor.draw_handler_remove(self._handle, 'WINDOW')
            return {'FINISHED'}

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

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        if context.area.type == 'NODE_EDITOR':
            # the arguments we pass the the callback
            args = (self, context)
            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
            self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')

            self.mouse_path = []

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "NODE_EDITOR not found, cannot run operator")
            return {'CANCELLED'}


def register():
    bpy.utils.register_class(ModalDrawOperator)

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

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

Another take, from this answer, slightly modified for the node editor. The call to bpy.ops.object.modal_operator('INVOKE_SCREEN') is only needed to add the mouse event UI.

import bpy
import blf
from bpy.props import IntProperty, FloatProperty
import bgl

class BGLWidget:
    handle = None

    def __init__(self, op, context, areatype):

        # Calculate scroller width, dpi and pixelsize dependent
        self.pixel_size = context.user_preferences.system.pixel_size
        self.dpi = context.user_preferences.system.dpi
        self.dpi_fac = self.pixel_size * self.dpi / 72
        # A normal widget unit is 20, but the scroller is apparently 16
        self.scroller_width = 16 * self.dpi_fac

        self.op = op
        self.areatype = areatype

        self.handle = self.create_handle(context)
        theme = context.user_preferences.themes[0]
        self.theme = theme

    def create_handle(self, context):
        handle = self.areatype.draw_handler_add(
            self.draw_region,
            (context,),
           'WINDOW', 'POST_PIXEL')  
        return handle     

    def remove_handle(self):
        if self.handle:
            self.areatype.draw_handler_remove(self.handle, 'WINDOW') 
            self.handle = None   

    def draw_region(self, context):
        # check validity
        self.visualise(context)


    def draw_box(self, x, y, w, h, color=(0.0, 0.0, 0.0, 1.0)):
        #bgl.glDepthRange (0.1, 1.0)
        bgl.glColor4f(*color)
        bgl.glBegin(bgl.GL_QUADS)

        bgl.glVertex2f(x+w, y+h)
        bgl.glVertex2f(x, y+h) 
        bgl.glVertex2f(x, y) 
        bgl.glVertex2f(x+w, y)      
        bgl.glEnd()

    def visualise(self, context):
        # used to draw override in class def
        pass

class Button:
    def __init__(self, x, y, w, h, color=(1,1,1,1)):
        #draw a box
        self.x = 0
        self.y = 0
        self.w = w
        self.h = h
        self.color = color   

    def __str__(self):
        return "Button %s" % str(self.color)

    def __repr__(self):
        return "Button %d %d color(%s)" % (self.x, self.y, str(self.color))

    def in_box(self, x, y):
        return (self.x < x < self.x + self.w
                and self.y < y < self.y + self.h)


class ButtonWidget(BGLWidget):
    help_screen = -1
    buttons = []
    screen_buttons = {}
    def button(self, w, h):
        # add a new button
        b = Button(0, 0, w, h)
        self.buttons.append(b)
        return b


    def visualise(self, context):
        if self.help_screen > -1:
            print("HELP", self.help_screen)
            if context.screen.areas[self.help_screen] == context.area:
                self.draw_box(0, 0, 10000, 10000, color=(0, 1, 0, 1))
                context.area.tag_redraw()
        for b in self.buttons:
            self.draw_button(b, context)

    def draw_button(self, box, context):
        m = [i for i, a in enumerate(context.screen.areas) if a == context.area]
        if not len(m):
            return None
        key = "area%d" % m[0]
        b = self.screen_buttons.setdefault(key, Button(box.x, box.y, box.w, box.h, color=box.color))
        b.x = context.region.width - b.w - self.scroller_width
        b.y = context.region.height - b.h - self.scroller_width
        #print(b.x, b.y, b.w, b.h)  # debug shows box coords on draw
        self.draw_box(b.x, b.y, b.w, b.h, color=b.color)
        #self.screen_buttons[key] = b

    def mouse_over(self, screen, area_index, x, y):
        key = "area%d" % area_index
        box = self.screen_buttons.get(key, None)
        if box:
            area = screen.areas[area_index]
            if box.in_box(x, y):
                box.color = (1, 0, 0, 0)
                self.help_screen = area_index
                area.tag_redraw()
            else:
                self.help_screen = -1
                box.color = (0, 0, 1, 0)
            #self.screen_buttons[key] = box
            area.tag_redraw()


class ModalOperator(bpy.types.Operator):
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"

    def modal(self, context, event):
        def in_area(area, x, y):

            return (area.x < x < area.x + area.width 
                and area.y < y < area.y + area.height)

        screen = context.screen
        mx = event.mouse_x
        my = event.mouse_y
        #print(mx, my)        
        areas = [i for i, a in enumerate(screen.areas) if a.type.startswith('NODE_EDITOR')
                 and in_area(a, mx, my)]        

        for i in areas:
            a = screen.areas[i]
            region = a.regions[-1]
            x = mx - region.x
            y = my - region.y
            ui.mouse_over(screen, i, x, y)
            if event.type == 'LEFTMOUSE':

                print('PRESS in screen["%s"].areas["%d"]' % (screen.name, i))
                #click events ???

        if event.type in {'ESC'}:
            # dont have to remove the UI here

            ui.remove_handle()
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

def register():
    bpy.utils.register_class(ModalOperator)


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

if __name__ == "__main__":
    register()

    # create a UI
    context = bpy.context                 
    h = 50
    w = 200
    ui = ButtonWidget(None, context, bpy.types.SpaceNodeEditor)
    button = ui.button(w, h)
    for a in context.screen.areas:
        if a.type == 'NODE_EDITOR':
            a.tag_redraw()
    bpy.ops.object.modal_operator('INVOKE_SCREEN')
$\endgroup$

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.