More bugfixes in Create Links extension, validated by larger SVGs
This commit is contained in:
parent
89bf906191
commit
db05a9aef3
@ -52,7 +52,8 @@
|
|||||||
<param name="keep_selected" type="bool" gui-text="Keep selected elements">false</param>
|
<param name="keep_selected" type="bool" gui-text="Keep selected elements">false</param>
|
||||||
<param name="no_convert" type="bool" gui-text="Do not create output path(s) (cosmetic style only)">false</param>
|
<param name="no_convert" type="bool" gui-text="Do not create output path(s) (cosmetic style only)">false</param>
|
||||||
<param name="breakapart" type="bool" gui-text="Break apart output path(s) into segments" gui-description="Performs CTRL + SHIFT + K to break the new output path into it's parts">false</param>
|
<param name="breakapart" type="bool" gui-text="Break apart output path(s) into segments" gui-description="Performs CTRL + SHIFT + K to break the new output path into it's parts">false</param>
|
||||||
<param name="show_info" type="bool" gui-text="Print some length, pattern and filtering information" gui-description="Warning: might freeze InkScape forever if you have a lot of nodes because we create too much print output. Use for debugging only!">false</param>
|
<param name="show_info" type="bool" gui-text="Print length, pattern and filtering information/errors" gui-description="Warning: might freeze Inkscape forever if you have a lot of nodes because we create too much print output. Use for debugging only!">false</param>
|
||||||
|
<param name="skip_errors" type="bool" gui-text="Skip errors">false</param>
|
||||||
</vbox>
|
</vbox>
|
||||||
</hbox>
|
</hbox>
|
||||||
</page>
|
</page>
|
||||||
|
@ -60,29 +60,33 @@ class LinksCreator(inkex.EffectExtension):
|
|||||||
self.arg_parser.add_argument("--no_convert", type=inkex.Boolean, default=False, help="Do not create segments (cosmetic gaps only)")
|
self.arg_parser.add_argument("--no_convert", type=inkex.Boolean, default=False, help="Do not create segments (cosmetic gaps only)")
|
||||||
self.arg_parser.add_argument("--breakapart", type=inkex.Boolean, default=False, help="Performs CTRL + SHIFT + K to break the new output path into it's parts")
|
self.arg_parser.add_argument("--breakapart", type=inkex.Boolean, default=False, help="Performs CTRL + SHIFT + K to break the new output path into it's parts")
|
||||||
self.arg_parser.add_argument("--show_info", type=inkex.Boolean, default=False, help="Print some length and pattern information")
|
self.arg_parser.add_argument("--show_info", type=inkex.Boolean, default=False, help="Print some length and pattern information")
|
||||||
|
self.arg_parser.add_argument("--skip_errors", type=inkex.Boolean, default=False, help="Skip errors")
|
||||||
|
|
||||||
def breakContours(self, node, breakNodes = None): #this does the same as "CTRL + SHIFT + K"
|
def breakContours(self, node, breakNodes = None): #this does the same as "CTRL + SHIFT + K"
|
||||||
if breakNodes == None:
|
if breakNodes == None:
|
||||||
breakNodes = []
|
breakNodes = []
|
||||||
parent = node.getparent()
|
if node.tag == inkex.addNS('path','svg'):
|
||||||
idx = parent.index(node)
|
parent = node.getparent()
|
||||||
idSuffix = 0
|
idx = parent.index(node)
|
||||||
raw = node.path.to_arrays()
|
idSuffix = 0
|
||||||
subPaths, prev = [], 0
|
raw = node.path.to_arrays()
|
||||||
for i in range(len(raw)): # Breaks compound paths into simple paths
|
subPaths, prev = [], 0
|
||||||
if raw[i][0] == 'M' and i != 0:
|
for i in range(len(raw)): # Breaks compound paths into simple paths
|
||||||
subPaths.append(raw[prev:i])
|
if raw[i][0] == 'M' and i != 0:
|
||||||
prev = i
|
subPaths.append(raw[prev:i])
|
||||||
subPaths.append(raw[prev:])
|
prev = i
|
||||||
for subpath in subPaths:
|
subPaths.append(raw[prev:])
|
||||||
replacedNode = copy.copy(node)
|
for subpath in subPaths:
|
||||||
oldId = replacedNode.get('id')
|
replacedNode = copy.copy(node)
|
||||||
replacedNode.set('d', CubicSuperPath(subpath))
|
oldId = replacedNode.get('id')
|
||||||
replacedNode.set('id', oldId + str(idSuffix).zfill(5))
|
replacedNode.set('d', CubicSuperPath(subpath))
|
||||||
parent.insert(idx, replacedNode)
|
replacedNode.set('id', oldId + str(idSuffix).zfill(5))
|
||||||
idSuffix += 1
|
parent.insert(idx, replacedNode)
|
||||||
breakNodes.append(replacedNode)
|
idSuffix += 1
|
||||||
parent.remove(node)
|
breakNodes.append(replacedNode)
|
||||||
|
parent.remove(node)
|
||||||
|
for child in node:
|
||||||
|
self.breakContours(child, breakNodes)
|
||||||
return breakNodes
|
return breakNodes
|
||||||
|
|
||||||
def effect(self):
|
def effect(self):
|
||||||
@ -115,7 +119,7 @@ class LinksCreator(inkex.EffectExtension):
|
|||||||
if self.options.length_filter is True:
|
if self.options.length_filter is True:
|
||||||
if stotal < self.svg.unittouu(str(self.options.length_filter_value) + self.options.length_filter_unit):
|
if stotal < self.svg.unittouu(str(self.options.length_filter_value) + self.options.length_filter_unit):
|
||||||
if self.options.show_info is True:
|
if self.options.show_info is True:
|
||||||
inkex.utils.debug("node " + node.get('id') + " is shorter than minimum allowed length of {:1.3f} {}. Path length is {:1.3f} {}".format(self.options.length_filter_value, self.options.length_filter_unit, stotal, self.options.creationunit))
|
inkex.errormsg("node " + node.get('id') + " is shorter than minimum allowed length of {:1.3f} {}. Path length is {:1.3f} {}".format(self.options.length_filter_value, self.options.length_filter_unit, stotal, self.options.creationunit))
|
||||||
return #skip this loop iteration
|
return #skip this loop iteration
|
||||||
|
|
||||||
if self.options.creationunit == "percent":
|
if self.options.creationunit == "percent":
|
||||||
@ -124,54 +128,60 @@ class LinksCreator(inkex.EffectExtension):
|
|||||||
length_link = self.svg.unittouu(str(self.options.length_link) + self.options.creationunit)
|
length_link = self.svg.unittouu(str(self.options.length_link) + self.options.creationunit)
|
||||||
|
|
||||||
dashes = [] #central dashes array
|
dashes = [] #central dashes array
|
||||||
dashes.append(((stotal - length_link * self.options.link_count) / self.options.link_count) - 2 * length_link * (self.options.link_multiplicator))
|
|
||||||
dashes.append(length_link)
|
|
||||||
|
|
||||||
for i in range(0, self.options.link_multiplicator):
|
|
||||||
dashes.append(length_link) #stroke (=gap)
|
|
||||||
dashes.append(length_link) #gap
|
|
||||||
|
|
||||||
if self.options.creationtype == "use_existing" and self.options.no_convert is True:
|
|
||||||
inkex.errormsg("Nothing to do. Please select another creation method or disable cosmetic style output paths.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.options.creationtype == "entered_values":
|
if self.options.creationtype == "entered_values":
|
||||||
|
dashes.append(((stotal - length_link * self.options.link_count) / self.options.link_count) - 2 * length_link * (self.options.link_multiplicator))
|
||||||
|
dashes.append(length_link)
|
||||||
|
for i in range(0, self.options.link_multiplicator):
|
||||||
|
dashes.append(length_link) #stroke (=gap)
|
||||||
|
dashes.append(length_link) #gap
|
||||||
|
#validate dashes. May not be negative. Otherwise Inkscape will freeze forever. Reason: rendering issue
|
||||||
|
if any(dash < 0 for dash in dashes) == True:
|
||||||
|
inkex.errormsg("node " + node.get('id') + ": Error! Dash array may not contain negative numbers: " + ' '.join(format(dash, "1.3f") for dash in dashes) + ". Path skipped. Maybe it's too short. Adjust your link count, multiplicator and length accordingly, or set to unit '%'") if self.options.show_info is True else None
|
||||||
|
return False if self.options.skip_errors is True else exit(1)
|
||||||
|
|
||||||
if self.options.creationunit == "percent":
|
if self.options.creationunit == "percent":
|
||||||
stroke_dashoffset = self.options.link_offset / 100.0
|
stroke_dashoffset = self.options.link_offset / 100.0
|
||||||
else:
|
else:
|
||||||
stroke_dashoffset = self.svg.unittouu(str(self.options.link_offset) + self.options.creationunit)
|
stroke_dashoffset = self.svg.unittouu(str(self.options.link_offset) + self.options.creationunit)
|
||||||
|
|
||||||
if self.options.creationtype == "use_existing":
|
if self.options.creationtype == "use_existing":
|
||||||
|
if self.options.no_convert is True:
|
||||||
|
inkex.errormsg("node " + node.get('id') + ": Nothing to do. Please select another creation method or disable cosmetic style output paths.") if self.options.show_info is True else None
|
||||||
|
return False if self.options.skip_errors is True else exit(1)
|
||||||
stroke_dashoffset = 0
|
stroke_dashoffset = 0
|
||||||
style = node.style
|
style = node.style
|
||||||
if 'stroke-dashoffset' in style:
|
if 'stroke-dashoffset' in style:
|
||||||
stroke_dashoffset = style['stroke-dashoffset']
|
stroke_dashoffset = style['stroke-dashoffset']
|
||||||
try:
|
try:
|
||||||
floats = [float(dash) for dash in re.findall(r"[-+]?\d*\.\d+|\d+", style['stroke-dasharray'])]
|
floats = [float(dash) for dash in re.findall(r"[+]?\d*\.\d+|\d+", style['stroke-dasharray'])] #allow only positive values
|
||||||
if len(floats) > 0:
|
if len(floats) > 0:
|
||||||
dashes = floats #overwrite previously calculated values with custom input
|
dashes = floats #overwrite previously calculated values with custom input
|
||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except:
|
except:
|
||||||
inkex.errormsg("no dash style to continue with.")
|
inkex.errormsg("node " + node.get('id') + ": No dash style to continue with.") if self.options.show_info is True else None
|
||||||
return
|
return False if self.options.skip_errors is True else exit(1)
|
||||||
|
|
||||||
if self.options.creationtype == "custom_dashpattern":
|
if self.options.creationtype == "custom_dashpattern":
|
||||||
stroke_dashoffset = self.options.custom_dashoffset_value
|
stroke_dashoffset = self.options.custom_dashoffset_value
|
||||||
try:
|
try:
|
||||||
floats = [float(dash) for dash in re.findall(r"[-+]?\d*\.\d+|\d+", self.options.custom_dasharray_value)]
|
floats = [float(dash) for dash in re.findall(r"[+]?\d*\.\d+|\d+", self.options.custom_dasharray_value)] #allow only positive values
|
||||||
if len(floats) > 0:
|
if len(floats) > 0:
|
||||||
dashes = floats #overwrite previously calculated values with custom input
|
dashes = floats #overwrite previously calculated values with custom input
|
||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except:
|
except:
|
||||||
inkex.errormsg("Error in custom dasharray string (might be empty or does not contain any numbers).")
|
inkex.errormsg("node " + node.get('id') + ": Error in custom dasharray string (might be empty or does not contain any numbers).") if self.options.show_info is True else None
|
||||||
return
|
return False if self.options.skip_errors is True else exit(1)
|
||||||
|
|
||||||
|
#assign stroke dasharray from entered values, existing style or custom dashpattern
|
||||||
stroke_dasharray = ' '.join(format(dash, "1.3f") for dash in dashes)
|
stroke_dasharray = ' '.join(format(dash, "1.3f") for dash in dashes)
|
||||||
|
|
||||||
# check if the node has a style attribute. If not we create a blank one with a black stroke and without fill
|
# check if the node has a style attribute. If not we create a blank one with a black stroke and without fill
|
||||||
style = None
|
style = None
|
||||||
|
default_stroke_width = '1px'
|
||||||
|
default_stroke = '#000000'
|
||||||
if node.attrib.has_key('style'):
|
if node.attrib.has_key('style'):
|
||||||
style = node.get('style')
|
style = node.get('style')
|
||||||
if style.endswith(';') is False:
|
if style.endswith(';') is False:
|
||||||
@ -190,34 +200,39 @@ class LinksCreator(inkex.EffectExtension):
|
|||||||
declarations[i] = prop + ':{};'.format(stroke_dashoffset)
|
declarations[i] = prop + ':{};'.format(stroke_dashoffset)
|
||||||
node.set('style', ';'.join(declarations)) #apply new style to node
|
node.set('style', ';'.join(declarations)) #apply new style to node
|
||||||
|
|
||||||
#if has style attribute but the style attribute does not contain dasharray or dashoffset yet
|
#if has style attribute but the style attribute does not contain stroke, stroke-dasharray or stroke-dashoffset yet
|
||||||
style = node.style
|
style = node.style
|
||||||
|
if re.search('fill:(.*?)(;|$)', str(style)) is None:
|
||||||
|
style += 'fill:none;'
|
||||||
|
if re.search('(;|^)stroke:(.*?)(;|$)', str(style)) is None: #if "stroke" is None, add one. We need to distinguish because there's also attribute "-inkscape-stroke" that's why we check starting with ^ or ;
|
||||||
|
style += 'stroke:{};'.format(default_stroke)
|
||||||
|
if not 'stroke-width' in style:
|
||||||
|
style += 'stroke-width:{};'.format(default_stroke_width)
|
||||||
if not 'stroke-dasharray' in style:
|
if not 'stroke-dasharray' in style:
|
||||||
style = style + 'stroke-dasharray:{};'.format(stroke_dasharray)
|
style += 'stroke-dasharray:{};'.format(stroke_dasharray)
|
||||||
if not 'stroke-dashoffset' in style:
|
if not 'stroke-dashoffset' in style:
|
||||||
style = style + 'stroke-dashoffset:{};'.format(stroke_dashoffset)
|
style += 'stroke-dashoffset:{};'.format(stroke_dashoffset)
|
||||||
node.set('style', style)
|
node.set('style', style)
|
||||||
else:
|
else:
|
||||||
style = 'fill:none;stroke:#000000;stroke-width:1px;stroke-dasharray:{};stroke-dashoffset:{};'.format(stroke_dasharray, stroke_dashoffset)
|
style = 'fill:none;stroke:{};stroke-width:{};stroke-dasharray:{};stroke-dashoffset:{};'.format(default_stroke, default_stroke_width, stroke_dasharray, stroke_dashoffset)
|
||||||
node.set('style', style)
|
node.set('style', style)
|
||||||
|
|
||||||
# Print some info about values
|
# Print some info about values
|
||||||
if self.options.show_info is True:
|
if self.options.show_info is True:
|
||||||
inkex.utils.debug("node " + node.get('id'))
|
inkex.errormsg("node " + node.get('id') + ":")
|
||||||
if self.options.creationunit == "percent":
|
if self.options.creationunit == "percent":
|
||||||
inkex.utils.debug("total path length = {:1.3f} {}".format(stotal, self.svg.unit)) #show length, converted in selected unit
|
inkex.errormsg(" * total path length = {:1.3f} {}".format(stotal, self.svg.unit)) #show length, converted in selected unit
|
||||||
inkex.utils.debug("(calculated) offset: {:1.3f} %".format(stroke_dashoffset))
|
inkex.errormsg(" * (calculated) offset: {:1.3f} %".format(stroke_dashoffset))
|
||||||
if self.options.creationtype == "entered_values":
|
if self.options.creationtype == "entered_values":
|
||||||
inkex.utils.debug("(calculated) gap length: {:1.3f} %".format(length_link))
|
inkex.errormsg(" * (calculated) gap length: {:1.3f} %".format(length_link))
|
||||||
else:
|
else:
|
||||||
inkex.utils.debug("total path length = {:1.3f} {} ({:1.3f} {})".format(self.svg.uutounit(stotal, self.options.creationunit), self.options.creationunit, stotal, self.svg.unit)) #show length, converted in selected unit
|
inkex.errormsg(" * total path length = {:1.3f} {} ({:1.3f} {})".format(self.svg.uutounit(stotal, self.options.creationunit), self.options.creationunit, stotal, self.svg.unit)) #show length, converted in selected unit
|
||||||
inkex.utils.debug("(calculated) offset: {:1.3f} {}".format(self.svg.uutounit(stroke_dashoffset, self.options.creationunit), self.options.creationunit))
|
inkex.errormsg(" * (calculated) offset: {:1.3f} {}".format(self.svg.uutounit(stroke_dashoffset, self.options.creationunit), self.options.creationunit))
|
||||||
if self.options.creationtype == "entered_values":
|
if self.options.creationtype == "entered_values":
|
||||||
inkex.utils.debug("(calculated) gap length: {:1.3f} {}".format(length_link, self.options.creationunit))
|
inkex.errormsg(" * (calculated) gap length: {:1.3f} {}".format(length_link, self.options.creationunit))
|
||||||
if self.options.creationtype == "entered_values":
|
if self.options.creationtype == "entered_values":
|
||||||
inkex.utils.debug("total gaps = {}".format(self.options.link_count))
|
inkex.errormsg(" * total gaps = {}".format(self.options.link_count))
|
||||||
inkex.utils.debug("(calculated) dash/gap pattern: {} ({})".format(stroke_dasharray, self.svg.unit))
|
inkex.errormsg(" * (calculated) dash/gap pattern: {} ({})".format(stroke_dasharray, self.svg.unit))
|
||||||
inkex.utils.debug("--------------------------------------------------------------------------------------------------")
|
|
||||||
|
|
||||||
# Conversion step (split cosmetic path into real segments)
|
# Conversion step (split cosmetic path into real segments)
|
||||||
if self.options.no_convert is False:
|
if self.options.no_convert is False:
|
||||||
@ -263,20 +278,15 @@ class LinksCreator(inkex.EffectExtension):
|
|||||||
breakApartGroup = nodeParent.add(inkex.Group())
|
breakApartGroup = nodeParent.add(inkex.Group())
|
||||||
for breakOutputNode in breakOutputNodes:
|
for breakOutputNode in breakOutputNodes:
|
||||||
breakApartGroup.append(breakOutputNode)
|
breakApartGroup.append(breakOutputNode)
|
||||||
#inkex.utils.debug(replacedNode.get('id'))
|
#inkex.errormsg(replacedNode.get('id'))
|
||||||
#self.svg.selection.set(replacedNode.get('id')) #update selection to split paths segments (does not work, so commented out)
|
#self.svg.selection.set(replacedNode.get('id')) #update selection to split paths segments (does not work, so commented out)
|
||||||
|
|
||||||
if len(self.svg.selected) > 0:
|
if len(self.svg.selected) > 0:
|
||||||
pathNodes = self.svg.selection.filter(PathElement).values()
|
for node in self.svg.selection.values():
|
||||||
if len(pathNodes) > 0:
|
#at first we need to break down combined nodes to single path, otherwise dasharray cannot properly be applied
|
||||||
for node in pathNodes:
|
breakInputNodes = self.breakContours(node)
|
||||||
#at first we need to break down combined nodes to single path, otherwise dasharray cannot properly be applied
|
for breakInputNode in breakInputNodes:
|
||||||
breakInputNodes = self.breakContours(node)
|
createLinks(breakInputNode)
|
||||||
for breakInputNode in breakInputNodes:
|
|
||||||
createLinks(breakInputNode)
|
|
||||||
else:
|
|
||||||
inkex.errormsg('Selection does not contain any paths to work with. Maybe you need to convert objects to paths before.')
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
inkex.errormsg('Please select some paths first.')
|
inkex.errormsg('Please select some paths first.')
|
||||||
return
|
return
|
||||||
|
Reference in New Issue
Block a user