From 3fb3a020f8a3958ccc7ae56147a8ec97e9663516 Mon Sep 17 00:00:00 2001 From: Mario Voigt Date: Wed, 14 Apr 2021 11:43:30 +0200 Subject: [PATCH] Patched Create Links extension --- extensions/fablabchemnitz/create_links.inx | 25 ++- extensions/fablabchemnitz/create_links.py | 183 +++++++++++---------- 2 files changed, 116 insertions(+), 92 deletions(-) diff --git a/extensions/fablabchemnitz/create_links.inx b/extensions/fablabchemnitz/create_links.inx index 10cb0cdc..06188237 100644 --- a/extensions/fablabchemnitz/create_links.inx +++ b/extensions/fablabchemnitz/create_links.inx @@ -14,9 +14,10 @@ - + + @@ -26,20 +27,30 @@ 1 1.000 - 0 + 0 0.000 - 10 5 + 10 5.5 2.0 2.0 - false + false 0.000 + + + + + + + + + false - false - false + false + false + false - + diff --git a/extensions/fablabchemnitz/create_links.py b/extensions/fablabchemnitz/create_links.py index 99d8e09c..acbf1e0b 100644 --- a/extensions/fablabchemnitz/create_links.py +++ b/extensions/fablabchemnitz/create_links.py @@ -43,8 +43,8 @@ class LinksCreator(inkex.EffectExtension): super(LinksCreator, self).__init__() 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("--creationunit", default="mm", help="Creation Units") 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("--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") @@ -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("--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_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("--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("--show_info", type=inkex.Boolean, default=False, help="Print some length and pattern information") @@ -106,40 +108,43 @@ class LinksCreator(inkex.EffectExtension): 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 - if self.options.unit == "percent": + if self.options.length_filter is True: + if stotal < self.svg.unittouu(str(self.options.length_filter_value) + self.options.length_filter_unit): + 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)) + return #skip this loop iteration + + if self.options.creationunit == "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 stotal < self.options.length_filter_value: - 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)) - return #skip this loop iteration - - ''' - - A list of comma and/or white space separated s and s that specify the lengths of alternating dashes and gaps. - 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: - 50.236 2.0 → because 3 * 50.236 mm + 3 * 2.0 mm = 168.71 mm - - 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 = [] #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 == "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": try: @@ -149,28 +154,15 @@ class LinksCreator(inkex.EffectExtension): else: raise ValueError 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) - if self.options.unit == "percent": + if self.options.creationunit == "percent": stroke_dashoffset = self.options.link_offset / 100.0 else: - stroke_dashoffset = self.svg.unittouu(str(self.options.link_offset) + self.options.unit) - - 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("--------------------------------------------------------------------------------------------------") + stroke_dashoffset = self.svg.unittouu(str(self.options.link_offset) + self.options.creationunit) # check if the node has a style attribute. If not we create a blank one with a black stroke and without fill style = None @@ -202,50 +194,71 @@ class LinksCreator(inkex.EffectExtension): else: style = 'fill:none;stroke:#000000;stroke-width:1px;stroke-dasharray:{};stroke-dashoffset:{};'.format(stroke_dasharray, stroke_dashoffset) node.set('style', style) - - style = node.style #get the style again, but this time as style class - - new = [] - for sub in node.path.to_superpath(): - idash = 0 - dash = dashes[0] - length = float(stroke_dashoffset) - while dash < length: - length = length - dash - idash = (idash + 1) % len(dashes) - dash = dashes[idash] - new.append([sub[0][:]]) - i = 1 - while i < len(sub): - dash = dash - length - length = bezier.cspseglength(new[-1][-1], sub[i]) + + # 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("--------------------------------------------------------------------------------------------------") + + # Conversion step (split cosmetic path into real segments) + if self.options.no_convert is False: + style = node.style #get the style again, but this time as style class + + new = [] + for sub in node.path.to_superpath(): + idash = 0 + dash = dashes[0] + length = float(stroke_dashoffset) 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 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: - breakOutputNodes = self.breakContours(node) - breakApartGroup = nodeParent.add(inkex.Group()) - for breakOutputNode in breakOutputNodes: - breakApartGroup.append(breakOutputNode) - #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) + new.append([sub[0][:]]) + i = 1 + while i < len(sub): + dash = dash - length + length = bezier.cspseglength(new[-1][-1], sub[i]) + 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 + 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 + + # break apart the combined path to have multiple elements + if self.options.breakapart is True: + breakOutputNodes = self.breakContours(node) + breakApartGroup = nodeParent.add(inkex.Group()) + for breakOutputNode in breakOutputNodes: + breakApartGroup.append(breakOutputNode) + #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: pathNodes = self.svg.selection.filter(PathElement).values()