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)
collection.objects.link(convert_object)
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)
collection.objects.link(convert_object)
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 :
- 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)
collection.objects.link(convert_object)
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 :

- 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)
collection.objects.link(convert_object)
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.

