more options to handle offset paths
This commit is contained in:
parent
50f891d9ba
commit
2071c0227f
@ -31,6 +31,8 @@
|
|||||||
<option value="4">Open Round</option>
|
<option value="4">Open Round</option>
|
||||||
</param>
|
</param>
|
||||||
<param name="copy_org" type="bool" gui-text="Keep original path" gui-description="If enabled, keeps original path as a copy">false</param>
|
<param name="copy_org" type="bool" gui-text="Keep original path" gui-description="If enabled, keeps original path as a copy">false</param>
|
||||||
|
<param name="individual" type="bool" gui-text="Separate into individual paths" gui-description="If enabled, each offset curve will be an individual svg element">false</param>
|
||||||
|
|
||||||
</page>
|
</page>
|
||||||
<page name="tab_about" gui-text="About">
|
<page name="tab_about" gui-text="About">
|
||||||
<label appearance="header">Offset Paths</label>
|
<label appearance="header">Offset Paths</label>
|
||||||
|
@ -15,6 +15,7 @@ import inkex
|
|||||||
import math
|
import math
|
||||||
from inkex.paths import CubicSuperPath
|
from inkex.paths import CubicSuperPath
|
||||||
import re
|
import re
|
||||||
|
import copy
|
||||||
import pyclipper
|
import pyclipper
|
||||||
|
|
||||||
class OffsetPaths(inkex.EffectExtension):
|
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_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("--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("--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("--offset_increase", type=float, default=0.000, help="Offset increase between iterations")
|
||||||
pars.add_argument("--jointype", default="2", help="Join type")
|
pars.add_argument("--jointype", default="2", help="Join type")
|
||||||
pars.add_argument("--endtype", default="3", help="End 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("--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("--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):
|
def effect(self):
|
||||||
unit_factor = 1.0 / self.svg.uutounit(1.0, self.options.unit)
|
unit_factor = 1.0 / self.svg.uutounit(1.0, self.options.unit)
|
||||||
paths = self.svg.selection.filter(inkex.PathElement).values()
|
pathElements = self.svg.selection.filter(inkex.PathElement).values()
|
||||||
count = sum(1 for path in paths)
|
count = sum(1 for pathElement in pathElements)
|
||||||
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() #we need to call this twice because the sum function consumes the generator
|
||||||
if count == 0:
|
if count == 0:
|
||||||
inkex.errormsg("No paths selected.")
|
inkex.errormsg("No paths selected.")
|
||||||
exit()
|
exit()
|
||||||
for path in paths:
|
for pathElement in pathElements:
|
||||||
if path.tag == inkex.addNS('path','svg'):
|
csp = CubicSuperPath(pathElement.get('d'))
|
||||||
p = CubicSuperPath(path.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
|
JT = None
|
||||||
if self.options.jointype == "0":
|
if self.options.jointype == "0":
|
||||||
JT = pyclipper.JT_SQUARE
|
JT = pyclipper.JT_SQUARE
|
||||||
elif self.options.jointype == "1":
|
elif self.options.jointype == "1":
|
||||||
JT = pyclipper.JT_ROUND
|
JT = pyclipper.JT_ROUND
|
||||||
elif self.options.jointype == "2":
|
elif self.options.jointype == "2":
|
||||||
JT = pyclipper.JT_MITER
|
JT = pyclipper.JT_MITER
|
||||||
|
|
||||||
ET = None
|
ET = None
|
||||||
if self.options.endtype == "0":
|
if self.options.endtype == "0":
|
||||||
ET = pyclipper.ET_CLOSEDPOLYGON
|
ET = pyclipper.ET_CLOSEDPOLYGON
|
||||||
elif self.options.endtype == "1":
|
elif self.options.endtype == "1":
|
||||||
ET = pyclipper.ET_CLOSEDLINE
|
ET = pyclipper.ET_CLOSEDLINE
|
||||||
elif self.options.endtype == "2":
|
elif self.options.endtype == "2":
|
||||||
ET = pyclipper.ET_OPENBUTT
|
ET = pyclipper.ET_OPENBUTT
|
||||||
elif self.options.endtype == "3":
|
elif self.options.endtype == "3":
|
||||||
ET = pyclipper.ET_OPENSQUARE
|
ET = pyclipper.ET_OPENSQUARE
|
||||||
elif self.options.endtype == "4":
|
elif self.options.endtype == "4":
|
||||||
ET = pyclipper.ET_OPENROUND
|
ET = pyclipper.ET_OPENROUND
|
||||||
|
|
||||||
new = []
|
newPaths = []
|
||||||
|
|
||||||
# load in initial paths
|
# load in initial paths
|
||||||
for sub in p:
|
for subPath in csp:
|
||||||
sub_simple = []
|
sub_simple = []
|
||||||
for item in sub:
|
for item in subPath:
|
||||||
itemx = [float(z) * scale_factor for z in item[1]]
|
itemx = [float(z) * scale_factor for z in item[1]]
|
||||||
sub_simple.append(itemx)
|
sub_simple.append(itemx)
|
||||||
pco.AddPath(sub_simple, JT, ET)
|
pco.AddPath(sub_simple, JT, ET)
|
||||||
|
|
||||||
# calculate offset paths for different offset amounts
|
# calculate offset paths for different offset amounts
|
||||||
offset_list = []
|
offset_list = []
|
||||||
offset_list.append(self.options.init_offset * unit_factor)
|
offset_list.append(self.options.init_offset * unit_factor)
|
||||||
for i in range(0, self.options.offset_count):
|
for i in range(0, self.options.offset_count):
|
||||||
ofs_increase = +math.pow(float(i) * self.options.offset_increase * unit_factor, 2)
|
ofs_increase = +math.pow(float(i) * self.options.offset_increase * unit_factor, 2)
|
||||||
if self.options.offset_increase < 0:
|
if self.options.offset_increase < 0:
|
||||||
ofs_increase = -ofs_increase
|
ofs_increase = -ofs_increase
|
||||||
offset_list.append(offset_list[0] + float(i) * self.options.offset * unit_factor + ofs_increase * unit_factor)
|
offset_list.append(offset_list[0] + float(i) * self.options.offset * unit_factor + ofs_increase * unit_factor)
|
||||||
|
|
||||||
solutions = []
|
solutions = []
|
||||||
for offset in offset_list:
|
for offset in offset_list:
|
||||||
solution = pco.Execute(offset * scale_factor)
|
solution = pco.Execute(offset * scale_factor)
|
||||||
solutions.append(solution)
|
solutions.append(solution)
|
||||||
if len(solution)<=0:
|
if len(solution)<=0:
|
||||||
continue # no more loops to go, will provide no results.
|
continue # no more loops to go, will provide no results.
|
||||||
|
|
||||||
# re-arrange solutions to fit expected format & add to array
|
# re-arrange solutions to fit expected format & add to array
|
||||||
for solution in solutions:
|
for solution in solutions:
|
||||||
for sol in solution:
|
for sol in solution:
|
||||||
solx = [[float(s[0]) / scale_factor, float(s[1]) / scale_factor] for s in sol]
|
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 = [[a, a, a] for a in solx]
|
||||||
sol_p.append(sol_p[0][:])
|
sol_p.append(sol_p[0][:])
|
||||||
new.append(sol_p)
|
if sol_p not in newPaths:
|
||||||
|
newPaths.append(sol_p)
|
||||||
|
|
||||||
# add old, just to keep (make optional!)
|
if self.options.individual is True:
|
||||||
if self.options.copy_org:
|
parent = pathElement.getparent()
|
||||||
for sub in p:
|
idx = parent.index(pathElement)
|
||||||
new.append(sub)
|
idSuffix = 0
|
||||||
|
for newPath in newPaths:
|
||||||
path.set('d', CubicSuperPath(new))
|
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__':
|
if __name__ == '__main__':
|
||||||
OffsetPaths().run()
|
OffsetPaths().run()
|
Reference in New Issue
Block a user