For turning a hatch into a Zig-Zag tool path we sometimes encounter strange phenomena.
It boils down to relying on existent resources, circumvent flaws or re-invent sound methods.
What we can't fix is the result of the Hatching engine or offsets to Polylines (RPolygonOffset).
But sometimes it is indeed a flaw deep in the standard resources.
Little flaws in basic resources can have more impact than you can imagine because they are interwoven into all sorts of other resources.
e.g. Correcting the occasional NaN length of an ellipse was just adding 2 '=' signs and that fixed D2.
In the situation below we want the part of the boundary (Cyan + Blue) that connects the top left green line with any 'next' green candidate.
In the end it will be the circular path down to the second green line from the top at the left side.
But we also investigate the blue route to the right.
Obviously longer than going leftwards it will be discarded after evaluation.
We need to extract at least meaningful data to evaluate both routes.
Analogous to e.g. ShapeAlgorithms.autoSplit we relocate the starting vertex V0 of the Polyline to match with sp.
Then we look for the place to split the Polyline near ep with RPolyline.getDistancesFromStart(ep).
As mentioned on the image ... That returns two distances that are at least factor 2-3 different.
Notice that ep (almost) coincides with a vertex between a straight segment and a bulging segment (Arc).
There are cases where both distances are practically the same (Exactly on a vertex) and cases with more than two distances (...
Seen in several standard resources ...
The easy way out is to disregard all but the first distance, the shortest, split the Polyline there and done.
Easy, straightforward but also blind is prone to errors and that is the reason we had to abandon the use of ShapeAlgorithms in the first place.
How does RPolyline.getDistancesFromStart() works: (current reference)
A Polyline shape is typically handled as exploded segments or as Lines and/or Arcs.
First, the point must be within 0.0001 units from a segment.
True for the first two segments, even better, endpoints of the green lines are (nearly) on a boundary for sure.
The problem is not the first segment (Blue) but even for that a side-note is in order.
It is the Arc from an exploded polyline that is the issue ... Most probably once constructed as an Arc and merged as bulging segment.
Then we must know that the conversion of an Arc into a bulging segment and back is not 100% certain.
The more conversions back and forth, the more the accuracy starts to suffer.
Also meaning that any manipulation of Polylines induces minor flaws that grow exponentially.
RPolyline.relocateStartPoint(..) is most probably no exception to that.
Assumed to explode the Polyline, split one segment in two if required and merge the chain back starting at the given position.
What happens is that 10.9431... is equal to the length of segment 0 = 4.0316... plus the circumference of a circle with radius 1.1000
Like the legendary 'Battle of the bulge' it also took at least a month to figure that out.
This example was the key.
And where sits this flaw? ...
For segment 1 the resource RPolyline.getDistancesFromStart() exploits RArc.getDistanceFromStart()
And this relies on RMath.getAngleDifference(a1, a2)
When CW: result = RMath.getAngleDifference(ap, a1) * radius ... CCW uses the same resource but swaps angles.
Where ap is the direction from the center towards point ep and a1 is the Arc start-angle.
Code: Select all
a1 = ap = 1.5707963267949094 ; a2 = a1 = 1.5707963267948708
Adds 2Pi to the second angle when a1 >= a2 and that is true
=> a2 = 7.8539816339744573
ret = a2 - a1
=> ret = 6.2831853071795479
When ret >= 2Pi the value in ret is cleared to zeroThe returned length is then almost 2Pi times the radius = 6.911503837897502 instead of returning zero.
Way beyond the end-angle and almost a full circle ... Not at all on the Arc-segment and thus not on the Polyline.
Similar resources that evaluate for nearly circular or not over 2Pi typically use for example:
- if (ret >= 2*M_PI - RS.AngleTolerance) { ... }
Bug report see FS2700
The deeper issue is the 'Battle of the Bulge(s)' ...
The original Arc at the right in the drawing was a 90.000 degree CW Arc starting at 90.000 degrees and ending at zero degrees.
Then we expect a bulging factor of -0.4142135623730950 and not -0.4142135623730875.
This minute difference has already an influence on the center position, the sweep and the radius when converted back to an Arc.
And those have an influence on the Arc start and end angle towards the two vertices.
Making the start angle a2 = a1 in the above 1.5707963267948708 and not Pi/2 or 1.5707963267948966 as intended.
But the boundary or any Polyline is merged from loose Line and Arc shapes at some point.
What may disregard the exact position of one ending of these shapes on merging except the sweep angle for Arc shapes.
And it was probably reversed for a proper orientation and we did relocate its starting position ...
... That are all manipulations of the original data.
The accuracy error is still very small but the consequences are there.
Writing code that handles or (re-)constructs a Polyline I tend to work with vertices and avoid addShape() where possible.
But I need to rely on some resources and on some Pro resources in a proxy without knowing how they work.
About RLine.getDistanceFromStart():
- You have to look close but it uses/allows for 3D information and then it becomes really fuzzy.
This resource also exploits RLine.getVectorTo() like many other do.
getVectorTo() would fail on a non-processable line segment (2D length < 1e-6) returning RVector.invalid
This resource is not guarded for that, an angle would turn NaN and comparing with NaN is about always false.
getVectorTo() is in favor for the start point within 1e-6 in 2D, based on a distance.
=> A circular tolerance area centered on the start.
If used to determine 'On the line' the tolerance is 1000 times larger than for the rest of the line.
Further handling in 2D is new (v3.30.1.2) but no issue with 2D data.
Advicable: When getVectorTo() returns the RVector.nullVector, do the same test on a reversed line to be sure.
It is not in favor for the endpoint, just a fraction beyond the line end is 'Not on the line'.
Depending the limited flag it may also return a vector that is not orthogonal to the line.
While getVectorTo() exists for most shapes, it works very differently for Lines or Arcs
Regards,
CVH