1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
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.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
22 "name": "Freestyle SVG Exporter",
23 "author": "Folkert de Vries",
25 "blender": (2, 72, 1),
26 "location": "Properties > Render > Freestyle SVG Export",
27 "description": "Exports Freestyle's stylized edges in SVG format",
34 import parameter_editor
38 import xml
.etree
.cElementTree
as et
40 from bpy
.app
.handlers
import persistent
41 from collections
import OrderedDict
42 from functools
import partial
43 from mathutils
import Vector
45 from freestyle
.types
import (
52 from freestyle
.utils
import (
60 from freestyle
.functions
import (
64 from freestyle
.predicates
import (
77 QuantitativeInvisibilityUP1D
,
82 from freestyle
.chainingiterators
import ChainPredicateIterator
83 from parameter_editor
import get_dashed_pattern
85 from bpy
.props
import (
92 # use utf-8 here to keep ElementTree happy, end result is utf-16
93 svg_primitive
= """<?xml version="1.0" encoding="ascii" standalone="no"?>
94 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
95 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
96 <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{:d}" height="{:d}">
102 "inkscape": "http://www.inkscape.org/namespaces/inkscape",
103 "svg": "http://www.w3.org/2000/svg",
104 "sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
105 "": "http://www.w3.org/2000/svg",
109 # wrap XMLElem.find, so the namespaces don't need to be given as an argument
110 def find_xml_elem(obj
, search
, namespaces
, *, all
=False):
112 return obj
.findall(search
, namespaces
=namespaces
)
113 return obj
.find(search
, namespaces
=namespaces
)
115 find_svg_elem
= partial(find_xml_elem
, namespaces
=namespaces
)
118 def render_height(scene
):
119 return int(scene
.render
.resolution_y
* scene
.render
.resolution_percentage
/ 100)
122 def render_width(scene
):
123 return int(scene
.render
.resolution_x
* scene
.render
.resolution_percentage
/ 100)
126 def format_rgb(color
):
127 return 'rgb({}, {}, {})'.format(*(int(v
* 255) for v
in color
))
130 # stores the state of the render, used to differ between animation and single frame renders.
133 # Note that this flag is set to False only after the first frame
134 # has been written to file.
139 def render_init(scene
):
140 RenderState
.is_preview
= True
144 def render_write(scene
):
145 RenderState
.is_preview
= False
148 def is_preview_render(scene
):
149 return RenderState
.is_preview
or scene
.svg_export
.mode
== 'FRAME'
152 def create_path(scene
):
153 """Creates the output path for the svg file"""
154 path
= os
.path
.dirname(scene
.render
.frame_path())
155 file_dir_path
= os
.path
.dirname(bpy
.data
.filepath
)
157 # try to use the given path if it is absolute
158 if os
.path
.isabs(path
):
161 # otherwise, use current file's location as a start for the relative path
162 elif bpy
.data
.is_saved
and file_dir_path
:
163 dirname
= os
.path
.normpath(os
.path
.join(file_dir_path
, path
))
165 # otherwise, use the folder from which blender was called as the start
167 dirname
= os
.path
.abspath(bpy
.path
.abspath(path
))
170 basename
= bpy
.path
.basename(scene
.render
.filepath
)
171 if scene
.svg_export
.mode
== 'FRAME':
172 frame
= "{:04d}".format(scene
.frame_current
)
174 frame
= "{:04d}-{:04d}".format(scene
.frame_start
, scene
.frame_end
)
176 return os
.path
.join(dirname
, basename
+ frame
+ ".svg")
179 class SVGExporterLinesetPanel(bpy
.types
.Panel
):
180 """Creates a Panel in the Render Layers context of the properties editor"""
181 bl_idname
= "RENDER_PT_SVGExporterLinesetPanel"
182 bl_space_type
= 'PROPERTIES'
183 bl_label
= "Freestyle Line Style SVG Export"
184 bl_region_type
= 'WINDOW'
185 bl_context
= "render_layer"
187 def draw(self
, context
):
190 scene
= context
.scene
191 svg
= scene
.svg_export
192 freestyle
= scene
.render
.layers
.active
.freestyle_settings
195 linestyle
= freestyle
.linesets
.active
.linestyle
197 except AttributeError:
198 # Linestyles can be removed, so 0 linestyles is possible.
199 # there is nothing to draw in those cases.
200 # see https://developer.blender.org/T49855
204 layout
.active
= (svg
.use_svg_export
and freestyle
.mode
!= 'SCRIPT')
206 column
= row
.column()
207 column
.prop(linestyle
, 'use_export_strokes')
209 column
= row
.column()
210 column
.active
= svg
.object_fill
211 column
.prop(linestyle
, 'use_export_fills')
214 row
.prop(linestyle
, "stroke_color_mode", expand
=True)
217 class SVGExport(bpy
.types
.PropertyGroup
):
218 """Implements the properties for the SVG exporter"""
219 bl_idname
= "RENDER_PT_svg_export"
221 use_svg_export
= BoolProperty(
223 description
="Export Freestyle edges to an .svg format",
225 split_at_invisible
= BoolProperty(
226 name
="Split at Invisible",
227 description
="Split the stroke at an invisible vertex",
229 object_fill
= BoolProperty(
230 name
="Fill Contours",
231 description
="Fill the contour with the object's material color",
236 ('FRAME', "Frame", "Export a single frame", 0),
237 ('ANIMATION', "Animation", "Export an animation", 1),
241 line_join_type
= EnumProperty(
244 ('MITTER', "Mitter", "Corners are sharp", 0),
245 ('ROUND', "Round", "Corners are smoothed", 1),
246 ('BEVEL', "Bevel", "Corners are bevelled", 2),
252 class SVGExporterPanel(bpy
.types
.Panel
):
253 """Creates a Panel in the render context of the properties editor"""
254 bl_idname
= "RENDER_PT_SVGExporterPanel"
255 bl_space_type
= 'PROPERTIES'
256 bl_label
= "Freestyle SVG Export"
257 bl_region_type
= 'WINDOW'
258 bl_context
= "render"
260 def draw_header(self
, context
):
261 self
.layout
.prop(context
.scene
.svg_export
, "use_svg_export", text
="")
263 def draw(self
, context
):
266 scene
= context
.scene
267 svg
= scene
.svg_export
268 freestyle
= scene
.render
.layers
.active
.freestyle_settings
270 layout
.active
= (svg
.use_svg_export
and freestyle
.mode
!= 'SCRIPT')
273 row
.prop(svg
, "mode", expand
=True)
276 row
.prop(svg
, "split_at_invisible")
277 row
.prop(svg
, "object_fill")
280 row
.prop(svg
, "line_join_type", expand
=True)
284 def svg_export_header(scene
):
285 if not (scene
.render
.use_freestyle
and scene
.svg_export
.use_svg_export
):
288 # write the header only for the first frame when animation is being rendered
289 if not is_preview_render(scene
) and scene
.frame_current
!= scene
.frame_start
:
292 # this may fail still. The error is printed to the console.
293 with
open(create_path(scene
), "w") as f
:
294 f
.write(svg_primitive
.format(render_width(scene
), render_height(scene
)))
298 def svg_export_animation(scene
):
299 """makes an animation of the exported SVG file """
300 render
= scene
.render
301 svg
= scene
.svg_export
303 if render
.use_freestyle
and svg
.use_svg_export
and not is_preview_render(scene
):
304 write_animation(create_path(scene
), scene
.frame_start
, render
.fps
)
307 def write_animation(filepath
, frame_begin
, fps
):
308 """Adds animate tags to the specified file."""
309 tree
= et
.parse(filepath
)
310 root
= tree
.getroot()
312 linesets
= find_svg_elem(tree
, ".//svg:g[@inkscape:groupmode='lineset']", all
=True)
313 for i
, lineset
in enumerate(linesets
):
314 name
= lineset
.get('id')
315 frames
= find_svg_elem(lineset
, ".//svg:g[@inkscape:groupmode='frame']", all
=True)
316 n_of_frames
= len(frames
)
317 keyTimes
= ";".join(str(round(x
/ n_of_frames
, 3)) for x
in range(n_of_frames
)) + ";1"
320 'attributeName': 'display',
321 'values': "none;" * (n_of_frames
- 1) + "inline;none",
322 'repeatCount': 'indefinite',
323 'keyTimes': keyTimes
,
324 'dur': "{:.3f}s".format(n_of_frames
/ fps
),
327 for j
, frame
in enumerate(frames
):
328 id = 'anim_{}_{:06n}'.format(name
, j
+ frame_begin
)
330 frame_anim
= et
.XML('<animate id="{}" begin="{:.3f}s" />'.format(id, (j
- n_of_frames
) / fps
))
331 # add per-lineset style attributes
332 frame_anim
.attrib
.update(style
)
333 # add to the current frame
334 frame
.append(frame_anim
)
338 tree
.write(filepath
, encoding
='ascii', xml_declaration
=True)
341 # - StrokeShaders - #
342 class SVGPathShader(StrokeShader
):
343 """Stroke Shader for writing stroke data to a .svg file."""
344 def __init__(self
, name
, style
, filepath
, res_y
, split_at_invisible
, stroke_color_mode
, frame_current
):
345 StrokeShader
.__init
__(self
)
346 # attribute 'name' of 'StrokeShader' objects is not writable, so _name is used
348 self
.filepath
= filepath
350 self
.frame_current
= frame_current
352 self
.split_at_invisible
= split_at_invisible
353 self
.stroke_color_mode
= stroke_color_mode
# BASE | FIRST | LAST
358 def from_lineset(cls
, lineset
, filepath
, res_y
, split_at_invisible
, use_stroke_color
, frame_current
, *, name
=""):
359 """Builds a SVGPathShader using data from the given lineset"""
360 name
= name
or lineset
.name
361 linestyle
= lineset
.linestyle
362 # extract style attributes from the linestyle and scene
363 svg
= getCurrentScene().svg_export
366 'stroke-width': linestyle
.thickness
,
367 'stroke-linecap': linestyle
.caps
.lower(),
368 'stroke-opacity': linestyle
.alpha
,
369 'stroke': format_rgb(linestyle
.color
),
370 'stroke-linejoin': svg
.line_join_type
.lower(),
372 # get dashed line pattern (if specified)
373 if linestyle
.use_dashed_line
:
374 style
['stroke-dasharray'] = ",".join(str(elem
) for elem
in get_dashed_pattern(linestyle
))
376 return cls(name
, style
, filepath
, res_y
, split_at_invisible
, use_stroke_color
, frame_current
)
380 def pathgen(stroke
, style
, height
, split_at_invisible
, stroke_color_mode
, f
=lambda v
: not v
.attribute
.visible
):
381 """Generator that creates SVG paths (as strings) from the current stroke """
385 if stroke_color_mode
!= 'BASE':
386 # try to use the color of the first or last vertex
388 index
= 0 if stroke_color_mode
== 'FIRST' else -1
389 color
= format_rgb(stroke
[index
].attribute
.color
)
390 style
["stroke"] = color
391 except (ValueError, IndexError):
392 # default is linestyle base color
395 # put style attributes into a single svg path definition
396 path
= '\n<path ' + "".join('{}="{}" '.format(k
, v
) for k
, v
in style
.items()) + 'd=" M '
403 yield '{:.3f}, {:.3f} '.format(x
, height
- y
)
404 if split_at_invisible
and v
.attribute
.visible
is False:
405 # end current and start new path;
407 # fast-forward till the next visible vertex
408 it
= itertools
.dropwhile(f
, it
)
409 # yield next visible vertex
410 svert
= next(it
, None)
414 yield '{:.3f}, {:.3f} '.format(x
, height
- y
)
418 def shade(self
, stroke
):
419 stroke_to_paths
= "".join(self
.pathgen(stroke
, self
.style
, self
.h
, self
.split_at_invisible
, self
.stroke_color_mode
)).split("\n")
420 # convert to actual XML. Empty strokes are empty strings; they are ignored.
421 self
.elements
.extend(et
.XML(elem
) for elem
in stroke_to_paths
if elem
) # if len(elem.strip()) > len(self.path))
424 """Write SVG data tree to file """
425 tree
= et
.parse(self
.filepath
)
426 root
= tree
.getroot()
428 scene
= bpy
.context
.scene
430 # create <g> for lineset as a whole (don't overwrite)
431 # when rendering an animation, frames will be nested in here, otherwise a group of strokes and optionally fills.
432 lineset_group
= find_svg_elem(tree
, ".//svg:g[@id='{}']".format(name
))
433 if lineset_group
is None:
434 lineset_group
= et
.XML('<g/>')
435 lineset_group
.attrib
= {
437 'xmlns:inkscape': namespaces
["inkscape"],
438 'inkscape:groupmode': 'lineset',
439 'inkscape:label': name
,
441 root
.append(lineset_group
)
443 # create <g> for the current frame
444 id = "frame_{:04n}".format(self
.frame_current
)
446 stroke_group
= et
.XML("<g/>")
447 stroke_group
.attrib
= {
448 'xmlns:inkscape': namespaces
["inkscape"],
449 'inkscape:groupmode': 'layer',
451 'inkscape:label': 'strokes'
454 stroke_group
.extend(self
.elements
)
455 if scene
.svg_export
.mode
== 'ANIMATION':
456 frame_group
= et
.XML("<g/>")
457 frame_group
.attrib
= {'id': id, 'inkscape:groupmode': 'frame', 'inkscape:label': id}
458 frame_group
.append(stroke_group
)
459 lineset_group
.append(frame_group
)
461 lineset_group
.append(stroke_group
)
464 print("SVG Export: writing to", self
.filepath
)
466 tree
.write(self
.filepath
, encoding
='ascii', xml_declaration
=True)
469 class SVGFillBuilder
:
470 def __init__(self
, filepath
, height
, name
):
471 self
.filepath
= filepath
473 self
.stroke_to_fill
= partial(self
.stroke_to_svg
, height
=height
)
476 def pathgen(vertices
, path
, height
):
478 for point
in vertices
:
480 yield '{:.3f}, {:.3f} '.format(x
, height
- y
)
481 yield ' z" />' # closes the path; connects the current to the first point
485 def get_merged_strokes(strokes
):
486 def extend_stroke(stroke
, vertices
):
487 for vert
in map(StrokeVertex
, vertices
):
488 stroke
.insert_vertex(vert
, stroke
.stroke_vertices_end())
491 base_strokes
= tuple(stroke
for stroke
in strokes
if not is_poly_clockwise(stroke
))
492 merged_strokes
= OrderedDict((s
, list()) for s
in base_strokes
)
494 for stroke
in filter(is_poly_clockwise
, strokes
):
495 for base
in base_strokes
:
496 # don't merge when diffuse colors don't match
497 if diffuse_from_stroke(stroke
) != diffuse_from_stroke(stroke
):
499 # only merge when the 'hole' is inside the base
500 elif stroke_inside_stroke(stroke
, base
):
501 merged_strokes
[base
].append(stroke
)
503 # if it isn't a hole, it is likely that there are two strokes belonging
504 # to the same object separated by another object. let's try to join them
505 elif (get_object_name(base
) == get_object_name(stroke
) and
506 diffuse_from_stroke(stroke
) == diffuse_from_stroke(stroke
)):
507 base
= extend_stroke(base
, (sv
for sv
in stroke
))
510 # if all else fails, treat this stroke as a base stroke
511 merged_strokes
.update({stroke
: []})
512 return merged_strokes
515 def stroke_to_svg(self
, stroke
, height
, parameters
=None):
516 if parameters
is None:
517 *color
, alpha
= diffuse_from_stroke(stroke
)
518 color
= tuple(int(255 * c
) for c
in color
)
520 'fill_rule': 'evenodd',
522 'fill-opacity': alpha
,
523 'fill': 'rgb' + repr(color
),
525 param_str
= " ".join('{}="{}"'.format(k
, v
) for k
, v
in parameters
.items())
526 path
= '<path {} d=" M '.format(param_str
)
527 vertices
= (svert
.point
for svert
in stroke
)
528 s
= "".join(self
.pathgen(vertices
, path
, height
))
532 def create_fill_elements(self
, strokes
):
533 """Creates ElementTree objects by merging stroke objects together and turning them into SVG paths."""
534 merged_strokes
= self
.get_merged_strokes(strokes
)
535 for k
, v
in merged_strokes
.items():
536 base
= self
.stroke_to_fill(k
)
537 fills
= (self
.stroke_to_fill(stroke
).get("d") for stroke
in v
)
538 merged_points
= " ".join(fills
)
539 base
.attrib
['d'] += merged_points
542 def write(self
, strokes
):
543 """Write SVG data tree to file """
545 tree
= et
.parse(self
.filepath
)
546 root
= tree
.getroot()
547 scene
= bpy
.context
.scene
550 lineset_group
= find_svg_elem(tree
, ".//svg:g[@id='{}']".format(self
._name
))
551 if lineset_group
is None:
552 lineset_group
= et
.XML('<g/>')
553 lineset_group
.attrib
= {
555 'xmlns:inkscape': namespaces
["inkscape"],
556 'inkscape:groupmode': 'lineset',
557 'inkscape:label': name
,
559 root
.append(lineset_group
)
560 print('added new lineset group ', name
)
563 # <g> for the fills of the current frame
564 fill_group
= et
.XML('<g/>')
565 fill_group
.attrib
= {
566 'xmlns:inkscape': namespaces
["inkscape"],
567 'inkscape:groupmode': 'layer',
568 'inkscape:label': 'fills',
572 fill_elements
= self
.create_fill_elements(strokes
)
573 fill_group
.extend(reversed(tuple(fill_elements
)))
574 if scene
.svg_export
.mode
== 'ANIMATION':
575 # add the fills to the <g> of the current frame
576 frame_group
= find_svg_elem(lineset_group
, ".//svg:g[@id='frame_{:04n}']".format(scene
.frame_current
))
577 frame_group
.insert(0, fill_group
)
579 lineset_group
.insert(0, fill_group
)
583 tree
.write(self
.filepath
, encoding
='ascii', xml_declaration
=True)
586 def stroke_inside_stroke(a
, b
):
587 box_a
= BoundingBox
.from_sequence(svert
.point
for svert
in a
)
588 box_b
= BoundingBox
.from_sequence(svert
.point
for svert
in b
)
589 return box_a
.inside(box_b
)
592 def diffuse_from_stroke(stroke
, curvemat
=CurveMaterialF0D()):
593 material
= curvemat(Interface0DIterator(stroke
))
594 return material
.diffuse
597 class ParameterEditorCallback(object):
598 """Object to store callbacks for the Parameter Editor in"""
599 def lineset_pre(self
, scene
, layer
, lineset
):
600 raise NotImplementedError()
602 def modifier_post(self
, scene
, layer
, lineset
):
603 raise NotImplementedError()
605 def lineset_post(self
, scene
, layer
, lineset
):
606 raise NotImplementedError()
610 class SVGPathShaderCallback(ParameterEditorCallback
):
612 def poll(cls
, scene
, linestyle
):
613 return scene
.render
.use_freestyle
and scene
.svg_export
.use_svg_export
and linestyle
.use_export_strokes
616 def modifier_post(cls
, scene
, layer
, lineset
):
617 if not cls
.poll(scene
, lineset
.linestyle
):
620 split
= scene
.svg_export
.split_at_invisible
621 stroke_color_mode
= lineset
.linestyle
.stroke_color_mode
622 cls
.shader
= SVGPathShader
.from_lineset(
623 lineset
, create_path(scene
),
624 render_height(scene
), split
, stroke_color_mode
, scene
.frame_current
, name
=layer
.name
+ '_' + lineset
.name
)
628 def lineset_post(cls
, scene
, layer
, lineset
):
629 if not cls
.poll(scene
, lineset
.linestyle
):
634 class SVGFillShaderCallback(ParameterEditorCallback
):
636 def poll(cls
, scene
, linestyle
):
637 return scene
.render
.use_freestyle
and scene
.svg_export
.use_svg_export
and scene
.svg_export
.object_fill
and linestyle
.use_export_fills
640 def lineset_post(cls
, scene
, layer
, lineset
):
641 if not cls
.poll(scene
, lineset
.linestyle
):
644 # reset the stroke selection (but don't delete the already generated strokes)
645 Operators
.reset(delete_strokes
=False)
646 # Unary Predicates: visible and correct edge nature
648 QuantitativeInvisibilityUP1D(0),
649 OrUP1D(ExternalContourUP1D(),
650 pyNatureUP1D(Nature
.BORDER
)),
652 # select the new edges
653 Operators
.select(upred
)
657 NotBP1D(pyZDiscontinuityBP1D()),
659 bpred
= OrBP1D(bpred
, AndBP1D(NotBP1D(bpred
), AndBP1D(SameShapeIdBP1D(), MaterialBP1D())))
661 Operators
.bidirectional_chain(ChainPredicateIterator(upred
, bpred
))
663 collector
= StrokeCollector()
664 Operators
.create(TrueUP1D(), [collector
])
666 builder
= SVGFillBuilder(create_path(scene
), render_height(scene
), layer
.name
+ '_' + lineset
.name
)
667 builder
.write(collector
.strokes
)
668 # make strokes used for filling invisible
669 for stroke
in collector
.strokes
:
671 svert
.attribute
.visible
= False
675 def indent_xml(elem
, level
=0, indentsize
=4):
676 """Prettifies XML code (used in SVG exporter) """
677 i
= "\n" + level
* " " * indentsize
679 if not elem
.text
or not elem
.text
.strip():
680 elem
.text
= i
+ " " * indentsize
681 if not elem
.tail
or not elem
.tail
.strip():
684 indent_xml(elem
, level
+ 1)
685 if not elem
.tail
or not elem
.tail
.strip():
687 elif level
and (not elem
.tail
or not elem
.tail
.strip()):
691 def register_namespaces(namespaces
=namespaces
):
692 for name
, url
in namespaces
.items():
693 if name
!= 'svg': # creates invalid xml
694 et
.register_namespace(name
, url
)
697 def handle_versions(self
):
698 # We don't modify startup file because it assumes to
699 # have all the default values only.
700 if not bpy
.data
.is_saved
:
703 # Revision https://developer.blender.org/rBA861519e44adc5674545fa18202dc43c4c20f2d1d
704 # changed the default for fills.
705 # fix by Sergey https://developer.blender.org/T46150
706 if bpy
.data
.version
<= (2, 76, 0):
707 for linestyle
in bpy
.data
.linestyles
:
708 linestyle
.use_export_fills
= True
714 SVGExporterLinesetPanel
,
720 linestyle
= bpy
.types
.FreestyleLineStyle
721 linestyle
.use_export_strokes
= BoolProperty(
722 name
="Export Strokes",
723 description
="Export strokes for this Line Style",
726 linestyle
.stroke_color_mode
= EnumProperty(
727 name
="Stroke Color Mode",
729 ('BASE', "Base Color", "Use the linestyle's base color", 0),
730 ('FIRST', "First Vertex", "Use the color of a stroke's first vertex", 1),
731 ('FINAL', "Final Vertex", "Use the color of a stroke's final vertex", 2),
735 linestyle
.use_export_fills
= BoolProperty(
737 description
="Export fills for this Line Style",
742 bpy
.utils
.register_class(cls
)
743 bpy
.types
.Scene
.svg_export
= PointerProperty(type=SVGExport
)
747 bpy
.app
.handlers
.render_init
.append(render_init
)
748 bpy
.app
.handlers
.render_write
.append(render_write
)
749 bpy
.app
.handlers
.render_pre
.append(svg_export_header
)
750 bpy
.app
.handlers
.render_complete
.append(svg_export_animation
)
752 # manipulate shaders list
753 parameter_editor
.callbacks_modifiers_post
.append(SVGPathShaderCallback
.modifier_post
)
754 parameter_editor
.callbacks_lineset_post
.append(SVGPathShaderCallback
.lineset_post
)
755 parameter_editor
.callbacks_lineset_post
.append(SVGFillShaderCallback
.lineset_post
)
757 # register namespaces
758 register_namespaces()
761 bpy
.app
.handlers
.version_update
.append(handle_versions
)
767 bpy
.utils
.unregister_class(cls
)
768 del bpy
.types
.Scene
.svg_export
769 linestyle
= bpy
.types
.FreestyleLineStyle
770 del linestyle
.use_export_strokes
771 del linestyle
.use_export_fills
774 bpy
.app
.handlers
.render_init
.remove(render_init
)
775 bpy
.app
.handlers
.render_write
.remove(render_write
)
776 bpy
.app
.handlers
.render_pre
.remove(svg_export_header
)
777 bpy
.app
.handlers
.render_complete
.remove(svg_export_animation
)
779 # manipulate shaders list
780 parameter_editor
.callbacks_modifiers_post
.remove(SVGPathShaderCallback
.modifier_post
)
781 parameter_editor
.callbacks_lineset_post
.remove(SVGPathShaderCallback
.lineset_post
)
782 parameter_editor
.callbacks_lineset_post
.remove(SVGFillShaderCallback
.lineset_post
)
784 bpy
.app
.handlers
.version_update
.remove(handle_versions
)
787 if __name__
== "__main__":