4
$\begingroup$

I'm trying to figure out if it's possible to enable a grid overlay like one would see in Gimp or photoshop, for the UV editor of blender. Here's a picture showing what I mean:

enter image description here

The grid is a 16x16, showing the bounds of each individual tile. Is there a way to replicate this sort of grid within the blender uv editor? So that I can see the boundaries of said tiles easier? Here is what my uv editor looks like in blender:

enter image description here

A grid overlay would really help me out, here. However, I can't seem to find an option for it, and Google turned up nothing useful.

Thanks!

$\endgroup$

1 Answer 1

4
$\begingroup$

Add grid to uv editor via gpu module

  1. run script

enter image description here

# v2022_4_22

import bpy, bgl, blf, gpu
from gpu_extras.batch import batch_for_shader
from bpy.app.handlers import persistent

line_width = 2
line_color = (0.5, 0.8, 1.0, 0.9)
size_x     = 8 # int > 0
size_y     = 8


shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
class BOX:
    def __init__(self, L, R, B, T):
        self.L = L
        self.R = R
        self.B = B
        self.T = T
    def upd(self):
        L = self.L
        R = self.R
        B = self.B
        T = self.T
        self.batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [[L, B], [L, T], [R, T], [R, B], [L, B]]})
    def draw(self):
        self.batch.draw(shader)
    def is_match(self, L, R, B, T):
        return L == self.L and R == self.R and B == self.B and T == self.T

class LINE:
    def __init__(self, x0, y0, x1, y1):
        self.x0 = x0
        self.y0 = y0
        self.x1 = x1
        self.y1 = y1
    def upd(self):
        x0 = self.x0
        y0 = self.y0
        x1 = self.x1
        y1 = self.y1
        self.batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [[x0, y0], [x1, y1]]})
    def draw(self):
        self.batch.draw(shader)

def draw_callback_px(self, context):
    try:
        bgl.glEnable(bgl.GL_BLEND)
        bgl.glLineWidth(line_width)
        shader.bind()
        shader.uniform_float("color", line_color)
        self.box.draw()
        for l in self.line_x:   l.draw()
        for l in self.line_y:   l.draw()

        # restore opengl defaults
        bgl.glLineWidth(1)
        bgl.glDisable(bgl.GL_BLEND)
    except:
        bpy.types.SpaceImageEditor.draw_handler_remove(ModalDrawOperator.handler, 'WINDOW')
        ModalDrawOperator.handler = None
        bpy.app.handlers.load_post.remove(run_when_open_new_blend_file)


class ModalDrawOperator(bpy.types.Operator):
    bl_idname = "view2d.modal_operator"
    bl_label = "Simple Modal View2D Operator"
    handler = None

    def fin(self):
        self.box = None
        self.line_x.clear()
        self.line_y.clear()
        cls = self.__class__
        if cls.handler is not None:
            bpy.types.SpaceImageEditor.draw_handler_remove(cls.handler, 'WINDOW')
            cls.handler = None
            bpy.app.handlers.load_post.remove(run_when_open_new_blend_file)
        if bpy.context.area is not None:
            bpy.context.area.tag_redraw()
        print("END")

    def modal(self, context, event):
        if context.area is None:
            self.fin()
            return {'CANCELLED'}
        if event.type in 'ESC':
            self.fin()
            return {'CANCELLED'}

        region      = self.region
        L, B        = region.view2d.view_to_region(0, 0)
        R, T        = region.view2d.view_to_region(1, 1)
        w           = (R - L) / size_x
        h           = (T - B) / size_y
        # print(L, R, B, T)
        if self.box.is_match(L, R, B, T) is False:
            self.box.L = L
            self.box.R = R
            self.box.B = B
            self.box.T = T
            self.box.upd()

            x = L + w
            for l in self.line_x:
                l.x0 = x
                l.y0 = B
                l.x1 = x
                l.y1 = T
                l.upd()
                x += w
            y = B + h
            for l in self.line_y:
                l.x0 = L
                l.y0 = y
                l.x1 = R
                l.y1 = y
                l.upd()
                y += h

            self.L = L
            self.R = R
            self.B = B
            self.T = T
            context.area.tag_redraw()


        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        print("INVOKE")
        if self.__class__.handler is not None:  return {'CANCELLED'}
        if context.area.type == 'IMAGE_EDITOR':
            areas = bpy.context.screen.areas
            area_types = [a.type for a in areas]
            image_area = [a for a in areas if a.type == 'IMAGE_EDITOR']

            if image_area:
                image_area = image_area[0]
                editor      = image_area.spaces[0]
                # zoom = editor.zoom[0]
                region      = image_area.regions[-1]
                L, B        = region.view2d.view_to_region(0, 0)
                R, T        = region.view2d.view_to_region(1, 1)
                w           = (R - L) / size_x
                h           = (T - B) / size_y
                self.L = L
                self.R = R
                self.B = B
                self.T = T
                self.region = region
            else:
                self.report({'WARNING'}, "Area not found, cannot run operator")
                return {'CANCELLED'}

            self.box    = BOX(L, R, B, T)
            self.box.upd()

            self.line_x = []
            line        = self.line_x
            x           = L + w
            for r in range(size_x - 1):
                l = LINE(x, B, x, T)
                line.append(l)
                l.upd()
                x += w
            self.line_y = []
            line        = self.line_y
            y           = B + h
            for r in range(size_y - 1):
                l = LINE(L, y, R, y)
                line.append(l)
                l.upd()
                y += h


            args = (self, context)
            if self.__class__.handler is None:
                self.__class__.handler = bpy.types.SpaceImageEditor.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
                bpy.app.handlers.load_post.append(run_when_open_new_blend_file)
            context.window_manager.modal_handler_add(self)
            context.area.tag_redraw()
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "Editor not found, cannot run operator")
            return {'CANCELLED'}


@persistent
def run_when_open_new_blend_file(dummy):
    if ModalDrawOperator.handler is not None:
        bpy.types.SpaceImageEditor.draw_handler_remove(ModalDrawOperator.handler, 'WINDOW')
        ModalDrawOperator.handler = None

addon_keymaps = []
def register():
    bpy.utils.register_class(ModalDrawOperator)

    # Add the hotkey
    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon
    if kc:
        km = wm.keyconfigs.addon.keymaps.new(name='Image', space_type='IMAGE_EDITOR')
        kmi = km.keymap_items.new("view2d.modal_operator", type='F9', value='PRESS', ctrl=True)
        addon_keymaps.append((km, kmi))

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

    # Remove the hotkey
    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()

if __name__ == "__main__":
    register()
  1. Press Ctrl F9 in uv editor. Esc to End the operator.

enter image description here

Known Issues/Bugs

  1. invalid when the image area outside the region.
$\endgroup$
1
  • $\begingroup$ Awesome. I'm going to be trying this here soon. I'll go ahead and upvote you and select you as the answer. Thank you so much for this! $\endgroup$
    – Krythic
    Apr 20, 2022 at 20:58

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .