示例#1
0
    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))
示例#2
0
    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
示例#4
0
        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()
示例#5
0
    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)