#!/usr/bin/env python ''' Inkscape extension to subdivide the selected bezier paths based on max length value or count Copyright (C) 2018 Shrinivas Kulkarni 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. ''' import inkex, cubicsuperpath, bezmisc, simpletransform, sys import copy from math import ceil DEF_ERR_MARGIN = 0.0001 def getPartsFromCubicSuper(cspath): parts = [] for subpath in cspath: part = [] prevBezPt = None for i, bezierPt in enumerate(subpath): if(prevBezPt != None): seg = [prevBezPt[1], prevBezPt[2], bezierPt[0], bezierPt[1]] part.append(seg) prevBezPt = bezierPt parts.append(part) return parts def getCubicSuperFromParts(parts): cbsuper = [] for part in parts: subpath = [] lastPt = None pt = None for seg in part: if(pt == None): ptLeft = seg[0] pt = seg[0] ptRight = seg[1] subpath.append([ptLeft, pt, ptRight]) ptLeft = seg[2] pt = seg[3] subpath.append([ptLeft, pt, pt]) cbsuper.append(subpath) return cbsuper def floatCmpWithMargin(float1, float2, margin = DEF_ERR_MARGIN): return abs(float1 - float2) < margin class SubdividePathEffect(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) self.OptionParser.add_option('--maxLength', action = 'store', type = 'float', dest = 'maxLength', default = '10', help = 'Maximum Length of New Segments') self.OptionParser.add_option('--unit', action = 'store', type = 'string', dest = 'unit', default = 'mm', help = 'Unit of Measurement') self.OptionParser.add_option('--precision', action = 'store', type = 'int', dest = 'precision', default = '5', help = 'Number of significant digits') self.OptionParser.add_option("--tab", action="store", type="string", dest="tab", default="sampling", help="Tab") self.OptionParser.add_option("--separateSegs", action="store", type="inkbool", dest="separateSegs", default=True) def effect(self): maxL = None separateSegs = self.options.separateSegs if(self.options.unit != 'perc' and self.options.unit != 'count'): maxL = self.options.maxLength * self.unittouu('1'+self.options.unit) # ~ inkex.errormsg(_(str(maxL))) tolerance = 10 ** (-1 * self.options.precision) selections = self.selected pathNodes = self.document.xpath('//svg:path',namespaces=inkex.NSS) paths = [(pathNode.get('id'), cubicsuperpath.parsePath(pathNode.get('d'))) \ for pathNode in pathNodes if (pathNode.get('id') in selections.keys())] if(len(paths) > 0): for key, cspath in paths: parts = getPartsFromCubicSuper(cspath) partsSplit = False for i, part in enumerate(parts): newSegs = [] for j, seg in enumerate(part): segL = bezmisc.bezierlengthSimpson((seg[0], seg[1], seg[2], seg[3]), tolerance = tolerance) if(maxL != None): divL = maxL elif(self.options.unit == 'perc'): divL = segL * self.options.maxLength / 100 else: divL = segL / ceil(self.options.maxLength) if(segL > divL): coveredL = 0 s = seg s1 = None s2 = DEF_ERR_MARGIN #Just in case while(not floatCmpWithMargin(segL, coveredL)): if(s == seg): sL = segL else: sL = bezmisc.bezierlengthSimpson((s[0], s[1], s[2], s[3]), tolerance = tolerance) if(floatCmpWithMargin(segL, coveredL + divL)): s2 = s break else: if(segL > (coveredL + divL)): t1L = divL else: t1L = segL - coveredL t1 = bezmisc.beziertatlength((s[0], s[1], s[2], s[3]), l = t1L / sL , tolerance = tolerance) s1, s2 = bezmisc.beziersplitatt((s[0], s[1], s[2], s[3]), t1) coveredL += t1L newSegs.append(s1) s = s2 newSegs.append(s2) else: newSegs.append(seg) if(len(newSegs) > len(part)): parts[i] = newSegs partsSplit = True if(partsSplit or separateSegs): elem = selections[key] if(separateSegs): parent = self.getParentNode(elem) idx = parent.index(elem) parent.remove(elem) allSegs = [seg for part in parts for seg in part] idSuffix = 0 for seg in allSegs: cspath = getCubicSuperFromParts([[seg]]) newElem = copy.copy(elem) oldId = newElem.get('id') newElem.set('d', cubicsuperpath.formatPath(cspath)) newElem.set('id', oldId + str(idSuffix).zfill(5)) parent.insert(idx, newElem) idSuffix += 1 else: cspath = getCubicSuperFromParts(parts) elem.set('d', cubicsuperpath.formatPath(cspath)) effect = SubdividePathEffect() effect.affect()