abey79 / vpype

The Swiss-Army-knife command-line tool for plotter vector graphics.
https://vpype.readthedocs.io/
MIT License
699 stars 61 forks source link

Feature: convert sharp corners to round corners #515

Open jiangzhouq opened 2 years ago

jiangzhouq commented 2 years ago

When I use a knife to cut, the sharp corners are not handled very well, easy to lift,

abey79 commented 2 years ago

I'm not sure I understand what you mean. Do you mean this?

image

jiangzhouq commented 2 years ago

I'm not sure I understand what you mean. Do you mean this?

image

yes , man!can vpype do this job? I read the guide and didn’t find.

abey79 commented 2 years ago

No, there is no such feature for the time being. It would make for a great addition and/or plug-in though!

jiangzhouq commented 2 years ago

Thx for reply!

abey79 commented 2 years ago

We can keep his open for records. It's a good feature request.

dev-89 commented 2 years ago

Hi guys, that really sounds like a cool feature to implement. Furthermore this excellent Stack Overflow post describes the geometry of the problem at hand very well. I just have a concrete implementation question at hand: should I simply add a "rounder_corners" method to the LineCollection class? Effectively this method would create out of two LineStrings two shortened LineStrings and one LinearRing.

abey79 commented 2 years ago

@dev-89 Thanks for reaching out!

I wouldn't cut/merge paths with this feature (where "path", in vpype, is always a line string – technically a numpy array of complex – where the last point might be identical to the first if it's closes). So the function would take a path, and return another path with its angle rounded (based on a radius and some quantisation control parameter).

I would first create a function to operate on a single path (typically in vpype/geometry.py). I'd skip adding a member in LineCollection and just create a roundcorner command in vpype_cli (maybe in vpype_cli/operations.py).

dev-89 commented 2 years ago

Right, I think I understand now. Will get coding in the coming days and any further questions we can discuss over the PR.

tatarize commented 1 year ago

There's other more simple methods of doing this. You're focusing on just replacing the corner with an arc. But really you can just segmentize the segments. Apply smooth (#216) repeatedly. Then simplify the segments.

  1. Segmentize: If we have a heavy corner detected where the angle shift is beyond tolerance. We take the lines that lead to and from this corner and turn them into 50 tiny lines.
  2. Smooth: We then apply smooth repeatedly where we find all 3 points in series, and we move the middle point towards the midpoint of the first and last point. amount * (p1 - p2) + p2
  3. Simplify: We check the determinate (or merely the segment angle (arctan2)) and we delete the point if the line is still going straight enough (within tolerances). This way if we ran simplify just after segmentize it would restore the original lines. This differs a bit from linesimplify in that we delete all points won't deviate a line by more than 1% (for example). It's basically simplify but for angles and not distance.

And most of these things are already there and can be applied pretty easy. We only really need as many segments as we're applying the smooth. So if we apply smooth 10 times only the 10 closest points could be affected. You can also segment just part of a line nearest to the sharp corner, or some distance along that line (sort of radius)).

tatarize commented 1 year ago

Keep in mind a strong point of this proposed algorithm is the use of numpy. You can apply this to line segments really easily with the code you already have without bunch of other libraries.

The segmentize is easy. It's linear interpolation between the two complex points.

Smooth is a little bit more complex, but follows pretty nicely (spitballing).

midpoint = (line[2:] + line[:-2]) / 2.0
middlepoint = line[1:-1]
line[1:-1] = amount * (midpoint - middlepoint) + middlepoint)

The simplify isn't too bad. Again spitballing.

first = line[2:]
mid = line[1:-1]
last = line[:-2]
x1 = np.real(first)
y1 = np.real(first)
x2 = np.real(mid)
y2 = np.real(mid)
x3 = x2
y3 = y2
x4 = np.real(last)
y4 = np.real(last)
denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)

Then delete the points where abs(denom) < 1e-3 or whatever. At 0 the lines are straight. It's used more often to find intersections and at 0 lines are parallel but here since p2 == p3 they are just connected there.

So all straight forward with the code you have currently.