def effect(self): clippingLineSegments = None pathTag = inkex.addNS('path','svg') groupTag = inkex.addNS('g','svg') error_messages = [] for id in self.options.ids: # the selection, top-down node = self.selected[id] if node.tag == pathTag: if clippingLineSegments is None: # first path is the clipper (clippingLineSegments, errors) = self.simplepathToLineSegments(simplepath.parsePath(node.get('d'))) error_messages.extend(['{}: {}'.format(id, err) for err in errors]) else: # do all the work! segmentsToClip, errors = self.simplepathToLineSegments(simplepath.parsePath(node.get('d'))) error_messages.extend(['{}: {}'.format(id, err) for err in errors]) clippedSegments = self.clipLineSegments(segmentsToClip, clippingLineSegments) if len(clippedSegments) != 0: # replace the path node.set('d', simplepath.formatPath(self.linesgmentsToSimplePath(clippedSegments))) else: # don't put back an empty path(?) could perhaps put move, move? inkex.debug('Object {} clipped to nothing, will not be updated.'.format(node.get('id'))) elif node.tag == groupTag: # we don't look inside groups for paths inkex.debug('Group object {} will be ignored. Please ungroup before running the script.'.format(id)) else: # something else inkex.debug('Object {} is not of type path ({}), and will be ignored. Current type "{}".'.format(id, pathTag, node.tag)) for error in error_messages: inkex.debug(error)
def point_generator(path, mat, flatness): rirCount = 0 if len(simplepath.parsePath(path)) == 0: return simple_path = simplepath.parsePath(path) startX,startY = float(simple_path[0][1][0]), float(simple_path[0][1][1]) command = simple_path[0][0][0] yield startX, startY, command p = cubicsuperpath.parsePath(path) if mat: simpletransform.applyTransformToPath(mat, p) commandPile = [] for sp in p: cspsubdiv.subdiv( sp, flatness) for csp in sp: ctrl_pt1 = csp[0] ctrl_pt2 = csp[1] end_pt = csp[2] command = csp[-1] commandPile.append(command) yield end_pt[0], end_pt[1], command rirCount += 1
def effect(self): for id, node in self.selected.iteritems(): if node.tagName == 'path': self.group = self.document.createElement('svg:g') node.parentNode.appendChild(self.group) new = self.document.createElement('svg:path') try: t = node.attributes.getNamedItem('transform').value self.group.setAttribute('transform', t) except AttributeError: pass s = simplestyle.parseStyle(node.attributes.getNamedItem('style').value) s['stroke-linecap']='round' s['stroke-width']=self.options.dotsize new.setAttribute('style', simplestyle.formatStyle(s)) a =[] p = simplepath.parsePath(node.attributes.getNamedItem('d').value) num = 1 for cmd,params in p: if cmd != 'Z': a.append(['M',params[-2:]]) a.append(['L',params[-2:]]) self.addText(self.group,params[-2],params[-1],num) num += 1 new.setAttribute('d', simplepath.formatPath(a)) self.group.appendChild(new) node.parentNode.removeChild(node)
def effect(self): for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path','svg'): p = simplepath.parsePath(node.get('d')) a =[] pen = None subPathStart = None for cmd,params in p: if cmd == 'C': a.extend([['M', params[:2]], ['L', pen], ['M', params[2:4]], ['L', params[-2:]]]) if cmd == 'Q': a.extend([['M', params[:2]], ['L', pen], ['M', params[:2]], ['L', params[-2:]]]) if cmd == 'M': subPathStart = params if cmd == 'Z': pen = subPathStart else: pen = params[-2:] if len(a) > 0: s = {'stroke-linejoin': 'miter', 'stroke-width': '1.0px', 'stroke-opacity': '1.0', 'fill-opacity': '1.0', 'stroke': '#000000', 'stroke-linecap': 'butt', 'fill': 'none'} attribs = {'style':simplestyle.formatStyle(s),'d':simplepath.formatPath(a)} inkex.etree.SubElement(node.getparent(), inkex.addNS('path','svg'), attribs)
def realistic_stitch(start, end): """Generate a stitch vector path given a start and end point.""" end = Point(*end) start = Point(*start) stitch_length = (end - start).length() stitch_center = (end + start) / 2.0 stitch_direction = (end - start) stitch_angle = math.atan2(stitch_direction.y, stitch_direction.x) stitch_length = max(0, stitch_length - 0.2 * PIXELS_PER_MM) # create the path by filling in the length in the template path = simplepath.parsePath(stitch_path % stitch_length) # rotate the path to match the stitch rotation_center_x = -stitch_length / 2.0 rotation_center_y = stitch_height / 2.0 simplepath.rotatePath(path, stitch_angle, cx=rotation_center_x, cy=rotation_center_y) # move the path to the location of the stitch simplepath.translatePath(path, stitch_center.x - rotation_center_x, stitch_center.y - rotation_center_y) return simplepath.formatPath(path)
def path_bounding_box(self, elem, parent_transform=None): """ Returns [min_x, min_y], [max_x, max_y] of the transformed element. (It doesn't make any sense to return the untransformed bounding box, with the intent of transforming it later, because the min/max points will be completely different points) The returned bounding box includes stroke-width offset. This function uses a simplistic algorithm & doesn't take curves or arcs into account, just node positions. """ # If we have a Live Path Effect, modify original-d. If anyone clamours # for it, we could make an option to ignore paths with Live Path Effects original_d = '{%s}original-d' % inkex.NSS['inkscape'] path = simplepath.parsePath(elem.attrib.get(original_d, elem.attrib['d'])) transform = self.transform(elem, parent_transform=parent_transform) offset = self.elem_offset(elem, parent_transform) min_x = min_y = max_x = max_y = 0 for i in range(len(path)): x, y = self.pathxy(path, i) x, y = transform_point(transform, (x, y)) if i == 0: min_x = max_x = x min_y = max_y = y else: min_x = min(x, min_x) min_y = min(y, min_y) max_x = max(x, max_x) max_y = max(y, max_y) return (min_x-offset, min_y-offset), (max_x+offset, max_y+offset)
def plotPath(self, node, matTransform): """ Plot the path while applying the transformation defined by the matrix matTransform. """ filledPath = (simplestyle.parseStyle(node.get("style")).get("fill", "none") != "none") # Plan: Turn this path into a cubicsuperpath (list of beziers)... d = node.get("d") if len(simplepath.parsePath(d)) == 0: return p = cubicsuperpath.parsePath(d) # ... and apply the transformation to each point. simpletransform.applyTransformToPath(matTransform, p) # p is now a list of lists of cubic beziers [cp1, cp2, endp] # where the start-point is the last point of the previous segment. # For some reason the inkscape extensions uses csp[1] as the coordinates of each point, # but that makes it seem like they are using control point 2 as line points. # Maybe that is a side-effect of the CSP subdivion process? TODO realPoints = [] for sp in p: cspsubdiv.subdiv(sp, self.smoothness) for csp in sp: realPoints.append( (csp[1][0], csp[1][1]) ) self.rawObjects.append( (filledPath, realPoints) )
def effect(self): for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path', 'svg'): d = node.get('d') p = simplepath.parsePath(d) last = [] subPathStart = [] for cmd, params in p: if cmd == 'C': if self.options.behave <= 1: #shorten handles towards end points params[:2] = pointAtPercent( params[:2], last[:], self.options.percent) params[2:4] = pointAtPercent( params[2:4], params[-2:], self.options.percent) else: #shorten handles towards thirds of the segment dest1 = pointAtPercent(last[:], params[-2:], 33.3) dest2 = pointAtPercent(params[-2:], last[:], 33.3) params[:2] = pointAtPercent( params[:2], dest1[:], self.options.percent) params[2:4] = pointAtPercent( params[2:4], dest2[:], self.options.percent) elif cmd == 'Q': dest = pointAtPercent(last[:], params[-2:], 50) params[:2] = pointAtPercent(params[:2], dest, self.options.percent) if cmd == 'M': subPathStart = params[-2:] if cmd == 'Z': last = subPathStart[:] else: last = params[-2:] node.set('d', simplepath.formatPath(p))
def convert_element_to_path(node): """Convert an SVG element into a simplepath. This handles paths, rectangles, circles, ellipses, lines, and polylines. Anything else raises and exception. """ node_tag = get_node_tag(node) # node tag stripped of namespace part if node_tag == 'path': return simplepath.parsePath(node.get('d')) elif node_tag == 'rect': return convert_rect_to_path(node) elif node_tag == 'line': return convert_line_to_path(node) elif node_tag == 'circle': return convert_circle_to_path(node) elif node_tag == 'ellipse': return convert_ellipse_to_path(node) elif node_tag == 'polyline': return convert_polyline_to_path(node) elif node_tag == 'polygon': return convert_polygon_to_path(node) elif node_tag == 'text': raise Exception(_('Unable to convert text; please convert text to paths first.')) elif node_tag == 'image': raise Exception(_('Unable to convert bitmap images; please convert them to line art first.')) else: raise Exception(_('Unable to convert this SVG element to a path: <%s>') % (node.tag))
def load(self, node, mat): #JiB #split the syle in separate tuples and assign to path s = node.get('style') if s: self.style = s for item in s.split(';'): attribute , value = item.split(':') #print attribute, value value = value.replace('px', '') setattr(self, attribute.replace('-','') , value) #print getattr(self, attribute.replace('-','') ) #print ";" + self.strokewidth #print attribute.replace('-','') d = node.get('d') if len(simplepath.parsePath(d)) == 0: return p = cubicsuperpath.parsePath(d) applyTransformToPath(mat, p) # p is now a list of lists of cubic beziers [ctrl p1, ctrl p2, endpoint] # where the start-point is the last point in the previous segment self.segments = [] for sp in p: points = [] subdivideCubicPath(sp,0.2) # TODO: smoothness preference for csp in sp: points.append((csp[1][0],csp[1][1])) self.segments.append(points)
def addDot(self, node): self.group = inkex.etree.SubElement(node.getparent(), inkex.addNS("g", "svg")) self.dotGroup = inkex.etree.SubElement(self.group, inkex.addNS("g", "svg")) self.numGroup = inkex.etree.SubElement(self.group, inkex.addNS("g", "svg")) try: t = node.get("transform") self.group.set("transform", t) except: pass style = simplestyle.formatStyle({"stroke": "none", "fill": "#000"}) a = [] p = simplepath.parsePath(node.get("d")) self.separateLastAndFirst(p) num = self.options.start for cmd, params in p: if cmd != "Z" and cmd != "z": dot_att = { "style": style, "r": str(inkex.unittouu(self.options.dotsize) / 2), "cx": str(params[-2]), "cy": str(params[-1]), } inkex.etree.SubElement(self.dotGroup, inkex.addNS("circle", "svg"), dot_att) self.addText( self.numGroup, params[-2] + (inkex.unittouu(self.options.dotsize) / 2), params[-1] - (inkex.unittouu(self.options.dotsize) / 2), num, ) num += self.options.step node.getparent().remove(node)
def effect(self): #References: Minimum Requirements for Creating a DXF File of a 3D Model By Paul Bourke # NURB Curves: A Guide for the Uninitiated By Philip J. Schneider # The NURBS Book By Les Piegl and Wayne Tiller (Springer, 1995) self.dxf_add("999\nDXF created by Inkscape\n") self.dxf_add(dxf_templates.r14_header) scale = 25.4/90.0 h = inkex.unittouu(self.document.getroot().xpath('@height', namespaces=inkex.NSS)[0]) path = '//svg:path' for node in self.document.getroot().xpath(path, namespaces=inkex.NSS): d = node.get('d') sim = simplepath.parsePath(d) if len(sim): simplepath.scalePath(sim,scale,-scale) simplepath.translatePath(sim,0,h*scale) p = cubicsuperpath.CubicSuperPath(sim) for sub in p: for i in range(len(sub)-1): s = sub[i] e = sub[i+1] if s[1] == s[2] and e[0] == e[1]: self.dxf_line([s[1],e[1]]) elif (self.options.ROBO == 'true'): self.ROBO_spline([s[1],s[2],e[0],e[1]]) else: self.dxf_spline([s[1],s[2],e[0],e[1]]) if self.options.ROBO == 'true': self.ROBO_output() self.LWPOLY_output() self.dxf_add(dxf_templates.r14_footer)
def effect(self): for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path', 'svg'): self.group = inkex.etree.SubElement(node.getparent(), inkex.addNS('g', 'svg')) new = inkex.etree.SubElement(self.group, inkex.addNS('path', 'svg')) try: t = node.get('transform') self.group.set('transform', t) except: pass s = simplestyle.parseStyle(node.get('style')) s['stroke-linecap'] = 'round' s['stroke-width'] = self.options.dotsize new.set('style', simplestyle.formatStyle(s)) a = [] p = simplepath.parsePath(node.get('d')) num = 1 for cmd, params in p: if cmd != 'Z': a.append(['M', params[-2:]]) a.append(['L', params[-2:]]) self.addText(self.group, params[-2], params[-1], num) num += 1 new.set('d', simplepath.formatPath(a)) node.clear()
def get_start_end(self, node): """Given a node, return the start and end points""" if node.tag != inkex.addNS('path', 'svg'): inkex.errormsg( "Groups are not supported, please ungroup for better results") return (0, 0, 0, 0) d = node.get('d') sp = simplepath.parsePath(d) # simplepath converts coordinates to absolute and cleans them up, but # these are still some big assumptions here, are they always valid? TODO startX = sp[0][1][0] startY = sp[0][1][1] if sp[-1][0] == 'Z': # go back to start endX = startX endY = startY else: endX = sp[-1][1][-2] endY = sp[-1][1][-1] transform = node.get('transform') if transform: transform = simpletransform.parseTransform(transform) sx, sy = conv(startX, startY, transform) ex, ey = conv(endX, endY, transform) return (sx, sy, ex, ey)
def snap_path_scale(self, elem, parent_transform=None): # If we have a Live Path Effect, modify original-d. If anyone clamours # for it, we could make an option to ignore paths with Live Path Effects original_d = '{%s}original-d' % inkex.NSS['inkscape'] path = simplepath.parsePath( elem.attrib.get(original_d, elem.attrib['d'])) transform = self.transform(elem, parent_transform=parent_transform) min_xy, max_xy = self.path_bounding_box(elem, parent_transform) width = max_xy[0] - min_xy[0] height = max_xy[1] - min_xy[1] # In case somebody tries to snap a 0-high element, # or a curve/arc with all nodes in a line, and of course # because we should always check for divide-by-zero! if (width == 0 or height == 0): return rescale = round(width) / width, round(height) / height min_xy = transform_point(transform, min_xy, inverse=True) max_xy = transform_point(transform, max_xy, inverse=True) for i in range(len(path)): self.transform_path_node([[1, 0, -min_xy[0]], [0, 1, -min_xy[1]]], path, i) # center transform self.transform_path_node([[rescale[0], 0, 0], [0, rescale[1], 0]], path, i) self.transform_path_node([[1, 0, +min_xy[0]], [0, 1, +min_xy[1]]], path, i) # uncenter transform path = simplepath.formatPath(path) if original_d in elem.attrib: elem.attrib[original_d] = path else: elem.attrib['d'] = path
def load(self, node, mat): #JiB #split the syle in separate tuples and assign to path s = node.get('style') if s: self.style = s for item in s.split(';'): attribute, value = item.split(':') #print attribute, value value = value.replace('px', '') setattr(self, attribute.replace('-', ''), value) #print getattr(self, attribute.replace('-','') ) #print ";" + self.strokewidth #print attribute.replace('-','') d = node.get('d') if len(simplepath.parsePath(d)) == 0: return p = cubicsuperpath.parsePath(d) applyTransformToPath(mat, p) # p is now a list of lists of cubic beziers [ctrl p1, ctrl p2, endpoint] # where the start-point is the last point in the previous segment self.segments = [] for sp in p: points = [] subdivideCubicPath(sp, 0.2) # TODO: smoothness preference for csp in sp: points.append((csp[1][0], csp[1][1])) self.segments.append(points)
def effect(self): totalPathCoords = [] for node in self.document.getroot().iter(): if node.tag == '{http://www.w3.org/2000/svg}path': # TODO: Ugly d = node.get('d') path = parsePath(d) pathCoords = [] for point in path: x = point[1][0] y = point[1][1] pathCoords.append([x, y]) revPathCoords = pathCoords[:] # TODO: Ugly revPathCoords.reverse() if isInTotalPaths(pathCoords, totalPathCoords, self.options.radius): inkex.debug("I WANT TO BREAK FREE") node.getparent().remove(node) elif self.options.reverse and isInTotalPaths(revPathCoords, totalPathCoords, self.options.radius): inkex.debug("I WANT TO BREAK FREE") node.getparent().remove(node) else: totalPathCoords.append(pathCoords)
def snap_path_scale(self, elem, parent_transform=None): # If we have a Live Path Effect, modify original-d. If anyone clamours # for it, we could make an option to ignore paths with Live Path Effects original_d = "{%s}original-d" % inkex.NSS["inkscape"] path = simplepath.parsePath(elem.attrib.get(original_d, elem.attrib["d"])) transform = self.transform(elem, parent_transform=parent_transform) min_xy, max_xy = self.path_bounding_box(elem, parent_transform) width = max_xy[0] - min_xy[0] height = max_xy[1] - min_xy[1] # In case somebody tries to snap a 0-high element, # or a curve/arc with all nodes in a line, and of course # because we should always check for divide-by-zero! if width == 0 or height == 0: return rescale = round(width) / width, round(height) / height min_xy = transform_point(transform, min_xy, inverse=True) max_xy = transform_point(transform, max_xy, inverse=True) for i in range(len(path)): self.transform_path_node([[1, 0, -min_xy[0]], [0, 1, -min_xy[1]]], path, i) # center transform self.transform_path_node([[rescale[0], 0, 0], [0, rescale[1], 0]], path, i) self.transform_path_node([[1, 0, +min_xy[0]], [0, 1, +min_xy[1]]], path, i) # uncenter transform path = simplepath.formatPath(path) if original_d in elem.attrib: elem.attrib[original_d] = path else: elem.attrib["d"] = path
def test_simplepath(self): """Test simplepath API""" import simplepath data = 'M12 34L56 78Z' path = simplepath.parsePath(data) self.assertEqual(path, [['M', [12., 34.]], ['L', [56., 78.]], ['Z', []]]) d_out = simplepath.formatPath(path) d_out = d_out.replace('.0', '') self.assertEqual(data.replace(' ', ''), d_out.replace(' ', '')) simplepath.translatePath(path, -3, -4) self.assertEqual(path, [['M', [9., 30.]], ['L', [53., 74.]], ['Z', []]]) simplepath.scalePath(path, 10, 20) self.assertEqual(path, [['M', [90., 600.]], ['L', [530., 1480.]], ['Z', []]]) simplepath.rotatePath(path, math.pi / 2.0, cx=5, cy=7) approxed = [[code, approx(coords)] for (code, coords) in path] self.assertEqual( approxed, [['M', [-588., 92.]], ['L', [-1468., 532.]], ['Z', []]])
def convertToSegments(cls, path_node): path_start = None currentPoint = None # work on copy to be shure not breaking anything path = copy.deepcopy(path_node) # apply transformation info on path, otherwise dealing with transform would be a mess simpletransform.fuseTransform(path) for cmd, params in simplepath.parsePath(path.get('d')): print_('cmd, params', cmd, params) if cmd == 'M': if(path_start is None): path_start = params currentPoint = params elif cmd == 'L': yield Segment(currentPoint, params) currentPoint = params elif cmd in ['A', 'Q', 'C']: yield Segment(currentPoint, params[-2:], command=cmd, extra_parameters=params[:-2]) currentPoint = params[-2:] elif (cmd == 'Z'): # Z is a line between the last point and the start of the shape yield Segment(currentPoint, path_start) currentPoint = None path_start = None else: inkex.errormsg("Path Command %s not managed Yet" % cmd)
def load(self, node, mat): a = node.get('style').split(";") d = dict(s.split(':') for s in a) if d['stroke'] == "#ff0000": self.cutStyle = 2 elif d['stroke'] == "#0000ff": self.cutStyle = 3 else: self.cutStyle = 1 d = node.get('d') if len(simplepath.parsePath(d)) == 0: return p = cubicsuperpath.parsePath(d) applyTransformToPath(mat, p) # p is now a list of lists of cubic beziers [ctrl p1, ctrl p2, endpoint] # where the start-point is the last point in the previous segment self.segments = [] for sp in p: points = [] subdivideCubicPath(sp, 0.2) # TODO: smoothness preference for csp in sp: points.append((csp[1][0], csp[1][1])) self.segments.append(points)
def get_n_points_from_path( node, n):#returns a list of first n points (x,y) in an SVG path-representing node p = simplepath.parsePath(node.get('d')) #parse the path xi = [] #temporary storage for x and y (will combine at end) yi = [] for cmd,params in p: #a parsed path is made up of (cmd, params) pairs defs = simplepath.pathdefs[cmd] for i in range(defs[1]): if defs[3][i] == 'x' and len(xi) < n:#only collect the first three xi.append(params[i]) elif defs[3][i] == 'y' and len(yi) < n:#only collect the first three yi.append(params[i]) if len(xi) == n and len(yi) == n: points = [] # returned pairs of points for i in range(n): xi[i] = Draw_From_Triangle.unittouu(e, str(xi[i]) + 'px') yi[i] = Draw_From_Triangle.unittouu(e, str(yi[i]) + 'px') points.append( [ xi[i], yi[i] ] ) else: #inkex.errormsg(_('Error: Not enough nodes to gather coordinates.')) #fail silently and exit, rather than invoke an error console return [] #return a blank return points
def snap_path_scale(self, elem, parent_transform=None): # If we have a Live Path Effect, modify original-d. If anyone clamours # for it, we could make an option to ignore paths with Live Path Effects original_d = '{%s}original-d' % inkex.NSS['inkscape'] path = simplepath.parsePath(elem.attrib.get(original_d, elem.attrib['d'])) transform = self.transform(elem, parent_transform=parent_transform) min_xy, max_xy = self.path_bounding_box(elem, parent_transform) width = max_xy[0] - min_xy[0] height = max_xy[1] - min_xy[1] rescale = round(width)/width, round(height)/height min_xy = transform_point(transform, min_xy, inverse=True) max_xy = transform_point(transform, max_xy, inverse=True) for i in range(len(path)): self.transform_path_node([[1, 0, -min_xy[0]], [0, 1, -min_xy[1]]], path, i) # center transform self.transform_path_node([[rescale[0], 0, 0], [0, rescale[1], 0]], path, i) self.transform_path_node([[1, 0, +min_xy[0]], [0, 1, +min_xy[1]]], path, i) # uncenter transform path = simplepath.formatPath(path) if original_d in elem.attrib: elem.attrib[original_d] = path else: elem.attrib['d'] = path
def effect(self): for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path','svg'): self.group = inkex.etree.SubElement(node.getparent(),inkex.addNS('g','svg')) new = inkex.etree.SubElement(self.group,inkex.addNS('path','svg')) try: t = node.get('transform') self.group.set('transform', t) except: pass s = simplestyle.parseStyle(node.get('style')) s['stroke-linecap']='round' s['stroke-width']=self.options.dotsize new.set('style', simplestyle.formatStyle(s)) a =[] p = simplepath.parsePath(node.get('d')) num = 1 for cmd,params in p: if cmd != 'Z': a.append(['M',params[-2:]]) a.append(['L',params[-2:]]) self.addText(self.group,params[-2],params[-1],num) num += 1 new.set('d', simplepath.formatPath(a)) node.clear()
def snap_path_pos(self, elem, parent_transform=None): """ Goes through each node in the given path and modifies it as necessary in order to shift the entire path by the required (calculated) distance. """ # If we have a Live Path Effect, modify original-d. If anyone clamours # for it, we could make an option to ignore paths with Live Path Effects original_d = '{%s}original-d' % inkex.NSS['inkscape'] path = simplepath.parsePath( elem.attrib.get(original_d, elem.attrib['d'])) transform = self.get_transform(elem, parent_transform) min_xy, max_xy = self.path_bounding_box(elem, parent_transform) fractional_offset = min_xy[0] - round(min_xy[0]), min_xy[1] - round( min_xy[1]) - self.document_offset fractional_offset = transform_dimensions(transform, fractional_offset[0], fractional_offset[1], inverse=True) for i in range(len(path)): self.transform_path_node( [[1, 0, -fractional_offset[0]], [0, 1, -fractional_offset[1]]], path, i) path = simplepath.formatPath(path) if original_d in elem.attrib: elem.attrib[original_d] = path else: elem.attrib['d'] = path
def movePath(self, node, x, y, origin): tagName = self.getTagName(node) if tagName != "path": inkex.debug('movePath only works on SVG Path elements. Argument was of type "' + tagName + '"') return False path = simplepath.parsePath(node.get("d")) id = node.get("id") box = list(simpletransform.computeBBox([node])) offset_x = box[0] - x offset_y = box[2] - (y) for cmd in path: params = cmd[1] i = 0 while i < len(params): if i % 2 == 0: # inkex.debug('x point at ' + str( round( params[i] ))) params[i] = params[i] - offset_x # inkex.debug('moved to ' + str( round( params[i] ))) else: # inkex.debug('y point at ' + str( round( params[i]) )) params[i] = params[i] - offset_y # inkex.debug('moved to ' + str( round( params[i] ))) i = i + 1 return simplepath.formatPath(path)
def readTangents(nid, aSize=0.1, doPrint=False): el = e.getElementById(nid) if el is None: print "Cant find ", nid return points, svgL = shapereco.toArray(simplepath.parsePath(el.get('d'))) #print points tg = shapereco.buildTangents(points) res = e.checkForCircle(points, tg) e.points = points e.tg = tg (deltasD, angles, fits, dAdD) = e.temp #clList=shapereco.clusterValues(angles,aSize,refScaleAbs='abs') #clList.sort(key=lambda cl:len(cl)) #print [len(cl) for cl in clList] #print (len(clList[-1])+len(clList[-2]))/float(len(angles)) #print res deltasDD = (deltasD[1:] - deltasD[:-1]) belowT, count = True, 0 x, y, w, h = shapereco.computeBox(points) for i, v in enumerate(dAdD): if v > 6 and belowT: count += 1 belowT = False belowT = (v < 6) if doPrint: print 'deltasD ', deltasD print 'deltasDD ', deltasDD #print 'dist to fit=', dist_to_fit return deltasD[-1] / 3.14, count, numpy.sum( deltasDD[numpy.where(dAdD < 0.3)]) / (deltasD[-1] - deltasD[0])
def effect(self): for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path', 'svg'): d = node.get('d') p = simplepath.parsePath(d) last = [] subPathStart = [] for cmd,params in p: if cmd == 'C': if self.options.behave <= 1: #shorten handles towards end points params[:2] = pointAtPercent(params[:2],last[:],self.options.percent) params[2:4] = pointAtPercent(params[2:4],params[-2:],self.options.percent) else: #shorten handles towards thirds of the segment dest1 = pointAtPercent(last[:],params[-2:],33.3) dest2 = pointAtPercent(params[-2:],last[:],33.3) params[:2] = pointAtPercent(params[:2],dest1[:],self.options.percent) params[2:4] = pointAtPercent(params[2:4],dest2[:],self.options.percent) elif cmd == 'Q': dest = pointAtPercent(last[:],params[-2:],50) params[:2] = pointAtPercent(params[:2],dest,self.options.percent) if cmd == 'M': subPathStart = params[-2:] if cmd == 'Z': last = subPathStart[:] else: last = params[-2:] node.set('d',simplepath.formatPath(p))
def path_bounding_box(self, elem, parent_transform=None): """ Returns [min_x, min_y], [max_x, max_y] of the transformed element. (It doesn't make any sense to return the untransformed bounding box, with the intent of transforming it later, because the min/max points will be completely different points) The returned bounding box includes stroke-width offset. This function uses a simplistic algorithm & doesn't take curves or arcs into account, just node positions. """ # If we have a Live Path Effect, modify original-d. If anyone clamours # for it, we could make an option to ignore paths with Live Path Effects original_d = "{%s}original-d" % inkex.NSS["inkscape"] path = simplepath.parsePath(elem.attrib.get(original_d, elem.attrib["d"])) transform = self.transform(elem, parent_transform=parent_transform) offset = self.elem_offset(elem, parent_transform) min_x = min_y = max_x = max_y = 0 for i in range(len(path)): x, y = self.pathxy(path, i) x, y = transform_point(transform, (x, y)) if i == 0: min_x = max_x = x min_y = max_y = y else: min_x = min(x, min_x) min_y = min(y, min_y) max_x = max(x, max_x) max_y = max(y, max_y) return (min_x - offset, min_y - offset), (max_x + offset, max_y + offset)
def parseArc(self,node,parent): #self.parsing_context = 'arc' style = node.get('style') style = self.parseStyleAttribute(style) arc = { 'id':node.get('id',''), 'svg':'arc', 'label':str(node.get(inkex.addNS('label', 'inkscape'),'')), 'cx': node.get(inkex.addNS('cx', 'sodipodi'),''), 'cy':node.get(inkex.addNS('cy', 'sodipodi'),''), 'rx':node.get(inkex.addNS('rx', 'sodipodi'),''), 'ry':node.get(inkex.addNS('ry', 'sodipodi'),''), 'path':simplepath.parsePath(node.get('d')), 'd':node.get('d',''), 'box':list(simpletransform.computeBBox([node])), 'fill':style.get('fill',''), 'fill-opacity':style.get('fillOpacity',''), 'stroke':style.get('stroke',''), 'stroke-width':style.get('strokeWidth',''), 'stroke-opacity':style.get('strokeOpacity',''), 'transform':node.get('transform','') } if(self.reposition): arc['path'] = self.movePath(node,0,0,'tl') else: arc['path'] = arc['d'] parent.append(arc)
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 pathdata_last_point(path): ''' Return the last (X,Y) point from an SVG path data string Input: A path data string; the text of the 'd' attribute of an SVG path Output: Two floats in a list representing the x and y coordinates of the last point ''' command, params = simplepath.parsePath(path)[ -1] # parsePath splits path into segments if command.upper() == 'Z': return pathdata_first_point(path) # Trivial case ''' Otherwise: The last command should be in the set 'MLCQA' - All commands converted to absolute by parsePath. - Can ignore Z (case handled) - Can ignore H,V, since those are converted to L by parsePath. - Can ignore S, converted to C by parsePath. - Can ignore T, converted to Q by parsePath. MLCQA: Commands all ending in (X,Y) pair. ''' x_val = params[-2] # Second to last parameter given y_val = params[-1] # Last parameter given return [x_val, y_val]
def effect(self): for id, node in self.selected.iteritems(): if node.tagName == 'path': p = simplepath.parsePath(node.attributes.getNamedItem('d').value) a =[] pen = None subPathStart = None for cmd,params in p: if cmd == 'C': a.extend([['M', params[:2]], ['L', pen], ['M', params[2:4]], ['L', params[-2:]]]) if cmd == 'Q': a.extend([['M', params[:2]], ['L', pen], ['M', params[:2]], ['L', params[-2:]]]) if cmd == 'M': subPathStart = params if cmd == 'Z': pen = subPathStart else: pen = params[-2:] if len(a) > 0: new = self.document.createElement('svg:path') s = {'stroke-linejoin': 'miter', 'stroke-width': '1.0px', 'stroke-opacity': '1.0', 'fill-opacity': '1.0', 'stroke': '#000000', 'stroke-linecap': 'butt', 'fill': 'none'} new.setAttribute('style', simplestyle.formatStyle(s)) new.setAttribute('d', simplepath.formatPath(a)) node.parentNode.appendChild(new)
def path_to_segments(node, smoothness=0.1): ''' Generator to convert a path node to an interator on segmented paths (bezier curves broken to approximated straights lines). ''' mat = simpletransform.composeParents(node, [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) d = node.get('d') if len(simplepath.parsePath(d)) == 0: return p = cubicsuperpath.parsePath(d) simpletransform.applyTransformToPath(mat, p) # p is now a list of lists of cubic beziers [ctrl p1, ctrl p2, endpoint] # where the start-point is the last point in the previous segment for sp in p: path = [] subdivideCubicPath(sp, smoothness) for csp in sp: path.append([csp[1][0], csp[1][1]]) yield path
def effect(self): if len(self.options.ids) < 2: raise Exception, "Two paths must be selected. The 1st is the letter, the 2nd is the envelope and must have 4 sides." exit() letterElement = self.selected[self.options.ids[0]] envelopeElement = self.selected[self.options.ids[1]] if letterElement.tag != inkex.addNS( 'path', 'svg') or envelopeElement.tag != inkex.addNS( 'path', 'svg'): raise Exception, "Both letter and envelope must be SVG paths." exit() axes = extractMorphAxes(simplepath.parsePath(envelopeElement.get('d'))) if axes is None: raise Exception, "No axes found on envelope." axisCount = len(axes) if axisCount < 4: raise Exception, "The envelope path has less than 4 segments." for i in range(0, 4): if axes[i] is None: raise Exception, "axes[%i] is None" % i # morph the enveloped element according to the axes morphElement(letterElement, envelopeElement, axes)
def effect(self): for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path', 'svg'): d = node.get('d') p = simplepath.parsePath(d) a = [] first = 1 for cmd, params in p: if cmd != 'Z': if first == 1: x1 = params[-2] y1 = params[-1] a.append(['M', params[-2:]]) first = 2 else: x2 = params[-2] y2 = params[-1] self.fractalize(a, x1, y1, x2, y2, self.options.subdivs, self.options.smooth) x1 = x2 y1 = y2 a.append(['L', params[-2:]]) node.set('d', simplepath.formatPath(a))
def get_n_points_from_path( node, n ): #returns a list of first n points (x,y) in an SVG path-representing node p = simplepath.parsePath(node.get('d')) #parse the path xi = [] #temporary storage for x and y (will combine at end) yi = [] for cmd, params in p: #a parsed path is made up of (cmd, params) pairs defs = simplepath.pathdefs[cmd] for i in range(defs[1]): if defs[3][i] == 'x' and len( xi) < n: #only collect the first three xi.append(params[i]) elif defs[3][i] == 'y' and len( yi) < n: #only collect the first three yi.append(params[i]) if len(xi) == n and len(yi) == n: points = [] # returned pairs of points for i in range(n): points.append([xi[i], yi[i]]) else: #inkex.errormsg(_('Error: Not enough nodes to gather coordinates.')) #fail silently and exit, rather than invoke an error console return [] #return a blank return points
def plotPath( self, path, matTransform ): ''' Plot the path while applying the transformation defined by the matrix [matTransform]. ''' # turn this path into a cubicsuperpath (list of beziers)... d = path.get( 'd' ) if len( simplepath.parsePath( d ) ) == 0: return p = cubicsuperpath.parsePath( d ) # ...and apply the transformation to each point applyTransformToPath( matTransform, p ) # p is now a list of lists of cubic beziers [control pt1, control pt2, endpoint] # where the start-point is the last point in the previous segment. for sp in p: plot_utils.subdivideCubicPath( sp, self.options.smoothness ) nIndex = 0 for csp in sp: if self.bStopped: return self.fX = 2 * float( csp[1][0] ) / self.step_scaling_factor self.fY = 2 * float( csp[1][1] ) / self.step_scaling_factor # store home if self.ptFirst is None: # if we should start at center, then the first line segment should draw from there if self.options.startCentered: self.fPrevX = self.svgWidth / ( self.step_scaling_factor ) self.fPrevY = self.svgHeight / ( self.step_scaling_factor ) self.ptFirst = ( self.fPrevX, self.fPrevY ) else: self.ptFirst = ( self.fX, self.fY ) if self.plotCurrentLayer: if nIndex == 0: if (plot_utils.distance(self.fX - self.fPrevX,self.fY - self.fPrevY) > eggbot_conf.MIN_GAP): # Only raise pen between two points if there is at least a 1 step gap between them. self.penUp() self.virtualPenIsUp = True elif nIndex == 1: self.penDown() self.virtualPenIsUp = False nIndex += 1 if self.plotCurrentLayer: self.plotLineAndTime() self.fPrevX = self.fX self.fPrevY = self.fY
def effect(self): """This is the main entry point""" # based partially on the restack.py extension if len(self.selected) > 0: # TODO check for non-path elements? # TODO it seems like the order of selection is not consistent # => self.selected is a dict so it has no meaningful order and should not be used to evaluate the original path length #fid = open("/home/matthew/debug.txt", "w") # for each selected item # I can think of two options: # 1. Iterate over all paths in root, then iterate over all layers, and their paths # 2. Some magic with xpath? (would this limit us to specific node types?) objlist = [] for id, node in self.selected.iteritems(): (sx, sy, ex, ey) = self.get_start_end(node) path = Path(id, (sx, sy), (ex, ey)) objlist.append(path) # sort / order the objects sort_order, air_distance_default, air_distance_ordered = find_ordering( objlist, self.options.allowReverse) reverseCount = 0 for path in sort_order: node = self.selected[path.id] if node.tag == inkex.addNS('path', 'svg'): node_sp = simplepath.parsePath(node.get('d')) if (path.reversed): node_sp_string = reversePath(node_sp) reverseCount += 1 else: node_sp_string = simplepath.formatPath(node_sp) node.set('d', node_sp_string) #keep in mind the different selected ids might have different parents self.getParentNode(node).append(node) inkex.errormsg("Reversed {} paths.".format(reverseCount)) #fid.close() if air_distance_default > 0: # don't divide by zero. :P improvement_pct = 100 * ( (air_distance_default - air_distance_ordered) / (air_distance_default)) inkex.errormsg( gettext.gettext( "Selected paths have been reordered and optimized for quicker EggBot plotting.\n\nDefault air-distance: %d\nOptimized air-distance: %d\nDistance reduced by: %1.2d%%\n\nHave a nice day!" % (air_distance_default, air_distance_ordered, improvement_pct))) else: inkex.errormsg( gettext.gettext( "Unable to start. Please select multiple distinct paths. :)" ))
def _handle_path(self, node): try: raw_path = node.get('d') p = simplepath.parsePath(raw_path) except: logging.warning('Failed to parse path %s, will ignore it', raw_path) p = None return p, []
def point_generator(path): if len(simplepath.parsePath(path)) == 0: return simple_path = simplepath.parsePath(path) startX,startY = float(simple_path[0][1][0]), float(simple_path[0][1][1]) yield startX, startY p = cubicsuperpath.parsePath(path) for sp in p: cspsubdiv.subdiv( sp, .2 ) for csp in sp: ctrl_pt1 = csp[0] ctrl_pt2 = csp[1] end_pt = csp[2] yield end_pt[0], end_pt[1],
def plotPath( self, path, matTransform ): ''' Plot the path while applying the transformation defined by the matrix [matTransform]. ''' # turn this path into a cubicsuperpath (list of beziers)... d = path.get( 'd' ) if len( simplepath.parsePath( d ) ) == 0: return p = cubicsuperpath.parsePath( d ) # ...and apply the transformation to each point applyTransformToPath( matTransform, p ) # p is now a list of lists of cubic beziers [control pt1, control pt2, endpoint] # where the start-point is the last point in the previous segment. for sp in p: plot_utils.subdivideCubicPath( sp, self.options.smoothness ) nIndex = 0 for csp in sp: if self.bStopped: return if self.plotCurrentLayer: if nIndex == 0: self.penUp() self.virtualPenIsUp = True elif nIndex == 1: self.penDown() self.virtualPenIsUp = False nIndex += 1 self.fX = 2 * float( csp[1][0] ) / self.step_scaling_factor self.fY = 2 * float( csp[1][1] ) / self.step_scaling_factor # store home if self.ptFirst is None: # if we should start at center, then the first line segment should draw from there if self.options.startCentered: self.fPrevX = self.svgWidth / ( self.step_scaling_factor ) self.fPrevY = self.svgHeight / ( self.step_scaling_factor ) self.ptFirst = ( self.fPrevX, self.fPrevY ) else: self.ptFirst = ( self.fX, self.fY ) if self.plotCurrentLayer: self.plotLineAndTime() self.fPrevX = self.fX self.fPrevY = self.fY
def effect(self): """ This method is called first, and sets up the self.commands list for later output. """ svg = self.document.getroot() # find document width and height, used to scale down self.doc_width = inkex.unittouu(svg.get('width')) self.doc_height = inkex.unittouu(svg.get('height')) # add header self.commands.append("^DF;") self.commands.append("! 1;") self.commands.append("H;") self.commands.append("@ %d %d;" % (self.options.z_down, self.options.z_up)) self.commands.append("V {0};F {0};\n".format(self.options.feed_rate_moving)) self.commands.append("Z 0 0 %d;" % self.options.z_up) # mostly borrowed from hgpl_output.py lastX = 0 lastY = 0 # find paths in layers i = 0 layerPath = '//svg:g[@inkscape:groupmode="layer"]' for layer in svg.xpath(layerPath, namespaces=inkex.NSS): i += 1 nodePath = ('//svg:g[@inkscape:groupmode="layer"][%d]/descendant::svg:path') % i for node in svg.xpath(nodePath, namespaces=inkex.NSS): # these next lines added from this patch to fix the transformation issues - http://launchpadlibrarian.net/36269154/hpgl_output.py.patch # possibly also want to try this code: https://bugs.launchpad.net/inkscape/+bug/600472/+attachment/1475310/+files/hpgl_output.py transforms = node.xpath("./ancestor-or-self::svg:*[@transform]",namespaces=inkex.NSS) matrix = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] for parenttransform in transforms: newmatrix = simpletransform.parseTransform(parenttransform.get("transform")) matrix = simpletransform.composeTransform(matrix, newmatrix) d = node.get('d') if len(simplepath.parsePath(d)): p = cubicsuperpath.parsePath(d) simpletransform.applyTransformToPath(matrix, p) # this line is also from the transform-fixing patch mentioned above cspsubdiv.cspsubdiv(p, self.options.flat) for sp in p: first = True for csp in sp: if first: x, y = self.conv_coords(csp[1][0], self.doc_height - csp[1][1]) self.commands.append("Z %d %d %d;" % (x, y, self.options.z_up)) self.commands.append("V {0};F {0};".format(self.options.feed_rate_cutting)) first = False x, y = self.conv_coords(csp[1][0], self.doc_height - csp[1][1]) self.commands.append("Z %d %d %d;" % (x, y, self.options.z_down)) lastX = x lastY = y self.commands.append("V {0};F {0};".format(self.options.feed_rate_moving)) self.commands.append("Z %d %d %d;" % (lastX, lastY, self.options.z_up)) self.commands.append("Z 0 0 %d;" % self.options.z_up) self.commands.append("H;")
def getTranslatedPath(d, posX, posY): if(CommonDefs.inkVer == 1.0): path = Path(d) path.translate(posX, posY, inplace = True) return path.to_superpath().__str__() else: path = simplepath.parsePath(d) simplepath.translatePath(path, posX, posY) return simplepath.formatPath(path)
def effect(self): self.vx = math.cos(math.radians( self.options.angle)) * self.options.magnitude self.vy = math.sin(math.radians( self.options.angle)) * self.options.magnitude for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path', 'svg'): group = inkex.etree.SubElement(node.getparent(), inkex.addNS('g', 'svg')) self.facegroup = inkex.etree.SubElement( group, inkex.addNS('g', 'svg')) group.append(node) t = node.get('transform') if t: group.set('transform', t) node.set('transform', '') s = node.get('style') self.facegroup.set('style', s) p = simplepath.parsePath(node.get('d')) for cmd, params in p: tees = [] if cmd == 'C': bez = (last, params[:2], params[2:4], params[-2:]) tees = [ t for t in bezmisc.beziertatslope( bez, (self.vy, self.vx)) if 0 < t < 1 ] tees.sort() segments = [] if len(tees) == 0 and cmd in ['L', 'C']: segments.append([cmd, params[:]]) elif len(tees) == 1: one, two = bezmisc.beziersplitatt(bez, tees[0]) segments.append([cmd, list(one[1] + one[2] + one[3])]) segments.append([cmd, list(two[1] + two[2] + two[3])]) elif len(tees) == 2: one, two = bezmisc.beziersplitatt(bez, tees[0]) two, three = bezmisc.beziersplitatt(two, tees[1]) segments.append([cmd, list(one[1] + one[2] + one[3])]) segments.append([cmd, list(two[1] + two[2] + two[3])]) segments.append( [cmd, list(three[1] + three[2] + three[3])]) for seg in segments: self.makeface(last, seg) last = seg[1][-2:] if cmd == 'M': subPathStart = params[-2:] if cmd == 'Z': last = subPathStart else: last = params[-2:]
def __call__(self, elem): # logger.debug(elem.attrib) path = elem.get("d") if path is None: return [] parsed = simplepath.parsePath(path) self.paths.append((parsed, elem)) # logger.debug(parsed) return []
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 # parsePath() may raise an exception. This is okay sp = simplepath.parsePath(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.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 effect(self): clippingLineSegments = None pathTag = inkex.addNS('path', 'svg') groupTag = inkex.addNS('g', 'svg') error_messages = [] for id in self.options.ids: # the selection, top-down node = self.selected[id] if node.tag == pathTag: if clippingLineSegments is None: # first path is the clipper (clippingLineSegments, errors) = self.simplepathToLineSegments( simplepath.parsePath(node.get('d'))) error_messages.extend( ['{}: {}'.format(id, err) for err in errors]) else: # do all the work! segmentsToClip, errors = self.simplepathToLineSegments( simplepath.parsePath(node.get('d'))) error_messages.extend( ['{}: {}'.format(id, err) for err in errors]) clippedSegments = self.clipLineSegments( segmentsToClip, clippingLineSegments) if len(clippedSegments) != 0: # replace the path node.set( 'd', simplepath.formatPath( self.linesgmentsToSimplePath(clippedSegments))) else: # don't put back an empty path(?) could perhaps put move, move? inkex.debug( 'Object {} clipped to nothing, will not be updated.' .format(node.get('id'))) elif node.tag == groupTag: # we don't look inside groups for paths inkex.debug( 'Group object {} will be ignored. Please ungroup before running the script.' .format(id)) else: # something else inkex.debug( 'Object {} is not of type path ({}), and will be ignored. Current type "{}".' .format(id, pathTag, node.tag)) for error in error_messages: inkex.debug(error)
def addPathVertices( self, path, node=None, transform=None, cloneTransform=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 # parsePath() may raise an exception. This is okay sp = simplepath.parsePath( 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.CubicSuperPath( sp ) if ( not p ) or ( len( p ) == 0 ): # Probably never happens, but... return #if transform: # simpletransform.applyTransformToPath( transform, p ) # 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, float( 0.2 ) ) for csp in sp: # Add this vertex to the list of vetices 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 len( subpath_list ) == 0: 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] = cloneTransform
def process_path(self, ipath, mat_x, mat_y, term1): mat = simpletransform.composeParents( ipath, [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) path_string = ipath.get('d') svg_path = simplepath.parsePath(path_string) paths = [] # for path in svg_path: # inkex.debug("svg_path: " + str(svg_path)) for path in svg_path: if path[0] == 'M' or path[0] == 'L': paths.append([path[0], [path[1]]]) elif path[0] == 'C' or path[0] == 'Q': pts = [] if path[0] == 'C': num = 3 else: num = 2 for i in range(0, num): pt = [path[1][2 * i], path[1][2 * i + 1]] pts.append(pt) paths.append([path[0], pts]) elif path[0] == 'Z': paths.append(['Z', []]) # inkex.debug("paths: " + str(paths) + "\n\n") mat = simpletransform.invertTransform(mat) # simpletransform.applyTransformToPath(mat, p) for path in paths: for pt in path[1]: simpletransform.applyTransformToPoint(mat, pt) # do transformation for path in paths: for pt in path[1]: self.project_point(pt, mat_x, mat_y, term1) # back to original form res_paths = [] for path in paths: if path[0] == 'C' or path[0] == 'Q': flat_pts = [] for pt in path[1]: flat_pts.append(pt[0]) flat_pts.append(pt[1]) res_paths.append([path[0], flat_pts]) elif path[0] == 'M' or path[0] == 'L': res_paths.append([path[0], path[1][0]]) elif path[0] == 'Z': res_paths.append(path) # inkex.debug("res_paths: " + str(res_paths)) res_svg_paths = simplepath.formatPath(res_paths) # inkex.debug("res_svg_paths: " + str(res_svg_paths)) ipath.set('d', res_svg_paths)
def effect(self): linewidth=int(self.options.linewidth) # メインのルート要素であるSVGを取得 svg = self.document.getroot() pathlist=svg.findall(ALL+"{"+inkex.NSS['svg']+"}path") for path in pathlist: if path == None: inkex.errormsg(u"パスを書いてください!!") #パスの頂点座標を取得 vals=simplepath.parsePath(path.get('d')) bone=[] for cmd,vert in vals: #たまに空のが入ってるため、それ対策 if len(vert) != 0: bone.append(Vertex(vert[0],vert[1])) outVertexArray=stripline(bone,linewidth,self.options.logfilename) pointer=0 for t in range(len(outVertexArray)-2): tri=Triangle(outVertexArray[pointer][0],outVertexArray[pointer+1][0],outVertexArray[pointer+2][0]) stripstr=tri.toSVG() color2="blue" if outVertexArray[pointer][1]: color="blue" else: color="red" pointer+=1 attributes={"points":stripstr, "style":"fill:"+color2+";stroke:"+color2+";stroke-width:"+str(linewidth/3),"fill-opacity":"0.5"} pth =inkex.etree.Element("polygon",attributes) svg.append(pth) pointer=0 #+−を示す点を描く for t in range(len(outVertexArray)): if outVertexArray[pointer][1]: point=Plus(outVertexArray[pointer][0].x,outVertexArray[pointer][0].y,linewidth/3) color="blue" else: point=Minus(outVertexArray[pointer][0].x,outVertexArray[pointer][0].y,linewidth/3) color="red" if pointer==0: color="#6f0018"#暗い赤 point.appendToSVG(color,svg) #svg.append(Circle.toSVG(outVertexArray[pointer][0].x,outVertexArray[pointer][0].y,linewidth/3,color,0)) pointer+=1 pointer=0 pathstr="M " for t in range(len(outVertexArray)): pathstr+=str(outVertexArray[pointer][0].x)+" "+str(outVertexArray[pointer][0].y)+" " pointer+=1 att3={"d":pathstr,"r":"1","fill":"none","stroke-width":"1","stroke":"white"} pt=inkex.etree.Element("path",att3)
def plotPath( self, path, matTransform ): # MIP ''' Plot the path while applying the transformation defined by the matrix [matTransform]. ''' # turn this path into a cubicsuperpath (list of beziers)... d = path.get( 'd' ) if len( simplepath.parsePath( d ) ) == 0: return p = cubicsuperpath.parsePath( d ) # ...and apply the transformation to each point applyTransformToPath( matTransform, p ) # p is now a list of lists of cubic beziers [control pt1, control pt2, endpoint] # where the start-point is the last point in the previous segment. for sp in p: subdivideCubicPath( sp, self.options.smoothness ) nIndex = 0 for csp in sp: if self.bStopped: return if self.plotCurrentLayer: if nIndex == 0: # Pen up to start of curve self.ms.cmd_pen_up() self.virtualPenIsUp = True elif nIndex == 1: # Pen down to the end of the curve self.ms.cmd_pen_down() self.virtualPenIsUp = False nIndex += 1 self.fX = float( csp[1][0] ) self.fY = float( csp[1][1] ) # Precondition: where the heck are we coming from? if self.ptFirst is None: self.fPrevX = self.ms.area_x() self.fPrevY = self.ms.area_y() self.ptFirst = (self.fPrevX, self.fPrevY) if self.plotCurrentLayer: self.plotLineAndTime() self.fPrevX = self.fX self.fPrevY = self.fY self.ms.cmd_pen_up() # Raise the pen at the end of each curve
def load(self, node, mat): self.id = node.get('id') d = node.get('d') if len(simplepath.parsePath(d)) == 0: return p = cubicsuperpath.parsePath(d) applyTransformToPath(mat, p) # p is now a list of lists of cubic beziers [ctrl p1, ctrl p2, endpoint] # where the start-point is the last point in the previous segment self.points = [] self.paths = [] for sp in p: path = [] subdivide_cubic_path(sp, 0.2) # TODO: smoothness preference for csp in sp: point = [csp[1][0], csp[1][1]] if point not in self.points: self.points.append(point) path.append(self.points.index(point)) self.paths.append(path) # get color style = node.get('style') try: hexcolor = re.search('fill:#([0-9a-fA-f]{6})', style).group(1) rgb = [ int(hexcolor[0:2], 16), int(hexcolor[2:4], 16), int(hexcolor[4:6], 16) ] except (TypeError, AttributeError): rgb = None # get optional opacity try: opacity = float(re.search('(?:^|;)opacity:([0-9]*\.?[0-9]+)', style).group(1)) except (TypeError, AttributeError): opacity = 1.0 # get optional fill-opacity (of course there is more than one way to set opacity of paths...) try: fill_opacity = float(re.search('(?:^|;)fill-opacity:([0-9]*\.?[0-9]+)', style).group(1)) except (TypeError, AttributeError): fill_opacity = 1.0 if rgb: self.color = [ rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0, opacity * fill_opacity ] else: self.color = None
def loadPath(svgPath): extensionDir = os.path.normpath(os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]))) tree = inkex.etree.parse(extensionDir + "/" + svgPath) root = tree.getroot() pathElement = root.find("{http://www.w3.org/2000/svg}path") if pathElement == None: return None, 0, 0 d = pathElement.get("d") width = float(root.get("width")) height = float(root.get("height")) return simplepath.parsePath(d), width, height # Currently we only support a single path
def point_generator(path, mat, flatness): if len(simplepath.parsePath(path)) == 0: return simple_path = simplepath.parsePath(path) startX,startY = float(simple_path[0][1][0]), float(simple_path[0][1][1]) yield startX, startY p = cubicsuperpath.parsePath(path) if mat: simpletransform.applyTransformToPath(mat, p) for sp in p: cspsubdiv.subdiv( sp, flatness) for csp in sp: ctrl_pt1 = csp[0] ctrl_pt2 = csp[1] end_pt = csp[2] yield end_pt[0], end_pt[1],
def effect(self): self.vx = math.cos(math.radians(self.options.angle))*self.options.magnitude self.vy = math.sin(math.radians(self.options.angle))*self.options.magnitude for id, node in self.selected.iteritems(): if node.tagName == 'path': group = self.document.createElement('svg:g') self.facegroup = self.document.createElement('svg:g') node.parentNode.appendChild(group) group.appendChild(self.facegroup) group.appendChild(node) try: t = node.attributes.getNamedItem('transform').value group.setAttribute('transform', t) node.attributes.getNamedItem('transform').value="" except AttributeError: pass s = node.attributes.getNamedItem('style').value self.facegroup.setAttribute('style', s) p = simplepath.parsePath(node.attributes.getNamedItem('d').value) for cmd,params in p: tees = [] if cmd == 'C': bez = (last,params[:2],params[2:4],params[-2:]) tees = [t for t in bezmisc.beziertatslope(bez,(self.vy,self.vx)) if 0<t<1] tees.sort() segments = [] if len(tees) == 0 and cmd in ['L','C']: segments.append([cmd,params[:]]) elif len(tees) == 1: one,two = bezmisc.beziersplitatt(bez,tees[0]) segments.append([cmd,list(one[1]+one[2]+one[3])]) segments.append([cmd,list(two[1]+two[2]+two[3])]) elif len(tees) == 2: one,two = bezmisc.beziersplitatt(bez,tees[0]) two,three = bezmisc.beziersplitatt(two,tees[1]) segments.append([cmd,list(one[1]+one[2]+one[3])]) segments.append([cmd,list(two[1]+two[2]+two[3])]) segments.append([cmd,list(three[1]+three[2]+three[3])]) for seg in segments: self.makeface(last,seg) last = seg[1][-2:] if cmd == 'M': subPathStart = params[-2:] if cmd == 'Z': last = subPathStart else: last = params[-2:]
def parsePath(d): ''' Parse line and replace quadratic bezier segments and arcs by cubic bezier segments. ''' import simplepath p = simplepath.parsePath(d) if any(cmd not in 'MLCZ' for (cmd,params) in p): import cubicsuperpath csp = cubicsuperpath.CubicSuperPath(p) p = cubicsuperpath.unCubicSuperPath(csp) return p