Source code for polysmooth

#!/usr/bin/env python
#-----------------------------------------------------------------------------#
#    Copyright 2012-2016 Claude Zervas
#    email: claude@utlco.com
#-----------------------------------------------------------------------------#
"""Smooth non-G1 path nodes using Bezier curves and draw it as SVG.

Works for polyline/polygons made of line/arc segments.

This can be invoked as an Inkscape extension or from the command line.
"""
# Python 3 compatibility boilerplate
from __future__ import (absolute_import, division, unicode_literals)
# from future_builtins import (ascii, filter, hex, map, oct, zip)

import gettext
import logging

import geom.debug
from geom import bezier
from geom import polygon
from inkscape import inkext
from svg import geomsvg

_ = gettext.gettext
logger = logging.getLogger(__name__)


[docs]class PolySmooth(inkext.InkscapeExtension): """An Inkscape extension that smoothes polygons. """ # Command line options OPTIONSPEC = ( inkext.ExtOption('--simplify', type='inkbool', default=False, help=_('Simplify polylines first')), inkext.ExtOption('--simplify-tolerance', type='docunits', default=.01, help=_('Tolerance for simplification')), inkext.ExtOption('--smoothness', '-s', type='int', default=50, help=_('Smoothness in percent')), inkext.ExtOption('--new-layer', type='inkbool', default=False, help=_('Create new layer for output.')), inkext.ExtOption('--match-style', type='inkbool', default=True, help=_('Match style of input path.')), inkext.ExtOption('--polysmooth-stroke', help=_('CSS stroke color')), inkext.ExtOption('--polysmooth-stroke-width', help=_('CSS stroke width')), ) # SVG CSS inline style template _styles = { 'polysmooth': 'fill:none;stroke-opacity:1.0;stroke-linejoin:round;' 'stroke-width:${polysmooth_stroke_width};' 'stroke:${polysmooth_stroke};', } # Default style template values _style_defaults = { 'polysmooth_stroke_width': '1px', 'polysmooth_stroke': '#000', } # Default layer name for smoothed output _LAYER_NAME = 'polysmooth'
[docs] def run(self): """Main entry point for Inkscape extensions. """ # Set up debug SVG output context. geom.debug.set_svg_context(self.debug_svg) # Update CSS inline styles from templates and/or options self._styles.update(self.svg.styles_from_templates( self._styles, self._style_defaults, self.options.__dict__)) # Get a list of selected SVG shape elements and their transforms parent_transform = None # if not self.options.new_layer: # # This will prevent the parent layer transform from being applied # # twice when the original element is replaced by the smoothed one. # parent_transform = transform2d.IDENTITY_MATRIX svg_elements = self.svg.get_shape_elements( self.get_elements(), parent_transform=parent_transform, accumulate_transform=self.options.new_layer) if not svg_elements: # Nothing selected or document is empty return # Create a new layer for the SVG output. if self.options.new_layer: new_layer = self.svg.create_layer(self._LAYER_NAME, incr_suffix=True) default_style = self._styles['polysmooth'] smoothness = self.options.smoothness / 100.0 for element, element_transform in svg_elements: # Convert the SVG element to Line/Arc/CubicBezier paths pathlist = geomsvg.svg_element_to_geometry( element, element_transform=element_transform) for path in pathlist: if self.options.simplify: path = self.simplify_polylines( path, self.options.simplify_tolerance) new_path = bezier.smooth_path(path, smoothness) if not new_path: # Ignore failures and keep going... # This should only happen if there are segments # that are neither arc nor line. continue if self.options.new_layer: parent = new_layer else: # Replace the original element with the smoothed one parent = element.getparent() if parent is not None: parent.remove(element) style = default_style if self.options.match_style: style = element.get('style') path_is_closed = (path[-1].p2 == path[0].p1) self.svg.create_polypath(new_path, style=style, close_path=path_is_closed, parent=parent)
[docs] def simplify_polylines(self, path, tolerance): """Simplify any polylines in the path. """ new_path = [] # Find polylines polyline = [] for segment in path: if isinstance(segment, geom.Line): polyline.append(segment) elif len(polyline) > 1: # Simplify the polyline and then add it back to the path. polyline = self._simplify_polyline(polyline, tolerance) new_path.extend(polyline) # Reset accumulated polyline polyline = [] else: new_path.append(segment) if polyline: polyline = self._simplify_polyline(polyline, tolerance) new_path.extend(polyline) return new_path
def _simplify_polyline(self, path, tolerance): points1, points2 = zip(*path) points1 = list(points1) # points2 = list(points2) # points1.append(points2[-1]) points1.append(path[-1][1]) points = polygon.simplify_polyline_rdp(points1, tolerance) new_path = [] prev_pt = points[0] for next_pt in points[1:]: next_line = geom.Line(prev_pt, next_pt) new_path.append(next_line) prev_pt = next_pt return new_path
if __name__ == '__main__': PolySmooth().main(PolySmooth.OPTIONSPEC)