[Bf-extensions-cvs] [c6595a3] master: D785: Freestyle integrated SVG export.

Tamito Kajiyama noreply at git.blender.org
Mon Dec 8 03:08:38 CET 2014


Commit: c6595a3584e3f59018a8ad86a7de3d0c2bf4105a
Author: Tamito Kajiyama
Date:   Mon Dec 8 11:02:05 2014 +0900
Branches: master
https://developer.blender.org/rBAc6595a3584e3f59018a8ad86a7de3d0c2bf4105a

D785: Freestyle integrated SVG export.

This patch implements SVG exporting to Freestyle.  This feature is implemented
as an add-on, and can be enabled in the user preferences (render section).

Current features of the exporter include:
* a user interface.
* integration with the parameter editor and some of its settings, in particular:
  color, transparency, thickness, visibility, stroke caps, and dashes.
* support for animation.
* some extra attributes that make manipulation of the exporter's result in Inkscape easier.
* the ability to export fills (a closed external contour with the underlying material's color).

Differential revision: https://developer.blender.org/D785

Author: flokkievids (Folkert de Vries)

Reviewed by: campbellbarton (Campbell Barton), sergey (Sergey Sharybin),
    dna (Dan Eicher), kjym3 (Tamito Kajiyama)

Contributor: hva (francesco fantoni)

===================================================================

A	render_freestyle_svg.py

===================================================================

diff --git a/render_freestyle_svg.py b/render_freestyle_svg.py
new file mode 100644
index 0000000..9147030
--- /dev/null
+++ b/render_freestyle_svg.py
@@ -0,0 +1,542 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+bl_info = {
+    "name": "Export Freestyle edges to an .svg format",
+    "author": "Folkert de Vries",
+    "version": (1, 0),
+    "blender": (2, 72, 1),
+    "location": "properties > render > SVG Export",
+    "description": "Adds the functionality of exporting Freestyle's stylized edges as an .svg file",
+    "warning": "",
+    "wiki_url": "",
+    "category": "Render",
+    }
+
+import bpy
+import parameter_editor
+import itertools
+import os
+
+import xml.etree.cElementTree as et
+
+from freestyle.types import (
+        StrokeShader,
+        Interface0DIterator,
+        Operators,
+        )
+from freestyle.utils import getCurrentScene
+from freestyle.functions import GetShapeF1D, CurveMaterialF0D
+from freestyle.predicates import (
+        AndUP1D,
+        ContourUP1D,
+        SameShapeIdBP1D,
+        NotUP1D,
+        QuantitativeInvisibilityUP1D,
+        TrueUP1D,
+        pyZBP1D,
+        )
+from freestyle.chainingiterators import ChainPredicateIterator
+from parameter_editor import get_dashed_pattern
+
+from bpy.props import (
+        BoolProperty,
+        EnumProperty,
+        PointerProperty,
+        )
+from bpy.app.handlers import persistent
+from collections import OrderedDict
+from mathutils import Vector
+
+
+# use utf-8 here to keep ElementTree happy, end result is utf-16
+svg_primitive = """<?xml version="1.0" encoding="ascii" 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",
+    }
+
+
+def render_height(scene):
+    return int(scene.render.resolution_y * scene.render.resolution_percentage / 100)
+
+
+def render_width(scene):
+    return int(scene.render.resolution_x * scene.render.resolution_percentage / 100)
+
+
+class RenderState:
+    # Note that this flag is set to False only after the first frame
+    # has been written to file.
+    is_preview = True
+
+
+ at persistent
+def render_init(scene):
+    RenderState.is_preview = True
+
+
+ at persistent
+def render_write(scene):
+    RenderState.is_preview = False
+
+
+def is_preview_render(scene):
+    return RenderState.is_preview or scene.svg_export.mode == 'FRAME'
+
+
+def create_path(scene):
+    """Creates the output path for the svg file"""
+    dirname = os.path.dirname(scene.render.frame_path())
+    basename = bpy.path.basename(scene.render.filepath)
+    if scene.svg_export.mode == 'FRAME':
+        frame = "{:04d}".format(scene.frame_current)
+    else:
+        frame = "{:04d}-{:04d}".format(scene.frame_start, scene.frame_end)
+    return os.path.join(dirname, basename + frame + ".svg")
+
+
+class SVGExport(bpy.types.PropertyGroup):
+    """Implements the properties for the SVG exporter"""
+    bl_idname = "RENDER_PT_svg_export"
+
+    use_svg_export = BoolProperty(
+            name="SVG Export",
+            description="Export Freestyle edges to an .svg format",
+            )
+    split_at_invisible = BoolProperty(
+            name="Split at Invisible",
+            description="Split the stroke at an invisible vertex",
+            )
+    object_fill = BoolProperty(
+            name="Fill Contours",
+            description="Fill the contour with the object's material color",
+            )
+    mode = EnumProperty(
+            name="Mode",
+            items=(
+                ('FRAME', "Frame", "Export a single frame", 0),
+                ('ANIMATION', "Animation", "Export an animation", 1),
+                ),
+            default='FRAME',
+            )
+    line_join_type = EnumProperty(
+            name="Linejoin",
+            items=(
+                ('MITTER', "Mitter", "Corners are sharp", 0),
+                ('ROUND', "Round", "Corners are smoothed", 1),
+                ('BEVEL', "Bevel", "Corners are bevelled", 2),
+                ),
+            default='ROUND',
+            )
+
+
+class SVGExporterPanel(bpy.types.Panel):
+    """Creates a Panel in the render context of the properties editor"""
+    bl_idname = "RENDER_PT_SVGExporterPanel"
+    bl_space_type = 'PROPERTIES'
+    bl_label = "Freestyle SVG Export"
+    bl_region_type = 'WINDOW'
+    bl_context = "render"
+
+    def draw_header(self, context):
+        self.layout.prop(context.scene.svg_export, "use_svg_export", text="")
+
+    def draw(self, context):
+        layout = self.layout
+
+        scene = context.scene
+        svg = scene.svg_export
+        freestyle = scene.render.layers.active.freestyle_settings
+
+        layout.active = (svg.use_svg_export and freestyle.mode != 'SCRIPT')
+
+        row = layout.row()
+        row.prop(svg, "mode", expand=True)
+
+        row = layout.row()
+        row.prop(svg, "split_at_invisible")
+        row.prop(svg, "object_fill")
+
+        row = layout.row()
+        row.prop(svg, "line_join_type", expand=True)
+
+
+ at persistent
+def svg_export_header(scene):
+    if not (scene.render.use_freestyle and scene.svg_export.use_svg_export):
+        return
+
+    # write the header only for the first frame when animation is being rendered
+    if not is_preview_render(scene) and scene.frame_current != scene.frame_start:
+        return
+
+    # this may fail still. The error is printed to the console.
+    with open(create_path(scene), "w") as f:
+        f.write(svg_primitive.format(render_width(scene), render_height(scene)))
+
+
+ at persistent
+def svg_export_animation(scene):
+    """makes an animation of the exported SVG file """
+    render = scene.render
+    svg = scene.svg_export
+
+    if render.use_freestyle and svg.use_svg_export and not is_preview_render(scene):
+        write_animation(create_path(scene), scene.frame_start, render.fps)
+
+
+def write_animation(filepath, frame_begin, fps):
+    """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 itertools.repeat(None, len(frames))
+
+        print("-" * 10, "animate", "-" * 10)
+
+        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',
+            }
+
+        print(style)
+        print(n_of_frames)
+
+        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='ascii', xml_declaration=True)
+
+
+# - StrokeShaders - #
+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 and scene
+        svg = getCurrentScene().svg_export
+        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)),
+            'stroke-linejoin': svg.line_join_type.lower(),
+            }
+        # get dashed line pattern (if specified)
+        if linestyle.use_dashed_line:
+            style['stroke-dasharray'] = ",".join(str(elem) for elem in

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list