From 2071c0227fa0226196854327073c4abf5d658c28 Mon Sep 17 00:00:00 2001 From: Mario Voigt Date: Thu, 10 Jun 2021 00:31:53 +0200 Subject: [PATCH] more options to handle offset paths --- .../offset_paths/offset_paths.inx | 2 + .../offset_paths/offset_paths.py | 144 ++++++++++-------- 2 files changed, 82 insertions(+), 64 deletions(-) diff --git a/extensions/fablabchemnitz/offset_paths/offset_paths.inx b/extensions/fablabchemnitz/offset_paths/offset_paths.inx index 61fae9bb..2e0a8168 100644 --- a/extensions/fablabchemnitz/offset_paths/offset_paths.inx +++ b/extensions/fablabchemnitz/offset_paths/offset_paths.inx @@ -31,6 +31,8 @@ false + false + diff --git a/extensions/fablabchemnitz/offset_paths/offset_paths.py b/extensions/fablabchemnitz/offset_paths/offset_paths.py index 5aa59ea8..52e25796 100644 --- a/extensions/fablabchemnitz/offset_paths/offset_paths.py +++ b/extensions/fablabchemnitz/offset_paths/offset_paths.py @@ -15,6 +15,7 @@ import inkex import math from inkex.paths import CubicSuperPath import re +import copy import pyclipper class OffsetPaths(inkex.EffectExtension): @@ -25,89 +26,104 @@ class OffsetPaths(inkex.EffectExtension): pars.add_argument("--offset_count", type=int, default=1, help="Number of offset paths") pars.add_argument("--offset", type=float, default=1.000, help="Offset amount") pars.add_argument("--init_offset", type=float, default=0.000, help="Initial Offset Amount") - pars.add_argument("--copy_org", type=inkex.Boolean, default=True, help="copy original path") pars.add_argument("--offset_increase", type=float, default=0.000, help="Offset increase between iterations") pars.add_argument("--jointype", default="2", help="Join type") pars.add_argument("--endtype", default="3", help="End type") pars.add_argument("--miterlimit", type=float, default=3.0, help="Miter limit") pars.add_argument("--clipperscale", type=int, default=1024, help="Scaling factor. Should be a multiplicator of 2, like 2^4=16 or 2^10=1024. The higher the scale factor the higher the quality.") + pars.add_argument("--copy_org", type=inkex.Boolean, default=True, help="copy original path") + pars.add_argument("--individual", type=inkex.Boolean, default=True, help="Separate into individual paths") + + def effect(self): unit_factor = 1.0 / self.svg.uutounit(1.0, self.options.unit) - paths = self.svg.selection.filter(inkex.PathElement).values() - count = sum(1 for path in paths) - paths = self.svg.selection.filter(inkex.PathElement).values() #we need to call this twice because the sum function consumes the generator + pathElements = self.svg.selection.filter(inkex.PathElement).values() + count = sum(1 for pathElement in pathElements) + pathElements = self.svg.selection.filter(inkex.PathElement).values() #we need to call this twice because the sum function consumes the generator if count == 0: inkex.errormsg("No paths selected.") exit() - for path in paths: - if path.tag == inkex.addNS('path','svg'): - p = CubicSuperPath(path.get('d')) + for pathElement in pathElements: + csp = CubicSuperPath(pathElement.get('d')) - scale_factor = self.options.clipperscale # 2 ** 32 = 1024 - see also https://github.com/fonttools/pyclipper/wiki/Deprecating-SCALING_FACTOR + scale_factor = self.options.clipperscale # 2 ** 32 = 1024 - see also https://github.com/fonttools/pyclipper/wiki/Deprecating-SCALING_FACTOR - pco = pyclipper.PyclipperOffset(self.options.miterlimit) + pco = pyclipper.PyclipperOffset(self.options.miterlimit) + + JT = None + if self.options.jointype == "0": + JT = pyclipper.JT_SQUARE + elif self.options.jointype == "1": + JT = pyclipper.JT_ROUND + elif self.options.jointype == "2": + JT = pyclipper.JT_MITER - JT = None - if self.options.jointype == "0": - JT = pyclipper.JT_SQUARE - elif self.options.jointype == "1": - JT = pyclipper.JT_ROUND - elif self.options.jointype == "2": - JT = pyclipper.JT_MITER - - ET = None - if self.options.endtype == "0": - ET = pyclipper.ET_CLOSEDPOLYGON - elif self.options.endtype == "1": - ET = pyclipper.ET_CLOSEDLINE - elif self.options.endtype == "2": - ET = pyclipper.ET_OPENBUTT - elif self.options.endtype == "3": - ET = pyclipper.ET_OPENSQUARE - elif self.options.endtype == "4": - ET = pyclipper.ET_OPENROUND - - new = [] + ET = None + if self.options.endtype == "0": + ET = pyclipper.ET_CLOSEDPOLYGON + elif self.options.endtype == "1": + ET = pyclipper.ET_CLOSEDLINE + elif self.options.endtype == "2": + ET = pyclipper.ET_OPENBUTT + elif self.options.endtype == "3": + ET = pyclipper.ET_OPENSQUARE + elif self.options.endtype == "4": + ET = pyclipper.ET_OPENROUND + + newPaths = [] - # load in initial paths - for sub in p: - sub_simple = [] - for item in sub: - itemx = [float(z) * scale_factor for z in item[1]] - sub_simple.append(itemx) - pco.AddPath(sub_simple, JT, ET) + # load in initial paths + for subPath in csp: + sub_simple = [] + for item in subPath: + itemx = [float(z) * scale_factor for z in item[1]] + sub_simple.append(itemx) + pco.AddPath(sub_simple, JT, ET) - # calculate offset paths for different offset amounts - offset_list = [] - offset_list.append(self.options.init_offset * unit_factor) - for i in range(0, self.options.offset_count): - ofs_increase = +math.pow(float(i) * self.options.offset_increase * unit_factor, 2) - if self.options.offset_increase < 0: - ofs_increase = -ofs_increase - offset_list.append(offset_list[0] + float(i) * self.options.offset * unit_factor + ofs_increase * unit_factor) + # calculate offset paths for different offset amounts + offset_list = [] + offset_list.append(self.options.init_offset * unit_factor) + for i in range(0, self.options.offset_count): + ofs_increase = +math.pow(float(i) * self.options.offset_increase * unit_factor, 2) + if self.options.offset_increase < 0: + ofs_increase = -ofs_increase + offset_list.append(offset_list[0] + float(i) * self.options.offset * unit_factor + ofs_increase * unit_factor) - solutions = [] - for offset in offset_list: - solution = pco.Execute(offset * scale_factor) - solutions.append(solution) - if len(solution)<=0: - continue # no more loops to go, will provide no results. + solutions = [] + for offset in offset_list: + solution = pco.Execute(offset * scale_factor) + solutions.append(solution) + if len(solution)<=0: + continue # no more loops to go, will provide no results. - # re-arrange solutions to fit expected format & add to array - for solution in solutions: - for sol in solution: - solx = [[float(s[0]) / scale_factor, float(s[1]) / scale_factor] for s in sol] - sol_p = [[a, a, a] for a in solx] - sol_p.append(sol_p[0][:]) - new.append(sol_p) + # re-arrange solutions to fit expected format & add to array + for solution in solutions: + for sol in solution: + solx = [[float(s[0]) / scale_factor, float(s[1]) / scale_factor] for s in sol] + sol_p = [[a, a, a] for a in solx] + sol_p.append(sol_p[0][:]) + if sol_p not in newPaths: + newPaths.append(sol_p) - # add old, just to keep (make optional!) - if self.options.copy_org: - for sub in p: - new.append(sub) - - path.set('d', CubicSuperPath(new)) + if self.options.individual is True: + parent = pathElement.getparent() + idx = parent.index(pathElement) + idSuffix = 0 + for newPath in newPaths: + copyElement = copy.copy(pathElement) + elementId = copyElement.get('id') + copyElement.path = CubicSuperPath(newPath) + copyElement.set('id', elementId + str(idSuffix)) + parent.insert(idx, copyElement) + idSuffix += 1 + if self.options.copy_org is False: + pathElement.delete() + else: + if self.options.copy_org is True: + for subPath in csp: + newPaths.append(subPath) + pathElement.set('d', CubicSuperPath(newPaths)) if __name__ == '__main__': OffsetPaths().run() \ No newline at end of file