Freestyle integrated SVG export
AbandonedPublic

Authored by Tamito Kajiyama (kjym3) on Sep 11 2014, 11:19 PM.

Details

Summary

This patch implements SVG exporting to Freestyle.

This has been a much requested feature to be added to Freestyle. Although the current version is rough, it is already quite usable.

current features:

  • The system creates a new SVG layer for every Freestyle linestyle. The different layers are correctly sorted.
  • The SVG paths use data from the linestyle, so the base color of a linestyle becomes the color of the path, idem for dashes and stroke thickness.
  • possibility to split at invisible strokes, useful when exporting for instance dashed lines or linestyles with a blueprintshader
  • Added callbacks before and after the freestyle rendering process

limitations:

  • exporting animations is currently not possible
  • usage with python scripting mode requires a little work, but is possible right now

I'd like to thank Francesco Fantoni and Jarno Leppänen for their Freestyle SVG exporter. Their work got me up to speed quickly.

@Tym Kavu (TK)

I decided to create properties_freestyle.py to register the handlers. Since integration with the core structure (UI, parameter editor) is needed it does not make sense to make it an addon. thanks for this tip!

Diff Detail

Repository
rBA Blender Add-ons
Branch
temp-freestyle-svg
There are a very large number of changes, so older changes are hidden. Show Older Changes

animation for developer meeting minutes

Thanks for the response flokkievids. I did build the INSTALL and made the most recent patch changes, but still no luck. I am getting an error message in visual studio express 2013. Any ideas?

Folkert de Vries (flokkievids) updated this object.

the animation callback wouldn't execute correctly if object fill was disabled. this has now been fixed.

a preview animation can be found here
(hosted on a friend's server, if anyone has a good way of hosting svg files, please let me know)

I'd also really like to see your tests/renders with this feature!

@Paul McCollam (plender)

Well, be sure to use the latest everything (libs, revisions, ect.)
I can't get a lot of information from the build error. the issue seems to be with cmake. you might try to run cmake from the command line yourself and see if you can get a more descriptive error message. I believe your build type is debug: if you don't debug that's really not necessary (it might treat more warnings as errors, in my experience the build time is longer.), you can just use 'release'. furthermore, is this error specific to this patch or do you get the same error when building trunk?

Here is a quick test result with a character model of mine. The preview raster image is based on a screen capture of Firefox showing the SVG output (no retouching except for cut-n-paste operations). The blurry effect is made of two line sets, one with a thin dark line color and the other with a thick light color.

@Paul McCollam (plender),
I am also using Visual Studio Express 2013 for Desktop and CMake, so the build process should work fine. I confirmed that the Debug build works as fine as the Release build.

@Folkert de Vries (flokkievids),
The support for round caps seems not working properly (see the screen capture below). In Freestyle round caps are implemented as a variable width stroke having extra vertices at both ends of the stroke. The stroke-linecap="round" property seems working fine, but the extra vertices make strokes longer than expected.

A solution could be to insert SVGPathShader in front of RoundCapShader and SquareCapShader in the list of stroke shaders.

@Folkert de Vries (flokkievids) I can build trunk fine, so I am only having problems with the patch build. I'm sure I'm doing something stupid because I'm new to all of this. Everything is up to date. Would the CMake config/generate log be helpful? Or the MVSE output log? Thanks again for taking the time to help me out.

@Tamito Kajiyama (kjym3) Do you have CMake 3.0.2 and are you building 64bit?

@Paul McCollam (plender),
I use CMake 2.8.12.2 and usually build 64-bit Release configuration.

  • fixed bug with stroke tips (the suggested solution works and has no influence on the freestyle render result)
  • improved spelling
  • improved code style

@Paul McCollam (plender) and @Tamito Kajiyama (kjym3)

I never got 64-bit to work. build would be fine but icons would be missing. downgraded to 32-bit and all is good (with cmake 3.0.1 and VS13)

some small fixes

  • introduced bug requesting an attribute of nonetype. should have tested for this...
  • small stylistic changes

open issues:

  • the svg_mode item is undefined (in the UI, not actually) in a new .blend file. not sure how to fix this.
  • object fill is barely useful in more advanced cases. Again, I'm not sure how to fix this and this topic is much harder.

future improvements:

  • variable stroke width and position (at least still needs inkscape and chrome support, so holding off on this for now)
  • other SVG2 features if there is demand and if feasible.

I think this patch is now ready of thorough code review, hopefully merging it soon. If I missed anything in the above lists please let me know.

that's really a great work!
I'm glad I could contribute a bit, although indirectly.
very nice indeed.
Francesco

large improvement in fill quality:

The previous implementation could only handle trivial shapes, and would fail horribly on concave ones. This new implementation tries to fill the whole silhouette of an object. It is still not perfect, especially when multiple objects overlap, but already a massive improvement.

apart from the overlapping silhouettes, this approach sometimes also fails from certain viewpoints, most likely due to tiny defects in the silhouette detection. This makes animation with fills unreliable.

before:

after:

@francesco fantoni (hva)

Working of your reference made things so easy. Thanks a lot for it!

First of all I'm super happy to see this happening, wasn't aware how far this has progressed already; Thanks so much! :)
So ... I'll try this out on a project I'm currently working on and provide you with some real production feedback! ... But I got a minor build problem first ;)

I applied the patch with arc and am getting this while building:

[ 93%] /home/simonrepp/blender-dev/blender/source/blender/makesrna/intern/rna_scene.c: In function ‘rna_def_scene_render_data’:
/home/simonrepp/blender-dev/blender/source/blender/makesrna/intern/rna_scene.c:4864:2: error: ISO C90 forbids mixed declarations and code [-Werror=declaration-after-statement]
  static EnumPropertyItem freestyle_ui_svg_mode_items[] = {
  ^
cc1: some warnings being treated as errors

Doesn't look like a big deal, but still breaks the build process for me - I'm building on Fedora 20 with gcc 4.8.3 and all deps installed with the install_deps script. (Building trunk works fine). Should I fix this on my machine (compiler flags something) or does this call for actual changes in the code?

fix compiler error/warning with gcc

@Simon Repp (simonrepp)

thanks for your interest. this should fix your issue. you may be able to let gcc use a more recent c standard, which would treat this issue as a warning and continue building.

The error warns about the position of some code, which indeed was located poorly. Thanks for reporting and your feedback would be most welcome!

Perfect - builds fine now; Gave the exporter a super quick shot today too - magic! :)
Thanks a ton! I'll let you know my findings when I start experimenting with this in more detail in the next days!

@Folkert de Vries (flokkievids),
I agree with you that the patch is quite complete in terms of both functionality and coding style. The extension of filled polygons is a great plus!

One possible further revision if you like is to better support stand-alone style modules for the Python Scripting mode of Freestyle. Currently SVGPathShader depends on a line set and hence a line style. If I understand the code correctly, the line set is only necessary for its name (used to name an SVG group), whereas the line style is used for retrieving line stylization parameters. SVGPathShader could be independent of them, for instance by passing the line set name and the stylization parameters as arguments of the shader constructor. Then, a Python style module would be able to employ SVGPathShader by calling as follows:

shaders_list = [
    ...
    SVGPathShader(name = 'LineSet1',
                  attr = {'stroke': 'rgb(255, 0, 0)',
                          'stroke-opacity': 0.75,
                          'stroke-width': 5,
                          'stroke-linecap': 'round'}),
    ...]
Operator.create(TrueUP1D, shaders_list)

We can define some default values for all the input parameters so as to make them optional.

If you prefer to merge the patch as it is now, that is also fine to me. Any further improvements of the SVG exporter can be done later as you wish.

updates:

  • make SVGPathShader and SVGFillShader independent of lineset.
  • remove ViewshapeChainingIterator (now unused)
  • initialize functors as default arguments
  • manipulate Element.attrib directly (more clear, safes lines)

default arguments for SVGPathShader are a bit tricky, as the frame and filepath really can't be known and filling them may cause problems.

I finally found time to experiment with a first use-case: Building an interactive hoverable svg for web use, as directly as possible, and in this case overlayed above a rasterized freestyle render.

http://fdpl.foundation/svg-hover/

First verdict here is that it's not possible in a very direct way :) - which however is not a shortcoming of the SVG Exporter (which did it's job beautifully), but more because it's a very specific application for this rather general svg exporter. If this is something many people are interested in, it's probably more adequately placed in a seperate Add-on that could allow, for instance, mapping object names to path or group ids, etc.

One other, possibly more relevant aspect that I noticed myself starting to ponder is that, from an artist's perspective (or at least from my specific vector art workflow perspective), the "bonus functionality" of getting fills, might actually be far more interesting than getting the strokes from freestyle as such (which, without some of the modifier styling instantly feels much less useful to work with). Starting this train of thought - how to expand on fill exporting - however opens up a whole new world of questions, the scope of which clearly goes beyond the patch at hand here. Still this might be interesting to think about in the future, and possibly something to find artistic stakeholders for at some point in order to kick off a new module design process. ("Project BEER, Vector edition" if you will :))

It'll probably take some time until I get to do my next tests with this, blender conference coming up and all, but I'll keep you posted if I make some new discoveries here.

Meanwhile - Thanks so much! I think this should definitely go into 2.73, cause it rawks. :)

Hi @Folkert de Vries (flokkievids),
Thanks for the updates. The revision was really quick! I think the patch is quite ready to land. I noted a minor comment below that could be a final one. Could you please address it?

release/scripts/freestyle/modules/parameter_editor.py
1231 ↗(On Diff #2696)

I guess the SamplingShader here is not really necessary. SamplingShader just generates sub-sampled points in the middle of straight feature edges. When a feature edge is shorter than 100 pixels in the 2D image coordinate system, nothing will happen. When the edge is longer than 100 pixels, extra points are just inserted without visual changes. SamplingShader is not mandatory, and we can omit it if it does not have visual effects.

  • remove the samplingshader for fills

@Tamito Kajiyama (kjym3)

I thought the samplingshader was necessary for keeping the vertex count (of the strokes) down, but this is not the case. thanks for pointing this out.

@Simon Repp (simonrepp)

thanks for your feedback.I'll try to get it working better, but the current problem is freestyle's contour detection.

The concept you show is very interesting. This project opens up a lot of possibilities.

Many thanks for all the efforts so far to finish the patch!

This revision is now accepted and ready to land.Oct 18 2014, 11:49 AM

The patch has just been pushed to Git master (rB61a330baca0f). This functionality will provide Blender/Freestyle with a killer application benefiting a wide spectrum of 2D/3D line art!

Here is another test result with the SVG exporter:

@Folkert de Vries (flokkievids),
Would you like to write up a section of Blender 2.73 release notes to introduce the SVG exporter?
http://wiki.blender.org/index.php/Dev:Ref/Release_Notes/2.73/Freestyle

@Tamito Kajiyama (kjym3)

Thanks! I'll hopefully have time to write some documentation about the exporter this week.

@prout reprout (all) other subscribers

thanks for the feedback here.

Tamito Kajiyama (kjym3) requested changes to this revision.Oct 19 2014, 8:18 PM

@Folkert de Vries (flokkievids),
I talked with @Campbell Barton (campbellbarton) on IRC about this patch and came to the conclusion that the SVG exporter is better implemented as an add-on.

Instead of adding the SVG exporter stuff in place within the Parameter Editor code, first we introduce a couple of hooks to perform callback functions. Specifically we need two kinds of callbabcks.

One is those callback functions that are executed in the middle of creating the shaders_list. A callback function in this case returns a list of stroke shaders, which are then added to shaders_list. The SVG exporter in the form of an add-on relies on this hook to insert SVGPathShader to shaders_list.

The other is those callbacks that are executed after the call of Operators.create(). A callback in this case can do quite whatever operations. I guess the current scene and line set objects have to be passed to the callback function as arguments, so that per-line set operations can be easily done within the callback. Then the SVGFillShader stuff can be turned into a callback of this post-processing hook.

Moving the SVG exporter to an add-on is expected to be straightforward.

I stay at your disposal for any help and discussion you need, possibly through this patch page as well as through personal communications.

This revision now requires changes to proceed.Oct 19 2014, 8:18 PM

@Folkert de Vries (flokkievids) and @Campbell Barton (campbellbarton),
I have filed patch D839 to propose a set of hooks for Freestyle-specific callback procedures to modify the Freestyle line rendering process.
The hooks have been implemented locally within the implementation of the Freestyle Parameter Editor, since there is no reason to expose these Freestyle-specific hooks as part of the general callback mechanism in Blender.
Still the SVG exporter will have to rely on the global pre/post render callbacks for file I/O.
Hope this helps.

A first working version of the SVG exporter as an addon.

The file output now follows blender's and I've fixed some small mistakes and corner cases.

A next round of review would be most welcome. I'll be available in the upcoming weeks, so that hopefully this add-on can be in the 2.73 release.

  • initial freestyle addon
  • cleanup: remove unused imports, match style of existing addons
  • remove annotations (not really standardized yet)

Updated the patch (branch temp-freestyle-svg)
was preparing to commit to push to master, but saving animation fails, I get the exception.

File "/usr/lib/python3.4/xml/etree/ElementTree.py", line 587, in parse
  source = open(source, "rb")
   FileNotFoundError: [Errno 2] No such file or directory: '/tmp/0004.svg'

The issue seems to happen when you render an animation, but have the freestyle settings configured to Frame.

Tamito, could you check on this?

@Campbell Barton (campbellbarton),
Is there a way for add-ons to check if the currently running rendering process is for animation?
I think the Frame/Animation switch of the exporter is redundant and can be removed if such a check is possible.
It looks like (re->flag & R_ANIMATION) is the C expression to check if animation rendering is ongoing, but I couldn't find if the same checking can be done from within Python.

Campbell, could you please apply the following patch to the temp-freestyle-svg branch?
It looks like that I don't have a write access to /blender-addons.git.

@Tamito Kajiyama (kjym3), committed & added you to the @addons project (you should be able to commit now)

@Campbell Barton (campbellbarton), thanks for the commit and the write access to the addon repo.

Any idea on a way for add-ons to check if the currently running rendering process is for animation? If there is such a way, then the reported file I/O issue could be addressed to get the SVG add-on done.

@Campbell Barton (campbellbarton) and @Folkert de Vries (flokkievids),
Could you two please review the following patches intended to address the SVG animation saving issue?

The first patch introduces a way for Freestyle add-ons to test rendering conditions (specifically, to check if animation rendering is ongoing).

With the above patch committed to Git master, the SVG animation saving issue can be fixed as indicated in the second patch below. The svg_export.mode option is no longer necessary and can be removed.

Copied the first patch to D921 to make code review easier. Thanks.

from my side this is looking good. it might however be an idea to expose the render context through bpy (instead of freestyle) because it may have more general applications.
creating freestyle.context for two properties that really have nothing to do specifically with freestyle may not be the best option.

@Tamito Kajiyama (kjym3), uploaded: D924

This means you can attach a callback to the render when it writes an image.

However this isnt a perfect fit for the addon. It could work this way.

  • bpy.app.handlers.render_init - sets a module level variable to false (write-to-file = False)
  • bpy.app.handlers.render_write - sets the variable to true.
  • bpy.app.handlers.render_complete (and freestyle callbacks) - checks writing variable is set, if so, write SVG files.

@Campbell Barton (campbellbarton),

Thanks a lot for the code revision for the render_write handler. I believe the new handler is useful in general, but it addresses the animation saving issue only partly. Specifically, a callback for the render_init handler in the SVG add-on needs to know if animation rendering is ongoing, while that information is only available after the render_write hook is executed.

UPDATE: There seems a solution by deferring the file I/O to a later stage of the add-on execution. I will further look into it and come back with updates.

@Folkert de Vries (flokkievids),

I had the same thought that is_animation and is_preview flags are useful in general, but the proposed changes D921 took a different approach in line with rB79a389ee3711 that added the two flags to the RenderEngine class for representing external render engines (such as Cycles). I preferred the proposed apprach because the two flags that are states and not user options, and I could not find a good place to store state information. Note that Scene (scene) and RenderSettings (scene.render) store user options.

@Campbell Barton (campbellbarton),

Here is another attempt to fix the animation saving issue using the render_write hook. The patch is against the HEAD of the temp-freestyle-svg add-on branch.

@Folkert de Vries (flokkievids),

I took this opportunity to slightly change the meaning of the svg_export.mode option. Now an SVG animation file is created only when animation rendering is performed and the mode is set to ANIMATION. Otherwise, individual frames are written to separate SVG files. This way setting the mode to FRAME makes sense even for animation rendering. What do you think about this revision?

@Tamito Kajiyama (kjym3)

This seems a logical and intuitive change, thanks.

Generally fine, note

  • is_animation May be set when rendering a still image. eg: bpy.ops.render.render(write_still=1), suggest to invert bool and call is_preview
  • def render_animation(scene): sounds like it renders the animation, suggest call is_render_preview

also worth double checking...

  • bpy.ops.render.render(write_still=1) saves a single svg
  • rendering a video file correctly saves SVG's

Note:

return scene.render.frame_path(frame).split(".")[0] + ".svg"

Should be:

framepath = scene.render.frame_path(frame)
if scene.render.use_file_extension:
     framepath = framepath.rsplit(".", 1)[0]
 return framepath + ".svg"

@Campbell Barton (campbellbarton),

Thanks for the quick review. Here is an updated patch addressing all the suggested changes and tests.

The bool flag was inverted to is_preview and the test function was renamed to is_preview_render(). Both bpy.ops.render.render(write_still=1) and movie file formats were tested, confirming that the SVG export works fine.

It is remarked that SVG file names now depend only on the svg_export.mode option. Specifically, 0001.svg, 0002.svg and so on are created when the mode is set to FRAME, so as to allow for generating per-frame SVG files in both still and animation rendering. When the mode is set to ANIMATION, the output file is always like 0001-0250.svg, i.e., in line with the file naming convention for movie file formats in Blender.

@Tamito Kajiyama (kjym3), generally good.

Though Im not sure why you'd want to remove the file component from the SVG?

you might want to have /my/anim/test1_0001.png & /my/anim/test1_0001.svg, 0002 etc...

Campbell, thanks for checking the code. The patch is actually supposed to save files like /my/anim/test1_0001.svg, /my/anim/test1_0002.svg, and so on. If this is considered okay, then I think the patch is done.

@Tamito Kajiyama (kjym3) - could you commit the changes to the branch? - I can double check and merge the addon.

Commit done in the temp-freestyle-svg branch: rBA7a331da7671a.

Checked and seems logic isn't right, simple F12 render with SVG addon enabled is writing files.

Oh is writing files upon simple F12 rendering considered wrong? Raster images can be saved by F3 after simple F12 rendering, but that cannot be done for SVG rendering results atm. Do you think SVG files should be written when rendering animation or when saving a raster image by F3?

@Tamito Kajiyama (kjym3) - I had the impression we would save SVG's from Blender, only when blender was saving images.
but this isn't really obvious whats ideal since F3 saving doesnt have a way to write the SVG.

So for now, think it can be left as is, and committed to master.

I've tested the addon, and it works very nicely.
the only remark is that it doesn't seem to use the "layer" groupmode in SVG for different kinds of entities (e.g. fills, frame lines), that would be a nice feature because Inkscape users could have the different entities already divided in layers when opening the resulting svg in Inkscape.
Also, when rendering an animation in a single svg, having the frames not separated in layers makes it quite difficult to use the resulting file for further elaboration in Inkscape.
For example, this is the layer structure in Inkscape of a file rendered with the old set of scripts I used to use:

This comment was removed by francesco fantoni (hva).

@Campbell Barton (campbellbarton),
Thanks again for the code review. I will proceed with the merge. Would you like to separately keep commit logs of the temp-freestyle-svg branch?

@francesco fantoni (hva), @Folkert de Vries (flokkievids),
Francesco, thank you for the test and feedback. I just got to know that Inkscape supports nested layers. Since the SVG exporter does grouping a lot with the svg:g tag and inkscape:groupmode attribute, code revisions addressing the suggested layer fix are likely minimum. @Folkert de Vries (flokkievids), would you like to work on the fix? We will enter BCon4 this Sunday and hence code updates need to be quick.

@Tamito Kajiyama (kjym3). it can just go as one commit. dont think history so-far is interesting.

@francesco fantoni (hva)

thanks for the tests and comments. I hope I understand your suggestion correctly. What I've implemented is

<g frame>
  <g fills /> 
  <g strokes />
</>

below are the patch file and an example result.

@Tamito Kajiyama (kjym3)
I'll have a look at the release notes in the morning.

@Folkert de Vries (flokkievids),

Apparently Inkscape layers are svg:g elements at least having three attributes id="someName", inkscape:label="someName" and inkscape:groupmode="layer".

How about organizing layers as follows? This is for animation SVG files. For still renders strokes and fills could be top-level layers without Frame_nnnnnn imho.

<g Frame_000001>
    <g strokes>
        <g LineSet1>
        <g LineSet2>
          :
    <g fills>
<g Frame_000002>
   :

Below a manually modified SVG file based on yours is attached following the proposed layer organization.

Inkscape shows the layer hierarchy of the file as follows.

@Folkert de Vries (flokkievids)
I tried your modifications, but I guess Tamito is right: you have to set id="someName", inkscape:label="someName" and inkscape:groupmode="layer" in order to show groups as layers in Inkscape. Obviously Inkscape is not the only target for svg files, but I think is quite nice to have the possibility of managing frames and linesets as layers in Inkscape.

@francesco fantoni (hva) & @Tamito Kajiyama (kjym3)

I've revised the patch, and it now seems to work correctly in inkscape.

@Folkert de Vries (flokkievids) and @Tamito Kajiyama (kjym3)
looks fine to me, although I personally would rather have the 'frame' layers as parent layers containing linesets as nested layers, but I suppose that is really a matter of personal workflow habits.

hello,

i have this problem on chrome (animation):

and:
"SVG's SMIL animations (<animate>, <set>, etc.) are deprecated and will be removed. Please use CSS animations or Web animations instead."

cheers, van