From 9d42df831e39e36a67b9532c6bbb7a88b81fc7d4 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Thu, 23 Feb 2023 11:25:40 -0800 Subject: [PATCH] remove per-shape polygonization state --- masque/builder/builder.py | 6 +++--- masque/pattern.py | 14 ++++++------ masque/shapes/__init__.py | 2 +- masque/shapes/arc.py | 45 +++++++++++++++------------------------ masque/shapes/circle.py | 37 ++++++++++---------------------- masque/shapes/ellipse.py | 36 +++++++++---------------------- masque/shapes/path.py | 14 +++++++----- masque/shapes/polygon.py | 4 ++-- masque/shapes/shape.py | 2 +- masque/shapes/text.py | 18 +++++++++------- 10 files changed, 71 insertions(+), 107 deletions(-) diff --git a/masque/builder/builder.py b/masque/builder/builder.py index e289cd9..52aafaa 100644 --- a/masque/builder/builder.py +++ b/masque/builder/builder.py @@ -9,7 +9,7 @@ from numpy.typing import ArrayLike from ..pattern import Pattern, NamedPattern from ..ref import Ref -from ..library import MutableLibrary +from ..library import MutableLibrary, Tree from ..error import PortError, BuildError from ..ports import PortList, Port from ..abstract import Abstract @@ -492,7 +492,6 @@ class Builder(PortList): return s - class Pather(Builder): """ TODO DOCUMENT Builder @@ -657,7 +656,8 @@ class Pather(Builder): if tools is None and hasattr(source, 'tools') and isinstance(source.tools, dict): tools = source.tools - new = Pather.from_builder(Builder.interface( + new = Pather.from_builder( + Builder.interface( source=source, library=library, in_prefix=in_prefix, diff --git a/masque/pattern.py b/masque/pattern.py index 542d139..2a72a63 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -214,18 +214,18 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): def polygonize( self: P, - poly_num_points: Optional[int] = None, - poly_max_arclen: Optional[float] = None, + num_points: Optional[int] = None, + max_arclen: Optional[float] = None, ) -> P: """ Calls `.to_polygons(...)` on all the shapes in this Pattern, replacing them with the returned polygons. Arguments are passed directly to `shape.to_polygons(...)`. Args: - poly_num_points: Number of points to use for each polygon. Can be overridden by - `poly_max_arclen` if that results in more points. Optional, defaults to shapes' + num_points: Number of points to use for each polygon. Can be overridden by + `max_arclen` if that results in more points. Optional, defaults to shapes' internal defaults. - poly_max_arclen: Maximum arclength which can be approximated by a single line + max_arclen: Maximum arclength which can be approximated by a single line segment. Optional, defaults to shapes' internal defaults. Returns: @@ -233,7 +233,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): """ old_shapes = self.shapes self.shapes = list(chain.from_iterable(( - shape.to_polygons(poly_num_points, poly_max_arclen) + shape.to_polygons(num_points, max_arclen) for shape in old_shapes))) return self @@ -713,7 +713,7 @@ class NamedPattern(Pattern): def __deepcopy__(self, memo: Optional[Dict] = None) -> Pattern: return Pattern.__deepcopy__(self, memo) - def as_pattern(self) -> Pattern: + def as_pattern(self) -> Pattern: return Pattern( shapes=self.shapes, labels=self.labels, diff --git a/masque/shapes/__init__.py b/masque/shapes/__init__.py index 4a513e9..ab4a83a 100644 --- a/masque/shapes/__init__.py +++ b/masque/shapes/__init__.py @@ -3,7 +3,7 @@ Shapes for use with the Pattern class, as well as the Shape abstract class from which they are derived. """ -from .shape import Shape, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS +from .shape import Shape, normalized_shape_tuple, DEFAULT_POLY_NUM_VERTICES from .polygon import Polygon from .circle import Circle diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index 3e6e8bd..cb07a81 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -6,7 +6,7 @@ import numpy from numpy import pi from numpy.typing import NDArray, ArrayLike -from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS +from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_VERTICES from ..error import PatternError from ..repetition import Repetition from ..utils import is_scalar, layer_t, annotations_t @@ -23,7 +23,6 @@ class Arc(Shape): """ __slots__ = ( '_radii', '_angles', '_width', '_rotation', - 'poly_num_points', 'poly_max_arclen', # Inherited '_offset', '_layer', '_repetition', '_annotations', ) @@ -40,12 +39,6 @@ class Arc(Shape): _width: float """ Width of the arc """ - poly_num_points: Optional[int] - """ Sets the default number of points for `.polygonize()` """ - - poly_max_arclen: Optional[float] - """ Sets the default max segement length for `.polygonize()` """ - # radius properties @property def radii(self) -> Any: # TODO mypy#3004 NDArray[numpy.float64]: @@ -160,8 +153,6 @@ class Arc(Shape): angles: ArrayLike, width: float, *, - poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS, - poly_max_arclen: Optional[float] = None, offset: ArrayLike = (0.0, 0.0), rotation: float = 0, mirrored: Sequence[bool] = (False, False), @@ -191,8 +182,6 @@ class Arc(Shape): self.repetition = repetition self.annotations = annotations if annotations is not None else {} self.layer = layer - self.poly_num_points = poly_num_points - self.poly_max_arclen = poly_max_arclen [self.mirror(a) for a, do in enumerate(mirrored) if do] def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Arc': @@ -206,15 +195,10 @@ class Arc(Shape): def to_polygons( self, - poly_num_points: Optional[int] = None, - poly_max_arclen: Optional[float] = None, + num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES, + max_arclen: Optional[float] = None, ) -> List[Polygon]: - if poly_num_points is None: - poly_num_points = self.poly_num_points - if poly_max_arclen is None: - poly_max_arclen = self.poly_max_arclen - - if (poly_num_points is None) and (poly_max_arclen is None): + if (num_vertices is None) and (max_arclen is None): raise PatternError('Max number of points and arclength left unspecified' + ' (default was also overridden)') @@ -232,18 +216,18 @@ class Arc(Shape): perimeter = abs(a0 - a1) / (2 * pi) * ellipse_perimeter # TODO: make this more accurate n = [] - if poly_num_points is not None: - n += [poly_num_points] - if poly_max_arclen is not None: - n += [perimeter / poly_max_arclen] - num_points = int(round(max(n))) + if num_vertices is not None: + n += [num_vertices] + if max_arclen is not None: + n += [perimeter / max_arclen] + num_vertices = int(round(max(n))) wh = self.width / 2.0 if wh == r0 or wh == r1: thetas_inner = numpy.zeros(1) # Don't generate multiple vertices if we're at the origin else: - thetas_inner = numpy.linspace(a_ranges[0][1], a_ranges[0][0], num_points, endpoint=True) - thetas_outer = numpy.linspace(a_ranges[1][0], a_ranges[1][1], num_points, endpoint=True) + thetas_inner = numpy.linspace(a_ranges[0][1], a_ranges[0][0], num_vertices, endpoint=True) + thetas_outer = numpy.linspace(a_ranges[1][0], a_ranges[1][1], num_vertices, endpoint=True) 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)) @@ -370,7 +354,12 @@ class Arc(Shape): return ((type(self), radii, angles, width / norm_value, self.layer), (self.offset, scale / norm_value, rotation, False), - lambda: Arc(radii=radii * norm_value, angles=angles, width=width * norm_value, layer=self.layer)) + lambda: Arc( + radii=radii * norm_value, + angles=angles, + width=width * norm_value, + layer=self.layer, + )) def get_cap_edges(self) -> NDArray[numpy.float64]: ''' diff --git a/masque/shapes/circle.py b/masque/shapes/circle.py index 545c459..e74da28 100644 --- a/masque/shapes/circle.py +++ b/masque/shapes/circle.py @@ -5,7 +5,7 @@ import numpy from numpy import pi from numpy.typing import NDArray, ArrayLike -from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS +from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_VERTICES from ..error import PatternError from ..repetition import Repetition from ..utils import is_scalar, layer_t, annotations_t @@ -16,7 +16,7 @@ class Circle(Shape): A circle, which has a position and radius. """ __slots__ = ( - '_radius', 'poly_num_points', 'poly_max_arclen', + '_radius', # Inherited '_offset', '_layer', '_repetition', '_annotations', ) @@ -24,12 +24,6 @@ class Circle(Shape): _radius: float """ Circle radius """ - poly_num_points: Optional[int] - """ Sets the default number of points for `.polygonize()` """ - - poly_max_arclen: Optional[float] - """ Sets the default max segement length for `.polygonize()` """ - # radius property @property def radius(self) -> float: @@ -50,8 +44,6 @@ class Circle(Shape): self, radius: float, *, - poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS, - poly_max_arclen: Optional[float] = None, offset: ArrayLike = (0.0, 0.0), layer: layer_t = 0, repetition: Optional[Repetition] = None, @@ -71,8 +63,6 @@ class Circle(Shape): self.repetition = repetition self.annotations = annotations if annotations is not None else {} self.layer = layer - self.poly_num_points = poly_num_points - self.poly_max_arclen = poly_max_arclen def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Circle': memo = {} if memo is None else memo @@ -83,25 +73,20 @@ class Circle(Shape): def to_polygons( self, - poly_num_points: Optional[int] = None, - poly_max_arclen: Optional[float] = None, + num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES, + max_arclen: Optional[float] = None, ) -> List[Polygon]: - if poly_num_points is None: - poly_num_points = self.poly_num_points - if poly_max_arclen is None: - poly_max_arclen = self.poly_max_arclen - - if (poly_num_points is None) and (poly_max_arclen is None): + if (num_vertices is None) and (max_arclen is None): raise PatternError('Number of points and arclength left ' 'unspecified (default was also overridden)') n: List[float] = [] - if poly_num_points is not None: - n += [poly_num_points] - if poly_max_arclen is not None: - n += [2 * pi * self.radius / poly_max_arclen] - num_points = int(round(max(n))) - thetas = numpy.linspace(2 * pi, 0, num_points, endpoint=False) + if num_vertices is not None: + n += [num_vertices] + if max_arclen is not None: + n += [2 * pi * self.radius / max_arclen] + num_vertices = int(round(max(n))) + thetas = numpy.linspace(2 * pi, 0, num_vertices, endpoint=False) xs = numpy.cos(thetas) * self.radius ys = numpy.sin(thetas) * self.radius xys = numpy.vstack((xs, ys)).T diff --git a/masque/shapes/ellipse.py b/masque/shapes/ellipse.py index 6b39225..d3de929 100644 --- a/masque/shapes/ellipse.py +++ b/masque/shapes/ellipse.py @@ -6,7 +6,7 @@ import numpy from numpy import pi from numpy.typing import ArrayLike, NDArray -from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS +from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_VERTICES from ..error import PatternError from ..repetition import Repetition from ..utils import is_scalar, rotation_matrix_2d, layer_t, annotations_t @@ -19,7 +19,6 @@ class Ellipse(Shape): """ __slots__ = ( '_radii', '_rotation', - 'poly_num_points', 'poly_max_arclen', # Inherited '_offset', '_layer', '_repetition', '_annotations', ) @@ -30,12 +29,6 @@ class Ellipse(Shape): _rotation: float """ Angle from x-axis to first radius (ccw, radians) """ - poly_num_points: Optional[int] - """ Sets the default number of points for `.polygonize()` """ - - poly_max_arclen: Optional[float] - """ Sets the default max segement length for `.polygonize()` """ - # radius properties @property def radii(self) -> Any: # TODO mypy#3004 NDArray[numpy.float64]: @@ -95,8 +88,6 @@ class Ellipse(Shape): self, radii: ArrayLike, *, - poly_num_points: Optional[int] = DEFAULT_POLY_NUM_POINTS, - poly_max_arclen: Optional[float] = None, offset: ArrayLike = (0.0, 0.0), rotation: float = 0, mirrored: Sequence[bool] = (False, False), @@ -122,8 +113,6 @@ class Ellipse(Shape): self.annotations = annotations if annotations is not None else {} self.layer = layer [self.mirror(a) for a, do in enumerate(mirrored) if do] - self.poly_num_points = poly_num_points - self.poly_max_arclen = poly_max_arclen def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Ellipse': memo = {} if memo is None else memo @@ -135,15 +124,10 @@ class Ellipse(Shape): def to_polygons( self, - poly_num_points: Optional[int] = None, - poly_max_arclen: Optional[float] = None, + num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES, + max_arclen: Optional[float] = None, ) -> List[Polygon]: - if poly_num_points is None: - poly_num_points = self.poly_num_points - if poly_max_arclen is None: - poly_max_arclen = self.poly_max_arclen - - if (poly_num_points is None) and (poly_max_arclen is None): + if (num_vertices is None) and (max_arclen is None): raise PatternError('Number of points and arclength left unspecified' ' (default was also overridden)') @@ -156,12 +140,12 @@ class Ellipse(Shape): perimeter = pi * (r1 + r0) * (1 + 3 * h / (10 + math.sqrt(4 - 3 * h))) n = [] - if poly_num_points is not None: - n += [poly_num_points] - if poly_max_arclen is not None: - n += [perimeter / poly_max_arclen] - num_points = int(round(max(n))) - thetas = numpy.linspace(2 * pi, 0, num_points, endpoint=False) + if num_vertices is not None: + n += [num_vertices] + if max_arclen is not None: + n += [perimeter / max_arclen] + num_vertices = int(round(max(n))) + thetas = numpy.linspace(2 * pi, 0, num_vertices, endpoint=False) sin_th, cos_th = (numpy.sin(thetas), numpy.cos(thetas)) xs = r0 * cos_th diff --git a/masque/shapes/path.py b/masque/shapes/path.py index 628e2ab..852a8a2 100644 --- a/masque/shapes/path.py +++ b/masque/shapes/path.py @@ -243,8 +243,8 @@ class Path(Shape): def to_polygons( self, - poly_num_points: Optional[int] = None, - poly_max_arclen: Optional[float] = None, + num_vertices: Optional[int] = None, + max_arclen: Optional[float] = None, ) -> List['Polygon']: extensions = self._calculate_cap_extensions() @@ -311,7 +311,7 @@ class Path(Shape): #for vert in v: # not sure if every vertex, or just ends? for vert in [v[0], v[-1]]: circ = Circle(offset=vert, radius=self.width / 2, layer=self.layer) - polys += circ.to_polygons(poly_num_points=poly_num_points, poly_max_arclen=poly_max_arclen) + polys += circ.to_polygons(num_vertices=num_vertices, max_arclen=max_arclen) return polys @@ -372,8 +372,12 @@ class Path(Shape): return ((type(self), reordered_vertices.data.tobytes(), width0, self.cap, self.layer), (offset, scale / norm_value, rotation, False), - lambda: Path(reordered_vertices * norm_value, width=self.width * norm_value, - cap=self.cap, layer=self.layer)) + lambda: Path( + reordered_vertices * norm_value, + width=self.width * norm_value, + cap=self.cap, + layer=self.layer, + )) def clean_vertices(self) -> 'Path': """ diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index 8eade70..a4b1291 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -333,8 +333,8 @@ class Polygon(Shape): def to_polygons( self, - poly_num_points: Optional[int] = None, # unused - poly_max_arclen: Optional[float] = None, # unused + num_vertices: Optional[int] = None, # unused + max_arclen: Optional[float] = None, # unused ) -> List['Polygon']: return [copy.deepcopy(self)] diff --git a/masque/shapes/shape.py b/masque/shapes/shape.py index 3e4c1bb..6784013 100644 --- a/masque/shapes/shape.py +++ b/masque/shapes/shape.py @@ -23,7 +23,7 @@ normalized_shape_tuple = Tuple[ # ## Module-wide defaults # Default number of points per polygon for shapes -DEFAULT_POLY_NUM_POINTS = 24 +DEFAULT_POLY_NUM_VERTICES = 24 T = TypeVar('T', bound='Shape') diff --git a/masque/shapes/text.py b/masque/shapes/text.py index 62ef960..4663f49 100644 --- a/masque/shapes/text.py +++ b/masque/shapes/text.py @@ -110,8 +110,8 @@ class Text(RotatableImpl, Shape): def to_polygons( self, - poly_num_points: Optional[int] = None, # unused - poly_max_arclen: Optional[float] = None, # unused + num_vertices: Optional[int] = None, # unused + max_arclen: Optional[float] = None, # unused ) -> List[Polygon]: all_polygons = [] total_advance = 0.0 @@ -146,12 +146,14 @@ class Text(RotatableImpl, Shape): rotation %= 2 * pi return ((type(self), self.string, self.font_path, self.layer), (self.offset, self.height / norm_value, rotation, mirror_x), - lambda: Text(string=self.string, - height=self.height * norm_value, - font_path=self.font_path, - rotation=rotation, - mirrored=(mirror_x, False), - layer=self.layer)) + lambda: Text( + string=self.string, + height=self.height * norm_value, + font_path=self.font_path, + rotation=rotation, + mirrored=(mirror_x, False), + layer=self.layer, + )) def get_bounds(self) -> NDArray[numpy.float64]: # rotation makes this a huge pain when using slot.advance and glyph.bbox(), so