Skip to content

Instantly share code, notes, and snippets.

@jeacom25b
Last active November 9, 2021 23:08
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jeacom25b/a48f00b41eca7c7a190d3b7cbf934ac2 to your computer and use it in GitHub Desktop.
Save jeacom25b/a48f00b41eca7c7a190d3b7cbf934ac2 to your computer and use it in GitHub Desktop.
Utility module for blender 2.8, a line renderer for visual debugging and line drawing.
'''
Created by Jeacom
This is a utility module for drawing lines in the 3D viewport on Blender 2.8
using the GPU Api
The idea is to get rid of messy draw functions and data that is hard to keep track.
This class works directly like a callable draw handler and keeps track of all the geometry data.
'''
__all__ = ["LineRenderer",
"BLEND",
"MULTIPLY_BLEND",
"ADDITIVE_BLEND"]
import bpy
import bgl
import gpu
from gpu_extras.batch import batch_for_shader
from mathutils import Matrix, Vector
# Blend Modes
BLEND = 0
MULTIPLY_BLEND = 1
ADDITIVE_BLEND = 2
class LineRenderer:
def __init__(self):
# Useful for rendering in the same space of an object
self.matrix = Matrix().Identity(4)
# X-ray mode, draw through solid objects
self.draw_on_top = False
# Blend mode to choose, set it to one of the blend constants.
self.blend_mode = BLEND
# Width of the lines
self.line_width = 2.5
# Handler Placeholder
self.draw_handler = None
self._coords = []
self._colors = []
self._line_shader = gpu.shader.from_builtin("3D_SMOOTH_COLOR")
self._line_batch = batch_for_shader(self._line_shader, 'LINES', {"pos": self._coords, "color": self._colors})
def __call__(self, *args, **kwargs):
# __call__ Makes this object behave like a function.
# So you can add it like a draw handler.
self._draw()
def setup_handler(self):
# Utility function to easily add it as a draw handler
self.draw_handler = bpy.types.SpaceView3D.draw_handler_add(self, (), "WINDOW", "POST_VIEW")
def remove_handler(self):
# Utility function to remove the handler
self.draw_handler = bpy.types.SpaceView3D.draw_handler_remove(self.draw_handler, "WINDOW")
def update_batch(self):
# This takes the data rebuilds the shader batch.
# Call it every time you clear the data or add new lines, otherwize,
# You wont see changes in the viewport
coords = [self.matrix @ coord for coord in self._coords]
self._line_batch = batch_for_shader(self._line_shader, 'LINES', {"pos": coords, "color": self._colors})
def add_line(self, start, end, color1=(1, 0, 0, 1), color2=None):
# Simple add_line function, support color gradients,
# if only color1 is specified, it will be solid color (color1 on both ends)
# This doesnt render a line, it just adds the vectors and colors to the data
# so after calling update_batch(), it will be converted in a buffer Object
self._coords.append(Vector(start))
self._coords.append(Vector(end))
self._colors.append(color1)
if color2 is None:
self._colors.append(color1)
else:
self._colors.append(color2)
def clear_data(self):
# just clear all the data
self._coords.clear()
self._colors.clear()
def _start_drawing(self):
# This handles all the settings of the renderer before starting the draw stuff
if self.blend_mode == BLEND:
bgl.glEnable(bgl.GL_BLEND)
elif self.blend_mode == MULTIPLY_BLEND:
bgl.glEnable(bgl.GL_BLEND)
bgl.glBlendFunc(bgl.GL_DST_COLOR, bgl.GL_ZERO)
elif self.blend_mode == ADDITIVE_BLEND:
bgl.glEnable(bgl.GL_BLEND)
bgl.glBlendFunc(bgl.GL_ONE, bgl.GL_ONE)
if self.draw_on_top:
bgl.glDisable(bgl.GL_DEPTH_TEST)
bgl.glLineWidth(self.line_width)
def _stop_drawing(self):
# just reset some OpenGL stuff to not interfere with other drawings in the viewport
# its not absolutely necessary but makes it safer.
bgl.glDisable(bgl.GL_BLEND)
bgl.glLineWidth(1)
if self.draw_on_top:
bgl.glEnable(bgl.GL_DEPTH_TEST)
def _draw(self):
# This should be called by __call__,
# just regular routines for rendering in the viewport as a draw_handler
self._start_drawing()
batch = self._line_batch
self._line_shader.bind()
batch.draw(self._line_shader)
self._stop_drawing()
if __name__ == "__main__":
# Simple example, run it on blender's text editor.
# create a new instance of the class
renderer = LineRenderer()
# add lines to ir
renderer.add_line((10,0,0), (-10,0,0), color1=(1,0,0,1), color2=(0,0,1,1))
renderer.add_line((0,0,0), (0,0,5), color1=(0,1,0,1), color2=(0,1,1,1))
# enable X ray mode/see through objects and set Blend mode to Additive
renderer.draw_on_top = True
renderer.blend_mode = ADDITIVE_BLEND
# set line width to 5
renderer.line_width = 5
# Important, update batch always when adding
# new lines, otherwize they wont render.
renderer.update_batch()
# setup draw handler, optionally, you can call bpy.SpaceView3D.draw_handler_add()
renderer.setup_handler()
@ChristopherSchubert
Copy link

This is really awesome, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment