|
|
|
@ -2,12 +2,11 @@
|
|
|
|
|
Functionality for reading/writing elements (geometry, text labels,
|
|
|
|
|
structure references) and associated properties.
|
|
|
|
|
"""
|
|
|
|
|
from typing import Optional, IO, TypeVar, Type, Union
|
|
|
|
|
from typing import Dict, Tuple, Optional, BinaryIO, TypeVar, Type, Union
|
|
|
|
|
from abc import ABCMeta, abstractmethod
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
|
|
|
|
import numpy
|
|
|
|
|
from numpy.typing import NDArray
|
|
|
|
|
import numpy # type: ignore
|
|
|
|
|
|
|
|
|
|
from .basic import KlamathError
|
|
|
|
|
from .record import Record
|
|
|
|
@ -29,7 +28,8 @@ T = TypeVar('T', bound='Text')
|
|
|
|
|
X = TypeVar('X', bound='Box')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_properties(stream: IO[bytes]) -> dict[int, bytes]:
|
|
|
|
|
|
|
|
|
|
def read_properties(stream: BinaryIO) -> Dict[int, bytes]:
|
|
|
|
|
"""
|
|
|
|
|
Read element properties.
|
|
|
|
|
|
|
|
|
@ -56,7 +56,7 @@ def read_properties(stream: IO[bytes]) -> dict[int, bytes]:
|
|
|
|
|
return properties
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def write_properties(stream: IO[bytes], properties: dict[int, bytes]) -> int:
|
|
|
|
|
def write_properties(stream: BinaryIO, properties: Dict[int, bytes]) -> int:
|
|
|
|
|
"""
|
|
|
|
|
Write element properties.
|
|
|
|
|
|
|
|
|
@ -78,7 +78,7 @@ class Element(metaclass=ABCMeta):
|
|
|
|
|
"""
|
|
|
|
|
@classmethod
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def read(cls: Type[E], stream: IO[bytes]) -> E:
|
|
|
|
|
def read(cls: Type[E], stream: BinaryIO) -> E:
|
|
|
|
|
"""
|
|
|
|
|
Read from a stream to construct this object.
|
|
|
|
|
Consumes up to (and including) the ENDEL record.
|
|
|
|
@ -92,7 +92,7 @@ class Element(metaclass=ABCMeta):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def write(self, stream: IO[bytes]) -> int:
|
|
|
|
|
def write(self, stream: BinaryIO) -> int:
|
|
|
|
|
"""
|
|
|
|
|
Write this element to a stream.
|
|
|
|
|
Finishes with an ENDEL record.
|
|
|
|
@ -131,7 +131,7 @@ class Reference(Element):
|
|
|
|
|
angle_deg: float
|
|
|
|
|
""" Rotation (degrees counterclockwise) """
|
|
|
|
|
|
|
|
|
|
xy: NDArray[numpy.int32]
|
|
|
|
|
xy: numpy.ndarray
|
|
|
|
|
"""
|
|
|
|
|
(For SREF) Location in the parent structure corresponding to the instance's origin (0, 0).
|
|
|
|
|
(For AREF) 3 locations:
|
|
|
|
@ -144,14 +144,14 @@ class Reference(Element):
|
|
|
|
|
basis vectors to match it.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
colrow: tuple[int, int] | NDArray[numpy.int16] | None
|
|
|
|
|
colrow: Optional[Union[Tuple[int, int], numpy.ndarray]]
|
|
|
|
|
""" Number of columns and rows (AREF) or None (SREF) """
|
|
|
|
|
|
|
|
|
|
properties: dict[int, bytes]
|
|
|
|
|
properties: Dict[int, bytes]
|
|
|
|
|
""" Properties associated with this reference. """
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def read(cls: Type[R], stream: IO[bytes]) -> R:
|
|
|
|
|
def read(cls: Type[R], stream: BinaryIO) -> R:
|
|
|
|
|
invert_y = False
|
|
|
|
|
mag = 1
|
|
|
|
|
angle_deg = 0
|
|
|
|
@ -174,17 +174,10 @@ class Reference(Element):
|
|
|
|
|
size, tag = Record.read_header(stream)
|
|
|
|
|
xy = XY.read_data(stream, size).reshape(-1, 2)
|
|
|
|
|
properties = read_properties(stream)
|
|
|
|
|
return cls(
|
|
|
|
|
struct_name=struct_name,
|
|
|
|
|
xy=xy,
|
|
|
|
|
properties=properties,
|
|
|
|
|
colrow=colrow,
|
|
|
|
|
invert_y=invert_y,
|
|
|
|
|
mag=mag,
|
|
|
|
|
angle_deg=angle_deg,
|
|
|
|
|
)
|
|
|
|
|
return cls(struct_name=struct_name, xy=xy, properties=properties, colrow=colrow,
|
|
|
|
|
invert_y=invert_y, mag=mag, angle_deg=angle_deg)
|
|
|
|
|
|
|
|
|
|
def write(self, stream: IO[bytes]) -> int:
|
|
|
|
|
def write(self, stream: BinaryIO) -> int:
|
|
|
|
|
b = 0
|
|
|
|
|
if self.colrow is None:
|
|
|
|
|
b += SREF.write(stream, None)
|
|
|
|
@ -196,7 +189,7 @@ class Reference(Element):
|
|
|
|
|
b += STRANS.write(stream, int(self.invert_y) << 15)
|
|
|
|
|
if self.mag != 1:
|
|
|
|
|
b += MAG.write(stream, self.mag)
|
|
|
|
|
if self.angle_deg != 0:
|
|
|
|
|
if self.angle_deg !=0:
|
|
|
|
|
b += ANGLE.write(stream, self.angle_deg)
|
|
|
|
|
|
|
|
|
|
if self.colrow is not None:
|
|
|
|
@ -223,24 +216,24 @@ class Boundary(Element):
|
|
|
|
|
"""
|
|
|
|
|
__slots__ = ('layer', 'xy', 'properties')
|
|
|
|
|
|
|
|
|
|
layer: tuple[int, int]
|
|
|
|
|
layer: Tuple[int, int]
|
|
|
|
|
""" (layer, data_type) tuple """
|
|
|
|
|
|
|
|
|
|
xy: NDArray[numpy.int32]
|
|
|
|
|
xy: numpy.ndarray
|
|
|
|
|
""" Ordered vertices of the shape. First and last points should be identical. """
|
|
|
|
|
|
|
|
|
|
properties: dict[int, bytes]
|
|
|
|
|
properties: Dict[int, bytes]
|
|
|
|
|
""" Properties for the element. """
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def read(cls: Type[B], stream: IO[bytes]) -> B:
|
|
|
|
|
def read(cls: Type[B], stream: BinaryIO) -> B:
|
|
|
|
|
layer = LAYER.skip_and_read(stream)[0]
|
|
|
|
|
dtype = DATATYPE.read(stream)[0]
|
|
|
|
|
xy = XY.read(stream).reshape(-1, 2)
|
|
|
|
|
properties = read_properties(stream)
|
|
|
|
|
return cls(layer=(layer, dtype), xy=xy, properties=properties)
|
|
|
|
|
|
|
|
|
|
def write(self, stream: IO[bytes]) -> int:
|
|
|
|
|
def write(self, stream: BinaryIO) -> int:
|
|
|
|
|
b = BOUNDARY.write(stream, None)
|
|
|
|
|
b += LAYER.write(stream, self.layer[0])
|
|
|
|
|
b += DATATYPE.write(stream, self.layer[1])
|
|
|
|
@ -260,7 +253,7 @@ class Path(Element):
|
|
|
|
|
"""
|
|
|
|
|
__slots__ = ('layer', 'xy', 'properties', 'path_type', 'width', 'extension')
|
|
|
|
|
|
|
|
|
|
layer: tuple[int, int]
|
|
|
|
|
layer: Tuple[int, int]
|
|
|
|
|
""" (layer, data_type) tuple """
|
|
|
|
|
|
|
|
|
|
path_type: int
|
|
|
|
@ -269,17 +262,17 @@ class Path(Element):
|
|
|
|
|
width: int
|
|
|
|
|
""" Path width """
|
|
|
|
|
|
|
|
|
|
extension: tuple[int, int]
|
|
|
|
|
extension: Tuple[int, int]
|
|
|
|
|
""" Extension when using path_type=4. Ignored otherwise. """
|
|
|
|
|
|
|
|
|
|
xy: NDArray[numpy.int32]
|
|
|
|
|
xy: numpy.ndarray
|
|
|
|
|
""" Path centerline coordinates """
|
|
|
|
|
|
|
|
|
|
properties: dict[int, bytes]
|
|
|
|
|
properties: Dict[int, bytes]
|
|
|
|
|
""" Properties for the element. """
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def read(cls: Type[P], stream: IO[bytes]) -> P:
|
|
|
|
|
def read(cls: Type[P], stream: BinaryIO) -> P:
|
|
|
|
|
path_type = 0
|
|
|
|
|
width = 0
|
|
|
|
|
bgn_ext = 0
|
|
|
|
@ -306,7 +299,7 @@ class Path(Element):
|
|
|
|
|
properties=properties, extension=(bgn_ext, end_ext),
|
|
|
|
|
path_type=path_type, width=width)
|
|
|
|
|
|
|
|
|
|
def write(self, stream: IO[bytes]) -> int:
|
|
|
|
|
def write(self, stream: BinaryIO) -> int:
|
|
|
|
|
b = PATH.write(stream, None)
|
|
|
|
|
b += LAYER.write(stream, self.layer[0])
|
|
|
|
|
b += DATATYPE.write(stream, self.layer[1])
|
|
|
|
@ -334,24 +327,24 @@ class Box(Element):
|
|
|
|
|
"""
|
|
|
|
|
__slots__ = ('layer', 'xy', 'properties')
|
|
|
|
|
|
|
|
|
|
layer: tuple[int, int]
|
|
|
|
|
layer: Tuple[int, int]
|
|
|
|
|
""" (layer, box_type) tuple """
|
|
|
|
|
|
|
|
|
|
xy: NDArray[numpy.int32]
|
|
|
|
|
xy: numpy.ndarray
|
|
|
|
|
""" Box coordinates (5 pairs) """
|
|
|
|
|
|
|
|
|
|
properties: dict[int, bytes]
|
|
|
|
|
properties: Dict[int, bytes]
|
|
|
|
|
""" Properties for the element. """
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def read(cls: Type[X], stream: IO[bytes]) -> X:
|
|
|
|
|
def read(cls: Type[X], stream: BinaryIO) -> X:
|
|
|
|
|
layer = LAYER.skip_and_read(stream)[0]
|
|
|
|
|
dtype = BOXTYPE.read(stream)[0]
|
|
|
|
|
xy = XY.read(stream).reshape(-1, 2)
|
|
|
|
|
properties = read_properties(stream)
|
|
|
|
|
return cls(layer=(layer, dtype), xy=xy, properties=properties)
|
|
|
|
|
|
|
|
|
|
def write(self, stream: IO[bytes]) -> int:
|
|
|
|
|
def write(self, stream: BinaryIO) -> int:
|
|
|
|
|
b = BOX.write(stream, None)
|
|
|
|
|
b += LAYER.write(stream, self.layer[0])
|
|
|
|
|
b += BOXTYPE.write(stream, self.layer[1])
|
|
|
|
@ -368,24 +361,24 @@ class Node(Element):
|
|
|
|
|
"""
|
|
|
|
|
__slots__ = ('layer', 'xy', 'properties')
|
|
|
|
|
|
|
|
|
|
layer: tuple[int, int]
|
|
|
|
|
layer: Tuple[int, int]
|
|
|
|
|
""" (layer, node_type) tuple """
|
|
|
|
|
|
|
|
|
|
xy: NDArray[numpy.int32]
|
|
|
|
|
xy: numpy.ndarray
|
|
|
|
|
""" 1-50 pairs of coordinates. """
|
|
|
|
|
|
|
|
|
|
properties: dict[int, bytes]
|
|
|
|
|
properties: Dict[int, bytes]
|
|
|
|
|
""" Properties for the element. """
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def read(cls: Type[N], stream: IO[bytes]) -> N:
|
|
|
|
|
def read(cls: Type[N], stream: BinaryIO) -> N:
|
|
|
|
|
layer = LAYER.skip_and_read(stream)[0]
|
|
|
|
|
dtype = NODETYPE.read(stream)[0]
|
|
|
|
|
xy = XY.read(stream).reshape(-1, 2)
|
|
|
|
|
properties = read_properties(stream)
|
|
|
|
|
return cls(layer=(layer, dtype), xy=xy, properties=properties)
|
|
|
|
|
|
|
|
|
|
def write(self, stream: IO[bytes]) -> int:
|
|
|
|
|
def write(self, stream: BinaryIO) -> int:
|
|
|
|
|
b = NODE.write(stream, None)
|
|
|
|
|
b += LAYER.write(stream, self.layer[0])
|
|
|
|
|
b += NODETYPE.write(stream, self.layer[1])
|
|
|
|
@ -403,7 +396,7 @@ class Text(Element):
|
|
|
|
|
__slots__ = ('layer', 'xy', 'properties', 'presentation', 'path_type',
|
|
|
|
|
'width', 'invert_y', 'mag', 'angle_deg', 'string')
|
|
|
|
|
|
|
|
|
|
layer: tuple[int, int]
|
|
|
|
|
layer: Tuple[int, int]
|
|
|
|
|
""" (layer, node_type) tuple """
|
|
|
|
|
|
|
|
|
|
presentation: int
|
|
|
|
@ -428,17 +421,17 @@ class Text(Element):
|
|
|
|
|
angle_deg: float
|
|
|
|
|
""" Rotation (ccw). Default 0. """
|
|
|
|
|
|
|
|
|
|
xy: NDArray[numpy.int32]
|
|
|
|
|
xy: numpy.ndarray
|
|
|
|
|
""" Position (1 pair only) """
|
|
|
|
|
|
|
|
|
|
string: bytes
|
|
|
|
|
""" Text content """
|
|
|
|
|
|
|
|
|
|
properties: dict[int, bytes]
|
|
|
|
|
properties: Dict[int, bytes]
|
|
|
|
|
""" Properties for the element. """
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def read(cls: Type[T], stream: IO[bytes]) -> T:
|
|
|
|
|
def read(cls: Type[T], stream: BinaryIO) -> T:
|
|
|
|
|
path_type = 0
|
|
|
|
|
presentation = 0
|
|
|
|
|
invert_y = False
|
|
|
|
@ -474,7 +467,7 @@ class Text(Element):
|
|
|
|
|
string=string, presentation=presentation, path_type=path_type,
|
|
|
|
|
width=width, invert_y=invert_y, mag=mag, angle_deg=angle_deg)
|
|
|
|
|
|
|
|
|
|
def write(self, stream: IO[bytes]) -> int:
|
|
|
|
|
def write(self, stream: BinaryIO) -> int:
|
|
|
|
|
b = TEXT.write(stream, None)
|
|
|
|
|
b += LAYER.write(stream, self.layer[0])
|
|
|
|
|
b += TEXTTYPE.write(stream, self.layer[1])
|
|
|
|
@ -488,7 +481,7 @@ class Text(Element):
|
|
|
|
|
b += STRANS.write(stream, int(self.invert_y) << 15)
|
|
|
|
|
if self.mag != 1:
|
|
|
|
|
b += MAG.write(stream, self.mag)
|
|
|
|
|
if self.angle_deg != 0:
|
|
|
|
|
if self.angle_deg !=0:
|
|
|
|
|
b += ANGLE.write(stream, self.angle_deg)
|
|
|
|
|
b += XY.write(stream, self.xy)
|
|
|
|
|
b += STRING.write(stream, self.string)
|
|
|
|
|