Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/freestyle/modules/freestyle/utils.py14
-rw-r--r--release/scripts/freestyle/modules/parameter_editor.py71
-rw-r--r--release/scripts/freestyle/modules/svg_export.py305
-rw-r--r--release/scripts/startup/bl_ui/properties_freestyle.py19
-rw-r--r--release/scripts/startup/freestyle_builtins.py14
-rw-r--r--source/blender/blenkernel/intern/scene.c3
-rw-r--r--source/blender/blenloader/intern/versioning_270.c13
-rw-r--r--source/blender/blenloader/intern/versioning_defaults.c1
-rw-r--r--source/blender/makesdna/DNA_scene_types.h14
-rw-r--r--source/blender/makesrna/intern/rna_scene.c36
10 files changed, 472 insertions, 18 deletions
diff --git a/release/scripts/freestyle/modules/freestyle/utils.py b/release/scripts/freestyle/modules/freestyle/utils.py
index 6c5e1d5887a..a8e4743ae4b 100644
--- a/release/scripts/freestyle/modules/freestyle/utils.py
+++ b/release/scripts/freestyle/modules/freestyle/utils.py
@@ -86,6 +86,20 @@ def bounding_box(stroke):
x, y = zip(*(svert.point for svert in stroke))
return (Vector((min(x), min(y))), Vector((max(x), max(y))))
+def get_dashed_pattern(linestyle):
+ """Extracts the dashed pattern from the various UI options """
+ pattern = []
+ if linestyle.dash1 > 0 and linestyle.gap1 > 0:
+ pattern.append(linestyle.dash1)
+ pattern.append(linestyle.gap1)
+ if linestyle.dash2 > 0 and linestyle.gap2 > 0:
+ pattern.append(linestyle.dash2)
+ pattern.append(linestyle.gap2)
+ if linestyle.dash3 > 0 and linestyle.gap3 > 0:
+ pattern.append(linestyle.dash3)
+ pattern.append(linestyle.gap3)
+ return pattern
+
# -- General helper functions -- #
diff --git a/release/scripts/freestyle/modules/parameter_editor.py b/release/scripts/freestyle/modules/parameter_editor.py
index 9ac5c665f1e..0498213e6f4 100644
--- a/release/scripts/freestyle/modules/parameter_editor.py
+++ b/release/scripts/freestyle/modules/parameter_editor.py
@@ -59,6 +59,7 @@ from freestyle.predicates import (
NotUP1D,
OrUP1D,
QuantitativeInvisibilityUP1D,
+ SameShapeIdBP1D,
TrueBP1D,
TrueUP1D,
WithinImageBoundaryUP1D,
@@ -97,7 +98,8 @@ from freestyle.utils import (
stroke_normal,
bound,
pairwise,
- BoundedProperty
+ BoundedProperty,
+ get_dashed_pattern,
)
from _freestyle import (
blendRamp,
@@ -105,10 +107,19 @@ from _freestyle import (
evaluateCurveMappingF,
)
+from svg_export import (
+ SVGPathShader,
+ SVGFillShader,
+ ShapeZ,
+ )
+
import time
+
from mathutils import Vector
from math import pi, sin, cos, acos, radians
from itertools import cycle, tee
+from bpy.path import abspath
+from os.path import isfile
class ColorRampModifier(StrokeShader):
@@ -419,7 +430,7 @@ class ColorMaterialShader(ColorRampModifier):
for svert in it:
material = self.func(it)
if self.attribute == 'LINE':
- b = material.line[0:3]
+ b = material.line[0:3]
elif self.attribute == 'DIFF':
b = material.diffuse[0:3]
else:
@@ -887,7 +898,6 @@ integration_types = {
# main function for parameter processing
-
def process(layer_name, lineset_name):
scene = getCurrentScene()
layer = scene.render.layers[layer_name]
@@ -1172,24 +1182,51 @@ def process(layer_name, lineset_name):
has_tex = True
if has_tex:
shaders_list.append(StrokeTextureStepShader(linestyle.texture_spacing))
+ # -- Dashed line -- #
+ if linestyle.use_dashed_line:
+ pattern = get_dashed_pattern(linestyle)
+ if len(pattern) > 0:
+ shaders_list.append(DashedLineShader(pattern))
+ # -- SVG export -- #
+ render = scene.render
+ filepath = abspath(render.svg_path)
+ # if the export path is invalid: log to console, but continue normal rendering
+ if render.use_svg_export:
+ if not isfile(filepath):
+ print("Error: SVG export: path is invalid")
+ else:
+ height = render.resolution_y * render.resolution_percentage / 100
+ split_at_inv = render.svg_split_at_invisible
+ frame_current = scene.frame_current
+ # SVGPathShader: keep reference and add to shader list
+ renderer = SVGPathShader.from_lineset(lineset, filepath, height, split_at_inv, frame_current)
+ shaders_list.append(renderer)
+
# -- Stroke caps -- #
+ # appended after svg shader to ensure correct svg output
if linestyle.caps == 'ROUND':
shaders_list.append(RoundCapShader())
elif linestyle.caps == 'SQUARE':
shaders_list.append(SquareCapShader())
- # -- Dashed line -- #
- if linestyle.use_dashed_line:
- pattern = []
- if linestyle.dash1 > 0 and linestyle.gap1 > 0:
- pattern.append(linestyle.dash1)
- pattern.append(linestyle.gap1)
- if linestyle.dash2 > 0 and linestyle.gap2 > 0:
- pattern.append(linestyle.dash2)
- pattern.append(linestyle.gap2)
- if linestyle.dash3 > 0 and linestyle.gap3 > 0:
- pattern.append(linestyle.dash3)
- pattern.append(linestyle.gap3)
- if len(pattern) > 0:
- shaders_list.append(DashedLineShader(pattern))
+
# create strokes using the shaders list
Operators.create(TrueUP1D(), shaders_list)
+
+ if render.use_svg_export and isfile(filepath):
+ # write svg output to file
+ renderer.write()
+ if render.svg_use_object_fill:
+ # reset the stroke selection (but don't delete the already generated ones)
+ Operators.reset(delete_strokes=False)
+ # shape detection
+ upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ContourUP1D())
+ Operators.select(upred)
+ # chain when the same shape and visible
+ bpred = SameShapeIdBP1D()
+ Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(QuantitativeInvisibilityUP1D(0)))
+ # sort according to the distance from camera
+ Operators.sort(ShapeZ(scene))
+ # render and write fills
+ renderer = SVGFillShader(filepath, height, lineset.name)
+ Operators.create(TrueUP1D(), [renderer,])
+ renderer.write()
diff --git a/release/scripts/freestyle/modules/svg_export.py b/release/scripts/freestyle/modules/svg_export.py
new file mode 100644
index 00000000000..09566730891
--- /dev/null
+++ b/release/scripts/freestyle/modules/svg_export.py
@@ -0,0 +1,305 @@
+import bpy
+import xml.etree.cElementTree as et
+
+from bpy.path import abspath
+from bpy.app.handlers import persistent
+from bpy_extras.object_utils import world_to_camera_view
+
+from freestyle.types import StrokeShader, ChainingIterator, BinaryPredicate1D, Interface0DIterator, AdjacencyIterator
+from freestyle.utils import getCurrentScene, get_dashed_pattern, get_test_stroke
+from freestyle.functions import GetShapeF1D, CurveMaterialF0D
+
+from itertools import dropwhile, repeat
+from collections import OrderedDict
+
+__all__ = (
+ "SVGPathShader",
+ "SVGFillShader",
+ "ShapeZ",
+ "indent_xml",
+ "svg_export_header",
+ "svg_export_animation",
+ )
+
+# register namespaces
+et.register_namespace("", "http://www.w3.org/2000/svg")
+et.register_namespace("inkscape", "http://www.inkscape.org/namespaces/inkscape")
+et.register_namespace("sodipodi", "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd")
+
+
+# use utf-8 here to keep ElementTree happy, end result is utf-16
+svg_primitive = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{:d}" height="{:d}">
+</svg>"""
+
+
+# xml namespaces
+namespaces = {
+ "inkscape": "http://www.inkscape.org/namespaces/inkscape",
+ "svg": "http://www.w3.org/2000/svg",
+ }
+
+# - SVG export - #
+class SVGPathShader(StrokeShader):
+ """Stroke Shader for writing stroke data to a .svg file."""
+ def __init__(self, name, style, filepath, res_y, split_at_invisible, frame_current):
+ StrokeShader.__init__(self)
+ # attribute 'name' of 'StrokeShader' objects is not writable, so _name is used
+ self._name = name
+ self.filepath = filepath
+ self.h = res_y
+ self.frame_current = frame_current
+ self.elements = []
+ self.split_at_invisible = split_at_invisible
+ # put style attributes into a single svg path definition
+ self.path = '\n<path ' + "".join('{}="{}" '.format(k, v) for k, v in style.items()) + 'd=" M '
+
+ @classmethod
+ def from_lineset(cls, lineset, filepath, res_y, split_at_invisible, frame_current, *, name=""):
+ """Builds a SVGPathShader using data from the given lineset"""
+ name = name or lineset.name
+ linestyle = lineset.linestyle
+ # extract style attributes from the linestyle
+ style = {
+ 'fill': 'none',
+ 'stroke-width': linestyle.thickness,
+ 'stroke-linecap': linestyle.caps.lower(),
+ 'stroke-opacity': linestyle.alpha,
+ 'stroke': 'rgb({}, {}, {})'.format(*(int(c * 255) for c in linestyle.color))
+ }
+ # get dashed line pattern (if specified)
+ if linestyle.use_dashed_line:
+ style['stroke-dasharray'] = ",".join(str(elem) for elem in get_dashed_pattern(linestyle))
+ # return instance
+ return cls(name, style, filepath, res_y, split_at_invisible, frame_current)
+
+ @staticmethod
+ def pathgen(stroke, path, height, split_at_invisible, f=lambda v: not v.attribute.visible):
+ """Generator that creates SVG paths (as strings) from the current stroke """
+ it = iter(stroke)
+ # start first path
+ yield path
+ for v in it:
+ x, y = v.point
+ yield '{:.3f}, {:.3f} '.format(x, height - y)
+ if split_at_invisible and v.attribute.visible == False:
+ # end current and start new path;
+ yield '" />' + path
+ # fast-forward till the next visible vertex
+ it = dropwhile(f, it)
+ # yield next visible vertex
+ svert = next(it, None)
+ if svert is None:
+ break
+ x, y = svert.point
+ yield '{:.3f}, {:.3f} '.format(x, height - y)
+ # close current path
+ yield '" />'
+
+ def shade(self, stroke):
+ stroke_to_paths = "".join(self.pathgen(stroke, self.path, self.h, self.split_at_invisible)).split("\n")
+ # convert to actual XML, check to prevent empty paths
+ self.elements.extend(et.XML(elem) for elem in stroke_to_paths if len(elem.strip()) > len(self.path))
+
+ def write(self):
+ """Write SVG data tree to file """
+ tree = et.parse(self.filepath)
+ root = tree.getroot()
+ name = self._name
+
+ # make <g> for lineset as a whole (don't overwrite)
+ lineset_group = tree.find(".//svg:g[@id='{}']".format(name), namespaces=namespaces)
+ if lineset_group is None:
+ lineset_group = et.XML('<g/>')
+ lineset_group.attrib = {
+ 'id': name,
+ 'xmlns:inkscape': namespaces["inkscape"],
+ 'inkscape:groupmode': 'lineset',
+ 'inkscape:label': name,
+ }
+ root.insert(0, lineset_group)
+
+ # make <g> for the current frame
+ id = "{}_frame_{:06n}".format(name, self.frame_current)
+ frame_group = et.XML("<g/>")
+ frame_group.attrib = {'id': id, 'inkscape:groupmode': 'frame', 'inkscape:label': id}
+ frame_group.extend(self.elements)
+ lineset_group.append(frame_group)
+
+ # write SVG to file
+ indent_xml(root)
+ tree.write(self.filepath, encoding='UTF-16', xml_declaration=True)
+
+# - Fill export - #
+class ShapeZ(BinaryPredicate1D):
+ """Sort ViewShapes by their z-index"""
+ def __init__(self, scene):
+ BinaryPredicate1D.__init__(self)
+ self.z_map = dict()
+ self.scene = scene
+
+ def __call__(self, i1, i2):
+ return self.get_z_curve(i1) < self.get_z_curve(i2)
+
+ def get_z_curve(self, curve, func=GetShapeF1D()):
+ shape = func(curve)[0]
+ # get the shapes z-index
+ z = self.z_map.get(shape.id.first)
+ if z is None:
+ o = bpy.data.objects[shape.name]
+ z = world_to_camera_view(self.scene, self.scene.camera, o.location).z
+ self.z_map[shape.id.first] = z
+ return z
+
+
+class SVGFillShader(StrokeShader):
+ """Creates SVG fills from the current stroke set"""
+ def __init__(self, filepath, height, name):
+ StrokeShader.__init__(self)
+ # use an ordered dict to maintain input and z-order
+ self.shape_map = OrderedDict()
+ self.filepath = filepath
+ self.h = height
+ self._name = name
+
+ def shade(self, stroke, func=GetShapeF1D(), curvemat=CurveMaterialF0D()):
+ shape = func(stroke)[0]
+ shape = shape.id.first
+ item = self.shape_map.get(shape)
+ if len(stroke) > 2:
+ if item is not None:
+ item[0].append(stroke)
+ else:
+ # the shape is not yet present, let's create it.
+ material = curvemat(Interface0DIterator(stroke))
+ *color, alpha = material.diffuse
+ self.shape_map[shape] = ([stroke], color, alpha)
+ # make the strokes of the second drawing invisible
+ for v in stroke:
+ v.attribute.visible = False
+
+ @staticmethod
+ def pathgen(vertices, path, height):
+ yield path
+ for point in vertices:
+ x, y = point
+ yield '{:.3f}, {:.3f} '.format(x, height - y)
+ yield 'z" />' # closes the path; connects the current to the first point
+
+ def write(self):
+ """Write SVG data tree to file """
+ # initialize SVG
+ tree = et.parse(self.filepath)
+ root = tree.getroot()
+ name = self._name
+
+ # create XML elements from the acquired data
+ elems = []
+ path = '<path fill-rule="evenodd" stroke="none" fill-opacity="{}" fill="rgb({}, {}, {})" d=" M '
+ for strokes, col, alpha in self.shape_map.values():
+ p = path.format(alpha, *(int(255 * c) for c in col))
+ for stroke in strokes:
+ elems.append(et.XML("".join(self.pathgen((sv.point for sv in stroke), p, self.h))))
+
+ # make <g> for lineset as a whole (don't overwrite)
+ lineset_group = tree.find(".//svg:g[@id='{}']".format(name), namespaces=namespaces)
+ if lineset_group is None:
+ lineset_group = et.XML('<g/>')
+ lineset_group.attrib = {
+ 'id': name,
+ 'xmlns:inkscape': namespaces["inkscape"],
+ 'inkscape:groupmode': 'lineset',
+ 'inkscape:label': name,
+ }
+ root.insert(0, lineset_group)
+
+ # make <g> for fills
+ frame_group = et.XML('<g />')
+ frame_group.attrib = {'id': "layer_fills", 'inkscape:groupmode': 'fills', 'inkscape:label': 'fills'}
+ # reverse the elements so they are correctly ordered in the image
+ frame_group.extend(reversed(elems))
+ lineset_group.insert(0, frame_group)
+
+ # write SVG to file
+ indent_xml(root)
+ tree.write(self.filepath, encoding='UTF-16', xml_declaration=True)
+
+
+def indent_xml(elem, level=0, indentsize=4):
+ """Prettifies XML code (used in SVG exporter) """
+ i = "\n" + level * " " * indentsize
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + " " * indentsize
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ indent_xml(elem, level + 1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ elif level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
+# - callbacks - #
+@persistent
+def svg_export_header(scene):
+ render = scene.render
+ if not (render.use_freestyle and render.use_svg_export):
+ return
+ #create new file (overwrite existing)
+ width, height = render.resolution_x, render.resolution_y
+ scale = render.resolution_percentage / 100
+
+ try:
+ with open(abspath(render.svg_path), "w") as f:
+ f.write(svg_primitive.format(int(width * scale), int(height * scale)))
+ except:
+ # invalid path is properly handled in the parameter editor
+ print("SVG export: invalid path")
+
+@persistent
+def svg_export_animation(scene):
+ """makes an animation of the exported SVG file """
+ render = scene.render
+ if render.use_freestyle and render.use_svg_export and render.svg_mode == 'ANIMATION':
+ write_animation(abspath(render.svg_path), scene.frame_start, render.fps)
+
+
+def write_animation(filepath, frame_begin, fps=25):
+ """Adds animate tags to the specified file."""
+ tree = et.parse(filepath)
+ root = tree.getroot()
+
+ linesets = tree.findall(".//svg:g[@inkscape:groupmode='lineset']", namespaces=namespaces)
+ for i, lineset in enumerate(linesets):
+ name = lineset.get('id')
+ frames = lineset.findall(".//svg:g[@inkscape:groupmode='frame']", namespaces=namespaces)
+ fills = lineset.findall(".//svg:g[@inkscape:groupmode='fills']", namespaces=namespaces)
+ fills = reversed(fills) if fills else repeat(None, len(frames))
+
+ n_of_frames = len(frames)
+ keyTimes = ";".join(str(round(x / n_of_frames, 3)) for x in range(n_of_frames)) + ";1"
+
+ style = {
+ 'attributeName': 'display',
+ 'values': "none;" * (n_of_frames - 1) + "inline;none",
+ 'repeatCount': 'indefinite',
+ 'keyTimes': keyTimes,
+ 'dur': str(n_of_frames / fps) + 's',
+ }
+
+ for j, (frame, fill) in enumerate(zip(frames, fills)):
+ id = 'anim_{}_{:06n}'.format(name, j + frame_begin)
+ # create animate tag
+ frame_anim = et.XML('<animate id="{}" begin="{}s" />'.format(id, (j - n_of_frames) / fps))
+ # add per-lineset style attributes
+ frame_anim.attrib.update(style)
+ # add to the current frame
+ frame.append(frame_anim)
+ # append the animation to the associated fill as well (if valid)
+ if fill is not None:
+ fill.append(frame_anim)
+
+ # write SVG to file
+ indent_xml(root)
+ tree.write(filepath, encoding='UTF-16', xml_declaration=True)
diff --git a/release/scripts/startup/bl_ui/properties_freestyle.py b/release/scripts/startup/bl_ui/properties_freestyle.py
index 606842db01d..132230f51fe 100644
--- a/release/scripts/startup/bl_ui/properties_freestyle.py
+++ b/release/scripts/startup/bl_ui/properties_freestyle.py
@@ -47,8 +47,8 @@ class RENDER_PT_freestyle(RenderFreestyleButtonsPanel, Panel):
def draw(self, context):
layout = self.layout
-
rd = context.scene.render
+ freestyle = rd.layers.active.freestyle_settings
layout.active = rd.use_freestyle
@@ -59,6 +59,23 @@ class RENDER_PT_freestyle(RenderFreestyleButtonsPanel, Panel):
if (rd.line_thickness_mode == 'ABSOLUTE'):
layout.prop(rd, "line_thickness")
+ row = layout.row()
+ #row.label(text="Use SVG Export")
+ row.prop(rd, "use_svg_export", text="SVG Export")
+
+ row = layout.row()
+ row.active = rd.use_svg_export and freestyle.mode != 'SCRIPT'
+ row.prop(rd, "svg_mode", expand=True)
+
+ row = layout.row()
+ row.active = rd.use_svg_export and freestyle.mode != 'SCRIPT'
+ row.prop(rd, "svg_path", text="")
+
+ row = layout.row()
+ row.active = rd.use_svg_export
+ row.prop(rd, "svg_split_at_invisible")
+ row.prop(rd, "svg_use_object_fill")
+
# Render layer properties
diff --git a/release/scripts/startup/freestyle_builtins.py b/release/scripts/startup/freestyle_builtins.py
new file mode 100644
index 00000000000..10be9d99eec
--- /dev/null
+++ b/release/scripts/startup/freestyle_builtins.py
@@ -0,0 +1,14 @@
+import bpy
+
+from svg_export import svg_export_header, svg_export_animation
+
+def register():
+ bpy.app.handlers.render_init.append(svg_export_header)
+ bpy.app.handlers.render_complete.append(svg_export_animation)
+
+def unregister():
+ bpy.app.handlers.render_init.remove(svg_export_header)
+ bpy.app.handlers.render_complete.remove(svg_export_animation)
+
+if __name__ == '__main__':
+ register()
diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c
index 5bfd6e8a120..bec42809cd8 100644
--- a/source/blender/blenkernel/intern/scene.c
+++ b/source/blender/blenkernel/intern/scene.c
@@ -467,6 +467,9 @@ Scene *BKE_scene_add(Main *bmain, const char *name)
sce->r.posthue = 0.0;
sce->r.postsat = 1.0;
+ /* Freestyle SVG Export */
+ sce->r.svg_mode = FREESTYLE_CONTROL_SVG_FRAME;
+
sce->r.bake_mode = 1; /* prevent to include render stuff here */
sce->r.bake_filter = 16;
sce->r.bake_osa = 5;
diff --git a/source/blender/blenloader/intern/versioning_270.c b/source/blender/blenloader/intern/versioning_270.c
index bd1b5f2c269..86556707e6f 100644
--- a/source/blender/blenloader/intern/versioning_270.c
+++ b/source/blender/blenloader/intern/versioning_270.c
@@ -435,4 +435,17 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main)
}
}
}
+
+ // uncomment when commiting
+ /*
+ if (!MAIN_VERSION_ATLEAST(main, 273, 0)) {
+ Scene *scene;
+
+ for (scene = main->scene.first; scene; scene = scene->id.next) {
+ scene->r.svg_mode = FREESTYLE_CONTROL_SVG_FRAME;
+ }
+ }
+ */
+
+
}
diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c
index 1afcac313a2..cfe90fe042c 100644
--- a/source/blender/blenloader/intern/versioning_defaults.c
+++ b/source/blender/blenloader/intern/versioning_defaults.c
@@ -76,6 +76,7 @@ void BLO_update_defaults_startup_blend(Main *bmain)
for (scene = bmain->scene.first; scene; scene = scene->id.next) {
scene->r.im_format.planes = R_IMF_PLANES_RGBA;
scene->r.im_format.compress = 15;
+ scene->r.svg_mode = FREESTYLE_CONTROL_SVG_FRAME;
for (srl = scene->r.layers.first; srl; srl = srl->next) {
srl->freestyleConfig.sphere_radius = 0.1f;
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index f8f962107f6..04a9291ba67 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -600,6 +600,11 @@ typedef struct RenderData {
int line_thickness_mode;
float unit_line_thickness; /* in pixels */
+ /* Freestyle SVG Export */
+ char svg_path[1024]; /* 1024 = FILE_MAX */
+ int svg_mode;
+ int svg_flag;
+
/* render engine */
char engine[32];
@@ -610,6 +615,15 @@ typedef struct RenderData {
int pad;
} RenderData;
+/* RenderData::svg_flag */
+#define FREESTYLE_SVG_EXPORT (1 << 0)
+#define FREESTYLE_SVG_SPLIT_AT_INVISIBLE (1 << 1)
+#define FREESTYLE_SVG_OBJECT_FILL (1 << 2)
+
+/* RenderData::svg_mode */
+#define FREESTYLE_CONTROL_SVG_FRAME 1
+#define FREESTYLE_CONTROL_SVG_ANIMATION 2
+
/* *************************************************************** */
/* Render Conversion/Simplfication Settings */
diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c
index 70ce87ab68b..665779c03e9 100644
--- a/source/blender/makesrna/intern/rna_scene.c
+++ b/source/blender/makesrna/intern/rna_scene.c
@@ -4330,6 +4330,13 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
{R_OUTPUT_NONE, "NONE", 0, "Keep UI", "Images are rendered without forcing UI changes"},
{0, NULL, 0, NULL, NULL}
};
+
+ /* Freestyle SVG export */
+ static EnumPropertyItem freestyle_ui_svg_mode_items[] = {
+ {FREESTYLE_CONTROL_SVG_FRAME, "FRAME", 0, "Frame", "Write a single frame to the SVG file"},
+ {FREESTYLE_CONTROL_SVG_ANIMATION, "ANIMATION", 0, "Animation", "Write an animation to the SVG file"},
+ {0, NULL, 0, NULL, NULL}
+ };
/* Bake */
static EnumPropertyItem bake_mode_items[] = {
@@ -4860,6 +4867,35 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
"Note: affects indirectly rendered scenes)");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
+ prop = RNA_def_property(srna, "use_svg_export", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "svg_flag", FREESTYLE_SVG_EXPORT);
+ RNA_def_property_ui_text(prop, "SVG export",
+ "Export the freestyle view map to the given location");
+ RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update");
+
+ prop = RNA_def_property(srna, "svg_split_at_invisible", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "svg_flag", FREESTYLE_SVG_SPLIT_AT_INVISIBLE);
+ RNA_def_property_ui_text(prop, "Split at Invisible",
+ "Start a new SVG path when encountering an invisible stroke vertex");
+ RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update");
+
+ prop = RNA_def_property(srna, "svg_use_object_fill", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "svg_flag", FREESTYLE_SVG_OBJECT_FILL);
+ RNA_def_property_ui_text(prop, "Object Fill",
+ "Fill surface patches within contours (doesn't always work as expected)");
+ RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update");
+
+ prop = RNA_def_property(srna, "svg_path", PROP_STRING, PROP_FILEPATH);
+ RNA_def_property_string_sdna(prop, NULL, "svg_path");
+ RNA_def_property_ui_text(prop, "SVG Output Path", "Path to store the svg ouput");
+ RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update");
+
+ prop = RNA_def_property(srna, "svg_mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "svg_mode");
+ RNA_def_property_enum_items(prop, freestyle_ui_svg_mode_items);
+ RNA_def_property_ui_text(prop, "SVG Export Mode", "Select the SVG export mode");
+ RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_Scene_freestyle_update");
+
/* Bake */
prop = RNA_def_property(srna, "bake_type", PROP_ENUM, PROP_NONE);