Skip to content

Instantly share code, notes, and snippets.

@tamask
Last active September 18, 2019 07:23
Show Gist options
  • Save tamask/d97ebbc36360c510932b29a2b3e50f84 to your computer and use it in GitHub Desktop.
Save tamask/d97ebbc36360c510932b29a2b3e50f84 to your computer and use it in GitHub Desktop.
Export xy-plane bezier/poly lines as svg outlines.
import os
import bpy
import math
import mathutils
bl_info = {
'name': 'Export SVG Lines (.svg)',
'author': 'Tamas Kemenczy',
'version': (0, 1),
'blender': (2, 6, 9),
'location': 'File > Import-Export > SVG Lines (.svg)',
'description': 'Flatten and export bezier curves to SVG',
'category': 'Import-Export',
}
def f(v):
return ('%.6f' % v).rstrip('0').rstrip('.')
def export_svg_lines(fp, objects):
min_x = max_x = min_y = max_y = 0
for obj in objects:
corners = [
obj.matrix_world * mathutils.Vector(v[:])
for v in obj.bound_box]
for c in corners:
min_x = min(min_x, c.x)
max_x = max(max_x, c.x)
min_y = min(min_y, c.y)
max_y = max(max_y, c.y)
extent = max((abs(min_x), abs(max_x), abs(min_y), abs(max_y))) + 0.1
extent = math.ceil(extent / 0.1) * 0.1
root_objects = set()
all_objects = []
curves = [obj for obj in objects if obj.type == 'CURVE']
for obj in curves:
all_objects.append(obj)
while obj.parent:
obj = obj.parent
all_objects.append(obj)
root_objects.add(obj)
data = (
('<svg viewBox="%s %s %s %s">'
'<g transform="scale(100,100)" '
'vector-effect="non-scaling-stroke" '
'fill="none" stroke="black" stroke-width="%s">') %
(f(-extent * 100),
f(-extent * 100),
f(extent * 200),
f(extent * 200), f(0.01))
)
for r in root_objects:
data += svg_object(r, all_objects)
data += '</g></svg>'
fp.write(data)
def svg_object(obj, all_objects):
out = '<g id="%s" transform="%s">' % (obj.name, svg_transform(obj))
if obj.type == 'CURVE':
out += svg_path(obj)
for child in obj.children:
if child in all_objects:
out += svg_object(child, all_objects)
out += '</g>'
return out
def svg_path(obj):
return '<path id="%s.%s" d="%s" />' % (obj.name, obj.data.name, svg_path_d(obj))
def svg_transform(obj):
return (
'translate(%s,%s) rotate(%s) scale(%s,%s)' % (
f(obj.location.x), f(-obj.location.y),
f(math.degrees(-obj.rotation_euler.z)),
f(obj.scale.x), f(obj.scale.y),
))
def svg_path_d(obj):
paths = []
for spline in obj.data.splines:
if spline.type == 'BEZIER':
path = bezier_to_commands(obj, spline)
paths.append(path)
if spline.type == 'POLY':
path = poly_to_commands(obj, spline)
paths.append(path)
return ' '.join(paths)
def bezier_to_commands(obj, spline):
points = []
q = mathutils.Euler((obj.rotation_euler.x, obj.rotation_euler.y, 0)).to_quaternion()
for p in spline.bezier_points:
points.extend((q * p.handle_left, q * p.co, q * p.handle_right))
if spline.use_cyclic_u:
p = points.pop(0)
points.append(p)
points.append(points[0])
else:
points.pop(0)
points.pop(len(points) - 1)
p = points[0]
commands = ['M %s,%s' % (f(p.x), f(-p.y)), 'C']
i = 1
while i < len(points):
p = points[i]
x1 = p.x
y1 = -p.y
i += 1
p = points[i]
x2 = p.x
y2 = -p.y
i += 1
p = points[i]
x = p.x
y = -p.y
i += 1
commands.append((
'%s,%s %s,%s %s,%s' %
(f(x1), f(y1), f(x2), f(y2), f(x), f(y))
))
if spline.use_cyclic_u:
commands.append('Z')
return ' '.join(commands)
def poly_to_commands(obj, spline):
points = []
q = mathutils.Euler((obj.rotation_euler.x, obj.rotation_euler.y, 0)).to_quaternion()
for p in spline.points:
points.append(p.co)
p = points[0]
commands = ['M %s,%s' % (f(p.x), f(-p.y)), 'L']
i = 1
while i < len(points):
p = points[i]
x1 = p.x
y1 = -p.y
i += 1
commands.append(('%s,%s' % (f(x1), f(y1))))
if spline.use_cyclic_u:
commands.append('Z')
return ' '.join(commands)
class SvgLineExporter(bpy.types.Operator):
bl_idname = 'export_scene.svg_lines'
bl_label = 'Export SVG Lines'
filepath = bpy.props.StringProperty(subtype='FILE_PATH',)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
objects = context.scene.objects
with open(self.filepath, 'w') as fp:
export_svg_lines(fp, context.scene.objects)
fp.flush()
return {'FINISHED'}
def invoke(self, context, event):
if not self.filepath:
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".svg")
path = os.path.dirname(self.filepath)
blendname = os.path.splitext(os.path.basename(bpy.data.filepath))[0]
filename = '%s.svg' % blendname
self.filepath = os.path.join(path, filename)
wm = context.window_manager
wm.fileselect_add(self)
return {'RUNNING_MODAL'}
def menu_export(self, context):
self.layout.operator(SvgLineExporter.bl_idname, text="SVG Lines (.svg)")
def register():
bpy.utils.register_class(SvgLineExporter)
bpy.types.INFO_MT_file_export.append(menu_export)
def unregister():
bpy.utils.unregister_class(SvgLineExporter)
bpy.types.INFO_MT_file_export.remove(menu_export)
if __name__ == '__main__':
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment