Add grid to uv editor via gpu module
- run script

# 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()
- Press
Ctrl
F9
in uv editor. Esc
to End the operator.

Known Issues/Bugs
- invalid when the image area outside the region.