mathandy / svgpathtools Goto Github PK
View Code? Open in Web Editor NEWA collection of tools for manipulating and analyzing SVG Path objects and Bezier curves.
License: MIT License
A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves.
License: MIT License
I was a bit puzzled by a discrepancy in the curvature of a complete path and the curvature of its corresponding segment. Try running the following
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc
seg1 = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j) # A cubic beginning at (300, 100) and ending at (200, 300)
seg2 = Line(200+300j, 250+350j) # A line beginning at (200, 300) and ending at (250, 350)
path = Path(seg1, seg2) # A path traversing the cubic and then the line
T=0.5
k = path.curvature(T)
idx,t = path.T2t(T)
k_seg = path[idx].curvature(t)
print(k,k_seg)
This bug arises because the derivatives of the Path object are taken with respect to the t
variable of the segment and not the T
variable of the Path object itself. This can be fixed by modifying the following lines.
svgpathtools/svgpathtools/path.py
Line 2070 in bf95944
svgpathtools/svgpathtools/path.py
Line 2071 in bf95944
It would also seem to be possible to use
return seg.curvature(t)
rather than the explicit formula itself.
It would be helpful if your great lib would offer a function which would not write to a .svg file, like the existing disvg()
, but returning the svgwrite.Drawing
object.
This way it would be possible to add additional stuff like defs
to the Drawing. Or is there already a way to do this?
Hi,
Thanks for your good library !
An SVG file contain a simple rect. If i open this SVG file with a rect (=closed) with :
paths,attribute=spt.svg2paths('test.svg')
I obtain :
paths
Out[14]:
[Path(Line(start=(28.571428299+29.5050621033j), end=(325.714281082+29.5050621033j)),
Line(start=(325.714281082+29.5050621033j), end=(325.714281082+280.933635711j)),
Line(start=(325.714281082+280.933635711j), end=(28.571428299+280.933635711j)),
Line(start=(28.571428299+280.933635711j), end=(28.571428299+29.5050621033j)))]
And now, if I display the d string, I obtain :
paths[0].d()
Out[15]: 'M 28.571428299,29.5050621033 L 325.714281082,29.5050621033 L 325.714281082,280.933635711 L 28.571428299,280.933635711 L 28.571428299,29.5050621033'
And internally :
paths[0].isclosed()
Out[11]: True
But when I open the file with :
spt.disvg(paths)
It's logically open (see d), not closed. I think the d attribute should be :
'M 28.571428299,29.5050621033 L 325.714281082,29.5050621033 L 325.714281082,280.933635711 L 28.571428299,280.933635711 Z'
The problem seem exist with circle so.
I think Z should be added to d field when path is closest : isn't it ?
Xavier HINAULT
France
I want output a d string using 'S' instead of 'C'. the d()
methode has the useSandT
argument to influence this, but I haven't found anyway to set this when using disvg()
.
The Document class uses a dict to lookup the functions for the leaf node objects and calls them from the svg_to_paths file but the polyline and polygon functions do not properly take a group they specifically take a points_string, so they actually fail when called like that.
"""converts the string from a polyline points-attribute to a string for a Path object d-attribute"""
Compare this to something like ellipse:
"""converts the parameters from an ellipse or a circle to a string for a Path object d-attribute"""
Do note, that simply fixing the polyline function to set polyline_d = polyline['d']
will seemingly change the functionality of dom2dict() in svg_to_paths:
if convert_polygons_to_paths:
pgons = [dom2dict(el) for el in doc.getElementsByTagName('polygon')]
d_strings += [polygon2pathd(pg['points']) for pg in pgons]
attribute_dictionary_list += pgons
I rewrote the functions in a consistent manner in the svg_parser.py file in pull request #69. Generally because it the inconsistency of extracting the points early for those two conversions bits was an eyesore.
There is a issue with closed paths. If the penultimate command ends on the start point, the z command creates a zero length svgpathtools.path.Line. That breaks a lot of things later, something like div by zero. I think a simple check for current_pos == start_pos in parser will do well.
I am drawing contours, referenced from origin (0,0). Those objects have strictly positive coordinates.
However, when drawing them with wsvg, the following issues appear:
(0, document_height)
), not global origin.Is there a way to avoid this behavior that sound like a bug to me ?
EDIT:
From my experiments, it looks like document coordinate system is oriented y-down, while SVG coordinate system is y-up. Is it normal ?
Could you suggest a method to extract text and their bounding boxe coordinates with respect to viewbox .
Example in an SVG created in Inkscape:
<g
id="g3450-3"
transform="matrix(-1e-4,-1,1,-1e-4,670.77002,132.92999)">
<path
inkscape:connector-curvature="0"
d="m 28.35,0 c 0,15.65621 -12.69107,28.3485 -28.34728,28.35"
style="fill:none;stroke:#000000;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1;stroke-dasharray:none"
id="path3452-8" />
The group transformation is ignored when calling svgpathtools.svg2paths()
.
We need the functionality used in the primitive conversion functions: ellipse2pathd
, polyline2pathd
, polygon2pathd
and rect2pathd
Unfortunately, as svgpathtools.__init__
exposes a function with the same name as the module svg2paths
, importing these externally is not possible.
Would it be possible to allow these to be importable? I am happy to do any refactoring required if this is a change you would accept. At the moment we have the code copy / pasted in our repo, and would be nicer to avoid this duplication.
Hi,
First of all, thank you for the svgpathtools.
It has saved me lots of time.
Really just a small issue.
I did not see a convenient way to obtain the inflection points of a bezier curve.
@mathandy, Would you accept a contribution for that?
All the best,
olaf
See code here. If you try to crop with T0 > T1, it checks if the path is closed, and only allows it if the path is open.
Saving long paths with a lot of segments is really slow. I figured out svgwrite runs by default with some debug flags. Svgwrite does some validation stuff like checking syntax of path's d attribute. The parser that they use runs amok.
I think the best approach is to disable debug mode and to turn validation off (#7). The data you put into svgwrite is valid, paths are syntactically correct. And if some arguments are broken, most programs can deal with it.
Hi folks - thanks for the great library.
I'm not sure if this functionality is within the scope of this library, but I'm going to put it up for discussion here anyway.
I'd like to approximate a CubicBezier
with a series of Line
objects. This has many applications, for example in drawing SVGs using an XY plotter (which is my specific need).
The trivial way to do this is to divide the curve into an equal number of points and create Line objects connecting each new segment along the path. This however is not guaranteed to give a good representation of the curve, and is likely to contain an excessive number of points.
A good way to make this approximation is to do it in an adaptive way. Break the curve into smaller curves (CubicBezier.split()
) until a certain flatness criterion is met. After doing a bit of research, this seems to be the most referneced article on the subject. The method is critiqued here, especially for not having consulted prior academic research on the subject.
I'd like to implement something like this, and possibly contribute it to this library if you see that it would fit. But before I do so, I'd like to get your recommendations on what algorithm would be appropriate for this purpose.
The antigrain algorithm is one possibility, and in the google groups thread there's another interesting suggestion:
A well-known flatness test is both cheaper and more reliable than the ones you have tried. The essential observation is that when the curve is a uniform speed straight line from end to end, the control points are evenly spaced from beginning to end. Therefore, our measure of how far we deviate from that ideal uses distance of the middle controls, not from the line itself, but from their ideal arrangement. Point 2 should be half-way between points 1 and 3; point 3 should be half-way between points 2 and 4.
This, too, can be improved. Yes, we can eliminate the square roots in the distance tests, retaining the Euclidean metric; but the taxicab metric is faster, and also safe. The length of displacement (x,y) in this metric is |x|+|y|.
What do you suggest as a good algorithm to implement in this case? Would love some feedback.
Hi there, I realize that Arc intersections are not fully implemented and not fully supported, but I'm opening this issue anyway to track some problems I ran in to. I don't have a solution to offer, but I do have some unit tests that expose the bug. The tests are in https://github.com/SebKuzminsky/svgpathtools/commits/buggy-arc-line-intersection.
Hello,
I have an svg with viewbox(0,0,612,792) . bbox on any path elements gives complex numbers .Is there a way to convert them into coordinates with respect to top left corner of viewbox?
Path(Line(start=(790.996+1827j), end=(790.996+1804j)))
I am using path[0].length() to find lines with certain length and replace them with lines across/along the viewbox at same locations.
If line1= x1,y1,x1,y2 then replacement would be x1,y1,x1,612
Is there anyway to get out svg to generate a relative path instead of an absolute one?
Instead of this:
<path d="M 0.0,1.0 L 2.0,1.0 L 3.0,2.0" fill="none" stroke="#000000" stroke-width="1.175"/>
something like this:
<path d="m 18.815667,1 h 2 l 1,1" fill="none" stroke="#000000" stroke-width="1.175"/>
Path.sub(t1, t2) to extract a subpath. E.g. Path.sub(0,0.5) to extract the first half path.
Hello, i am working on SVG to g-code convertor for bCNC and i need way to subdivide the SVG to list of paths made just from LINEs and ARCs. Because g-code does not support anything else except lines and arcs. Is there way to do this using your library? That would mean to approximate and replace both Bezier types using lines (or arcs).
Question as above.
Context: I am currently working on a lasercutting project. All our toolchains use mm as unit. Currently, when using paths2svg.wsvg, then the unit is set to px. Is there a way to change this to mm by default?
I found that there is an issue with intersection function. May be there is no issue but this code did something that I do not expect. So imagine there are 2 Paths and you want find out the intersection point.
from svgpathtools import *
# some shapes
A = Line(start=(0+200j), end=(300+200j))
B = CubicBezier(start=(50+150j), control1=(70+200j), control2=(120+250j), end=(200+300j))
# intersection points ((T1, seg1, t1), (T2, seg2, t2))
A_intersection = Path(A).intersect(B, justonemode=True)
B_intersection = Path(B).intersect(A, justonemode=True)
disvg([A,B], nodes=[A.point(A_intersection[0][0]), B.point(B_intersection[0][0])])
I except 2 nodes with approximately same coordinates, but they differs. May be using fixed indices at A_intersection[0][0]
is not the best option to figure out the T value of the path. Let's see what is in A_intersection
and B_intersection
:
>>> A_intersection
((0.33333333333333331, Line(start=200j, end=(200+200j)), 0.33333333333333331),
(0.40000000000000002,
CubicBezier(start=(50+150j), control1=(70+200j), control2=(120+250j), end=(200+300j)),
0.40000000000000002))
>>> B_intersection
((0.33333333333333331,
CubicBezier(start=(50+150j), control1=(70+200j), control2=(120+250j), end=(200+300j)),
0.33333333333333331),
(0.40000000000000002, Line(start=200j, end=(200+200j)), 0.40000000000000002))
It looks for me like if you search for intersection of Line
and CubicBezier
and return values of intersect()
on Line object have wrong positions. So you get something like ((T2, seg1, t2), (T1, seg2, t1))
First time using svgpathtools today, also not too experienced with python. I basically want to use svgpathtools to run some checks and based on them change the path element slightly. That's just how I got to the issue though:
The documentation specifies the following function arguments:
svg2paths(svg_file_location, return_svg_attributes=False, convert_circles_to_paths=True, convert_ellipses_to_paths=True, convert_lines_to_paths=True, convert_polylines_to_paths=True, convert_polygons_to_paths=True, convert_rectangles_to_paths=True)
I installed svgpathtools with pip3 install svgpathtools
. When checking for the function used with inspect.getsource(svg2paths)
, I get the following:
def svg2paths(svg_file_location,
convert_lines_to_paths=True,
convert_polylines_to_paths=True,
convert_polygons_to_paths=True,
return_svg_attributes=False):
"""
Converts an SVG file into a list of Path objects and a list of
dictionaries containing their attributes. This currently supports
SVG Path, Line, Polyline, and Polygon elements.
:param svg_file_location: the location of the svg file
:param convert_lines_to_paths: Set to False to disclude SVG-Line objects
(converted to Paths)
:param convert_polylines_to_paths: Set to False to disclude SVG-Polyline
objects (converted to Paths)
:param convert_polygons_to_paths: Set to False to disclude SVG-Polygon
objects (converted to Paths)
:param return_svg_attributes: Set to True and a dictionary of
svg-attributes will be extracted and returned
:return: list of Path objects, list of path attribute dictionaries, and
(optionally) a dictionary of svg-attributes
Where does this decrepancy come from? Just a general question, since until now, I didn't have any problems installing modules with pip3
I found a bug where two obviously non-intersecting Lines still report an intersection using Line.intersect(). The bug is captured in this commit: SebKuzminsky/svgpathtools@847b270b
It is highly sensitive to the precise starting and ending points of the two lines - if you round the values down to three significant places (like Line.__repr__() does) then the bug does not trigger.
I've run in to a couple of bugs with Arc.intersect(Arc).
I've documented/reproduced the bugs here: https://github.com/SebKuzminsky/svgpathtools/commits/arc-arc-intersect
I don't currently have a fix for this.
I found that there is a memory leak for function CubicBezier.length()
.
The test code is below:
for i in range(10000):
paths, _, _ = svg2paths2(file)
seg = paths[1][0]
if type(seg) is CubicBezier:
print(seg.length())
My test environment is OSX10.12, python3.6.
Apologies if this is not an issue and the way I'm doing it. I'm writing to an svg file using wsvg but it doesn't retain the style. Instead it seems to default to black and the stroke vanishes. When I add this style back in via illustrator then it works fine. Is there any way of keeping the style from when you read an svg to when you write it?
Path.area() calls poly() on each segment, but Arc() doesn't provide that method. The area can be approximated using linear interpolation of the Arc:
def approximate_path_area(path):
"""Approximates the path area by converting each Arc to 1,000
Lines."""
assert(path.isclosed())
tmp = svgpathtools.path.Path()
for seg in path:
if type(seg) == svgpathtools.path.Arc:
for i in range(0, 1000):
t0 = i/1000.0
t1 = (i+1)/1000.0
l = svgpathtools.path.Line(start=seg.point(t0), end=seg.point(t1))
tmp.append(l)
else:
tmp.append(seg)
return tmp.area()
Hi
I'm having a weird issue. i install svgpathtools (v 1.3.3) via a setup.py
script in a package i have. For some reason the installed package ends up with two similar files that are not present in the repo here, as far as i can tell.
Here is an example:
15:37:46•anders@xyz:/usr/local/lib/python3.5/dist-packages
cd svgpathtools-1.3.3-py3.5.egg
15:37:48•anders@xyz:/usr/local/lib/python3.5/dist-packages/svgpathtools-1.3.3-py3.5.egg
ls
EGG-INFO svgpathtools
15:37:48•anders@xyz:/usr/local/lib/python3.5/dist-packages/svgpathtools-1.3.3-py3.5.egg
cd svgpathtools
15:37:51•anders@xyz:/usr/local/lib/python3.5/dist-packages/svgpathtools-1.3.3-py3.5.egg/svgpathtools
ll
total 180K
-rw-r--r-- 1 root staff 14K feb. 25 15:37 bezier.py
-rw-r--r-- 1 root staff 654 feb. 25 15:37 directional_field.py
-rw-r--r-- 1 root staff 890 feb. 25 15:37 __init__.py
-rw-r--r-- 1 root staff 2,0K feb. 25 15:37 misctools.py
-rw-r--r-- 1 root staff 7,0K feb. 25 15:37 parser.py
-rw-r--r-- 1 root staff 92K feb. 25 15:37 path.py
-rw-r--r-- 1 root staff 15K feb. 25 15:37 paths2svg.py
-rw-r--r-- 1 root staff 439 feb. 25 15:37 pathtools.py
-rw-r--r-- 1 root staff 2,5K feb. 25 15:37 polytools.py
-rw-r--r-- 1 root staff 7,5K feb. 25 15:37 smoothing.py
-rw-r--r-- 1 root staff 4,8K feb. 25 15:37 svg2paths.py <-- NOTICE
-rw-r--r-- 1 root staff 8,3K feb. 25 15:37 svg_to_paths.py <-- NOTICE
15:37:51•anders@xyz:/usr/local/lib/python3.5/dist-packages/svgpathtools-1.3.3-py3.5.egg/svgpathtools
ag svg2paths
__init__.py
17: from .svg2paths import svg2paths, svg2paths2
svg2paths.py
2:The main tool being the svg2paths() function."""
34:def svg2paths(svg_file_location,
114:def svg2paths2(svg_file_location,
119: """Convenience function; identical to svg2paths() except that
120: return_svg_attributes=True by default. See svg2paths() docstring for more
122: return svg2paths(svg_file_location=svg_file_location,
svg_to_paths.py
2:The main tool being the svg2paths() function."""
92:def svg2paths(svg_file_location,
110: `svg2paths2()` function.
129: dict (optional): A dictionary of svg-attributes (see `svg2paths2()`).
193:def svg2paths2(svg_file_location,
201: """Convenience function; identical to svg2paths() except that
202: return_svg_attributes=True by default. See svg2paths() docstring for more
204: return svg2paths(svg_file_location=svg_file_location,
15:38:18•anders@xyz:/usr/local/lib/python3.5/dist-packages/svgpathtools-1.3.3-py3.5.egg/svgpathtools
I noticed this because one version of svg2paths
is capable of converting rectangles (svg rect
) and the other is not.
I made sure this package was not installed in this location before i performed the installation.
The wheel that is installed (https://files.pythonhosted.org/packages/71/96/cc91050f3b53c2cea0eda18f371d0584e7f43713ce606738384e8001a877/svgpathtools-1.3.3-py2.py3-none-any.whl#sha256=7f7bdafe2c03b312178460104705e1d554d8cf36c898bec41bdce9fed3504746) does contain both files. From the setuptools output:
Searching for svgpathtools==1.3.3
Reading https://pypi.python.org/simple/svgpathtools/
Downloading https://files.pythonhosted.org/packages/71/96/cc91050f3b53c2cea0eda18f371d0584e7f43713ce606738384e8001a877/svgpathtools-1.3.3-py2.py3-none-any.whl#sha256=7f7bdafe2c03b312178460104705e1d554d8cf36c898bec41bdce9fed3504746
Best match: svgpathtools 1.3.3
Processing svgpathtools-1.3.3-py2.py3-none-any.whl
Installing svgpathtools-1.3.3-py2.py3-none-any.whl to /usr/local/lib/python3.5/dist-packages
writing requirements to /usr/local/lib/python3.5/dist-packages/svgpathtools-1.3.3-py3.5.egg/EGG-INFO/requires.txt
Adding svgpathtools 1.3.3 to easy-install.pth file
Is this the intended contents of this package, or is there an error somewhere?
Happy to provide more info, but I don't know what is the intended behaviour here.
A.
Is there a reason the install_requires
line in setup.py
is commented out? Uncommenting this would make pip automatically install dependencies.
Currently, svgpathtools cannot parse/render this kind of path:
M 0,0 V 1 H 1 V 0 Z M 2,0 V 1 H 3 V 0 Z # (1)
The issue is that the path contains two 'Z's. Svgpathtools is not equipped to remember the middle Z. It will forget it, and print this instead to the file:
M 0,0 V 1 H 1 V 0 H 0 M 2,0 V 1 H 3 V 0 Z # (2)
But (1) renders like so:
Whereas (2) renders like so:
Also, svgpathtools cannot parse/render this kind of path:
M 0,0 H 1 M 1,0 V 1 # (3)
The issue is that the path contains an in-place move, which svgpathtools ignores. Svgpathtools will print this instead:
M 0,0 1,0 1,1 # (4)
But (3) renders as
Whereas (4) renders as
Basically, one way or the other, svgpathtools doesn't know how to properly handle paths with multiple subpaths. In fact, a "subpath" class is entirely missing.
I have been preparing a fork over at https://github.com/vistuleB/svgpathtools to remedy these issues. I hope this fork will be the object of a pull request at some point. The changes are quite extensive though and right now I am still preparing the new README for the new fork.
Interested people can take a look. The fork contains other new capabilities, such as path offsets, strokes, conversion of elliptical arcs to bezier curves, and new SaxDocument capabilities.
Now would be a good time to give me feedback, before I make the pull request.
Thanks.
Reading a file containing below svg throws error.
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-100 -100 200 200">
<circle r="100" />
</svg>
Below is the error.
In [5]: svgpathtools.svg2paths2('disvg_output.svg')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-5-b24a2854f246> in <module>
----> 1 svgpathtools.svg2paths2('../red-chalice/svgtransform/disvg_output.svg')
~/Data/Projects/svgpathtools/svgpathtools/svg_to_paths.py in svg2paths2(svg_file_location, return_svg_attributes, convert_circles_to_paths, convert_ellipses_to_paths, convert_lines_to_paths, convert_polylines_to_paths, convert_polygons_to_paths, convert_rectangles_to_paths)
213 convert_polylines_to_paths=convert_polylines_to_paths,
214 convert_polygons_to_paths=convert_polygons_to_paths,
--> 215 convert_rectangles_to_paths=convert_rectangles_to_paths)
~/Data/Projects/svgpathtools/svgpathtools/svg_to_paths.py in svg2paths(svg_file_location, return_svg_attributes, convert_circles_to_paths, convert_ellipses_to_paths, convert_lines_to_paths, convert_polylines_to_paths, convert_polygons_to_paths, convert_rectangles_to_paths)
176 if convert_circles_to_paths:
177 circles = [dom2dict(el) for el in doc.getElementsByTagName('circle')]
--> 178 d_strings += [ellipse2pathd(c) for c in circles]
179 attribute_dictionary_list += circles
180
~/Data/Projects/svgpathtools/svgpathtools/svg_to_paths.py in <listcomp>(.0)
176 if convert_circles_to_paths:
177 circles = [dom2dict(el) for el in doc.getElementsByTagName('circle')]
--> 178 d_strings += [ellipse2pathd(c) for c in circles]
179 attribute_dictionary_list += circles
180
~/Data/Projects/svgpathtools/svgpathtools/svg_to_paths.py in ellipse2pathd(ellipse)
37 ry = float(ry)
38
---> 39 cx = float(cx)
40 cy = float(cy)
41
TypeError: float() argument must be a string or a number, not 'NoneType'
SVG definition says, if cx and cy are not specified consider it as "0"
https://www.w3.org/TR/SVG11/shapes.html#CircleElementCXAttribute
I dig into the code and found that here default value should be 0
Computing length of each path returned by svg2paths() fails when reading the following svg:
https://www.dropbox.com/s/2b13vqykmvid0eg/13865.svg?dl=0
Procedure to reproduce:
from svgpathtools import svg2paths
paths, path_attrs = svg2paths('13865.svg', return_svg_attributes=False)
for path_id, path in enumerate(paths):
path_attr = path_attrs[path_id]
plen = int(path.length())
The error it throws:
/usr/local/lib/python3.5/dist-packages/svgpathtools/path.py in length(self, T0, T1, error, min_depth)
1899
1900 def length(self, T0=0, T1=1, error=LENGTH_ERROR, min_depth=LENGTH_MIN_DEPTH):
-> 1901 self._calc_lengths(error=error, min_depth=min_depth)
1902 if T0 == 0 and T1 == 1:
1903 return self._length
/usr/local/lib/python3.5/dist-packages/svgpathtools/path.py in _calc_lengths(self, error, min_depth)
1876 self._segments]
1877 self._length = sum(lengths)
-> 1878 self._lengths = [each/self._length for each in lengths]
1879
1880 def point(self, pos):
/usr/local/lib/python3.5/dist-packages/svgpathtools/path.py in <listcomp>(.0)
1876 self._segments]
1877 self._length = sum(lengths)
-> 1878 self._lengths = [each/self._length for each in lengths]
1879
1880 def point(self, pos):
ZeroDivisionError: float division by zero
The error seems to come from self._length being zero in path.py. It is unusual but possible as the sketch above has a path:
'M261 166 L261 166 '
which looks like a single dot on the canvas.
Adding an epsilon to self._length seems to be a workaround:
self._length = sum(lengths) + np.finfo(np.float32).eps
I haven't yet dived into why this is the case, but this svg generates empty lists for both the attributes and paths returned values for svg2paths. I'll try to look deeper into this later today.
<svg width="262" height="178" xmlns="http://www.w3.org/2000/svg"> <ellipse ry="51" rx="51" id="svg_3" cy="101" cx="77.5" stroke-width="1.5" stroke="#00ffff" fill="none"/> <ellipse ry="57.5" rx="57.5" id="svg_4" cy="100.5" cx="145" stroke-width="1.5" stroke="#ff0000" fill="none"/> </svg>
As is the parser for the SVG paths instances the Path object and the Line objects and the curve objects. It would be better if it didn't do that. Passing the path object into the parse_path command and as it parses the commands it would call a command on path that add_line() or add_curve() or start(), or calling these on a different class that simple delegated to segments.append(Line(start_pos,end_pos)) or whatever.
It would make that code rather independently useful, as anybody could cook up a parser object and throw it in there, even if they weren't going to actually use the rest of the path tools as they were intended. The code has some reasonable svg parse capabilities and those could be broken off and decoupled more effectively from the math commands and logic in the primary classes.
This could even be taken to the somewhat logical extreme of branching that project off completely and writing an svgread
module, which would be completely ambiguous as to where the data ends up. Just including the parsing tools as such loading the data in either a proper dom or the various other methods of loading and extracting that data. Including the svg path parsing. Which would basically yield proper calls parse that data. As is the intersection between the parser bits, the svgpathtools and svgwrite all seems kind of sloppy.
Hi, I've never contributed to an open source project before, I hardly know how to use git. (Mathematician here.)
Could someone guide me through my first pull request?
The modifications I've made (small to big)
-- correct bug in path_encloses_pt [current bug: double-counts intersections at segment endpoints]
-- add a "multisplit" functionality on top of "split" to paths
-- add a feature for approximating arc segments by a sequence of cubic bezier curves... so now any path can be turned into a purely bezier path... the user can control the accuracy of the approximation, as well as well as the "safety" (number of points measured along the curve to ensure said accuracy is respected)... (The cubic beziers I used are those described in the paper "Drawing an Elliptical Arc using Polylines, Quadratic or Cubic Bezier Curves" by L. Maisonobe.)
-- (the above could also be used for measuring area of paths with arc segments, though I didn't make the change since I don't need area myself)
-- path offsets: offset a path by some amount; exact offset is not possible for bezier curves (or arcs), so the user gets to choose an accuracy, and bezier curves are recursively (and adaptively) subdivided into smaller bezier curves (of same degree) until the desired accuracy is respected everywhere; accurately produces cusps, etc, for concave offsets; requires the "arc to bezier" transformation above for paths with elliptical arcs (i.e., elliptical arcs are first transformed into a sequence of cubic beziers, after which these are offset)
-- joins: afore-mentioned path offset accommodates the standard SVG segment joins: miter, bevel, and round; miter-limit also implemented
-- path stroke: two-sided path offset with added line caps and line cap options 'butt', 'square' and 'round'---basically, now one can implement a "stroke to path" operation, as found in popular software like inkscape in 1 command, without resorting to approximation by polylines, and with all the standard line cap/segment join SVG options
-- also works with discontinuous and/or closed paths and/or mixtures thereof :)
-- fun fact: the above offset and stroke operations are robust enough to be used with negative offsets / stroke widths... in such a case the endcaps of the path take an inverted shape, as expected.
I'd like to contribute all this to the repo, but will need a little bit of guidance... thanks...
I'm seeing this problem on the current tip of master, ae42197.
from svgpathtools.path import *
p = Path(Arc(start=(106.58928+132.95833j), radius=(40.82143+83.910713j), rotation=0.0, large_arc=False, sweep=True, end=(74.472411+214.93917j)),
Arc(start=(74.472411+214.93917j), radius=(40.82143+83.910713j), rotation=0.0, large_arc=False, sweep=True, end=(28.658655+167.9207j)),
Arc(start=(28.658655+167.9207j), radius=(40.82143+83.910713j), rotation=0.0, large_arc=False, sweep=True, end=(41.237335+65.887886j)),
Line(start=(41.237335+65.887886j), end=(65.767853+132.95833j)),
Line(start=(65.767853+132.95833j), end=(106.58928+132.95833j)))
p.area()
This hangs until I interrupt it with ^C, then I get this backtrace:
KeyboardInterrupt Traceback (most recent call last)
<ipython-input-4-38eb451037da> in <module>()
----> 1 p.area()
/home/seb/svg2gcode/svgpathtools/svgpathtools/path.pyc in area(self, chord_length)
2568 else:
2569 bezier_path_approximation.append(seg)
-> 2570 return area_without_arcs(Path(*bezier_path_approximation))
2571
2572 def intersect(self, other_curve, justonemode=False, tol=1e-12):
/home/seb/svg2gcode/svgpathtools/svgpathtools/path.pyc in area_without_arcs(path)
2548 for seg in path:
2549 x = real(seg.poly())
-> 2550 dy = imag(seg.poly()).deriv()
2551 integrand = x*dy
2552 integral = integrand.integ()
/home/seb/svg2gcode/svgpathtools/svgpathtools/polytools.pyc in imag(z)
66 def imag(z):
67 try:
---> 68 return np.poly1d(z.coeffs.imag)
69 except AttributeError:
70 return z.imag
/usr/lib/python2.7/dist-packages/numpy/lib/polynomial.pyc in __init__(self, c_or_r, r, variable)
1054 if len(c_or_r.shape) > 1:
1055 raise ValueError("Polynomial must be 1d only.")
-> 1056 c_or_r = trim_zeros(c_or_r, trim='f')
1057 if len(c_or_r) == 0:
1058 c_or_r = NX.array([0.])
/usr/lib/python2.7/dist-packages/numpy/lib/function_base.pyc in trim_zeros(filt, trim)
2097 else:
2098 last = last - 1
-> 2099 return filt[first:last]
2100
2101
KeyboardInterrupt:
The following code is taken from the README. I'm attempting to parse SVG images and then reconstruct them without any losses.
from svgpathtools import svg2paths2, wsvg
paths, attributes, svg_attributes = svg2paths2('def_ex.svg')
# Let's make a new SVG that's identical to the first
wsvg(paths, attributes=attributes, svg_attributes=svg_attributes, filename='def_ex_processed.svg')
When parsed the picture is modified, images below:
<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Some graphical objects to use -->
<defs>
<circle id="myCircle" cx="0" cy="0" r="5" />
</defs>
<!-- using my graphical objects -->
<use x="5" y="5" xlink:href="#myCircle"/>
</svg>
<?xml version="1.0" ?>
<svg baseProfile="full" height="600px" version="1.1" viewBox="0 0 10 10" width="600px" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs/>
<path cx="0" cy="0" d="M -5.0,0.0 A 5.0,5.0 0.0 1,0 5.0,0.0 A 5.0,5.0 0.0 1,0 -5.0,0.0" id="myCircle" r="5"/>
</svg>
Sorry If this can't easily be viewed I'm not sure of the best way to post SVG images to issues.
The images loses its positioning which is important for what I'm trying do. I believe the issue is occurring with the line <use x="5" y="5" xlink:href="#myCircle"/>
where svg2paths2 is unable to understand the reference to the myCircle def from before
I am not sure this is an issue exactly but is there a reason complex numbers are used to store the coordinates of points? It would make sense if it was converted to polar coordinates but unless I am misunderstanding things, it is using Cartesian and the real number is the X and the imaginary is the Y.
I think this is a holdover from the adaptation from the svg.path but I wanted to do some more work concerning the points and found it easier to just replace the complex numbers with a simple point class that has some convenience functions. My fork: https://github.com/brinnLabs/svgpathtools
Hi,
when I split iteratly a circle (if it is 4 Arcs for example - no problem if it's Bezier Curve), I obtain this error : math domain error
The problem is caused by acos() in parameterize function in Path.py
circle_arcsx4.svg.txt
. Value for u1.real can be -1.000000002 and it's the problem...
My solution : add this function in misctools (it should be useful everywhere...) :
#-- constrain(x,a,b)
def constrain(x,valMin,valMax):
if x < valMin :
return valMin
elif valMax < x :
return valMax
else :
return x
Import it in path.py :
from .misctools import BugException, constrain # added XH
And in def _parameterize, change :
if u1.imag > 0:
self.theta = degrees(acos(constrain(u1. real,-1.0, 1.0))) # acos need -1<=value<=1 - added XH
elif u1.imag < 0:
self.theta = -degrees(acos(constrain(u1. real,-1.0, 1.0))) # acos need -1<=value<=1 - added XH
And it's work well to iterate split on circle !
Test file joined. (remove .txt to obtain .svg - github seems not support svg file joined...)
Xavier HINAULT
France
For example m0 0h50v50zv20h20z
right now causes an error:
Traceback (most recent call last):
File "", line 1, in
File "C:\python36\lib\site-packages\svgpathtools-1.3.2-py3.6.egg\svgpathtools\parser.py", line 81, in parse_path
File "C:\python36\lib\site-packages\svgpathtools-1.3.2-py3.6.egg\svgpathtools\path.py", line 1883, in closed
ValueError: End does not coincide with a segment start.
But this is ok
Hi,
A Path object contain for example :
CubicBezier(start=(27.080179592571874+157.03434674518752j), control1=(27.61140677353281+155.774203506625j), control2=(28.261543320386714+154.57737991657814j), end=(29.016691803786323+153.45777340439065j)),
CubicBezier(start=(29.016691803786323+153.45777340439065j), control1=(29.771840287185935+152.33816689220313j), control2=(30.632000707131247+151.295777457875j), end=(31.583275634274997+150.34450253075j)),
CubicBezier(start=(31.583275634274997+150.34450253075j), control1=(32.53455056141875+149.393227603625j), control2=(33.57693999576094+148.53306718370314j), end=(34.696546507954295+147.77791870032814j)),
CubicBezier(start=(34.696546507954295+147.77791870032814j), control1=(35.81615302014765+147.02277021695315j), control2=(37.01297661019218+146.37263367012503j), end=(38.27311984874062+145.8414064891875j)),
CubicBezier(start=(38.27311984874062+145.8414064891875j), control1=(39.53326308728906+145.31017930824999j), control2=(40.8567259743414+144.89786149320312j), end=(42.229611080550384+144.61835047339065j)),
CubicBezier(start=(42.229611080550384+144.61835047339065j), control1=(43.60249618675937+144.33883945357815j), control2=(45.024803512125+144.192135229j), end=(46.4826356273+144.192135229j)))]
I want to reuse the start and end point from each object to build Line object with :
spt.Line(elem.point(0), elem.point(1))
where elem is an element in Path source
I obtain :
Line(start=(27.080179592571874+157.03434674518752j), end=(29.01669180378633+153.45777340439054j)),
Line(start=(29.016691803786323+153.45777340439065j), end=(31.58327563427502+150.34450253075013j)),
Line(start=(31.583275634274997+150.34450253075j), end=(34.69654650795428+147.77791870032806j)),
Line(start=(34.696546507954295+147.77791870032814j), end=(38.27311984874062+145.84140648918756j)),
Line(start=(38.27311984874062+145.8414064891875j), end=(42.2296110805504+144.61835047339065j)),
Line(start=(42.229611080550384+144.61835047339065j), end=(46.48263562729995+144.1921352290001j)))]
As you can see, there is a little difference :
29.016691803786323+153.45777340439065j give 29.01669180378633+153.45777340439054j
31.58327563427502+150.34450253075013j give 31.583275634274997+150.34450253075j
46.4826356273+144.192135229j give 46.48263562729995+144.1921352290001j
Only end point seem have this problem.
Logically, Path() obtained is not continuous, etc.
Is it possible to give a tolerance for continuous or limit precision to 6 or 8 decimal for example ?
Thanks in advance for your suggest.
I encountered a small typo in path.py when computing the curvature of a closed curve. The issue is caused by the following line:
svgpathtools/svgpathtools/path.py
Line 2063 in bf95944
changing this to joins_smoothly_with rectifies the problem.
Said there is a string in SVG: <polyline points="10, 20 30,40"/> #two spaces before 20
polyline2pathd() would return the conversion as u'M10 L20L30 40' rather than u'M10 20L30 40'.
With the following shapes intersecting in 2 places:
from svgpathtools.parser import parse_path
# clipping rectangle
p1 = parse_path('M0.0 0.0 L27.84765625 0.0 L27.84765625 242.6669922 L0.0 242.6669922 z')
# frame with curved corners
p2 = parse_path('M166.8359375,235.5478516c0,3.7773438-3.0859375,6.8691406-6.8701172,6.8691406H7.1108398c-3.7749023,0-6.8608398-3.0917969-6.8608398-6.8691406V7.1201172C0.25,3.3427734,3.3359375,0.25,7.1108398,0.25h152.8549805c3.7841797,0,6.8701172,3.0927734,6.8701172,6.8701172v228.4277344z')
I see this REPL output:
>>> print(len(p1.intersect(p2)))
2
>>> print(len(p2.intersect(p1)))
1
This seems to be caused by Path.intersect() removing some intersections from the result due to a typo. If I patch svgpathtools/path.py version 1.3.3 at line 2196 as follows:
< pts = [seg1.point(_t1) for _T1, _seg1, _t1 in list(zip(*intersection_list))[0]]
---
> pts = [_seg1.point(_t1) for _T1, _seg1, _t1 in list(zip(*intersection_list))[0]]
I get the expected result:
>>> print(len(p1.intersect(p2)))
2
>>> print(len(p2.intersect(p1)))
2
BTW, it would be nice to make a new release of svgpathtools and pick up all bug fixes accumulated since last summer, including a fix for this issue :)
It's a part of issue #1. If you try to store paths as svg, the values of height and width args are overided to "100%".
Apparently the condition if not (current_pos == start_pos)
in parse_path method of parser.py is causing an unnecessary segment to be added in some cases. Please try executing the code from the attached file.
Maybe, the floats should be replaced by Decimal to avoid rounding errors like these.
Function parse_path seems unable to parse some d_strings. The errors I got are "could not convert string to float: L" and "list index out of range".
To reproduce the problem, I enclose two example svg files:
https://www.dropbox.com/s/tilysgce9rzo6mj/n01791625_1116-5.svg?dl=0
https://www.dropbox.com/s/15ejoawdrq3lcou/n02439033_628-5.svg?dl=0
Read these files using svg2paths() throws the above error.
Its really handy being able to render a Path
inlined in IPython notebook
PR following shortly...
Heylo,
thanks for your comment on my Primer on Bézier Curves, it's always great to see an application for what I've documented, but I noticed your README.md has a section called "Credit where credit's due" - while you of course don't need to mention the primer by URL, it might be useful for others as a further reference link they can visit and sink their teeth into =)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.