diff options
author | Campbell Barton <ideasman42@gmail.com> | 2014-10-19 21:10:06 +0400 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2014-10-19 21:10:32 +0400 |
commit | 4dd5b635e8c5809561f35abc8cb9f42b8e104cf7 (patch) | |
tree | e99312e9d59f93eca806513653db9345a2ca25ce /release | |
parent | 4c86271bd9652c19f72728686452fb126ca67016 (diff) |
Revert "Freestyle: Built-in SVG exporter."
This reverts commit 61a330baca0ff9bb3cf477c04f539ef276a0356f.
This completes reverting D785
This feature is to be reworked into an addon.
Diffstat (limited to 'release')
-rw-r--r-- | release/scripts/freestyle/modules/freestyle/utils.py | 14 | ||||
-rw-r--r-- | release/scripts/freestyle/modules/parameter_editor.py | 71 | ||||
-rw-r--r-- | release/scripts/freestyle/modules/svg_export.py | 305 | ||||
-rw-r--r-- | release/scripts/startup/bl_ui/properties_freestyle.py | 19 | ||||
-rw-r--r-- | release/scripts/startup/freestyle_builtins.py | 14 |
5 files changed, 18 insertions, 405 deletions
diff --git a/release/scripts/freestyle/modules/freestyle/utils.py b/release/scripts/freestyle/modules/freestyle/utils.py index a8e4743ae4b..6c5e1d5887a 100644 --- a/release/scripts/freestyle/modules/freestyle/utils.py +++ b/release/scripts/freestyle/modules/freestyle/utils.py @@ -86,20 +86,6 @@ 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 0498213e6f4..9ac5c665f1e 100644 --- a/release/scripts/freestyle/modules/parameter_editor.py +++ b/release/scripts/freestyle/modules/parameter_editor.py @@ -59,7 +59,6 @@ from freestyle.predicates import ( NotUP1D, OrUP1D, QuantitativeInvisibilityUP1D, - SameShapeIdBP1D, TrueBP1D, TrueUP1D, WithinImageBoundaryUP1D, @@ -98,8 +97,7 @@ from freestyle.utils import ( stroke_normal, bound, pairwise, - BoundedProperty, - get_dashed_pattern, + BoundedProperty ) from _freestyle import ( blendRamp, @@ -107,19 +105,10 @@ 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): @@ -430,7 +419,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: @@ -898,6 +887,7 @@ integration_types = { # main function for parameter processing + def process(layer_name, lineset_name): scene = getCurrentScene() layer = scene.render.layers[layer_name] @@ -1182,51 +1172,24 @@ 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 deleted file mode 100644 index 09566730891..00000000000 --- a/release/scripts/freestyle/modules/svg_export.py +++ /dev/null @@ -1,305 +0,0 @@ -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 132230f51fe..606842db01d 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,23 +59,6 @@ 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 deleted file mode 100644 index 10be9d99eec..00000000000 --- a/release/scripts/startup/freestyle_builtins.py +++ /dev/null @@ -1,14 +0,0 @@ -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() |