From 832e3b46fa6f18c76d3c0cf7faa3f1820066742f Mon Sep 17 00:00:00 2001 From: jan Date: Sun, 2 Sep 2018 21:05:18 -0700 Subject: [PATCH] Add general angle-to-parameter helper function and improve accuracy of to_polygons --- masque/shapes/arc.py | 57 ++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index c1dadea..5c81b35 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -170,14 +170,12 @@ class Arc(Shape): r0, r1 = self.radii # Convert from polar angle to ellipse parameter (for [rx*cos(t), ry*sin(t)] representation) - a0, a1 = (numpy.arctan2(r0*numpy.sin(a), r1*numpy.cos(a)) for a in self.angles) - sign = numpy.sign(self.angles[1] - self.angles[0]) - if sign != numpy.sign(a1 - a0): - a1 += sign * 2 * pi + a_ranges = self._angles_to_parameters() # Approximate perimeter # Ramanujan, S., "Modular Equations and Approximations to ," # Quart. J. Pure. Appl. Math., vol. 45 (1913-1914), pp. 350-372 + a0, a1 = a_ranges[1] # use outer arc h = ((r1 - r0) / (r1 + r0)) ** 2 ellipse_perimeter = pi * (r1 + r0) * (1 + 3 * h / (10 + math.sqrt(4 - 3 * h))) perimeter = abs(a0 - a1) / (2 * pi) * ellipse_perimeter # TODO: make this more accurate @@ -187,18 +185,20 @@ class Arc(Shape): n += [poly_num_points] if poly_max_arclen is not None: n += [perimeter / poly_max_arclen] - thetas = numpy.linspace(a1, a0, max(n), endpoint=True) + thetas_inner = numpy.linspace(a_ranges[0][1], a_ranges[0][0], max(n), endpoint=True) + thetas_outer = numpy.linspace(a_ranges[1][0], a_ranges[1][1], max(n), endpoint=True) - sin_th, cos_th = (numpy.sin(thetas), numpy.cos(thetas)) + sin_th_i, cos_th_i = (numpy.sin(thetas_inner), numpy.cos(thetas_inner)) + sin_th_o, cos_th_o = (numpy.sin(thetas_outer), numpy.cos(thetas_outer)) wh = self.width / 2.0 - xs1 = (r0 + wh) * cos_th - ys1 = (r1 + wh) * sin_th - xs2 = (r0 - wh) * cos_th - ys2 = (r1 - wh) * sin_th + xs1 = (r0 + wh) * cos_th_o + ys1 = (r1 + wh) * sin_th_o + xs2 = (r0 - wh) * cos_th_i + ys2 = (r1 - wh) * sin_th_i - xs = numpy.hstack((xs1, xs2[::-1])) - ys = numpy.hstack((ys1, ys2[::-1])) + xs = numpy.hstack((xs1, xs2)) + ys = numpy.hstack((ys1, ys2)) xys = numpy.vstack((xs, ys)).T poly = Polygon(xys, dose=self.dose, layer=self.layer, offset=self.offset) @@ -218,25 +218,20 @@ class Arc(Shape): If the extrema are innaccessible due to arc constraints, check the arc endpoints instead. ''' + a_ranges = self._angles_to_parameters() + mins = [] maxs = [] - for sgn in (+1, -1): + for a, sgn in zip(a_ranges, (-1, +1)): wh = sgn * self.width/2 rx = self.radius_x + wh ry = self.radius_y + wh - # Create paremeter 'a' for parametrized ellipse - a0, a1 = (numpy.arctan2(rx*numpy.sin(a), ry*numpy.cos(a)) for a in self.angles) - sign = numpy.sign(self.angles[1] - self.angles[0]) - if sign != numpy.sign(a1 - a0): - a1 += sign * 2 * pi - - a = numpy.array((a0, a1)) + a0, a1 = a a0_offset = a0 - (a0 % (2 * pi)) sin_r = numpy.sin(self.rotation) cos_r = numpy.cos(self.rotation) - tan_r = numpy.tan(self.rotation) sin_a = numpy.sin(a) cos_a = numpy.cos(a) @@ -316,3 +311,23 @@ class Arc(Shape): return (type(self), radii, angles, width, self.layer), \ (self.offset, scale/norm_value, rotation, self.dose), \ lambda: Arc(radii=radii*norm_value, angles=angles, width=width, layer=self.layer) + + def _angles_to_parameters(self) -> numpy.ndarray: + ''' + :return: "Eccentric anomaly" parameter ranges for the inner and outer edges, in the form + [[a_min_inner, a_max_inner], [a_min_outer, a_max_outer]] + ''' + a = [] + for sgn in (-1, +1): + wh = sgn * self.width/2 + rx = self.radius_x + wh + ry = self.radius_y + wh + + # create paremeter 'a' for parametrized ellipse + a0, a1 = (numpy.arctan2(rx*numpy.sin(a), ry*numpy.cos(a)) for a in self.angles) + sign = numpy.sign(self.angles[1] - self.angles[0]) + if sign != numpy.sign(a1 - a0): + a1 += sign * 2 * pi + + a.append((a0, a1)) + return numpy.array(a)