-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
arc transform fixed #98
Conversation
I tested this arcTransformFix branch and found that it still displays the problem in #111. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran into similar problems working with svg2mod and using this library. I had one particular circular arc that had a matrix transformattion applied, which I think included 45° rotation. With the old code, that would result in an error:
File "/home/matthijs/.local/lib/python3.8/site-packages/svgpathtools/path.py", line 1376, in __init__
assert radius.real != 0 and radius.imag != 0
AssertionError
I couldn't easily reproduce this in a reduced example (due to floating errors, the value would just become very small but not 0), but this makes sense: If you rotate a 45° vector by 45°, one of the axis (so radius) values will become 0.
Note that even if no radius becomes 0, the resulting arc would be incorrect. It does not immediately lead to an error, though, since the Arc
constructor seems the Arc is incorrect and by default tries to fix it by scaling the ellipse radius to make it fit anyway. For this particular call to Arc()
, I think this is not a good idea: The transform should produce a correct arc, or bail out if it fails. I've left an inline comment with a suggestion to set autoscale_radius=False
to achieve this.
With that change made, I believe all rotation of arcs will fail with the current master (ValueError: No such elliptic arc exists.
).
This PR then neatly seems to fix that problem for rotations of circular arcs (i.e. rx == ry
), also when combined with scaling.
However, for rotation of elliptical arcs (rx != ry
), this PR does not produce correct transformations yet. The radius of the resulting ellipse seems to be correct, but I believe the rotation value of the Arc should also be updated. With the PR as-is (and my suggested change), I get the ValueError: No such elliptic arc exists.
error still for a rotated ellipse. However, when I manually correct the rotation value passed to the Arc constructor (i.e. by hardcoding it in the constructor call inside transform()
), the resulting arc is correct. So I suspect that all that's needed is to figure out the rotation from the matrix and apply that. I'm not sure if the current code already calculates this as a side effect maybe, but otherwise it could be a matter of taking an arbitrary vector, transform it using the matrix and then calculate the angle difference between the original and transformed vector maybe?
Note that I just tried very little cases, so this is by no means a comprehensive review. Also, I haven't dug into the math, so can't really comment on the implementation itself either.
I tested this arcTransformFix branch and found that it still displays the problem in #111.
IIUC, that problem is fixed by #112. This PR should be rebased on top of master (with that PR merged) to confirm that the combination still works, though (haven't tested that).
Thanks @Vrroom @SebKuzminsky @matthijskooijman. @Vrroom, please address the concerns mentioned above. |
Co-authored-by: Matthijs Kooijman <[email protected]>
Hello @matthijskooijman @SebKuzminsky @mathandy! Sorry for such a late reply. Github's notification system really confuses me 😅. @matthijskooijman I commited your suggestion into this PR. |
It's been a while since I looked at this, but re-reading my previous comment, I think you now ensured that incorrect arcs are no longer silently "corrected", but bail out instead. However, I think that elliptical arcs still do not produce correct results due to missing rotation, which was the other concern in my comment, which I think is still not addressed? |
Can you share a graphic on which this issue arises? It would help me understand better what the issue is |
Not easily, I'm no longer working with this code (and svg2mod which I worked on fixed things differently rather than switching to svgpathtools). But I think that it should be a matter of making an elliptical arc and rotating it. |
Does this SVG work to illustrate your point @matthijskooijman
|
Looks like it should. Does svgpathtools crash on this? If not, then maybe I was mistaken or something was fixed in the mean time (you did merge the master branch). |
It does seem to be working. I saved the above as
|
Hm, are you sure you're using the right version of the code? I just tried your branch with the above file:
|
I checked out the version |
@matthijskooijman Do you want to hop on a quick call to see what is wrong? |
Ok, I have the same commit checked out, so there must be some other difference. Interestingly, when I create a new virtualenv and run inside there, then it does work for me as well. Looking at dependencies, svgwrite is the same version inside and outside the venv, but numpy is newer. But downgrading numpy (from 1.20.3 to 1.17.4) inside the venv does not break it... Both use python 3.8.5. With some prints I confirmed that the input of the --- a/svgpathtools/path.py
+++ b/svgpathtools/path.py
@@ -319,11 +319,14 @@ def to_complex(v):
D = invT.T @ Q @ invT
eigvals = np.linalg.eigvals(D)
+ print(D)
+ print(eigvals)
rx = 1 / np.sqrt(eigvals[0])
ry = 1 / np.sqrt(eigvals[1])
new_radius = complex(rx, ry)
+ print(new_radius)
if new_radius.real == 0 or new_radius.imag == 0 :
return Line(new_start, new_end) I get this output:
Note that the D value is equal, but the resulting eigvalues is different. Also, the transform function runs a 4 times, of which the second one fails with an exception, but the values printed are the same all 4 times, so I'm showing only the first one. I also just noted that the eigenvalues, and thus also the radius values are reversed between the working and non-working cases. But then, the plot thickens... Running just the eigenvalues calculation, shows no difference, and returns the broken (wrong?) value in both cases.
Maybe this is due to rounding (I've copied the printed values for D, which might have been rounded?). I'm not sure what is going on here... Are you familiar enough with the math to check that 1) the value for D shown here is correct and 2) what the correct eigenvalue and radius should be? The input to the
I really should be doing other things, this problem is something that was relevant to me a while back but not really anymore right now. However, since I'm the only one that can reproduce this problem, I'll try to produce enough info to at least figure this out. Let's see if you can find anything from my above comments, if you then think it is useful to do some more interactive debugging in a call, I'm open to that. |
Changed the shorthand @ for np matrix multiplication to matmul
@matthijskooijman First of all I'm sorry that I didn't read your first comment properly. It pointed out a serious bug with rotation. I only realized this after re-reading my code. Regarding your concern about the D matrix and the eigenvalues, you might have noticed that in both cases, the eigenvalues are the same, just that their order is reversed. In one case you got
So how is that relevant to us? After a transformation, we are free to decide which is In my latest commit
I used the following test file to try out the new code and the path in
Thanks for this discussion. Please try it out now and see if any new bugs crop up! |
Sounds like a good analysis and fix, even though I do not follow the math completely. I can confirm that the code no longer raises an exception, so that's good. I don't have any readily available files or setup to test with, though. |
@mathandy @matthijskooijman Do you need me to add automated tests or something? Can we go ahead and merge this? |
I'm just a passer-by that happened to run in a similar issue, so I'm leaving that question for @mathandy. |
So when I first wrote the code to handle arcs I realized there are some discrepancies between how the math would suggest arcs would work, how the SVG docs on W3 suggested they should work, and how they actually rendered in standard browsers (e.g. Chrome). Can you give me a visual example of what this code fixes? BTW I tend to think of Chrome as the standard to go by, but really it depends on your application and I'm open to arguments to thinking otherwise. |
Consider the test function I provided earlier. Copying here for convenience:
This graphic looks like: The input With my contribution, it becomes: |
@mathandy Are we going to go forward with this? |
Thanks @Vrroom ! |
Hello, I found that the formula you were using to calculate the semi major and the semi minor axis of an arc after a transformation was wrong. Earlier, you were simply treating the radii as a vector and applying the transformation matrix directly to this vector.
This is somewhat incorrect. (Because for example, the radii shouldn't change if the matrix is a rotation + translation matrix but in your case they do).
I found the correct formula at here and have implemented it in this branch. I would appreciate any feedback and anything else required from my end to merge this with the master. Thanks for this awesome project!!!