├── .gitignore
├── README.md
├── __init__.py
└── exporter.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 |
3 | .DS_Store
4 |
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | blender.script.io_export_svg
2 | ===============
3 |
4 | exportscript for blender 2.80 to get BezierCurves to SVG.
5 |
6 | for more info please go [there](http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/Inkscape_SVG_Exporter)
7 |
8 | Getting
9 | ---
10 |
11 | Best way is to:
12 |
13 | git clone https://github.com/maybites/blender.script.io_export_svg
14 |
15 | and symlinking it to your addons-folder.
16 |
17 | That way, you can git pull later on and it will automatically refresh to the latest (theoretically-)good version.
18 |
19 | If you are looking for the script for blender 2.79 or later please clone branch master_2.79
20 |
21 |
22 | Lasercutting
23 | ---
24 |
25 | If you want to export the SVG for precise lasercutting and since the laser is not a precis point (in the folling example 0.2mm in diameter, but its depending on the machine), you have to ajust the vector with an outset (or inset). After exporting the SVG you can do that in almost any vector-drawing program, here the instructions for Inkscape:
26 |
27 | 1. Open the exported SVG-file
28 | 2. Press Shift+Ctrl+D
29 | 3. Change Standard units to mm
30 | 4. Change Userdefined units to mm and close the documents properties
31 | 5. Select the vector to change
32 | 6. Change the line thickness to 0.1mm
33 | 7. Zoom close to the line (to the upper left corner of the object, thats where the little handle of the next steps will appear) and set a help line on to the outer-perimeter of the vector. Set it to the inner-perimeter if you want to create an inset.
34 | 8. Switch to Path-modify-tool
35 | 9. Select Menu->Path->Dynamic Outset¨
36 | 10. Grab the little handle and set the inner-perimeter (or outer-perimeter for an inset) of the vector onto the previously set help-line
37 | 11. If your lasercutter needs a special linethickness to recognise the cutline, set the linethickness accordingly.
38 | 12. Done.
39 |
40 | Your vector is now exacly a 0.1mm perimeter of the previous form, and the laser will cut right at the edge of the shape you defined in blender.
41 |
42 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # ***** GPL LICENSE BLOCK *****
2 | #
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 | # All rights reserved.
16 | #
17 | # ***** GPL LICENSE BLOCK *****
18 |
19 | bl_info = {
20 | "name": "Export SVG Format (.svg)",
21 | "author": "Martin Froehlich (maybites.ch) + Matthew Ready (craxic.com)",
22 | "version": (0, 0, 7),
23 | "blender": (2, 80, 0),
24 | "location": "File > Export > Inkscape (.svg)",
25 | "description": "The script exports Blender 2.80 BezierCurves to SVG format.",
26 | "warning": "No success guaranteed. Doesn't like objectnames with special characters",
27 | "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/Inkscape_SVG_Exporter",
28 | "tracker_url": "",
29 | "category": "Import-Export"
30 | }
31 |
32 | # Ensure that we reload our dependencies if we ourselves are reloaded by Blender
33 | if "bpy" in locals():
34 | import imp;
35 | if "exporter" in locals():
36 | imp.reload(exporter);
37 |
38 | import bpy;
39 | from .exporter import Exporter;
40 |
41 | def menu_func_export_button(self, context):
42 | self.layout.operator(Exporter.bl_idname, text="Inkscape (.svg)");
43 |
44 | classes = [
45 | Exporter,
46 | ]
47 |
48 | def register():
49 | for cls in classes:
50 | bpy.utils.register_class(cls)
51 | bpy.types.TOPBAR_MT_file_export.append(menu_func_export_button)
52 |
53 | def unregister(): # note how unregistering is done in reverse
54 | bpy.types.TOPBAR_MT_file_export.remove(menu_func_export_button)
55 | for cls in reversed(classes):
56 | bpy.utils.unregister_class(cls)
57 |
58 | if __name__ == "__main__":
59 | register()
60 |
--------------------------------------------------------------------------------
/exporter.py:
--------------------------------------------------------------------------------
1 | # --------------------------------------------------------------------------
2 | # Copyright (C) 2015: Martin Froehlich (maybites.ch), Matthew Ready (craxic.com)
3 | #
4 | # based on code from the defunct export script by jm soler, jmsoler_at_free.fr
5 | #
6 | # This program is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU General Public License
8 | # as published by the Free Software Foundation; either version 2
9 | # of the License, or (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software Foundation,
18 | # --------------------------------------------------------------------------
19 |
20 | import bpy
21 | from bpy_extras.io_utils import ExportHelper
22 | from bpy.types import BezierSplinePoint
23 |
24 | XML_HEADER = """
25 |
26 | "
33 |
34 | MOVE_COMMAND = 'M{},{} '
35 | LINE_COMMAND = 'L{},{} '
36 | CURVE_COMMAND = 'C{},{} {},{} {},{} '
37 | JOIN_COMMAND = 'Z '
38 |
39 |
40 | class CoordinateContext:
41 | def __init__(self, min_x, min_y, max_x, max_y):
42 | self.min_x = min_x
43 | self.min_y = min_y
44 | self.max_x = max_x
45 | self.max_y = max_y
46 |
47 | def cx(self, x):
48 | return x - self.min_x
49 |
50 | def cy(self, y):
51 | return self.max_y - y
52 |
53 |
54 | def make_curve_command(previous, point, ctx, scale, translation):
55 | return CURVE_COMMAND.format(
56 | ctx.cx(previous.handle_right[0] * scale[0] + translation[0]),
57 | ctx.cy(previous.handle_right[1] * scale[1] + translation[1]),
58 | ctx.cx(point.handle_left[0] * scale[0] + translation[0]),
59 | ctx.cy(point.handle_left[1] * scale[1] + translation[1]),
60 | ctx.cx(point.co[0] * scale[0] + translation[0]),
61 | ctx.cy(point.co[1] * scale[1] + translation[1]))
62 |
63 |
64 | def point_str(point, scale, translation):
65 | if isinstance(point, BezierSplinePoint):
66 | return "[x: {}, y: {}, z: {},\n" \
67 | " x: {}, y: {}, z: {},\n" \
68 | " x: {}, y: {}, z: {}]".format(point.handle_left[0] * scale[0] + translation[0],
69 | point.handle_left[1] * scale[1] + translation[1],
70 | point.handle_left[2] * scale[2] + translation[2],
71 | point.co[0] * scale[0] + translation[0],
72 | point.co[1] * scale[1] + translation[1],
73 | point.co[2] * scale[2] + translation[2],
74 | point.handle_right[0] * scale[0] + translation[0],
75 | point.handle_right[1] * scale[1] + translation[1],
76 | point.handle_right[2] * scale[2] + translation[2])
77 | else:
78 | return "[x: {}, y: {}, z: {}]".format(point.co[0] * scale[0] + translation[0],
79 | point.co[1] * scale[1] + translation[1],
80 | point.co[2] * scale[2] + translation[2])
81 |
82 |
83 | class Exporter(bpy.types.Operator, ExportHelper):
84 | bl_idname = "export_svg_format.svg"
85 | bl_label = "Inkscape SVG Exporter"
86 | bl_options = {'PRESET'}
87 | filename_ext = ".svg"
88 |
89 | def execute(self, context):
90 | # Ensure Blender is currently in OBJECT mode to allow data access.
91 | bpy.ops.object.mode_set(mode='OBJECT')
92 |
93 | # Set the default return state to FINISHED
94 | result = {'FINISHED'}
95 |
96 | # Check that the currently selected object contains mesh data for
97 | # exporting
98 | curve = bpy.context.selected_objects[0]
99 | if not curve or curve.type != 'CURVE':
100 | raise NameError("Cannot export: object %s is not a curve" % curve)
101 |
102 | # this scales from blender to svg (blender 1.0 = 1m while svg 1.0 = 1mm)
103 | scale = [1000, 1000, 1000]; #curve.dimensions * 1000;
104 |
105 | # consider unit settings
106 | unit = context.scene.unit_settings
107 | if unit.system == 'METRIC':
108 | scale[:] = [s * unit.scale_length for s in scale]
109 | elif unit.system == 'IMPERIAL':
110 | self.report({'WARNING'}, "Imperial units not implemented! Scale of output is most probably incorrect!")
111 |
112 | # bound_box[0] == [left, bottom, down]
113 | # bound_box[7] == [right, top, up]
114 | # bound_box[i] == [x, y, z], 0 <= i <= 7
115 | min_x = curve.bound_box[0][0]
116 | max_x = curve.bound_box[7][0]
117 | min_y = curve.bound_box[0][1]
118 | max_y = curve.bound_box[7][1]
119 |
120 | ctx = CoordinateContext(min_x, min_y, max_x, max_y)
121 |
122 | # this calculates the documents size
123 | width = (max_x - min_x) * scale[0]
124 | height = (max_y - min_y) * scale[1]
125 |
126 | # this centers the object inside the document
127 | translation = [- scale[0] * curve.bound_box[0][0], scale[1] * curve.bound_box[0][1], scale[2] * curve.bound_box[0][2]]
128 |
129 | paths = []
130 | for spline in curve.data.splines:
131 | path = ""
132 | if spline.type == 'BEZIER':
133 | print("Exporting BEZIER curve.")
134 | first_curve_point = None
135 | previous = None
136 | for n, point in enumerate(spline.bezier_points):
137 | print("Point: " + point_str(point, scale, translation))
138 |
139 | if n == 0:
140 | first_curve_point = point
141 | path += MOVE_COMMAND.format(ctx.cx(point.co[0] * scale[0] + translation[0]),
142 | ctx.cy(point.co[1] * scale[1] + translation[1]))
143 | else:
144 | path += make_curve_command(previous, point, ctx, scale, translation)
145 | previous = point
146 |
147 | if spline.use_cyclic_u == 1:
148 | path += make_curve_command(previous, first_curve_point, ctx, scale, translation)
149 | path += JOIN_COMMAND
150 | elif spline.type == 'POLY':
151 | print("Exporting POLY curve.")
152 | for n, point in enumerate(spline.points):
153 | command = MOVE_COMMAND if n == 0 else LINE_COMMAND
154 | path += command.format(ctx.cx(point.co[0] * scale[0] + translation[0]),
155 | ctx.cy(point.co[1] * scale[1] + translation[1]))
156 | print("Point: " + point_str(point, scale, translation))
157 |
158 | if spline.use_cyclic_u == 1:
159 | path += JOIN_COMMAND
160 | paths.append(path)
161 |
162 | # Open the file for writing
163 | with open(self.filepath, 'w') as f:
164 | f.write(XML_HEADER.format(width, height, curve.name))
165 | for path in paths:
166 | f.write(XML_PATH.format(path))
167 | f.write(XML_END)
168 |
169 | return result
170 |
--------------------------------------------------------------------------------