# How can I convert a Grease Pencil object to curve or mesh without operators?

Geometry-holding objects have handy methods to get their data as an evaluated mesh :

However these can't be used on Grease Pencil objects and throw an exception :

RuntimeError: Error: Object does not have geometry data


There is an operator : bpy.ops.gpencil.convert which can be used to convert a grease pencil object to a curve object, but it can only convert one layer at a time and it requires holding a reference to a 3D view area, which might not always be available.

How can I convert a Grease Pencil object to a curve object or a mesh object using lower-level API instead of an operator ?

The limitation is easily explained by the complexity of a Grease Pencil object and its data structure, best explained on this other question.

A GP object can hold several layers, which can hold information for each frame of the animation, and each frame can hold an indefinite number of strokes, which can hold an indefinite number of points.

For the sake of ease of use the script will be considering the grease pencil object is not animated and holds only one frame worth of data, but it could be added as a feature afterwards.

It will however be able to export different objects for each layer in the GP object.

The final script :

import bpy

def main():
gpencil_object = bpy.data.objects["Suzanne"]
convert_gpencil_to_curve(gpencil_object, flatten_layers=True)
convert_gpencil_to_curve(gpencil_object, flatten_layers=False)
convert_gpencil_to_mesh(gpencil_object, flatten_layers=True)
convert_gpencil_to_mesh(gpencil_object, flatten_layers=False)

def new_convert_mesh_object(collection, name):
mesh = bpy.data.meshes.new(name=name)
convert_object = bpy.data.objects.new(name=name, object_data=mesh)
return convert_object

def new_convert_curve_object(collection, name):
curve = bpy.data.curves.new(name=name, type="CURVE")
curve.dimensions = "3D"
convert_object = bpy.data.objects.new(name=name, object_data=curve)
return convert_object

def convert_gpencil_to_mesh(gpencil_object, flatten_layers=True):
gp_col = gpencil_object.users_collection[0]
gp = gpencil_object.data
if flatten_layers:
obj = new_convert_mesh_object(gp_col, gpencil_object.name + "_mesh")
vertices = []
edges = []
for layer in gp.layers:
if not flatten_layers:
obj = new_convert_mesh_object(gp_col, f"{gpencil_object.name}_mesh_layer_{layer.info}")
vertices = []
edges = []
for frame in layer.frames:
for stroke in frame.strokes:
for i, point in enumerate(stroke.points):
vertices.append(point.co)
if i > 0:
edges.append((len(vertices) - 1,len(vertices) - 2))
if not flatten_layers:
obj.data.from_pydata(vertices, edges, ())
if flatten_layers:
obj.data.from_pydata(vertices, edges, ())

def convert_gpencil_to_curve(gpencil_object, flatten_layers=True):
gp_col = gpencil_object.users_collection[0]
gp = gpencil_object.data
if flatten_layers:
obj = new_convert_curve_object(gp_col, gpencil_object.name + "_curve")
for layer in gp.layers:
if not flatten_layers:
obj = new_convert_curve_object(gp_col, f"{gpencil_object.name}_curve_layer_{layer.info}")
for frame in layer.frames:
for stroke in frame.strokes:
spline = obj.data.splines.new(type="POLY")
spline.points.add(len(stroke.points) - 1) # Spline starts with 1 point
for i, point in enumerate(stroke.points):
spline.points[i].co = [v for v in point.co] + [1]

if __name__ == "__main__":
main()


The explanations :

1. Convert to curve

First we define a handy function to create a new curve object and link it to the GP object collection, then return it.

def new_convert_curve_object(collection, name):
curve = bpy.data.curves.new(name=name, type="CURVE")
curve.dimensions = "3D"
convert_object = bpy.data.objects.new(name=name, object_data=curve)
return convert_object


Then we define the behaviour of the curve converter :

def convert_gpencil_to_curve(gpencil_object, flatten_layers=True):
gp_col = gpencil_object.users_collection[0]
gp = gpencil_object.data
if flatten_layers:
obj = new_convert_curve_object(gp_col, gpencil_object.name + "_curve")
for layer in gp.layers:
if not flatten_layers:
obj = new_convert_curve_object(gp_col, f"{gpencil_object.name}_curve_layer_{layer.info}")
for frame in layer.frames:
for stroke in frame.strokes:
spline = obj.data.splines.new(type="POLY")
spline.points.add(len(stroke.points) - 1) # Spline starts with 1 point
for i, point in enumerate(stroke.points):
spline.points[i].co = [v for v in point.co] + [1]


Here we want either to create only one object and write splines to it if we flatten the GP object's layers, or create one curve object per layer and accordingly populate the splines.

We reference the gpencil object (Suzanne for testing purposes):

gpencil_object = bpy.data.objects["Suzanne"]


Then we call it with layer flattening :

convert_gpencil_to_curve(gpencil_object, flatten_layers=True)


A new curve is created :

And all the layers have been flattened :

Calling the method with flatten_layers=False yields :

convert_gpencil_to_curve(gpencil_object, flatten_layers=False)


Two curves have been added (one for each layer) :

And the objects are separated :

1. Convert to mesh

It works pretty much the same way. Define our handy object creator :

def new_convert_mesh_object(collection, name):
mesh = bpy.data.meshes.new(name=name)
convert_object = bpy.data.objects.new(name=name, object_data=mesh)
return convert_object


Define the converting process :

def convert_gpencil_to_mesh(gpencil_object, flatten_layers=True):
gp_col = gpencil_object.users_collection[0]
gp = gpencil_object.data
if flatten_layers:
obj = new_convert_mesh_object(gp_col, gpencil_object.name + "_mesh")
vertices = []
edges = []
for layer in gp.layers:
if not flatten_layers:
obj = new_convert_mesh_object(gp_col, f"{gpencil_object.name}_mesh_layer_{layer.info}")
vertices = []
edges = []
for frame in layer.frames:
for stroke in frame.strokes:
for i, point in enumerate(stroke.points):
vertices.append(point.co)
if i > 0:
edges.append((len(vertices) - 1,len(vertices) - 2))
if not flatten_layers:
obj.data.from_pydata(vertices, edges, ())
if flatten_layers:
obj.data.from_pydata(vertices, edges, ())

convert_gpencil_to_mesh(gpencil_object, flatten_layers=True)
convert_gpencil_to_mesh(gpencil_object, flatten_layers=False)


yields pretty much the same result as for the curve.