[shapes] move to per-shape purpose-built _from_raw constructors
This commit is contained in:
parent
3f63599abe
commit
37d462525c
11 changed files with 288 additions and 139 deletions
|
|
@ -323,26 +323,40 @@ def _gpath_to_mpath(gpath: klamath.library.Path, raw_mode: bool) -> tuple[layer_
|
|||
else:
|
||||
raise PatternError(f'Unrecognized path type: {gpath.path_type}')
|
||||
|
||||
mpath = Path(
|
||||
vertices=gpath.xy.astype(float),
|
||||
vertices = gpath.xy.astype(float)
|
||||
annotations = _properties_to_annotations(gpath.properties)
|
||||
cap_extensions = None
|
||||
if cap == Path.Cap.SquareCustom:
|
||||
cap_extensions = numpy.asarray(gpath.extension, dtype=float)
|
||||
|
||||
if raw_mode:
|
||||
mpath = Path._from_raw(
|
||||
vertices=vertices,
|
||||
width=gpath.width,
|
||||
cap=cap,
|
||||
offset=numpy.zeros(2),
|
||||
annotations=_properties_to_annotations(gpath.properties),
|
||||
raw=raw_mode,
|
||||
cap_extensions=cap_extensions,
|
||||
annotations=annotations,
|
||||
)
|
||||
else:
|
||||
mpath = Path(
|
||||
vertices=vertices,
|
||||
width=gpath.width,
|
||||
cap=cap,
|
||||
cap_extensions=cap_extensions,
|
||||
offset=numpy.zeros(2),
|
||||
annotations=annotations,
|
||||
)
|
||||
if cap == Path.Cap.SquareCustom:
|
||||
mpath.cap_extensions = gpath.extension
|
||||
return gpath.layer, mpath
|
||||
|
||||
|
||||
def _boundary_to_polygon(boundary: klamath.library.Boundary, raw_mode: bool) -> tuple[layer_t, Polygon]:
|
||||
return boundary.layer, Polygon(
|
||||
vertices=boundary.xy[:-1].astype(float),
|
||||
offset=numpy.zeros(2),
|
||||
annotations=_properties_to_annotations(boundary.properties),
|
||||
raw=raw_mode,
|
||||
)
|
||||
vertices = boundary.xy[:-1].astype(float)
|
||||
annotations = _properties_to_annotations(boundary.properties)
|
||||
if raw_mode:
|
||||
poly = Polygon._from_raw(vertices=vertices, annotations=annotations)
|
||||
else:
|
||||
poly = Polygon(vertices=vertices, offset=numpy.zeros(2), annotations=annotations)
|
||||
return boundary.layer, poly
|
||||
|
||||
|
||||
def _mrefs_to_grefs(refs: dict[str | None, list[Ref]]) -> list[klamath.library.Reference]:
|
||||
|
|
|
|||
|
|
@ -680,8 +680,23 @@ def _gpaths_to_mpaths(
|
|||
cap_extensions = None
|
||||
|
||||
annotations = _read_annotations(prop_offs, prop_key, prop_val, ee)
|
||||
path = Path(vertices=vertices, offset=ZERO_OFFSET, annotations=annotations, raw=raw_mode,
|
||||
width=width, cap=cap,cap_extensions=cap_extensions)
|
||||
if raw_mode:
|
||||
path = Path._from_raw(
|
||||
vertices=vertices,
|
||||
width=width,
|
||||
cap=cap,
|
||||
cap_extensions=cap_extensions,
|
||||
annotations=annotations,
|
||||
)
|
||||
else:
|
||||
path = Path(
|
||||
vertices=vertices,
|
||||
width=width,
|
||||
cap=cap,
|
||||
cap_extensions=cap_extensions,
|
||||
offset=ZERO_OFFSET,
|
||||
annotations=annotations,
|
||||
)
|
||||
pat.shapes[layer].append(path)
|
||||
|
||||
|
||||
|
|
@ -718,13 +733,13 @@ def _boundary_batches_to_polygons(
|
|||
if raw_mode:
|
||||
poly = Polygon._from_raw(vertices=vertices, annotations=None)
|
||||
else:
|
||||
poly = Polygon(vertices=vertices, offset=ZERO_OFFSET, annotations=None, raw=False)
|
||||
poly = Polygon(vertices=vertices, offset=ZERO_OFFSET, annotations=None)
|
||||
pat.shapes[layer].append(poly)
|
||||
else:
|
||||
if raw_mode:
|
||||
polys = PolyCollection._from_raw(vertex_lists=vertices, vertex_offsets=vertex_offsets, annotations=None)
|
||||
else:
|
||||
polys = PolyCollection(vertex_lists=vertices, vertex_offsets=vertex_offsets, offset=ZERO_OFFSET, annotations=None, raw=False)
|
||||
polys = PolyCollection(vertex_lists=vertices, vertex_offsets=vertex_offsets, offset=ZERO_OFFSET, annotations=None)
|
||||
pat.shapes[layer].append(polys)
|
||||
|
||||
|
||||
|
|
@ -755,7 +770,7 @@ def _rect_batches_to_rectcollections(
|
|||
if raw_mode:
|
||||
rect_collection = RectCollection._from_raw(rects=rects, annotations=None)
|
||||
else:
|
||||
rect_collection = RectCollection(rects=rects, offset=ZERO_OFFSET, annotations=None, raw=False)
|
||||
rect_collection = RectCollection(rects=rects, offset=ZERO_OFFSET, annotations=None)
|
||||
pat.shapes[layer].append(rect_collection)
|
||||
|
||||
|
||||
|
|
@ -790,7 +805,7 @@ def _boundary_props_to_polygons(
|
|||
if raw_mode:
|
||||
poly = Polygon._from_raw(vertices=vertices, annotations=annotations)
|
||||
else:
|
||||
poly = Polygon(vertices=vertices, offset=ZERO_OFFSET, annotations=annotations, raw=False)
|
||||
poly = Polygon(vertices=vertices, offset=ZERO_OFFSET, annotations=annotations)
|
||||
pat.shapes[layer].append(poly)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -159,20 +159,7 @@ class Arc(PositionableImpl, Shape):
|
|||
rotation: float = 0,
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t = None,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert isinstance(radii, numpy.ndarray)
|
||||
assert isinstance(angles, numpy.ndarray)
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
self._radii = radii
|
||||
self._angles = angles
|
||||
self._width = width
|
||||
self._offset = offset
|
||||
self._rotation = rotation
|
||||
self._repetition = repetition
|
||||
self._annotations = annotations
|
||||
else:
|
||||
self.radii = radii
|
||||
self.angles = angles
|
||||
self.width = width
|
||||
|
|
@ -181,6 +168,28 @@ class Arc(PositionableImpl, Shape):
|
|||
self.repetition = repetition
|
||||
self.annotations = annotations
|
||||
|
||||
@classmethod
|
||||
def _from_raw(
|
||||
cls,
|
||||
*,
|
||||
radii: NDArray[numpy.float64],
|
||||
angles: NDArray[numpy.float64],
|
||||
width: float,
|
||||
offset: NDArray[numpy.float64],
|
||||
rotation: float,
|
||||
annotations: annotations_t = None,
|
||||
repetition: Repetition | None = None,
|
||||
) -> 'Arc':
|
||||
new = cls.__new__(cls)
|
||||
new._radii = radii
|
||||
new._angles = angles
|
||||
new._width = width
|
||||
new._offset = offset
|
||||
new._rotation = rotation % (2 * pi)
|
||||
new._repetition = repetition
|
||||
new._annotations = annotations
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> 'Arc':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
|
|
|
|||
|
|
@ -50,20 +50,28 @@ class Circle(PositionableImpl, Shape):
|
|||
offset: ArrayLike = (0.0, 0.0),
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t = None,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
self._radius = radius
|
||||
self._offset = offset
|
||||
self._repetition = repetition
|
||||
self._annotations = annotations
|
||||
else:
|
||||
self.radius = radius
|
||||
self.offset = offset
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations
|
||||
|
||||
@classmethod
|
||||
def _from_raw(
|
||||
cls,
|
||||
*,
|
||||
radius: float,
|
||||
offset: NDArray[numpy.float64],
|
||||
annotations: annotations_t = None,
|
||||
repetition: Repetition | None = None,
|
||||
) -> 'Circle':
|
||||
new = cls.__new__(cls)
|
||||
new._radius = radius
|
||||
new._offset = offset
|
||||
new._repetition = repetition
|
||||
new._annotations = annotations
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> 'Circle':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
|
|
|
|||
|
|
@ -95,23 +95,31 @@ class Ellipse(PositionableImpl, Shape):
|
|||
rotation: float = 0,
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t = None,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert isinstance(radii, numpy.ndarray)
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
self._radii = radii
|
||||
self._offset = offset
|
||||
self._rotation = rotation
|
||||
self._repetition = repetition
|
||||
self._annotations = annotations
|
||||
else:
|
||||
self.radii = radii
|
||||
self.offset = offset
|
||||
self.rotation = rotation
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations
|
||||
|
||||
@classmethod
|
||||
def _from_raw(
|
||||
cls,
|
||||
*,
|
||||
radii: NDArray[numpy.float64],
|
||||
offset: NDArray[numpy.float64],
|
||||
rotation: float,
|
||||
annotations: annotations_t = None,
|
||||
repetition: Repetition | None = None,
|
||||
) -> Self:
|
||||
new = cls.__new__(cls)
|
||||
new._radii = radii
|
||||
new._offset = offset
|
||||
new._rotation = rotation % pi
|
||||
new._repetition = repetition
|
||||
new._annotations = annotations
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> Self:
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
|
|
|
|||
|
|
@ -201,20 +201,9 @@ class Path(Shape):
|
|||
rotation: float = 0,
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t = None,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
self._cap_extensions = None # Since .cap setter might access it
|
||||
|
||||
if raw:
|
||||
assert isinstance(vertices, numpy.ndarray)
|
||||
assert isinstance(cap_extensions, numpy.ndarray) or cap_extensions is None
|
||||
self._vertices = vertices
|
||||
self._repetition = repetition
|
||||
self._annotations = annotations
|
||||
self._width = width
|
||||
self._cap = cap
|
||||
self._cap_extensions = cap_extensions
|
||||
else:
|
||||
self.vertices = vertices
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations
|
||||
|
|
@ -229,6 +218,26 @@ class Path(Shape):
|
|||
if numpy.any(offset):
|
||||
self.translate(offset)
|
||||
|
||||
@classmethod
|
||||
def _from_raw(
|
||||
cls,
|
||||
*,
|
||||
vertices: NDArray[numpy.float64],
|
||||
width: float,
|
||||
cap: PathCap,
|
||||
cap_extensions: NDArray[numpy.float64] | None = None,
|
||||
annotations: annotations_t = None,
|
||||
repetition: Repetition | None = None,
|
||||
) -> Self:
|
||||
new = cls.__new__(cls)
|
||||
new._vertices = vertices
|
||||
new._width = width
|
||||
new._cap = cap
|
||||
new._cap_extensions = cap_extensions
|
||||
new._repetition = repetition
|
||||
new._annotations = annotations
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> 'Path':
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
|
|
|
|||
|
|
@ -100,17 +100,7 @@ class PolyCollection(Shape):
|
|||
rotation: float = 0.0,
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t = None,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert isinstance(vertex_lists, numpy.ndarray)
|
||||
assert isinstance(vertex_offsets, numpy.ndarray)
|
||||
assert numpy.issubdtype(vertex_offsets.dtype, numpy.integer)
|
||||
self._vertex_lists = vertex_lists
|
||||
self._vertex_offsets = vertex_offsets
|
||||
self._repetition = repetition
|
||||
self._annotations = annotations
|
||||
else:
|
||||
self._vertex_lists = numpy.asarray(vertex_lists, dtype=float)
|
||||
self._vertex_offsets = numpy.asarray(vertex_offsets, dtype=numpy.intp)
|
||||
self.repetition = repetition
|
||||
|
|
|
|||
|
|
@ -115,14 +115,7 @@ class Polygon(Shape):
|
|||
rotation: float = 0.0,
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t = None,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert isinstance(vertices, numpy.ndarray)
|
||||
self._vertices = vertices
|
||||
self._repetition = repetition
|
||||
self._annotations = annotations
|
||||
else:
|
||||
self.vertices = vertices
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations
|
||||
|
|
|
|||
|
|
@ -86,14 +86,7 @@ class RectCollection(Shape):
|
|||
rotation: float = 0.0,
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t = None,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert isinstance(rects, numpy.ndarray)
|
||||
self._rects = rects
|
||||
self._repetition = repetition
|
||||
self._annotations = annotations
|
||||
else:
|
||||
self.rects = rects
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations
|
||||
|
|
|
|||
|
|
@ -73,18 +73,7 @@ class Text(PositionableImpl, RotatableImpl, Shape):
|
|||
mirrored: bool = False,
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t = None,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
self._offset = offset
|
||||
self._string = string
|
||||
self._height = height
|
||||
self._rotation = rotation
|
||||
self._mirrored = mirrored
|
||||
self._repetition = repetition
|
||||
self._annotations = annotations
|
||||
else:
|
||||
self.offset = offset
|
||||
self.string = string
|
||||
self.height = height
|
||||
|
|
@ -94,6 +83,30 @@ class Text(PositionableImpl, RotatableImpl, Shape):
|
|||
self.annotations = annotations
|
||||
self.font_path = font_path
|
||||
|
||||
@classmethod
|
||||
def _from_raw(
|
||||
cls,
|
||||
*,
|
||||
string: str,
|
||||
height: float,
|
||||
font_path: str,
|
||||
offset: NDArray[numpy.float64],
|
||||
rotation: float,
|
||||
mirrored: bool,
|
||||
annotations: annotations_t = None,
|
||||
repetition: Repetition | None = None,
|
||||
) -> Self:
|
||||
new = cls.__new__(cls)
|
||||
new._offset = offset
|
||||
new._string = string
|
||||
new._height = height
|
||||
new._rotation = rotation % (2 * pi)
|
||||
new._mirrored = mirrored
|
||||
new._repetition = repetition
|
||||
new._annotations = annotations
|
||||
new.font_path = font_path
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> Self:
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
|
|
|
|||
97
masque/test/test_raw_constructors.py
Normal file
97
masque/test/test_raw_constructors.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.testing import assert_allclose
|
||||
|
||||
from ..shapes import Arc, Circle, Ellipse, Path, Text
|
||||
|
||||
|
||||
def test_circle_raw_constructor_matches_public() -> None:
|
||||
raw = Circle._from_raw(
|
||||
radius=5.0,
|
||||
offset=numpy.array([1.0, 2.0]),
|
||||
annotations={'1': ['circle']},
|
||||
)
|
||||
public = Circle(
|
||||
radius=5.0,
|
||||
offset=(1.0, 2.0),
|
||||
annotations={'1': ['circle']},
|
||||
)
|
||||
assert raw == public
|
||||
|
||||
|
||||
def test_ellipse_raw_constructor_matches_public() -> None:
|
||||
raw = Ellipse._from_raw(
|
||||
radii=numpy.array([3.0, 5.0]),
|
||||
offset=numpy.array([1.0, 2.0]),
|
||||
rotation=5 * pi / 2,
|
||||
annotations={'2': ['ellipse']},
|
||||
)
|
||||
public = Ellipse(
|
||||
radii=(3.0, 5.0),
|
||||
offset=(1.0, 2.0),
|
||||
rotation=5 * pi / 2,
|
||||
annotations={'2': ['ellipse']},
|
||||
)
|
||||
assert raw == public
|
||||
|
||||
|
||||
def test_arc_raw_constructor_matches_public() -> None:
|
||||
raw = Arc._from_raw(
|
||||
radii=numpy.array([10.0, 6.0]),
|
||||
angles=numpy.array([0.0, pi / 2]),
|
||||
width=2.0,
|
||||
offset=numpy.array([1.0, 2.0]),
|
||||
rotation=5 * pi / 2,
|
||||
annotations={'3': ['arc']},
|
||||
)
|
||||
public = Arc(
|
||||
radii=(10.0, 6.0),
|
||||
angles=(0.0, pi / 2),
|
||||
width=2.0,
|
||||
offset=(1.0, 2.0),
|
||||
rotation=5 * pi / 2,
|
||||
annotations={'3': ['arc']},
|
||||
)
|
||||
assert raw == public
|
||||
|
||||
|
||||
def test_path_raw_constructor_matches_public() -> None:
|
||||
raw = Path._from_raw(
|
||||
vertices=numpy.array([[0.0, 0.0], [10.0, 0.0], [10.0, 5.0]]),
|
||||
width=2.0,
|
||||
cap=Path.Cap.SquareCustom,
|
||||
cap_extensions=numpy.array([1.0, 3.0]),
|
||||
annotations={'4': ['path']},
|
||||
)
|
||||
public = Path(
|
||||
vertices=((0.0, 0.0), (10.0, 0.0), (10.0, 5.0)),
|
||||
width=2.0,
|
||||
cap=Path.Cap.SquareCustom,
|
||||
cap_extensions=(1.0, 3.0),
|
||||
annotations={'4': ['path']},
|
||||
)
|
||||
assert raw == public
|
||||
assert raw.cap_extensions is not None
|
||||
assert_allclose(raw.cap_extensions, [1.0, 3.0])
|
||||
|
||||
|
||||
def test_text_raw_constructor_matches_public() -> None:
|
||||
raw = Text._from_raw(
|
||||
string='RAW',
|
||||
height=12.0,
|
||||
font_path='font.otf',
|
||||
offset=numpy.array([1.0, 2.0]),
|
||||
rotation=5 * pi / 2,
|
||||
mirrored=True,
|
||||
annotations={'5': ['text']},
|
||||
)
|
||||
public = Text(
|
||||
string='RAW',
|
||||
height=12.0,
|
||||
font_path='font.otf',
|
||||
offset=(1.0, 2.0),
|
||||
rotation=5 * pi / 2,
|
||||
mirrored=True,
|
||||
annotations={'5': ['text']},
|
||||
)
|
||||
assert raw == public
|
||||
Loading…
Add table
Add a link
Reference in a new issue