Patched Create Links extension

This commit is contained in:
Mario Voigt 2021-04-14 11:43:30 +02:00
parent 965f5dbb0a
commit 3fb3a020f8
2 changed files with 116 additions and 92 deletions

View File

@ -14,9 +14,10 @@
<option value="custom_dasharray">Custom dash pattern</option> <option value="custom_dasharray">Custom dash pattern</option>
<option value="entered_values">Render by unit and link settings</option> <option value="entered_values">Render by unit and link settings</option>
</param> </param>
<param name="unit" type="optiongroup" appearance="combo" gui-text="Units"> <param name="creationunit" type="optiongroup" appearance="combo" gui-text="Creation Units">
<option value="mm">mm</option> <option value="mm">mm</option>
<option value="cm">cm</option> <option value="cm">cm</option>
<option value="m">m</option>
<option value="in">in</option> <option value="in">in</option>
<option value="pt">pt</option> <option value="pt">pt</option>
<option value="px">px</option> <option value="px">px</option>
@ -26,20 +27,30 @@
<label appearance="header">Creation: Link Settings</label> <label appearance="header">Creation: Link Settings</label>
<param name="link_count" type="int" min="1" max="9999" gui-text="Link count">1</param> <param name="link_count" type="int" min="1" max="9999" gui-text="Link count">1</param>
<param name="length_link" type="float" min="0.000" max="9999.000" precision="3" gui-text="Link length (the length of the gap)">1.000</param> <param name="length_link" type="float" min="0.000" max="9999.000" precision="3" gui-text="Link length (the length of the gap)">1.000</param>
<param name="link_multiplicator" type="int" min="0" max="9999" gui-text="Link multiplicator (experimental)" gui-description="If set, we create a set of multiple gaps of same size next to the main gap">0</param> <param name="link_multiplicator" type="int" min="0" max="9999" gui-text="Link multiplicator" gui-description="If set, we create a set of multiple gaps of same size next to the main gap">0</param>
<param name="link_offset" type="float" min="-9999.000.000" max="9999.000" precision="3" gui-text="Link offset (+/-)">0.000</param> <param name="link_offset" type="float" min="-9999.000.000" max="9999.000" precision="3" gui-text="Link offset (+/-)">0.000</param>
<label appearance="header">Creation: Custom Dash Pattern Settings</label> <label appearance="header">Creation: Custom Dash Pattern Settings</label>
<param name="custom_dasharray_value" type="string" gui-text="Dash pattern" gui-description="A list of separated lengths that specify the lengths of alternating dashes and gaps. Input only accepts numbers. It ignores percentages or other characters.">10 5</param> <param name="custom_dasharray_value" type="string" gui-text="Dash pattern" gui-description="A list of separated lengths that specify the lengths of alternating dashes and gaps. Input only accepts numbers. It ignores percentages or other characters.">10 5.5 2.0 2.0</param>
<label appearance="header">Other Settings</label> <label appearance="header">Other Settings</label>
<param name="length_filter" type="bool" gui-text="Enable path length filtering (uses always document's unit)">false</param> <param name="length_filter" type="bool" gui-text="Enable path length filtering">false</param>
<param name="length_filter_value" type="float" min="0.000" max="9999.000" precision="3" gui-text="Paths with length more than">0.000</param> <param name="length_filter_value" type="float" min="0.000" max="9999.000" precision="3" gui-text="Paths with length more than">0.000</param>
<param name="length_filter_unit" type="optiongroup" appearance="combo" gui-text="Length filter unit">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="m">m</option>
<option value="in">in</option>
<option value="pt">pt</option>
<option value="px">px</option>
<option value="pc">pc</option>
</param>
<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="breakapart" type="bool" gui-text="Break apart output paths" gui-description="Performs CTRL + SHIFT + K to break the new output path into it's parts">false</param> <param name="no_convert" type="bool" gui-text="Do not create output path(s) (cosmetic style only)">false</param>
<param name="show_info" type="bool" gui-text="Print some length and pattern 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="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>
</page> </page>
<page name="about" gui-text="About"> <page name="about" gui-text="About">
<label appearance="header">Create Links</label> <label appearance="header">Create Links</label>
<label>Stadtfabrikanten e.V. (2021)</label> <label>by Mario Voigt / Stadtfabrikanten e.V. (2021)</label>
<spacer/> <spacer/>
<label>This piece of software is part of the MightyScape for InkScape 1.0/1.1dev Extension Collection.</label> <label>This piece of software is part of the MightyScape for InkScape 1.0/1.1dev Extension Collection.</label>
<label>You found a bug or got some fresh code? Just report to mario.voigt@stadtfabrikanten.org. Thanks!</label> <label>You found a bug or got some fresh code? Just report to mario.voigt@stadtfabrikanten.org. Thanks!</label>

View File

@ -43,8 +43,8 @@ class LinksCreator(inkex.EffectExtension):
super(LinksCreator, self).__init__() super(LinksCreator, self).__init__()
self.arg_parser.add_argument("--main_tabs") self.arg_parser.add_argument("--main_tabs")
self.arg_parser.add_argument("--path_types", default="closed_paths", help="Apply for closed paths, open paths or both") self.arg_parser.add_argument("--path_types", default="closed_paths", help="Apply for closed paths, open paths or both")
self.arg_parser.add_argument("--creationunit", default="mm", help="Creation Units")
self.arg_parser.add_argument("--creationtype", default="entered_values", help="Creation") self.arg_parser.add_argument("--creationtype", default="entered_values", help="Creation")
self.arg_parser.add_argument("--unit", default="mm", help="Units")
self.arg_parser.add_argument("--link_count", type=int, default=1, help="Link count") self.arg_parser.add_argument("--link_count", type=int, default=1, help="Link count")
self.arg_parser.add_argument("--length_link", type=float, default=1.000, help="Link length") self.arg_parser.add_argument("--length_link", type=float, default=1.000, help="Link length")
self.arg_parser.add_argument("--link_multiplicator", type=int, default=1, help="If set, we create a set of multiple gaps of same size next to the main gap") self.arg_parser.add_argument("--link_multiplicator", type=int, default=1, help="If set, we create a set of multiple gaps of same size next to the main gap")
@ -52,7 +52,9 @@ class LinksCreator(inkex.EffectExtension):
self.arg_parser.add_argument("--custom_dasharray_value", default="", help="A list of separated lengths that specify the lengths of alternating dashes and gaps. Input only accepts numbers. It ignores percentages or other characters.") self.arg_parser.add_argument("--custom_dasharray_value", default="", help="A list of separated lengths that specify the lengths of alternating dashes and gaps. Input only accepts numbers. It ignores percentages or other characters.")
self.arg_parser.add_argument("--length_filter", type=inkex.Boolean, default=False, help="Enable path length filtering") self.arg_parser.add_argument("--length_filter", type=inkex.Boolean, default=False, help="Enable path length filtering")
self.arg_parser.add_argument("--length_filter_value", type=float, default=0.000, help="Paths with length more than") self.arg_parser.add_argument("--length_filter_value", type=float, default=0.000, help="Paths with length more than")
self.arg_parser.add_argument("--length_filter_unit", default="mm", help="Length filter unit")
self.arg_parser.add_argument("--keep_selected", type=inkex.Boolean, default=False, help="Keep selected elements") self.arg_parser.add_argument("--keep_selected", type=inkex.Boolean, default=False, help="Keep selected elements")
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")
@ -106,41 +108,44 @@ class LinksCreator(inkex.EffectExtension):
csp = node.path.transform(node.composed_transform()).to_superpath() csp = node.path.transform(node.composed_transform()).to_superpath()
slengths, stotal = csplength(csp) #get segment lengths and total length of path in document's internal unit slengths, stotal = csplength(csp) #get segment lengths and total length of path in document's internal unit
if self.options.unit == "percent":
length_link = (self.options.length_link / 100.0) * stotal
else:
length_link = self.svg.unittouu(str(self.options.length_link) + self.options.unit)
if self.options.length_filter is True: if self.options.length_filter is True:
if stotal < self.options.length_filter_value: 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, stotal)) 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))
return #skip this loop iteration return #skip this loop iteration
''' if self.options.creationunit == "percent":
<dasharray> length_link = (self.options.length_link / 100.0) * stotal
A list of comma and/or white space separated <length>s and <percentage>s that specify the lengths of alternating dashes and gaps. else:
If an odd number of values is provided, then the list of values is repeated to yield an even number of values. Thus, 5,3,2 is equivalent to 5,3,2,5,3,2. length_link = self.svg.unittouu(str(self.options.length_link) + self.options.creationunit)
If we want three gaps in a path with length of 168.71 mm and a gap length of 2 mm we set the stroke-dasharray to: dashes = [] #central dashes array
50.236 2.0 because 3 * 50.236 mm + 3 * 2.0 mm = 168.71 mm dashes.append(((stotal - length_link * self.options.link_count) / self.options.link_count) - 2 * length_link * (self.options.link_multiplicator))
examples having a circle with a circumference of length = 100:
- the array "20 5" will create 4 dashes with length = 20 and 4 gaps with length = 5 (20 + 5 + 20 + 5 + 20 + 5 + 20 + 5 = 100)
- the array "5 20" will create 4 dashes with length = 5 and 4 gaps with length = 20 (5 + 20 + 5 + 20 + 5 + 20 + 5 + 20 = 100)
- the array "5 15" will create 5 dashes with length = 5 and 5 gaps with length = 15 (5 + 15 + 5 + 15 + 5 + 15 + 5 + 15 + 5 + 15 = 100)
- the array "5 14" will create 6 dashes with length = 5 and 5 gaps with length = 15 (5 + 14 + 5 + 14 + 5 + 14 + 5 + 14 + 5 + 14 + 5 = 100) - the first dash will connect to the last dash fluently
in the examples above we always match the full length. But we do not always match it.
'''
dashes = []
dashes.append((stotal - length_link * self.options.link_count) / self.options.link_count)
dashes.append(length_link) dashes.append(length_link)
for i in range(0, self.options.link_multiplicator): for i in range(0, self.options.link_multiplicator):
dashes.append(length_link) #stroke (=gap) dashes.append(length_link) #stroke (=gap)
dashes.append(length_link) #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 == "use_existing":
stroke_dashoffset = 0
style = node.style
if 'stroke-dashoffset' in style:
stroke_dashoffset = style['stroke-dashoffset']
try:
floats = [float(dash) for dash in re.findall(r"[-+]?\d*\.\d+|\d+", style['stroke-dasharray'])]
if len(floats) > 0:
dashes = floats #overwrite previously calculated values with custom input
else:
raise ValueError
except:
inkex.errormsg("no dash style to continue with.")
return
if self.options.creationtype == "custom_dasharray": if self.options.creationtype == "custom_dasharray":
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)]
@ -149,28 +154,15 @@ class LinksCreator(inkex.EffectExtension):
else: else:
raise ValueError raise ValueError
except: except:
inkex.errormsg("Error in custom dasharray string (might be empty or does not contain any numbers). Using regular input instead ...") inkex.errormsg("Error in custom dasharray string (might be empty or does not contain any numbers).")
return
stroke_dasharray = ' '.join(format(dash, "1.3f") for dash in dashes) stroke_dasharray = ' '.join(format(dash, "1.3f") for dash in dashes)
if self.options.unit == "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.unit) stroke_dashoffset = self.svg.unittouu(str(self.options.link_offset) + self.options.creationunit)
if self.options.show_info is True:
inkex.utils.debug("node " + node.get('id'))
if self.options.unit == "percent":
inkex.utils.debug("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.utils.debug("(calculated) gap length: {:1.3f} %".format(length_link))
else:
inkex.utils.debug("total path length = {:1.3f} {} ({:1.3f} {})".format(self.svg.uutounit(stotal, self.options.unit), self.options.unit, 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.unit), self.options.unit))
inkex.utils.debug("(calculated) gap length: {:1.3f} {}".format(length_link, self.options.unit))
inkex.utils.debug("total gaps = {}".format(self.options.link_count))
inkex.utils.debug("(calculated) dash/gap pattern: {} ({})".format(stroke_dasharray, self.svg.unit))
inkex.utils.debug("--------------------------------------------------------------------------------------------------")
# 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
@ -203,49 +195,70 @@ class LinksCreator(inkex.EffectExtension):
style = 'fill:none;stroke:#000000;stroke-width:1px;stroke-dasharray:{};stroke-dashoffset:{};'.format(stroke_dasharray, stroke_dashoffset) style = 'fill:none;stroke:#000000;stroke-width:1px;stroke-dasharray:{};stroke-dashoffset:{};'.format(stroke_dasharray, stroke_dashoffset)
node.set('style', style) node.set('style', style)
style = node.style #get the style again, but this time as style class # Print some info about values
if self.options.show_info is True:
inkex.utils.debug("node " + node.get('id'))
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.utils.debug("(calculated) offset: {:1.3f} %".format(stroke_dashoffset))
if self.options.creationtype == "entered_values":
inkex.utils.debug("(calculated) gap length: {:1.3f} %".format(length_link))
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.utils.debug("(calculated) offset: {:1.3f} {}".format(self.svg.uutounit(stroke_dashoffset, self.options.creationunit), self.options.creationunit))
if self.options.creationtype == "entered_values":
inkex.utils.debug("(calculated) gap length: {:1.3f} {}".format(length_link, self.options.creationunit))
if self.options.creationtype == "entered_values":
inkex.utils.debug("total gaps = {}".format(self.options.link_count))
inkex.utils.debug("(calculated) dash/gap pattern: {} ({})".format(stroke_dasharray, self.svg.unit))
inkex.utils.debug("--------------------------------------------------------------------------------------------------")
new = [] # Conversion step (split cosmetic path into real segments)
for sub in node.path.to_superpath(): if self.options.no_convert is False:
idash = 0 style = node.style #get the style again, but this time as style class
dash = dashes[0]
length = float(stroke_dashoffset) new = []
while dash < length: for sub in node.path.to_superpath():
length = length - dash idash = 0
idash = (idash + 1) % len(dashes) dash = dashes[0]
dash = dashes[idash] length = float(stroke_dashoffset)
new.append([sub[0][:]])
i = 1
while i < len(sub):
dash = dash - length
length = bezier.cspseglength(new[-1][-1], sub[i])
while dash < length: while dash < length:
new[-1][-1], nxt, sub[i] = \
bezier.cspbezsplitatlength(new[-1][-1], sub[i], dash/length)
if idash % 2: # create a gap
new.append([nxt[:]])
else: # splice the curve
new[-1].append(nxt[:])
length = length - dash length = length - dash
idash = (idash + 1) % len(dashes) idash = (idash + 1) % len(dashes)
dash = dashes[idash] dash = dashes[idash]
if idash % 2: new.append([sub[0][:]])
new.append([sub[i]]) i = 1
else: while i < len(sub):
new[-1].append(sub[i]) dash = dash - length
i += 1 length = bezier.cspseglength(new[-1][-1], sub[i])
style.pop('stroke-dasharray') while dash < length:
node.pop('sodipodi:type') new[-1][-1], nxt, sub[i] = \
node.path = CubicSuperPath(new) bezier.cspbezsplitatlength(new[-1][-1], sub[i], dash/length)
node.style = style if idash % 2: # create a gap
new.append([nxt[:]])
else: # splice the curve
new[-1].append(nxt[:])
length = length - dash
idash = (idash + 1) % len(dashes)
dash = dashes[idash]
if idash % 2:
new.append([sub[i]])
else:
new[-1].append(sub[i])
i += 1
style.pop('stroke-dasharray')
node.pop('sodipodi:type')
node.path = CubicSuperPath(new)
node.style = style
if self.options.breakapart is True: # break apart the combined path to have multiple elements
breakOutputNodes = self.breakContours(node) if self.options.breakapart is True:
breakApartGroup = nodeParent.add(inkex.Group()) breakOutputNodes = self.breakContours(node)
for breakOutputNode in breakOutputNodes: breakApartGroup = nodeParent.add(inkex.Group())
breakApartGroup.append(breakOutputNode) for breakOutputNode in breakOutputNodes:
#inkex.utils.debug(replacedNode.get('id')) breakApartGroup.append(breakOutputNode)
#self.svg.selection.set(replacedNode.get('id')) #update selection to split paths segments (does not work, so commented out) #inkex.utils.debug(replacedNode.get('id'))
#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() pathNodes = self.svg.selection.filter(PathElement).values()