Compare commits

..

No commits in common. "master" and "transform_analysis" have entirely different histories.

2 changed files with 15 additions and 17 deletions

View File

@ -244,31 +244,30 @@ class Arc(Shape):
#t0 = ellipeinc(a0 - pi / 2, m) #t0 = ellipeinc(a0 - pi / 2, m)
#perimeter2 = r0 * (t1 - t0) #perimeter2 = r0 * (t1 - t0)
def get_arclens(n_pts: int, a0: float, a1: float, dr: float) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64]]: def get_arclens(n_pts: int, a0: float, a1: float) -> tuple[NDArray[numpy.float64], NDArray[numpy.float64]]:
""" Get `n_pts` arclengths """ """ Get `n_pts` arclengths """
t, dt = numpy.linspace(a0, a1, n_pts, retstep=True) # NOTE: could probably use an adaptive number of points t, dt = numpy.linspace(a0, a1, n_pts, retstep=True) # NOTE: could probably use an adaptive number of points
r0sin = (r0 + dr) * numpy.sin(t) r0sin = r0 * numpy.sin(t)
r1cos = (r1 + dr) * numpy.cos(t) r1cos = r1 * numpy.cos(t)
arc_dl = numpy.sqrt(r0sin * r0sin + r1cos * r1cos) arc_dl = numpy.sqrt(r0sin * r0sin + r1cos * r1cos)
#arc_lengths = numpy.diff(t) * (arc_dl[1:] + arc_dl[:-1]) / 2 #arc_lengths = numpy.diff(t) * (arc_dl[1:] + arc_dl[:-1]) / 2
arc_lengths = (arc_dl[1:] + arc_dl[:-1]) * numpy.abs(dt) / 2 arc_lengths = (arc_dl[1:] + arc_dl[:-1]) * numpy.abs(dt) / 2
return arc_lengths, t return arc_lengths, t
wh = self.width / 2.0
if num_vertices is not None: if num_vertices is not None:
n_pts = numpy.ceil(max(self.radii + wh) / min(self.radii) * num_vertices * 100).astype(int) n_pts = numpy.ceil(max(self.radii) / min(self.radii) * num_vertices * 100).astype(int)
perimeter_inner = get_arclens(n_pts, *a_ranges[0], dr=-wh)[0].sum() perimeter_inner = get_arclens(n_pts, *a_ranges[0])[0].sum()
perimeter_outer = get_arclens(n_pts, *a_ranges[1], dr= wh)[0].sum() perimeter_outer = get_arclens(n_pts, *a_ranges[1])[0].sum()
implied_arclen = (perimeter_outer + perimeter_inner + self.width * 2) / num_vertices implied_arclen = (perimeter_outer + perimeter_inner + self.width * 2) / num_vertices
max_arclen = min(implied_arclen, max_arclen if max_arclen is not None else numpy.inf) max_arclen = min(implied_arclen, max_arclen if max_arclen is not None else numpy.inf)
assert max_arclen is not None assert max_arclen is not None
def get_thetas(inner: bool) -> NDArray[numpy.float64]: def get_thetas(inner: bool) -> NDArray[numpy.float64]:
""" Figure out the parameter values at which we should place vertices to meet the arclength constraint""" """ Figure out the parameter values at which we should place vertices to meet the arclength constraint"""
dr = -wh if inner else wh #dr = -self.width / 2.0 * (-1 if inner else 1)
n_pts = numpy.ceil(2 * pi * max(self.radii + dr) / max_arclen).astype(int) n_pts = numpy.ceil(2 * pi * max(self.radii) / max_arclen).astype(int)
arc_lengths, thetas = get_arclens(n_pts, *a_ranges[0 if inner else 1], dr=dr) arc_lengths, thetas = get_arclens(n_pts, *a_ranges[0 if inner else 1])
keep = [0] keep = [0]
removable = (numpy.cumsum(arc_lengths) <= max_arclen) removable = (numpy.cumsum(arc_lengths) <= max_arclen)
@ -286,6 +285,7 @@ class Arc(Shape):
thetas = thetas[::-1] thetas = thetas[::-1]
return thetas return thetas
wh = self.width / 2.0
if wh in (r0, r1): if wh in (r0, r1):
thetas_inner = numpy.zeros(1) # Don't generate multiple vertices if we're at the origin thetas_inner = numpy.zeros(1) # Don't generate multiple vertices if we're at the origin
else: else:
@ -455,18 +455,17 @@ class Arc(Shape):
def _angles_to_parameters(self) -> NDArray[numpy.float64]: def _angles_to_parameters(self) -> NDArray[numpy.float64]:
""" """
Convert from polar angle to ellipse parameter (for [rx*cos(t), ry*sin(t)] representation)
Returns: Returns:
"Eccentric anomaly" parameter ranges for the inner and outer edges, in the form "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_min_inner, a_max_inner], [a_min_outer, a_max_outer]]`
""" """
a = [] a = []
for sgn in (-1, +1): for sgn in (-1, +1):
wh = sgn * self.width / 2.0 wh = sgn * self.width / 2
rx = self.radius_x + wh rx = self.radius_x + wh
ry = self.radius_y + 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) 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]) sign = numpy.sign(self.angles[1] - self.angles[0])
if sign != numpy.sign(a1 - a0): if sign != numpy.sign(a1 - a0):

View File

@ -20,7 +20,7 @@ class Polygon(Shape):
A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an
implicitly-closed boundary, and an offset. implicitly-closed boundary, and an offset.
Note that the setter for `Polygon.vertices` creates a copy of the Note that the setter for `Polygon.vertices` may creates a copy of the
passed vertex coordinates. passed vertex coordinates.
A `normalized_form(...)` is available, but can be quite slow with lots of vertices. A `normalized_form(...)` is available, but can be quite slow with lots of vertices.
@ -379,9 +379,8 @@ class Polygon(Shape):
def normalized_form(self, norm_value: float) -> normalized_shape_tuple: def normalized_form(self, norm_value: float) -> normalized_shape_tuple:
# Note: this function is going to be pretty slow for many-vertexed polygons, relative to # Note: this function is going to be pretty slow for many-vertexed polygons, relative to
# other shapes # other shapes
meanv = self.vertices.mean(axis=0) offset = self.vertices.mean(axis=0) + self.offset
zeroed_vertices = self.vertices - meanv zeroed_vertices = self.vertices - offset
offset = meanv + self.offset
scale = zeroed_vertices.std() scale = zeroed_vertices.std()
normed_vertices = zeroed_vertices / scale normed_vertices = zeroed_vertices / scale