def effect(self): # get number of digits prec = int(self.options.precision) scale = self.svg.unittouu('1px') # convert to document units self.options.offset *= scale factor = 1.0 if self.svg.get('viewBox'): factor = self.svg.scale / self.svg.unittouu('1px') self.options.fontsize /= factor factor *= scale / self.svg.unittouu('1' + self.options.unit) # loop over all selected paths for node in self.svg.selection.filter(inkex.PathElement): csp = node.path.transform(node.composed_transform()).to_superpath() if self.options.mtype == "length": slengths, stotal = csplength(csp) self.group = node.getparent().add(TextElement()) elif self.options.mtype == "area": stotal = abs(csparea(csp) * factor * self.options.scale) self.group = node.getparent().add(TextElement()) else: try: xc, yc = cspcofm(csp) except ValueError as err: raise inkex.AbortExtension(str(err)) self.group = node.getparent().add(inkex.PathElement()) self.group.set('id', 'MassCenter_' + node.get('id')) self.add_cross(self.group, xc, yc, scale) continue # Format the length as string val = round(stotal * factor * self.options.scale, prec) self.options.method(node, str(val))
def effect(self): self.options.threshold = self.svg.unittouu( str(self.options.threshold) + self.svg.unit) unit_factor = 1.0 / self.svg.uutounit(1.0, self.options.unit) if self.options.threshold == 0: return for element in self.document.xpath("//svg:path", namespaces=inkex.NSS): try: csp = element.path.transform( element.composed_transform()).to_superpath() if self.options.measure == "area": area = -csparea( csp ) #is returned as negative value. we need to invert with - if area < (self.options.threshold * (unit_factor * unit_factor)): element.delete() elif self.options.measure == "length": slengths, stotal = csplength( csp ) #get segment lengths and total length of path in document's internal unit if stotal < (self.options.threshold * unit_factor): element.delete() except Exception as e: pass
def getCubicLength(csp): if(CommonDefs.inkVer == 1.0): return bezier.csplength(csp)[1] else: parts = getPartsFromCubicSuper(cspath) curveLen = 0 for i, part in enumerate(parts): for j, seg in enumerate(part): curveLen += bezmisc.bezierlengthSimpson((seg[0], seg[1], seg[2], seg[3]), \ tolerance = tolerance) return curveLen
def createLinks(node): nodeParent = node.getparent() pathIsClosed = False path = node.path.to_arrays() #to_arrays() is deprecated. How to make more modern? if path[-1][0] == 'Z' or path[0][1] == path[-1][1]: #if first is last point the path is also closed. The "Z" command is not required pathIsClosed = True if self.options.path_types == 'open_paths' and pathIsClosed is True: return #skip this loop iteration elif self.options.path_types == 'closed_paths' and pathIsClosed is False: return #skip this loop iteration elif self.options.path_types == 'both': pass # if keeping is enabled we make of copy of the current node and insert it while modifying the original ones. We could also delete the original and modify a copy... if self.options.keep_selected is True: parent = node.getparent() idx = parent.index(node) copynode = copy.copy(node) parent.insert(idx, copynode) # we measure the length of the path to calculate the required dash configuration 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.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: self.msg("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.creationunit) dashes = [] #central dashes array if self.options.creationtype == "entered_values": dash_length = ((stotal - length_link * self.options.link_count) / self.options.link_count) - 2 * length_link * self.options.link_multiplicator dashes.append(dash_length) 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.switch_pattern is True: dashes = dashes[::-1] #reverse the array #validate dashes. May not be negative (dash or gap cannot be longer than the path itself). Otherwise Inkscape will freeze forever. Reason: rendering issue if any(dash <= 0.0 for dash in dashes) == True: if self.options.show_info is True: self.msg("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 '%'") return False if self.options.skip_errors is True else exit(1) 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.creationunit) if self.options.switch_pattern is True: stroke_dashoffset = stroke_dashoffset + ((self.options.link_multiplicator * 2) + 1) * length_link if self.options.creationtype == "use_existing": if self.options.no_convert is True: if self.options.show_info is True: self.msg("node " + node.get('id') + ": Nothing to do. Please select another creation method or disable cosmetic style output paths.") return False if self.options.skip_errors is True else exit(1) 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'])] #allow only positive values if len(floats) > 0: dashes = floats #overwrite previously calculated values with custom input else: raise ValueError except: if self.options.show_info is True: self.msg("node " + node.get('id') + ": No dash style to continue with.") return False if self.options.skip_errors is True else exit(1) if self.options.creationtype == "custom_dashpattern": stroke_dashoffset = self.options.custom_dashoffset_value try: 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: dashes = floats #overwrite previously calculated values with custom input else: raise ValueError except: if self.options.show_info is True: self.msg("node " + node.get('id') + ": Error in custom dasharray string (might be empty or does not contain any numbers).") 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) # check if the node has a style attribute. If not we create a blank one with a black stroke and without fill style = None default_fill = 'none' default_stroke_width = '1px' default_stroke = '#000000' if node.attrib.has_key('style'): style = node.get('style') if style.endswith(';') is False: style += ';' # if has style attribute and dasharray and/or dashoffset are present we modify it accordingly declarations = style.split(';') # parse the style content and check what we need to adjust for i, decl in enumerate(declarations): parts = decl.split(':', 2) if len(parts) == 2: (prop, val) = parts prop = prop.strip().lower() #if prop == 'fill': # declarations[i] = prop + ':{}'.format(default_fill) #if prop == 'stroke': # declarations[i] = prop + ':{}'.format(default_stroke) #if prop == 'stroke-width': # declarations[i] = prop + ':{}'.format(default_stroke_width) if prop == 'stroke-dasharray': #comma separated list of one or more float values declarations[i] = prop + ':{}'.format(stroke_dasharray) if prop == 'stroke-dashoffset': declarations[i] = prop + ':{}'.format(stroke_dashoffset) node.set('style', ';'.join(declarations)) #apply new style to node #if has style attribute but the style attribute does not contain fill, stroke, stroke-width, stroke-dasharray or stroke-dashoffset yet style = node.style if re.search('fill:(.*?)(;|$)', str(style)) is None: style += 'fill:{};'.format(default_fill) 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: style += 'stroke-dasharray:{};'.format(stroke_dasharray) if not 'stroke-dashoffset' in style: style += 'stroke-dashoffset:{};'.format(stroke_dashoffset) node.set('style', style) else: style = 'fill:{};stroke:{};stroke-width:{};stroke-dasharray:{};stroke-dashoffset:{};'.format(default_fill, default_stroke, default_stroke_width, stroke_dasharray, stroke_dashoffset) node.set('style', style) # Print some info about values if self.options.show_info is True: self.msg("node " + node.get('id') + ":") if self.options.creationunit == "percent": self.msg(" * total path length = {:1.3f} {}".format(stotal, self.svg.unit)) #show length, converted in selected unit self.msg(" * (calculated) offset: {:1.3f} %".format(stroke_dashoffset)) if self.options.creationtype == "entered_values": self.msg(" * (calculated) gap length: {:1.3f} %".format(length_link)) else: self.msg(" * 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 self.msg(" * (calculated) offset: {:1.3f} {}".format(self.svg.uutounit(stroke_dashoffset, self.options.creationunit), self.options.creationunit)) if self.options.creationtype == "entered_values": self.msg(" * (calculated) gap length: {:1.3f} {}".format(length_link, self.options.creationunit)) if self.options.creationtype == "entered_values": self.msg(" * total gaps = {}".format(self.options.link_count)) self.msg(" * (calculated) dash/gap pattern: {} ({})".format(stroke_dasharray, self.svg.unit)) # 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: 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]) 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') csp = CubicSuperPath(new) node.path = CubicSuperPath(new) node.style = style # break apart the combined path to have multiple elements if self.options.breakapart is True: breakOutputNodes = None breakOutputNodes = self.breakContours(node, breakOutputNodes) breakApartGroup = nodeParent.add(inkex.Group()) for breakOutputNode in breakOutputNodes: breakApartGroup.append(breakOutputNode) #self.msg(replacedNode.get('id')) #self.svg.selection.set(replacedNode.get('id')) #update selection to split paths segments (does not work, so commented out) #cleanup useless points p = breakOutputNode.path commandsCoords = p.to_arrays() # "m 45.250809,91.692739" - this path contains onyl one command - a single point if len(commandsCoords) == 1: breakOutputNode.delete() # "m 45.250809,91.692739 z" - this path contains two commands, but only one coordinate. # It's a single point, the path is closed by a Z command elif len(commandsCoords) == 2 and commandsCoords[0][1] == commandsCoords[1][1]: breakOutputNode.delete() # "m 45.250809,91.692739 l 45.250809,91.692739" - this path contains two commands, # but the first and second coordinate are the same. It will render als point elif len(commandsCoords) == 2 and commandsCoords[-1][0] == 'Z': breakOutputNode.delete() # "m 45.250809,91.692739 l 45.250809,91.692739 z" - this path contains three commands, # but the first and second coordinate are the same. It will render als point, the path is closed by a Z command elif len(commandsCoords) == 3 and commandsCoords[0][1] == commandsCoords[1][1] and commandsCoords[2][1] == 'Z': breakOutputNode.delete()
def effect(self): exponent = self.options.exponent if exponent >= 0: exponent += 1.0 else: exponent = 1.0 / (1.0 - exponent) steps = [1.0 / (self.options.steps + 1.0)] for i in range(self.options.steps - 1): steps.append(steps[0] + steps[-1]) steps = [step**exponent for step in steps] if self.options.zsort: # work around selection order swapping with Live Preview objects = self.svg.get_z_selected() else: # use selection order (default) objects = self.svg.selected objects = [ node for node in objects.values() if isinstance(node, inkex.PathElement) ] # prevents modification of original objects objects = copy.deepcopy(objects) for node in objects: node.apply_transform() objectpairs = pairwise(objects, start=False) for (elem1, elem2) in objectpairs: start = elem1.path.to_superpath() end = elem2.path.to_superpath() sst = copy.deepcopy(elem1.style) est = copy.deepcopy(elem2.style) basestyle = copy.deepcopy(sst) if 'stroke-width' in basestyle: basestyle['stroke-width'] = sst.interpolate_prop( est, 0, 'stroke-width') # prepare for experimental style tweening if self.options.style: styledefaults = Style({ 'opacity': 1.0, 'stroke-opacity': 1.0, 'fill-opacity': 1.0, 'stroke-width': 1.0, 'stroke': 'none', 'fill': 'none' }) for key in styledefaults: sst.setdefault(key, styledefaults[key]) est.setdefault(key, styledefaults[key]) isnotplain = lambda x: not (x == 'none' or x[:1] == '#') isgradient = lambda x: x.startswith('url(#') if isgradient(sst['stroke']) and isgradient(est['stroke']): strokestyle = 'gradient' elif isnotplain(sst['stroke']) or isnotplain( est['stroke']) or (sst['stroke'] == 'none' and est['stroke'] == 'none'): strokestyle = 'notplain' else: strokestyle = 'color' if isgradient(sst['fill']) and isgradient(est['fill']): fillstyle = 'gradient' elif isnotplain(sst['fill']) or isnotplain( est['fill']) or (sst['fill'] == 'none' and est['fill'] == 'none'): fillstyle = 'notplain' else: fillstyle = 'color' if strokestyle == 'color': if sst['stroke'] == 'none': sst['stroke-width'] = '0.0' sst['stroke-opacity'] = '0.0' sst['stroke'] = est['stroke'] elif est['stroke'] == 'none': est['stroke-width'] = '0.0' est['stroke-opacity'] = '0.0' est['stroke'] = sst['stroke'] if fillstyle == 'color': if sst['fill'] == 'none': sst['fill-opacity'] = '0.0' sst['fill'] = est['fill'] elif est['fill'] == 'none': est['fill-opacity'] = '0.0' est['fill'] = sst['fill'] if self.options.method == 2: # subdivide both paths into segments of relatively equal lengths slengths, stotal = csplength(start) elengths, etotal = csplength(end) lengths = {} t = 0 for sp in slengths: for l in sp: t += l / stotal lengths.setdefault(t, 0) lengths[t] += 1 t = 0 for sp in elengths: for l in sp: t += l / etotal lengths.setdefault(t, 0) lengths[t] += -1 sadd = [k for (k, v) in lengths.items() if v < 0] sadd.sort() eadd = [k for (k, v) in lengths.items() if v > 0] eadd.sort() t = 0 s = [[]] for sp in slengths: if not start[0]: s.append(start.pop(0)) s[-1].append(start[0].pop(0)) for l in sp: pt = t t += l / stotal if sadd and t > sadd[0]: while sadd and sadd[0] < t: nt = (sadd[0] - pt) / (t - pt) bezes = cspbezsplitatlength( s[-1][-1][:], start[0][0][:], nt) s[-1][-1:] = bezes[:2] start[0][0] = bezes[2] pt = sadd.pop(0) s[-1].append(start[0].pop(0)) t = 0 e = [[]] for sp in elengths: if not end[0]: e.append(end.pop(0)) e[-1].append(end[0].pop(0)) for l in sp: pt = t t += l / etotal if eadd and t > eadd[0]: while eadd and eadd[0] < t: nt = (eadd[0] - pt) / (t - pt) bezes = cspbezsplitatlength( e[-1][-1][:], end[0][0][:], nt) e[-1][-1:] = bezes[:2] end[0][0] = bezes[2] pt = eadd.pop(0) e[-1].append(end[0].pop(0)) start = s[:] end = e[:] else: # which path has fewer segments? lengthdiff = len(start) - len(end) # swap shortest first if lengthdiff > 0: start, end = end, start # subdivide the shorter path for x in range(abs(lengthdiff)): maxlen = 0 subpath = 0 segment = 0 for y in range(len(start)): for z in range(1, len(start[y])): leng = bezlenapprx(start[y][z - 1], start[y][z]) if leng > maxlen: maxlen = leng subpath = y segment = z sp1, sp2 = start[subpath][segment - 1:segment + 1] start[subpath][segment - 1:segment + 1] = cspbezsplit( sp1, sp2) # if swapped, swap them back if lengthdiff > 0: start, end = end, start # break paths so that corresponding subpaths have an equal number of segments s = [[]] e = [[]] while start and end: if start[0] and end[0]: s[-1].append(start[0].pop(0)) e[-1].append(end[0].pop(0)) elif end[0]: s.append(start.pop(0)) e[-1].append(end[0][0]) e.append([end[0].pop(0)]) elif start[0]: e.append(end.pop(0)) s[-1].append(start[0][0]) s.append([start[0].pop(0)]) else: s.append(start.pop(0)) e.append(end.pop(0)) if self.options.dup: steps = [0] + steps + [1] # create an interpolated path for each interval group = self.svg.get_current_layer().add(inkex.Group()) for time in steps: interp = [] # process subpaths for ssp, esp in zip(s, e): if not (ssp or esp): break interp.append([]) # process superpoints for sp, ep in zip(ssp, esp): if not (sp or ep): break interp[-1].append([]) # process points for p1, p2 in zip(sp, ep): if not (sp or ep): break interp[-1][-1].append(interppoints(p1, p2, time)) # remove final subpath if empty. if not interp[-1]: del interp[-1] # basic style interpolation if self.options.style: basestyle.update(sst.interpolate(est, time)) for prop in ['stroke', 'fill']: if isgradient(sst[prop]) and isgradient(est[prop]): gradid1 = sst[prop][4:-1] gradid2 = est[prop][4:-1] grad1 = self.svg.getElementById(gradid1) grad2 = self.svg.getElementById(gradid2) newgrad = grad1.interpolate(grad2, time) stops, orientation = newgrad.stops_and_orientation( ) self.svg.defs.add(orientation) basestyle[prop] = orientation.get_id(as_url=2) if len(stops): self.svg.defs.add(stops, orientation) orientation.set('xlink:href', stops.get_id(as_url=1)) new = group.add(inkex.PathElement()) new.style = basestyle new.path = CubicSuperPath(interp)