def test_group_with_number_of_rects_translated(self): group = Group() dx, dy = 5, 10 xmin, ymin = 1000, 1000 xmax, ymax = -1000, -1000 rects = [] for x, y, w, h in [ (10, 20, 5, 7), (30, 40, 5, 7), ]: rect = Rectangle(width=str(w), height=str(h), x=str(x), y=str(y)) rects.append(rect) xmin = min(xmin, x) xmax = max(xmax, x + w) ymin = min(ymin, y) ymax = max(ymax, y + h) group.add(rect) group.transform = Transform(translate=(dx, dy)) self.assert_bounding_box_is_equal(group, (dx + xmin, dx + xmax), (dy + ymin, dy + ymax))
def test_group_with_regular_rect(self): group = Group() x, y = 10, 20 w, h = 7, 20 rect = Rectangle(width=str(w), height=str(h), x=str(x), y=str(y)) group.add(rect) self.assert_bounding_box_is_equal(group, (x, x + w), (y, y + h))
def getNumbers( self, ticks: int, fmtstr: str, intan: float, inrad: float, style: Style ): "The numbers at the main-ticks. intan: shift perpendicular to axis, inrad: shift in axis." group = Group() if ticks <= 0: return group step = (self.max-self.min) / ticks for i in range(ticks+1): val = self.min + i * step point = self.transform(val) group.add( textAt( point.x-intan*self.sin+inrad*self.cos, point.y+intan*self.cos+inrad*self.sin, ("{0:"+fmtstr+"}").format(val), style) ) return group
def effect(self): if len(self.svg.selected) != 2: errormsg( _("Please select exact two objects:\n1. object representing path,\n2. object representing dots." )) return (iddot, dot) = self.svg.selected.popitem() (idpath, path) = self.svg.selected.popitem() bb = dot.bounding_box() parent = path.find('..') group = Group() parent.add(group) for point in path.path.end_points: clone = Use() clone.set('xlink:href', '#' + iddot) clone.set('x', point.x - bb.center.x) clone.set('y', point.y - bb.center.y) group.add(clone)
def getTicks( self, ticks: int, linevon: float, linebis: float, style: Style): "The main ticks, linevon and linebis are the start- and end-point perpendicular to the axis in svg-coordinates." group = Group() if ticks <= 0: return group step = (self.max-self.min) / ticks for i in range(ticks+1): val = self.min + i * step point = self.transform(val) line = Line( x1=str(point.x-linevon*self.sin), y1=str(point.y+linevon*self.cos), x2=str(point.x-linebis*self.sin), y2=str(point.y+linebis*self.cos) ) line.style = style group.add(line) return group
def test_group_nested_transform(self): group = Group() x, y = 10, 20 w, h = 7, 20 scale = 2 rect = Rectangle(width=str(w), height=str(h), x=str(x), y=str(y)) rect.transform = Transform(rotate=45, scale=scale) group.add(rect) group.transform = Transform(rotate=-45) # rotation is compensated, but scale is not a = rect.composed_transform() self.assert_bounding_box_is_equal(group, (scale * x, scale * (x + w)), (scale * y, scale * (y + h)))
def effect(self): if len(self.svg.selected) != 2: errormsg( _("Please select exact two objects:\n1. object representing path,\n2. object representing dots." )) return (iddot, dot) = self.svg.selected.popitem() (idpath, path) = self.svg.selected.popitem() bb = dot.bounding_box() parent = path.find('..') group = Group() parent.add(group) end_points = list(path.path.end_points) control_points = [] for cp in path.path.control_points: is_endpoint = False for ep in end_points: if cp.x == ep.x and cp.y == ep.y: is_endpoint = True break if not is_endpoint: control_points.append(cp) pointlist = [] if self.options.endpoints: pointlist += end_points if self.options.controlpoints: pointlist += control_points for point in pointlist: clone = Use() clone.set('xlink:href', '#' + iddot) clone.set('x', point.x - bb.center.x) clone.set('y', point.y - bb.center.y) group.add(clone)
def split_fill_and_stroke(path_node): """Split a path into two paths, one filled and one stroked Returns a the list [fill, stroke], where each is the XML element of the fill or stroke, or None. """ style = dict(inkex.Style.parse_str(path_node.get("style", ""))) # If there is only stroke or only fill, don't split anything if "fill" in style and style["fill"] == "none": if "stroke" not in style or style["stroke"] == "none": return [None, None] # Path has neither stroke nor fill else: return [None, path_node] if "stroke" not in style.keys() or style["stroke"] == "none": return [path_node, None] group = Group() fill = group.add(PathElement()) stroke = group.add(PathElement()) d = path_node.pop('d') if d is None: raise AssertionError("Cannot split stroke and fill of non-path element") nodetypes = path_node.pop('sodipodi:nodetypes', None) path_id = path_node.pop('id', str(id(path_node))) transform = path_node.pop('transform', None) path_node.pop('style') # Pass along all remaining attributes to the group for attrib_name, attrib_value in path_node.attrib.items(): group.set(attrib_name, attrib_value) group.set("id", path_id) # Next split apart the style attribute style_group = {} style_fill = {"stroke": "none", "fill": "#000000"} style_stroke = {"fill": "none", "stroke": "none"} for key in style.keys(): if key.startswith("fill"): style_fill[key] = style[key] elif key.startswith("stroke"): style_stroke[key] = style[key] elif key.startswith("marker"): style_stroke[key] = style[key] elif key.startswith("filter"): style_group[key] = style[key] else: style_fill[key] = style[key] style_stroke[key] = style[key] if len(style_group) != 0: group.set("style", str(inkex.Style(style_group))) fill.set("style", str(inkex.Style(style_fill))) stroke.set("style", str(inkex.Style(style_stroke))) # Finalize the two paths fill.set("d", d) stroke.set("d", d) if nodetypes is not None: fill.set('sodipodi:nodetypes', nodetypes) stroke.set('sodipodi:nodetypes', nodetypes) fill.set("id", path_id + "-fill") stroke.set("id", path_id + "-stroke") if transform is not None: fill.set("transform", transform) stroke.set("transform", transform) # Replace the original node with the group path_node.getparent().replace(path_node, group) return [fill, stroke]
def effect(self): # Create some styles first linewidth = self.svg.unittouu(str(self.options.stroke_width)+self.options.stroke_width_unit) cssstyle = { 'stroke': '#000000', 'stroke-width': linewidth, 'fill': 'none', 'stroke-linecap': 'butt', 'stroke-linejoin': 'round' } pathstyle = Style(cssstyle) cssstyle['stroke-linecap'] = 'square' borderstyle = Style(cssstyle) cssstyle['stroke-width'] = linewidth*0.6 tickstyle = Style(cssstyle) cssstyle['stroke-width'] = linewidth*.3 subtickstyle = Style(cssstyle) cssstyle['stroke'] = '#999' cssstyle['stroke-width'] = linewidth*0.5 gridstyle = Style(cssstyle) cssstyle['stroke-width'] = linewidth*0.2 subgridstyle = Style(cssstyle) self.ticksize = self.svg.unittouu("8px") self.subticksize = self.svg.unittouu("4px") self.fontsize = self.svg.unittouu("10pt") textstyle = { 'font-size': self.fontsize, 'font-family':self.options.font_family, 'fill-opacity': '1.0', 'stroke': 'none', 'text-align': 'end', 'text-anchor': 'end', 'font-weight': 'normal', 'font-style': 'normal', 'fill': '#000' } textrechtsbdg = Style(textstyle) textstyle['text-align']='center' textstyle['text-anchor']='middle' textzentriert = Style(textstyle) textstyle['font-size']=2*self.fontsize texttitle = Style(textstyle) # Check on selected object if len(self.svg.selected) != 1: raise AbortExtension(_("Please select exact one object, a rectangle is preferred.")) try: yidxarray = [ int(x) for x in re.split(r'[^0-9]',self.options.yidxs) if x ] except ValueError as e: raise AbortExtension(_("Split produces a string, that is not convertable to int.")) # get CSV data self.data = getCSVData(self.options.csv_file, self.options.csv_encoding, { 'delimiter': self.options.csv_delimiter.replace('\\t', '\t') }, self.options.ignorefirst, self.options.xidx, yidxarray) if self.data.len() < 2: raise AbortExtension(_("Less than 2 pairs of values. Nothing to plot.")) # sort by x-values for a proper line self.data.sort() self.data.calculateMinMax() # Get minima and maxima for x- and y-values self.xmin = self.data.getXMin() if self.options.xmin_autodetect else self.options.xmin self.xmax = self.data.getXMax() if self.options.xmax_autodetect else self.options.xmax if self.xmin >= self.xmax: raise AbortExtension(_('xmin > xmax')) self.ymin = self.data.getYMin() if self.options.ymin_autodetect else self.options.ymin self.ymax = self.data.getYMax() if self.options.ymax_autodetect else self.options.ymax if self.ymin >= self.ymax: raise AbortExtension(_('ymin > ymax')) # Get the parent of the node (nodeid,node) = self.svg.selected.popitem() self.bb = node.bounding_box() parent = node.getparent() group = Group() parent.add(group) # get axis and path xAchse = Axis(Vector2d(self.bb.left,self.bb.bottom),Vector2d(self.bb.right,self.bb.bottom),self.xmin,self.xmax) yAchse = Axis(Vector2d(self.bb.left,self.bb.bottom),Vector2d(self.bb.left ,self.bb.top) ,self.ymin,self.ymax) plot = [] for i in range(len(yidxarray)): plot.append( self.plotPath(pathstyle,i) ) # evaluate options and add all together if self.options.xgrid: group.add(xAchse.getTicks( self.options.xtickn, 0, -self.bb.height, gridstyle)) group.add(xAchse.getSubTicks(self.options.xtickn, self.options.xsubtickn, 0, -self.bb.height, subgridstyle)) if self.options.ygrid: group.add(yAchse.getTicks( self.options.ytickn, 0, self.bb.width, gridstyle)) group.add(yAchse.getSubTicks(self.options.ytickn, self.options.ysubtickn, 0, self.bb.width, subgridstyle)) if self.options.xtickn > 0: tlvon = -self.ticksize if self.options.xticksin else 0 tlbis = self.ticksize if self.options.xticksout else 0 group.add(xAchse.getTicks( self.options.xtickn, tlvon, tlbis, tickstyle)) group.add(xAchse.getSubTicks(self.options.xtickn, self.options.xsubtickn, .5*tlvon, .5*tlbis, subtickstyle)) group.add(xAchse.getNumbers( self.options.xtickn, self.options.xformat, 3*self.ticksize, 0, textzentriert)) if self.options.ytickn > 0: tlvon = -self.ticksize if self.options.yticksout else 0 tlbis = self.ticksize if self.options.yticksin else 0 group.add(yAchse.getTicks( self.options.ytickn, tlvon, tlbis, tickstyle)) group.add(yAchse.getSubTicks(self.options.ytickn, self.options.ysubtickn, .5*tlvon, .5*tlbis, subtickstyle)) group.add(yAchse.getNumbers( self.options.ytickn, self.options.yformat, -2*self.ticksize, -.3*self.fontsize, textrechtsbdg)) for i in range(len(yidxarray)): group.add(plot[i]) if self.options.border_left: group.add( self.createBorderLine('left', borderstyle) ) if self.options.border_bottom: group.add( self.createBorderLine('bottom', borderstyle) ) if self.options.border_right: group.add( self.createBorderLine('right', borderstyle) ) if self.options.border_top: group.add( self.createBorderLine('top', borderstyle) ) if self.options.label_title: group.add( textAt(self.bb.center.x,self.bb.minimum.y-self.fontsize,self.options.label_title, texttitle) ) if self.options.label_yaxis: group.add( textAt(self.bb.minimum.x-4*self.fontsize,self.bb.center.y,self.options.label_yaxis, textzentriert,-90) ) if self.options.label_xaxis: group.add( textAt(self.bb.center.x,self.bb.maximum.y+3.5*self.fontsize,self.options.label_xaxis, textzentriert) ) if self.options.storedata: group.set('data-values',str(self.data)) if self.options.remove: parent.remove(node)
class PathScatter(pathmodifier.Diffeo): def __init__(self): super(PathScatter, self).__init__() self.arg_parser.add_argument("-n", "--noffset", type=float, dest="noffset", default=0.0, help="normal offset") self.arg_parser.add_argument("-t", "--toffset", type=float, dest="toffset", default=0.0, help="tangential offset") self.arg_parser.add_argument( "-g", "--grouppick", type=inkex.Boolean, dest="grouppick", default=False, help="if pattern is a group then randomly pick group members") self.arg_parser.add_argument( "-m", "--pickmode", type=str, dest="pickmode", default="rand", help="group pick mode (rand=random seq=sequentially)") self.arg_parser.add_argument( "-f", "--follow", type=inkex.Boolean, dest="follow", default=True, help="choose between wave or snake effect") self.arg_parser.add_argument( "-s", "--stretch", type=inkex.Boolean, dest="stretch", default=True, help="repeat the path to fit deformer's length") self.arg_parser.add_argument("-p", "--space", type=float, dest="space", default=0.0) self.arg_parser.add_argument("-v", "--vertical", type=inkex.Boolean, dest="vertical", default=False, help="reference path is vertical") self.arg_parser.add_argument( "-d", "--duplicate", type=inkex.Boolean, dest="duplicate", default=False, help="duplicate pattern before deformation") self.arg_parser.add_argument( "-c", "--copymode", type=str, dest="copymode", default="clone", help="duplicate pattern before deformation") self.arg_parser.add_argument( "--tab", type=str, dest="tab", help="The selected UI-tab when OK was pressed") def prepareSelectionList(self): # first selected->pattern, all but first selected-> skeletons pattern_node = self.svg.selected.pop() self.gNode = Group() pattern_node.getparent().append(self.gNode) if self.options.copymode == "copy": self.patternNode = pattern_node.duplicate() elif self.options.copymode == "clone": # TODO: allow 4th option: duplicate the first copy and clone the next ones. self.patternNode = self.gNode.add(Use()) self.patternNode.href = pattern_node else: self.patternNode = pattern_node self.skeletons = self.svg.selected self.expand_clones(self.skeletons, True, False) self.objects_to_paths(self.skeletons, False) def lengthtotime(self, l): """ Receives an arc length l, and returns the index of the segment in self.skelcomp containing the corresponding point, to gether with the position of the point on this segment. If the deformer is closed, do computations modulo the total length. """ if self.skelcompIsClosed: l = l % sum(self.lengths) if l <= 0: return 0, l / self.lengths[0] i = 0 while (i < len(self.lengths)) and (self.lengths[i] <= l): l -= self.lengths[i] i += 1 t = l / self.lengths[min(i, len(self.lengths) - 1)] return i, t def localTransformAt(self, s, follow=True): """ receives a length, and returns the corresponding point and tangent of self.skelcomp if follow is set to false, returns only the translation """ i, t = self.lengthtotime(s) if i == len(self.skelcomp) - 1: x, y = bezier.between_point(self.skelcomp[i - 1], self.skelcomp[i], 1 + t) dx = (self.skelcomp[i][0] - self.skelcomp[i - 1][0]) / self.lengths[-1] dy = (self.skelcomp[i][1] - self.skelcomp[i - 1][1]) / self.lengths[-1] else: x, y = bezier.between_point(self.skelcomp[i], self.skelcomp[i + 1], t) dx = (self.skelcomp[i + 1][0] - self.skelcomp[i][0]) / self.lengths[i] dy = (self.skelcomp[i + 1][1] - self.skelcomp[i][1]) / self.lengths[i] if follow: mat = [[dx, -dy, x], [dy, dx, y]] else: mat = [[1, 0, x], [0, 1, y]] return mat def effect(self): if len(self.options.ids) < 2: inkex.errormsg(_("This extension requires two selected paths.")) return self.prepareSelectionList() # center at (0,0) bbox = self.patternNode.bounding_box() mat = [[1, 0, -bbox.center.x], [0, 1, -bbox.center.y]] if self.options.vertical: bbox = BoundingBox(-bbox.y, bbox.x) mat = (Transform([[0, -1, 0], [1, 0, 0]]) * Transform(mat)).matrix mat[1][2] += self.options.noffset self.patternNode.transform *= mat width = bbox.width dx = width + self.options.space # check if group and expand it patternList = [] if self.options.grouppick and isinstance(self.patternNode, Group): mat = self.patternNode.transform for child in self.patternNode: child.transform *= mat patternList.append(child) else: patternList.append(self.patternNode) # inkex.debug(patternList) counter = 0 for skelnode in self.skeletons.values(): self.curSekeleton = skelnode.path.to_superpath() for comp in self.curSekeleton: 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) if self.options.stretch: dx = width + self.options.space n = int( (length - self.options.toffset + self.options.space) / dx) if n > 0: dx = (length - self.options.toffset) / n xoffset = self.skelcomp[0][ 0] - bbox.x.minimum + self.options.toffset yoffset = self.skelcomp[0][ 1] - bbox.y.center - self.options.noffset s = self.options.toffset while s <= length: mat = self.localTransformAt(s, self.options.follow) if self.options.pickmode == "rand": clone = copy.deepcopy(patternList[random.randint( 0, len(patternList) - 1)]) if self.options.pickmode == "seq": clone = copy.deepcopy(patternList[counter]) counter = (counter + 1) % len(patternList) # !!!--> should it be given an id? # seems to work without this!?! myid = patternList[random.randint(0, len(patternList) - 1)].tag.split('}')[-1] clone.set("id", self.svg.get_unique_id(myid)) self.gNode.append(clone) clone.transform *= mat s += dx self.patternNode.getparent().remove(self.patternNode)