def effect(self): scale = self.options.scale scaleFrom = self.options.scaleFrom tolerance = 10**(-1 * self.options.precision) printOut = False selections = self.svg.selected pathNodes = self.document.xpath('//svg:path', namespaces=inkex.NSS) outStrs = [str(len(pathNodes))] paths = [(pathNode.get('id'), CubicSuperPath(pathNode.get('d'))) for pathNode in pathNodes] if (len(paths) > 1): srcPath = paths[-1][1] srclen = self.getLength(srcPath, tolerance) paths = paths[:len(paths) - 1] for key, cspath in paths: curveLen = self.getLength(cspath, tolerance) self.scaleCubicSuper(cspath, scaleFactor = scale * (srclen / curveLen), \ scaleFrom = scaleFrom) selections[key].set('d', CubicSuperPath(cspath)) else: inkex.errormsg( _("Please select at least two paths, with the path whose \ length is to be copied at the top. You may have to convert the shape \ to path with path->Object to Path."))
def getCubicSuperPath(d = None): if(CommonDefs.inkVer == 1.0): if(d == None): return CubicSuperPath([]) return CubicSuperPath(Path(d).to_superpath()) else: if(d == None): return [] return CubicSuperPath(simplepath.parsePath(d))
def effect(self): selections = self.svg.selected pathNodes = self.document.xpath('//svg:path', namespaces=inkex.NSS) paths = { p.get('id'): getPartsFromCubicSuper(CubicSuperPath(p.get('d'))) for p in pathNodes } #paths.keys() Order disturbed pathIds = [p.get('id') for p in pathNodes] if (len(paths) > 1): if (self.options.optimized): startPathId = pathIds[0] pathIds = getArrangedIds(paths, startPathId) newParts = [] firstElem = None for key in pathIds: parts = paths[key] # ~ parts = getPartsFromCubicSuper(cspath) start = parts[0][0][0] try: elem = self.svg.selected[key] if (len(newParts) == 0): newParts += parts[:] firstElem = elem else: if (vectCmpWithMargin(start, newParts[-1][-1][-1], margin=.01)): newParts[-1] += parts[0] else: newSeg = [ newParts[-1][-1][-1], newParts[-1][-1][-1], start, start ] newParts[-1].append(newSeg) newParts[-1] += parts[0] if (len(parts) > 1): newParts += parts[1:] parent = elem.getparent() idx = parent.index(elem) parent.remove(elem) except: pass #elem might come from group item - in this case we need to ignore it newElem = copy.copy(firstElem) oldId = firstElem.get('id') newElem.set('d', CubicSuperPath(getCubicSuperFromParts(newParts))) newElem.set('id', oldId + '_joined') parent.insert(idx, newElem)
def recursiveFuseTransform(self, node, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): transf = Transform(transf) * Transform(node.get("transform", None)) if 'transform' in node.attrib: del node.attrib['transform'] node = ApplyTransform.objectToPath(node) if 'd' in node.attrib: d = node.get('d') p = CubicSuperPath(d) p = Path(p).to_absolute().transform(transf, True) node.set('d', Path(CubicSuperPath(p).to_path())) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('polygon', 'svg'), inkex.addNS('polyline', 'svg') ]: points = node.get('points') points = points.strip().split(' ') for k, p in enumerate(points): if ',' in p: p = p.split(',') p = [float(p[0]), float(p[1])] applyTransformToPoint(transf, p) p = [str(p[0]), str(p[1])] p = ','.join(p) points[k] = p points = ' '.join(points) node.set('points', points) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('rect', 'svg'), inkex.addNS('text', 'svg'), inkex.addNS('image', 'svg'), inkex.addNS('use', 'svg'), inkex.addNS('circle', 'svg') ]: # node.set('transform', str(Transform(transf))) inkex.utils.errormsg("Shape %s not yet supported" % node.tag) else: # e.g. <g style="..."> self.scaleStrokeWidth(node, transf) for child in node.getchildren(): self.recursiveFuseTransform(child, transf)
def splitPath(inkex, node): dashes = [] try: # inkscape 1.0 style = dict(Style(node.get('style'))) except: # inkscape 0.9x style = simplestyle.parseStyle(node.get('style')) if 'stroke-dasharray' in style: if style['stroke-dasharray'].find(',') > 0: dashes = [ float(dash) for dash in style['stroke-dasharray'].split(',') if dash ] if dashes: try: # inkscape 1.0 p = CubicSuperPath(node.get('d')) except: # inkscape 0.9x p = cubicsuperpath.parsePath(node.get('d')) new = [] for sub in p: idash = 0 dash = dashes[0] length = 0 new.append([sub[0][:]]) i = 1 while i < len(sub): dash = dash - length length = cspseglength(new[-1][-1], sub[i]) while dash < length: new[-1][-1], next, sub[i] = cspbezsplitatlength( new[-1][-1], sub[i], dash / length) if idash % 2: # create a gap new.append([next[:]]) else: # splice the curve new[-1].append(next[:]) 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 try: # inkscape 1.0 node.set('d', CubicSuperPath(new)) except: # inkscape 0.9x node.set('d', cubicsuperpath.formatPath(new)) del style['stroke-dasharray'] try: # inkscape 1.0 node.set('style', Style(style)) except: # inkscape 0.9x node.set('style', simplestyle.formatStyle(style)) if node.get(inkex.addNS('type', 'sodipodi')): del node.attrib[inkex.addNS('type', 'sodipodi')]
def test_from_arrays(self): """SuperPath from arrays""" csp = CubicSuperPath([[ [[14, 173], [14, 173], (14, 173)], [(15, 171), (17, 168), (18, 168)], ], [ [(18, 167), (18, 167), [20, 165]], ((21, 164), [22, 162], (23, 162)), ]]) self.assertEqual( str(csp.to_path()), 'M 14 173 C 14 173 15 171 17 168 M 18 167 C 20 165 21 164 22 162')
def effect(self): unit_factor = 1.0 / self.svg.uutounit(1.0, self.options.unit) for id, node in self.svg.selected.items(): #get recent XY coordinates (top left corner of the bounding box) bbox = node.bounding_box() new_horiz_scale = self.options.expected_size * unit_factor / bbox.width new_vert_scale = self.options.expected_size * unit_factor / bbox.height if self.options.scale_type == "Horizontal": translation_matrix = [[new_horiz_scale, 0.0, 0.0], [0.0, 1.0, 0.0]] elif self.options.scale_type == "Vertical": translation_matrix = [[1.0, 0.0, 0.0], [0.0, new_vert_scale, 0.0]] else: #Uniform translation_matrix = [[new_horiz_scale, 0.0, 0.0], [0.0, new_vert_scale, 0.0]] node.transform = Transform(translation_matrix) * node.transform # now that the node moved we need to get the nodes XY coordinates again to fix them bbox_new = node.bounding_box() #inkex.utils.debug(cx) #inkex.utils.debug(cy) #inkex.utils.debug(cx_new) #inkex.utils.debug(cy_new) # We remove the transformation attribute from SVG XML tree in case it's regular path. In case the node is an object like arc,circle, ellipse or star we have the attribute "sodipodi:type". Then we do not play with it's path because the size transformation will not apply (this code block is ugly) if node.get('sodipodi:type') is None and 'd' in node.attrib: #inkex.utils.debug("it's a path!") d = node.get('d') p = CubicSuperPath(d) transf = Transform(node.get("transform", None)) if 'transform' in node.attrib: del node.attrib['transform'] p = Path(p).to_absolute().transform(transf, True) node.set('d', Path(CubicSuperPath(p).to_path())) #else: #inkex.utils.debug("it's an object!") #we perform second translation to reset the center of the path translation_matrix = [[ 1.0, 0.0, bbox.left - bbox_new.left + (bbox.width - bbox_new.width) / 2 ], [ 0.0, 1.0, bbox.top - bbox_new.top + (bbox.height - bbox_new.height) / 2 ]] node.transform = Transform(translation_matrix) * node.transform
def effect(self): path_strings = [] net_strings = ["M"] my_path = etree.Element(inkex.addNS('path', 'svg')) s = { 'stroke-width': self.options.s_width, 'stroke': '#000000', 'fill': 'none' } my_path.set('style', str(inkex.Style(s))) for id, node in self.svg.selected.items(): if node.tag == inkex.addNS('path', 'svg'): d = node.get('d') p = CubicSuperPath(Path(d)) for subpath in p: for i, csp in enumerate(subpath): path_strings.append("%f,%f" % (csp[1][0], csp[1][1])) node.set('d', str(Path(p))) while len(path_strings) > 0: net_strings.append(path_strings.pop(0)) if len(path_strings) > 0: net_strings.append(path_strings.pop()) my_path.set('d', " ".join(net_strings)) self.svg.get_current_layer().append(my_path)
def effect(self): if len(self.svg.selected) == 0: inkex.errormsg( _("Please select an object to perform the " + "exponential-distort transformation on.")) return for id, node in self.svg.selected.items(): type = node.get( "{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}type", "path") if node.tag != '{http://www.w3.org/2000/svg}path' or type != 'path': inkex.errormsg(node.tag + " is not a path. Type=" + type + ". Please use 'Path->Object to Path' first.") else: pts = CubicSuperPath(node.get('d')) bbox = self.computeBBox(pts) ## bbox (60.0, 160.0, 77.0, 197.0) ## pts [[[[60.0, 77.0], [60.0, 77.0], [60.0, 77.0]], [[60.0, 197.0], [60.0, 197.0], [60.0, 197.0]], [[70.0, 197.0], ... for p in pts: for pp in p: for ppp in pp: ppp[0] = self.x_exp_p_inplace(bbox, ppp[0]) node.set('d', str(pts))
def breakContours(self, node): #this does the same as "CTRL + SHIFT + K" if node.tag == inkex.addNS('path', 'svg'): parent = node.getparent() idx = parent.index(node) idSuffix = 0 raw = Path(node.get("d")).to_arrays() subPaths, prev = [], 0 for i in range( len(raw)): # Breaks compound paths into simple paths if raw[i][0] == 'M' and i != 0: subPaths.append(raw[prev:i]) prev = i subPaths.append(raw[prev:]) for subpath in subPaths: replacedNode = copy.copy(node) oldId = replacedNode.get('id') replacedNode.set('d', CubicSuperPath(subpath)) replacedNode.set('id', oldId + str(idSuffix).zfill(5)) parent.insert(idx, replacedNode) idSuffix += 1 self.replacedNodes.append(replacedNode) node.delete() for child in node: self.breakContours(child)
def path_round_it(self, node): if node.tag == inkex.addNS('path', 'svg'): d = node.get('d') p = CubicSuperPath(d) for subpath in p: for csp in subpath: delta = self.roundit(csp[1]) if self.options.along: csp[0][0] += delta[0] csp[0][1] += delta[1] csp[1][0] += delta[0] csp[1][1] += delta[1] if self.options.along: csp[2][0] += delta[0] csp[2][1] += delta[1] if self.options.ctrl: delta = self.roundit(csp[0]) csp[0][0] += delta[0] csp[0][1] += delta[1] delta = self.roundit(csp[2]) csp[2][0] += delta[0] csp[2][1] += delta[1] node.set('d', str(Path(p))) elif node.tag == inkex.addNS('g', 'svg'): for e in node: self.path_round_it(e)
def test_AZ(self): p = [ ['M', [0., 4.]], ['A', [3., 6., 0., 1, 1, 5., 4.]], ['Z', []], ] csp = CubicSuperPath(p) self.assertTrue(len(csp[0]) > 3)
def create_vert_blocks(self, group, gridx, style): path = lastpath = 0 blocks = [] count = 0 for node in gridx.iterchildren(): if node.tag == inkex.addNS('path','svg'): # which they ALL should because we just made them path = CubicSuperPath(node.get('d')) # turn it into a global C style SVG path #sys.stderr.write("count: %d\n"% count) if count == 0: # first one so use the right border spath = node.get('d') # work on string instead of cubicpath lastpoint = spath.split()[-2:] lastx = float(lastpoint[0]) lasty = float(lastpoint[1]) #sys.stderr.write("lastpoint: %s\n"% lastpoint) spath += ' %f %f %f %f %f %f' % (lastx+self.inner_radius/2,lasty, self.width-1.5*self.inner_radius,lasty, self.width-self.inner_radius, lasty) spath += ' %f %f %f %f %f %f' % (self.width-self.inner_radius/2,lasty, self.width,self.height-self.inner_radius/2, self.width,self.height-self.inner_radius) spath += ' %f %f %f %f %f %f' % (self.width,self.height-1.5*self.inner_radius, self.width, 1.5*self.inner_radius, self.width,self.inner_radius) spath += ' %f %f %f %f %f %f' % (self.width,self.inner_radius/2, self.width-self.inner_radius/2,0, self.width-self.inner_radius,0) spath += 'z' #sys.stderr.write("spath: %s\n"% spath) # name = "ColPieces_%d" % (count) attribs = { 'style':style, 'id':name, 'd':spath } n = etree.SubElement(group, inkex.addNS('path','svg'), attribs ) blocks.append(n) # for direct traversal later else: # internal line - concat a reversed version with the last one thispath = copy.deepcopy(path) for i in range(len(thispath[0])): # reverse the internal C pairs thispath[0][i].reverse() thispath[0].reverse() # reverse the entire line lastpath[0].extend(thispath[0]) # append name = "ColPieces_%d" % (count) attribs = { 'style':style, 'id':name, 'd':dirtyFormat(lastpath) } n = etree.SubElement(group, inkex.addNS('path','svg'), attribs ) blocks.append(n) # for direct traversal later n.set('d', n.get('d')+'z') # close it # count += 1 lastpath = path # do the last one (LHS) spath = node.get('d') # work on string instead of cubicpath lastpoint = spath.split()[-2:] lastx = float(lastpoint[0]) lasty = float(lastpoint[1]) #sys.stderr.write("lastpoint: %s\n"% lastpoint) spath += ' %f %f %f %f %f %f' % (lastx-self.inner_radius,lasty, 1.5*self.inner_radius, lasty, self.inner_radius,lasty) spath += ' %f %f %f %f %f %f' % (self.inner_radius/2,lasty, 0,lasty-self.inner_radius/2, 0,lasty-self.inner_radius) spath += ' %f %f %f %f %f %f' % (0,lasty-1.5*self.inner_radius, 0,1.5*self.inner_radius, 0,self.inner_radius) spath += ' %f %f %f %f %f %f' % (self.inner_radius/2,0, self.inner_radius,0, 1.5*self.inner_radius, 0) spath += 'z' # name = "ColPieces_%d" % (count) attribs = { 'style':style, 'id':name, 'd':spath } n = etree.SubElement(group, inkex.addNS('path','svg'), attribs ) blocks.append(n) # for direct traversal later # return(blocks)
def addPathVertices(self, path, node=None, transform=None, clone_transform=None): """ Decompose the path data from an SVG element into individual subpaths, each subpath consisting of absolute move to and line to coordinates. Place these coordinates into a list of polygon vertices. """ if (not path) or (len(path) == 0): # Nothing to do return sp = Path(path) if (not sp) or (len(sp) == 0): # Path must have been devoid of any real content return # Get a cubic super path p = CubicSuperPath(sp) if (not p) or (len(p) == 0): # Probably never happens, but... return # Now traverse the cubic super path subpath_list = [] subpath_vertices = [] for sp in p: if len(subpath_vertices): # There's a prior subpath: see if it is closed and should be saved if distanceSquared(subpath_vertices[0], subpath_vertices[-1]) < 1: # Keep the prior subpath: it appears to be a closed path subpath_list.append(subpath_vertices) subpath_vertices = [] subdivideCubicPath(sp, 0.2) for csp in sp: # Add this vertex to the list of vertices subpath_vertices.append(csp[1]) # Handle final subpath if len(subpath_vertices): if distanceSquared(subpath_vertices[0], subpath_vertices[-1]) < 1: # Path appears to be closed so let's keep it subpath_list.append(subpath_vertices) # Empty path? if not subpath_list: return # Store the list of subpaths in a dictionary keyed off of the path's node pointer self.paths[node] = subpath_list self.paths_clone_transform[node] = clone_transform
def envelope2coords(self, path_envelope): pp_envelope = CubicSuperPath(path_envelope) # inkex.debug(pp_envelope) c0 = pp_envelope[0][0][0] c1 = pp_envelope[0][1][0] c2 = pp_envelope[0][2][0] c3 = pp_envelope[0][3][0] # inkex.debug(str(c0)+" "+str(c1)+" "+str(c2)+" "+str(c3)) return [c0, c1, c2, c3]
def split_into_nodes(self, nodes_number=1000): for id, node in self.svg.selected.items(): if node.tag == inkex.addNS('path', 'svg'): p = CubicSuperPath(node.get('d')) new = [] for sub in p: new.append([sub[0][:]]) i = 1 while i <= len(sub) - 1: length = bezier.cspseglength(new[-1][-1], sub[i]) splits = nodes_number for s in range(int(splits), 1, -1): new[-1][-1], next, sub[ i] = bezier.cspbezsplitatlength( new[-1][-1], sub[i], 1.0 / s) new[-1].append(next[:]) new[-1].append(sub[i]) i += 1 node.set('d', str(CubicSuperPath(new)))
def _sekl_call(self, skeletons, p0, dx, bbox): if self.options.vertical: flipxy(p0) newp = [] for skelnode in skeletons.values(): self.curSekeleton = skelnode.path.to_superpath() if self.options.vertical: flipxy(self.curSekeleton) for comp in self.curSekeleton: path = copy.deepcopy(p0) self.skelcomp, self.lengths = linearize(comp) # !!!!>----> TODO: really test if path is closed! end point==start point is not enough! self.skelcompIsClosed = (self.skelcomp[0] == self.skelcomp[-1]) length = sum(self.lengths) xoffset = self.skelcomp[0][ 0] - bbox.x.minimum + self.options.toffset yoffset = self.skelcomp[0][ 1] - bbox.y.center - self.options.noffset if self.options.repeat: NbCopies = max( 1, int(round((length + self.options.space) / dx))) width = dx * NbCopies if not self.skelcompIsClosed: width -= self.options.space bbox.x.maximum = bbox.x.minimum + width new = [] for sub in path: for _ in range(NbCopies): new.append(copy.deepcopy(sub)) offset(sub, dx, 0) path = new for sub in path: offset(sub, xoffset, yoffset) if self.options.stretch: if not bbox.width: raise inkex.AbortExtension( "The 'stretch' option requires that the pattern must have non-zero width :\nPlease edit the pattern width." ) for sub in path: stretch(sub, length / bbox.width, 1, self.skelcomp[0]) for sub in path: for ctlpt in sub: self.apply_diffeomorphism(ctlpt[1], (ctlpt[0], ctlpt[2])) if self.options.vertical: flipxy(path) newp += path return CubicSuperPath(newp)
def test_QT(self): p = [ ['M', [0.0, 0.0]], ['Q', [3.0, 0.0, 3.0, 3.0]], ['T', [0.0, 6.0]], ] csp = CubicSuperPath(p) self.assertDeepAlmostEqual(csp, [[ [[0.0, 0.0], [0.0, 0.0], [2.0, 0.0]], [[3.0, 1.0], [3.0, 3.0], [3.0, 5.0]], [[2.0, 6.0], [0.0, 6.0], [0.0, 6.0]], ]])
def test_CS(self): p = [ ['M', [1.2, 2.3]], ['C', [4.5, 3.4, 5.6, 6.7, 8.9, 7.8]], ['S', [9.1, 1.2, 2.3, 3.4]], ] csp = CubicSuperPath(p) self.assertDeepAlmostEqual(csp, [[ [[1.2, 2.3], [1.2, 2.3], [4.5, 3.4]], [[5.6, 6.7], [8.9, 7.8], [12.2, 8.9]], [[9.1, 1.2], [2.3, 3.4], [2.3, 3.4]], ]])
def effect(self): if len(self.svg.selected) > 0: output_all = output_nodes = "" for node in self.svg.selection.filter(inkex.PathElement): node.apply_transform() p = CubicSuperPath(node.get('d')) for subpath in p: for csp in subpath: output_nodes += str(csp[1][0]) + "\t" + str(csp[1][1]) + "\n" output_nodes += "\n" sys.stderr.write(output_nodes.strip()) else: inkex.errormsg('Please select some paths first.') return
def envelope2coords(self, path_envelope): pp_envelope = CubicSuperPath(path_envelope) if len(pp_envelope[0]) < 4: inkex.errormsg( "The selected envelope (your second path) does not contain enough nodes. Check to have at least 4 nodes." ) exit() c0 = pp_envelope[0][0][0] c1 = pp_envelope[0][1][0] c2 = pp_envelope[0][2][0] c3 = pp_envelope[0][3][0] # inkex.debug(str(c0)+" "+str(c1)+" "+str(c2)+" "+str(c3)) return [c0, c1, c2, c3]
def test_LHV(self): p = [ ['M', [1.2, 2.3]], ['L', [3.4, 4.5]], ['H', [5.6]], ['V', [6.7]], ] csp = CubicSuperPath(p) self.assertDeepAlmostEqual(csp, [[ [[1.2, 2.3], [1.2, 2.3], [1.2, 2.3]], [[3.4, 4.5], [3.4, 4.5], [3.4, 4.5]], [[5.6, 4.5], [5.6, 4.5], [5.6, 4.5]], [[5.6, 6.7], [5.6, 6.7], [5.6, 6.7]], ]])
def effect(self): if len(self.svg.selected) > 0: global output_nodes, points #create numpy array of nodes n_array = [] #Iterate through all the selected objects in Inkscape for node in self.svg.selected.values(): #Check if the node is a path ("svg:path" node in XML ) #id = node.id if node.tag == inkex.addNS('path','svg'): # bake (or fuse) transform node.apply_transform() #turn into cubicsuperpath d = node.get('d') p = CubicSuperPath(d) for subpath in p: # there may be several paths joined together (e.g. holes) for csp in subpath: # groups of three to handle control points. # just the points no control points (handles) n_array.append(csp[1][0]) n_array.append(csp[1][1]) k = n.asarray(n_array) length = int(len(k)/2) c = k.reshape(length,2) hull_pts = qhull(c) pdata = '' for vertex in hull_pts: if pdata == '': pdata = 'M%f,%f' % (vertex[0], vertex[1] ) else: pdata += 'L %f,%f' % (vertex[0], vertex[1] ) pdata += ' Z' path = 'polygon' makeGroup = False paths_clone_transform = None self.joinWithNode(path, pdata, makeGroup, paths_clone_transform) else: inkex.errormsg('Please select some paths first.') return
def getCubicSuperFromParts(parts): cbsuper = [] for part in parts: subpath = [] lastPt = None pt = None for seg in part: if (pt == None): ptLeft = seg[0] pt = seg[0] ptRight = seg[1] subpath.append([ptLeft, pt, ptRight]) ptLeft = seg[2] pt = seg[3] subpath.append([ptLeft, pt, pt]) cbsuper.append(subpath) if (ver == 1.0): return CubicSuperPath(cbsuper) else: return cbsuper
def recursiveFuseTransform(self, node, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): # Modification by David Burghoff: # Since transforms apply to an object's clips, before applying the transform # we will need to duplicate the clip path and transform it clippathurl = node.get('clip-path') if clippathurl is not None and node.get("transform") is not None: myn = node while not (myn.getparent() == None): # get svg handle myn = myn.getparent() svg = myn clippath = svg.getElementById(clippathurl[5:-1]) if clippath is not None: d = clippath.duplicate() clippathurl = "url(#" + d.get("id") + ")" node.set("clip-path", clippathurl) for k in d.getchildren(): if k.get('transform') is not None: tr = Transform(node.get("transform")) * Transform( k.get('transform')) else: tr = Transform(node.get("transform")) k.set('transform', tr) transf = Transform(transf) * Transform(node.get("transform", None)) if 'transform' in node.attrib: del node.attrib['transform'] node = ApplyTransform.objectToPath(node) if transf == NULL_TRANSFORM: # Don't do anything if there is effectively no transform applied # reduces alerts for unsupported nodes pass elif 'd' in node.attrib: d = node.get('d') p = CubicSuperPath(d) p = Path(p).to_absolute().transform(transf, True) node.set('d', str(Path(CubicSuperPath(p).to_path()))) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('polygon', 'svg'), inkex.addNS('polyline', 'svg') ]: points = node.get('points') points = points.strip().split(' ') for k, p in enumerate(points): if ',' in p: p = p.split(',') p = [float(p[0]), float(p[1])] p = transf.apply_to_point(p) p = [str(p[0]), str(p[1])] p = ','.join(p) points[k] = p points = ' '.join(points) node.set('points', points) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS("ellipse", "svg"), inkex.addNS("circle", "svg") ]: def isequal(a, b): return abs(a - b) <= transf.absolute_tolerance if node.TAG == "ellipse": rx = float(node.get("rx")) ry = float(node.get("ry")) else: rx = float(node.get("r")) ry = rx cx = float(node.get("cx")) cy = float(node.get("cy")) sqxy1 = (cx - rx, cy - ry) sqxy2 = (cx + rx, cy - ry) sqxy3 = (cx + rx, cy + ry) newxy1 = transf.apply_to_point(sqxy1) newxy2 = transf.apply_to_point(sqxy2) newxy3 = transf.apply_to_point(sqxy3) node.set("cx", (newxy1[0] + newxy3[0]) / 2) node.set("cy", (newxy1[1] + newxy3[1]) / 2) edgex = math.sqrt( abs(newxy1[0] - newxy2[0])**2 + abs(newxy1[1] - newxy2[1])**2) edgey = math.sqrt( abs(newxy2[0] - newxy3[0])**2 + abs(newxy2[1] - newxy3[1])**2) if not isequal(edgex, edgey) and ( node.TAG == "circle" or not isequal(newxy2[0], newxy3[0]) or not isequal(newxy1[1], newxy2[1])): inkex.utils.errormsg( "Warning: Shape %s (%s) is approximate only, try Object to path first for better results" % (node.TAG, node.get("id"))) if node.TAG == "ellipse": node.set("rx", edgex / 2) node.set("ry", edgey / 2) else: node.set("r", edgex / 2) # Modficiation by David Burghoff: Added support for lines elif node.tag in inkex.addNS('line', 'svg'): x1 = node.get('x1') x2 = node.get('x2') y1 = node.get('y1') y2 = node.get('y2') p1 = transf.apply_to_point([x1, y1]) p2 = transf.apply_to_point([x2, y2]) node.set('x1', str(p1[0])) node.set('y1', str(p1[1])) node.set('x2', str(p2[0])) node.set('y2', str(p2[1])) self.scaleStrokeWidth(node, transf) elif node.typename in ['Rectangle']: x = float(node.get('x')) y = float(node.get('y')) w = float(node.get('width')) h = float(node.get('height')) pts = [[x, y], [x + w, y], [x + w, y + h], [x, y + h], [x, y]] xs = [] ys = [] for p in pts: p = transf.apply_to_point(p) xs.append(p.x) ys.append(p.y) node.set('x', str(min(xs))) node.set('y', str(min(ys))) node.set('width', str(max(xs) - min(xs))) node.set('height', str(max(ys) - min(ys))) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('text', 'svg'), inkex.addNS('image', 'svg'), inkex.addNS('use', 'svg') ]: inkex.utils.errormsg( "Shape %s (%s) not yet supported, try Object to path first" % (node.TAG, node.get("id"))) else: # e.g. <g style="..."> self.scaleStrokeWidth(node, transf) for child in node.getchildren(): self.recursiveFuseTransform(child, transf)
def recursiveFuseTransform(self, node, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): transf = Transform(transf) * Transform(node.get("transform", None)) if 'transform' in node.attrib: del node.attrib['transform'] node = ApplyTransform.objectToPath(node) if transf == NULL_TRANSFORM: # Don't do anything if there is effectively no transform applied # reduces alerts for unsupported nodes pass elif 'd' in node.attrib: d = node.get('d') p = CubicSuperPath(d) p = Path(p).to_absolute().transform(transf, True) node.set('d', Path(CubicSuperPath(p).to_path())) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('polygon', 'svg'), inkex.addNS('polyline', 'svg') ]: points = node.get('points') points = points.strip().split(' ') for k, p in enumerate(points): if ',' in p: p = p.split(',') p = [float(p[0]), float(p[1])] p = transf.apply_to_point(p) p = [str(p[0]), str(p[1])] p = ','.join(p) points[k] = p points = ' '.join(points) node.set('points', points) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS("ellipse", "svg"), inkex.addNS("circle", "svg") ]: def isequal(a, b): return abs(a - b) <= transf.absolute_tolerance if node.TAG == "ellipse": rx = float(node.get("rx")) ry = float(node.get("ry")) else: rx = float(node.get("r")) ry = rx cx = float(node.get("cx")) cy = float(node.get("cy")) sqxy1 = (cx - rx, cy - ry) sqxy2 = (cx + rx, cy - ry) sqxy3 = (cx + rx, cy + ry) newxy1 = transf.apply_to_point(sqxy1) newxy2 = transf.apply_to_point(sqxy2) newxy3 = transf.apply_to_point(sqxy3) node.set("cx", (newxy1[0] + newxy3[0]) / 2) node.set("cy", (newxy1[1] + newxy3[1]) / 2) edgex = math.sqrt( abs(newxy1[0] - newxy2[0])**2 + abs(newxy1[1] - newxy2[1])**2) edgey = math.sqrt( abs(newxy2[0] - newxy3[0])**2 + abs(newxy2[1] - newxy3[1])**2) if not isequal(edgex, edgey) and ( node.TAG == "circle" or not isequal(newxy2[0], newxy3[0]) or not isequal(newxy1[1], newxy2[1])): inkex.utils.errormsg( "Warning: Shape %s (%s) is approximate only, try Object to path first for better results" % (node.TAG, node.get("id"))) if node.TAG == "ellipse": node.set("rx", edgex / 2) node.set("ry", edgey / 2) else: node.set("r", edgex / 2) elif node.tag in [ inkex.addNS('rect', 'svg'), inkex.addNS('text', 'svg'), inkex.addNS('image', 'svg'), inkex.addNS('use', 'svg') ]: inkex.utils.errormsg( "Shape %s (%s) not yet supported, try Object to path first" % (node.TAG, node.get("id"))) else: # e.g. <g style="..."> self.scaleStrokeWidth(node, transf) for child in node.getchildren(): self.recursiveFuseTransform(child, transf)
def effect(self): if len(self.options.ids) != 2: inkex.errormsg( "This extension requires two selected objects. The first selected object must be the straight line with two nodes." ) exit() # drawing that will be scaled is selected second, must be a single object scalepath = self.svg.selected[self.options.ids[0]] drawing = self.svg.selected[self.options.ids[1]] if scalepath.tag != inkex.addNS('path', 'svg'): inkex.errormsg( "The first selected object is not a path.\nPlease select a straight line with two nodes instead." ) exit() # apply its transforms to the scaling path, so we get the correct coordinates to calculate path length scalepath.apply_transform() path = CubicSuperPath(scalepath.get('d')) if len(path) < 1 or len(path[0]) < 2: inkex.errormsg( "This extension requires that the first selected path be two nodes long." ) exit() # calculate path length p1_x = path[0][0][1][0] p1_y = path[0][0][1][1] p2_x = path[0][1][1][0] p2_y = path[0][1][1][1] p_length = self.svg.unittouu( str(distance((p1_x, p1_y), (p2_x, p2_y))) + self.svg.unit) # Find Drawing Scale if self.options.choosescale == 'metric': drawing_scale = int(self.options.metric) elif self.options.choosescale == 'imperial': drawing_scale = int(self.options.imperial) elif self.options.choosescale == 'custom': drawing_scale = self.options.custom_scale # calculate scaling center center = self.calc_scale_center(p1_x, p1_y, p2_x, p2_y) # calculate scaling factor target_length = self.svg.unittouu( str(self.options.length) + self.options.unit) factor = (target_length / p_length) / drawing_scale # inkex.debug("%s, %s %s" % (target_length, p_length, factor)) # Add scale ruler if self.options.showscale == "true": dist = int(self.options.unitlength) ruler_length = self.svg.unittouu(str(dist) + self.options.unit) / drawing_scale ruler_pos = (p1_x + (p2_x - p1_x) / 2, (p1_y + (p2_y - p1_y) / 2) - self.svg.unittouu('4 mm')) # TODO: add into current layer instead self.create_ruler(self.document.getroot(), ruler_length, ruler_pos, dist, drawing_scale) # Get drawing and current transformations for obj in (scalepath, drawing): # Scale both objects about the center, first translate back to origin scale_matrix = [[1, 0.0, -center[0]], [0.0, 1, -center[1]]] obj.transform = Transform(scale_matrix) * obj.transform # Then scale scale_matrix = [[factor, 0.0, 0.0], [0.0, factor, 0.0]] obj.transform = Transform(scale_matrix) * obj.transform # Then translate back to original scale center location scale_matrix = [[1, 0.0, center[0]], [0.0, 1, center[1]]] obj.transform = Transform(scale_matrix) * obj.transform
def effect(self): # Check that elements have been selected if len(self.options.ids) == 0: inkex.errormsg("Please select objects!") return # Drawing styles linestyle = { 'stroke': '#000000', 'stroke-width': str(self.svg.unittouu('1px')), 'fill': 'none' } facestyle = { 'stroke': '#000000', 'stroke-width': '0px', # str(self.svg.unittouu('1px')), 'fill': 'none' } # Handle the transformation of the current group parentGroup = (self.svg.selected[self.options.ids[0]]).getparent() svg = self.document.getroot() image_element = svg.find('.//{http://www.w3.org/2000/svg}image') if image_element is None: inkex.utils.debug("No image found") exit(1) self.path = self.checkImagePath( image_element) # This also ensures the file exists if self.path is None: # check if image is embedded or linked image_string = image_element.get( '{http://www.w3.org/1999/xlink}href') # find comma position i = 0 while i < 40: if image_string[i] == ',': break i = i + 1 img = Image.open( BytesIO(base64.b64decode(image_string[i + 1:len(image_string)]))) else: img = Image.open(self.path) extrinsic_image_width = float(image_element.get('width')) extrinsic_image_height = float(image_element.get('height')) (width, height) = img.size trans = self.getGlobalTransform(parentGroup) invtrans = None if trans: invtrans = self.invertTransform(trans) # Recovery of the selected objects pts = [] nodes = [] seeds = [] for id in self.options.ids: node = self.svg.selected[id] nodes.append(node) if (node.tag == "{http://www.w3.org/2000/svg}path" ): #If it is path # Get vertex coordinates of path points = CubicSuperPath(node.get('d')) for p in points[0]: pt = [p[1][0], p[1][1]] if trans: Transform(trans).apply_to_point(pt) pts.append(Point(pt[0], pt[1])) seeds.append(Point(p[1][0], p[1][1])) else: # For other shapes bbox = node.bounding_box() if bbox: cx = 0.5 * (bbox.left + bbox.top) cy = 0.5 * (bbox.top + bbox.bottom) pt = [cx, cy] if trans: Transform(trans).apply_to_point(pt) pts.append(Point(pt[0], pt[1])) seeds.append(Point(cx, cy)) pts.sort() seeds.sort() # In Creation of groups to store the result # Delaunay groupDelaunay = etree.SubElement(parentGroup, inkex.addNS('g', 'svg')) groupDelaunay.set(inkex.addNS('label', 'inkscape'), 'Delaunay') scale_x = float(extrinsic_image_width) / float(width) scale_y = float(extrinsic_image_height) / float(height) # Voronoi diagram generation triangles = voronoi.computeDelaunayTriangulation(seeds) for triangle in triangles: p1 = seeds[triangle[0]] p2 = seeds[triangle[1]] p3 = seeds[triangle[2]] cmds = [['M', [p1.x, p1.y]], ['L', [p2.x, p2.y]], ['L', [p3.x, p3.y]], ['Z', []]] path = etree.Element(inkex.addNS('path', 'svg')) path.set('d', str(Path(cmds))) middleX = (p1.x + p2.x + p3.x) / 3.0 middleY = (p1.y + p2.y + p3.y) / 3.0 if width > middleX and height > middleY and middleX >= 0 and middleY >= 0: pixelColor = img.getpixel((middleX, middleY)) facestyle["fill"] = str( inkex.Color((pixelColor[0], pixelColor[1], pixelColor[2]))) else: facestyle["fill"] = "black" path.set('style', str(inkex.Style(facestyle))) groupDelaunay.append(path)
def getParsedPath(d): if (ver == 1.0): return CubicSuperPath(Path(d).to_superpath()) else: return cubicsuperpath.parsePath(d)
def effect(self): if self.options.version: print(__version__) sys.exit(0) self.calc_unit_factor(self.options.units) if self.options.snap_ends is not None: self.snap_ends = self.options.snap_ends if self.options.close_loops is not None: self.close_loops = self.options.close_loops if self.options.chain_epsilon is not None: self.chain_epsilon = self.options.chain_epsilon if self.chain_epsilon < 0.001: self.chain_epsilon = 0.001 # keep a minimum. self.eps_sq = self.chain_epsilon * self.unit_factor * self.chain_epsilon * self.unit_factor if not len(self.svg.selected.items()): inkex.errormsg("Please select one or more objects.") return segments = [] for id, node in self.svg.selected.items(): if node.tag != inkex.addNS('path', 'svg'): inkex.errormsg( "Object " + id + " is not a path. Try\n - Path->Object to Path\n - Object->Ungroup" ) return if debug: inkex.utils.debug("id=" + str(id) + ", tag=" + str(node.tag)) path_d = CubicSuperPath(Path(node.get('d'))) sub_idx = -1 for sub in path_d: sub_idx += 1 # sub = [[[200.0, 300.0], [200.0, 300.0], [175.0, 290.0]], [[175.0, 265.0], [220.37694, 256.99876], [175.0, 240.0]], [[175.0, 215.0], [200.0, 200.0], [200.0, 200.0]]] # this is a path of three points. All the bezier handles are included. the Structure is: # [[handle0_OUT, point0, handle0_1], [handle1_0, point1, handle1_2], [handle2_1, point2, handle2_OUT]] # the _OUT handles at the end of the path are ignored. The data structure has them identical to their points. # if debug: inkex.utils.debug(" sub=" + str(sub)) end1 = [sub[0][1][0], sub[0][1][1]] end2 = [sub[-1][1][0], sub[-1][1][1]] # Remove trivial self revesal when building candidate segments list. if ((len(sub) == 3) and self.near_ends(end1, end2)): if debug: inkex.utils.debug( "dropping segment from self-reversing path, length:" + str(len(sub))) sub.pop() end2 = [sub[-1][1][0], sub[-1][1][1]] segments.append({ 'id': id, 'n': sub_idx, 'end1': end1, 'end2': end2, 'seg': sub }) if node.get(inkex.addNS('type', 'sodipodi')): del node.attrib[inkex.addNS('type', 'sodipodi')] if debug: inkex.utils.debug("-------- seen: ") for s in segments: if debug: inkex.utils.debug( str(s['id']) + ", " + str(s['n']) + ", " + str(s['end1']) + ", " + str(s['end2'])) # chain the segments obsoleted = 0 remaining = 0 for id, node in self.svg.selected.items(): path_d = CubicSuperPath(Path(node.get('d'))) # ATTENTION: for parsePath() it is the same, if first and last point coincide, or if the path is really closed. path_closed = True if re.search("z\s*$", node.get('d')) else False new = [] cur_idx = -1 for chain in path_d: cur_idx += 1 if not self.is_segment_done(id, cur_idx): # quadratic algorithm: we check both ends of the current segment. # If one of them is near another known end from the segments list, we # chain this segment to the current segment and remove it from the # list, # end1-end1 or end2-end2: The new segment is reversed. # end1-end2: The new segment is prepended to the current segment. # end2-end1: The new segment is appended to the current segment. self.set_segment_done( id, cur_idx, "output") # do not cross with ourselves. end1 = [chain[0][1][0], chain[0][1][1]] end2 = [chain[-1][1][0], chain[-1][1][1]] # Remove trivial self revesal when doing the actual chain operation. if ((len(chain) == 3) and self.near_ends(end1, end2)): chain.pop() end2 = [chain[-1][1][0], chain[-1][1][1]] segments_idx = 0 while segments_idx < len(segments): seg = segments[segments_idx] if self.is_segment_done(seg['id'], seg['n']): segments_idx += 1 continue if (self.near_ends(end1, seg['end1']) or self.near_ends(end2, seg['end2'])): seg['seg'] = self.reverse_segment(seg['seg']) seg['end1'], seg['end2'] = seg['end2'], seg['end1'] if debug: inkex.utils.debug("reversed seg " + str(seg['id']) + ", " + str(seg['n'])) if self.near_ends(end1, seg['end2']): # prepend seg to chain self.set_segment_done( seg['id'], seg['n'], 'prepended to ' + str(id) + ' ' + str(cur_idx)) chain = self.link_segments(seg['seg'], chain) end1 = [chain[0][1][0], chain[0][1][1]] segments_idx = 0 # this chain changed. re-visit all candidate continue if self.near_ends(end2, seg['end1']): # append seg to chain self.set_segment_done( seg['id'], seg['n'], 'appended to ' + str(id) + ' ' + str(cur_idx)) chain = self.link_segments(chain, seg['seg']) end2 = [chain[-1][1][0], chain[-1][1][1]] segments_idx = 0 # this chain changed. re-visit all candidate continue segments_idx += 1 # Now all joinable segments are joined. # Finally, we can check, if the resulting path is a closed path: # Closing a path here, isolates it from the rest. # But as we prefer to make the chain as long as possible, we close late. if self.near_ends( end1, end2) and not path_closed and self.close_loops: if debug: inkex.utils.debug("closing closeable loop " + str(id)) if self.snap_ends: # move first point to mid position x1n = (chain[0][1][0] + chain[-1][1][0]) * 0.5 y1n = (chain[0][1][1] + chain[-1][1][1]) * 0.5 chain[0][1][0], chain[0][1][1] = x1n, y1n # merge handle of the last point to the handle of the first point dx0e = chain[-1][0][0] - chain[-1][1][0] dy0e = chain[-1][0][1] - chain[-1][1][1] if debug: inkex.utils.debug("handle diff: " + str(dx0e) + str(dy0e)) # FIXME: this does not work. cubicsuperpath.formatPath() ignores this handle. chain[0][0][0], chain[0][0][ 1] = x1n + dx0e, y1n + dy0e # drop last point chain.pop() end2 = [chain[-1][1][0], chain[-1][1][1]] path_closed = True self.chained_count += 1 new.append(chain) if not len(new): # node.clear() if node.getparent() is not None: node.delete() obsoleted += 1 if debug: inkex.utils.debug("Path node obsoleted: " + str(id)) else: remaining += 1 # BUG: All previously closed loops, are open, after we convert them back with cubicsuperpath.formatPath() p_fmt = str(Path(CubicSuperPath(new).to_path().to_arrays())) if path_closed: p_fmt += " z" if debug: inkex.utils.debug("new path: " + str(p_fmt)) node.set('d', p_fmt) # statistics: if debug: inkex.utils.debug("Path nodes obsoleted: " + str(obsoleted) + "\nPath nodes remaining:" + str(remaining)) if self.min_missed_distance_sq is not None: if debug: inkex.utils.debug("min_missed_distance: " + str( math.sqrt(float(self.min_missed_distance_sq)) / self.unit_factor) + '>' + str(self.chain_epsilon) + str(self.options.units)) if debug: inkex.utils.debug("Successful link operations: " + str(self.chained_count))