"""
This module creates sketch objects with a neutral format for drawing.
Every other format is converted from this format.
If you need to save as a different format, you can use these
sketch objects to convert to the format you need.
Sketches are not meant to be modified.
They preserve the state of graphics objects at the time of drawing.
They are snapshots of the state of the objects and the Canvas at the time of drawing.
"""
from dataclasses import dataclass
from typing import List, Any
import numpy as np
from numpy import ndarray
from ..colors import colors
from .affine import identity_matrix
from .common import common_properties, Point
from .all_enums import Types, Anchor, FrameShape, CurveMode
from ..settings.settings import defaults
from ..geometry.geometry import homogenize
from ..helpers.utilities import decompose_transformations
Color = colors.Color
np.set_printoptions(legacy="1.21")
[docs]
@dataclass
class CircleSketch:
"""CircleSketch is a dataclass for creating a circle sketch object.
Attributes:
center (tuple): The center of the circle.
radius (float): The radius of the circle.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
"""
center: tuple
radius: float
xform_matrix: ndarray = None
def __post_init__(self):
"""Initialize the CircleSketch object."""
self.type = Types.SKETCH
self.subtype = Types.CIRCLE_SKETCH
if self.xform_matrix is None:
self.xform_matrix = identity_matrix()
center = self.center
else:
center = homogenize([self.center])
center = (center @ self.xform_matrix).tolist()[0][:2]
self.center = center
self.closed = True
[docs]
@dataclass
class EllipseSketch:
"""EllipseSketch is a dataclass for creating an ellipse sketch object.
Attributes:
center (tuple): The center of the ellipse.
x_radius (float): The x-axis radius of the ellipse.
y_radius (float): The y-axis radius of the ellipse.
angle (float, optional): The orientation angle. Defaults to 0.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
"""
center: tuple
x_radius: float
y_radius: float
angle: float = 0 # orientation angle
xform_matrix: ndarray = None
def __post_init__(self):
"""Initialize the EllipseSketch object."""
self.type = Types.SKETCH
self.subtype = Types.ELLIPSE_SKETCH
if self.xform_matrix is None:
self.xform_matrix = identity_matrix()
center = self.center
else:
center = homogenize([self.center])
center = (center @ self.xform_matrix).tolist()[0][:2]
self.center = center
self.closed = True
[docs]
@dataclass
class LineSketch:
"""LineSketch is a dataclass for creating a line sketch object.
Attributes:
vertices (list): The vertices of the line.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
"""
vertices: list
xform_matrix: ndarray = None
def __post_init__(self):
"""Initialize the LineSketch object."""
self.type = Types.SKETCH
self.subtype = Types.LINE_SKETCH
if self.xform_matrix is None:
self.xform_matrix = identity_matrix()
vertices = self.vertices
else:
vertices = homogenize(self.vertices)
vertices = vertices @ self.xform_matrix
self.vertices = [tuple(x) for x in vertices[:, :2]]
[docs]
@dataclass
class ShapeSketch:
"""ShapeSketch is a neutral format for drawing.
It contains geometry (only vertices for shapes) and style properties.
Style properties are not assigned during initialization.
They are not meant to be transformed, only to be drawn.
Sketches have no methods, only data.
They do not check anything, they just store data.
They are populated during sketch creation.
You should make sure the data is correct before creating a sketch.
Attributes:
vertices (list, optional): The vertices of the shape. Defaults to None.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
"""
vertices: list = None
xform_matrix: ndarray = None
def __post_init__(self):
"""Initialize the ShapeSketch object."""
self.type = Types.SKETCH
self.subtype = Types.SHAPE_SKETCH
if self.xform_matrix is None:
self.xform_matrix = identity_matrix()
vertices = self.vertices
else:
vertices = homogenize(self.vertices)
vertices = vertices @ self.xform_matrix
self.vertices = [tuple(x) for x in vertices[:, :2]]
[docs]
@dataclass
class BezierSketch:
"""BezierSketch is a dataclass for creating a bezier sketch object.
Attributes:
control_points (list): The control points of the bezier curve.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
mode (CurveMode, optional): The mode of the curve. Defaults to CurveMode.OPEN.
"""
control_points: list
xform_matrix: ndarray = None
mode: CurveMode = CurveMode.OPEN
def __post_init__(self):
"""Initialize the BezierSketch object."""
self.type = Types.SKETCH
self.subtype = Types.BEZIER_SKETCH
if self.xform_matrix is None:
self.xform_matrix = identity_matrix()
control_points = self.control_points
else:
control_points = homogenize(self.control_points)
control_points = control_points @ self.xform_matrix
self.control_points = [tuple(x) for x in control_points[:, :3]]
self.closed = False
[docs]
@dataclass
class ArcSketch:
"""ArcSketch is a dataclass for creating an arc sketch object.
Attributes:
center (tuple): The center of the arc.
start_angle (float): The start angle of the arc.
end_angle (float): The end angle of the arc.
radius (float): The radius of the arc.
radius2 (float, optional): The secondary radius of the arc. Defaults to None.
rot_angle (float, optional): The rotation angle of the arc. Defaults to 0.
start_point (tuple, optional): The start point of the arc. Defaults to None.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
mode (CurveMode, optional): The mode of the curve. Defaults to CurveMode.OPEN.
"""
center: tuple
start_angle: float
end_angle: float
radius: float
radius2: float = None
rot_angle: float = 0
start_point: tuple = None
xform_matrix: ndarray = None
mode: CurveMode = CurveMode.OPEN
def __post_init__(self):
"""Initialize the ArcSketch object."""
self.type = Types.SKETCH
self.subtype = Types.ARC_SKETCH
if self.xform_matrix is None:
self.xform_matrix = identity_matrix()
center = self.center
scale = 1
else:
center = homogenize([self.center])
center = (center @ self.xform_matrix).tolist()[0][:2]
_, _, scale = decompose_transformations(self.xform_matrix)
scale = scale[0]
n = defaults["tikz_nround"]
self.radius *= round(scale, n)
if self.radius2 is not None:
self.radius2 *= round(scale, n)
self.start_point = round(self.start_point[0], n), round(self.start_point[1], n)
self.closed = False
[docs]
@dataclass
class BatchSketch:
"""BatchSketch is a dataclass for creating a batch sketch object.
Attributes:
sketches (List[Types.SKETCH]): The list of sketches.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
"""
sketches: List[Types.SKETCH]
xform_matrix: ndarray = None
def __post_init__(self):
"""Initialize the BatchSketch object."""
self.type = Types.SKETCH
self.subtype = Types.BATCH_SKETCH
self.sketches = self.sketches
[docs]
@dataclass
class PathSketch:
"""PathSketch is a dataclass for creating a path sketch object.
Attributes:
sketches (List[Types.SKETCH]): The list of sketches.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
"""
sketches: List[Types.SKETCH]
xform_matrix: ndarray = None
def __post_init__(self):
"""Initialize the PathSketch object."""
self.type = Types.SKETCH
self.subtype = Types.PATH_SKETCH
if self.xform_matrix is None:
self.xform_matrix = identity_matrix()
[docs]
@dataclass
class LaceSketch:
"""LaceSketch is a dataclass for creating a lace sketch object.
Attributes:
fragment_sketches (List[ShapeSketch]): The list of fragment sketches.
plait_sketches (List[ShapeSketch]): The list of plait sketches.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
"""
fragment_sketches: List[ShapeSketch]
plait_sketches: List[ShapeSketch]
xform_matrix: ndarray = None
def __post_init__(self):
"""Initialize the LaceSketch object."""
self.type = Types.SKETCH
self.subtype = Types.LACESKETCH
if self.xform_matrix is None:
self.xform_matrix = identity_matrix()
[docs]
@dataclass
class FrameSketch:
"""FrameSketch is a dataclass for creating a frame sketch object.
Attributes:
frame_shape (FrameShape, optional): The shape of the frame. Defaults to "rectangle".
line_width (float, optional): The width of the line. Defaults to 1.
line_dash_array (list, optional): The dash array for the line. Defaults to None.
line_color (Color, optional): The color of the line. Defaults to colors.black.
back_color (Color, optional): The background color. Defaults to colors.white.
fill (bool, optional): Whether to fill the frame. Defaults to False.
stroke (bool, optional): Whether to stroke the frame. Defaults to True.
double (bool, optional): Whether to draw a double line. Defaults to False.
double_distance (float, optional): The distance between double lines. Defaults to 2.
inner_sep (float, optional): The inner separation. Defaults to 10.
outer_sep (float, optional): The outer separation. Defaults to 10.
smooth (bool, optional): Whether to smooth the frame. Defaults to False.
rounded_corners (bool, optional): Whether to round the corners. Defaults to False.
fillet_radius (float, optional): The radius of the fillet. Defaults to 10.
draw_fillets (bool, optional): Whether to draw fillets. Defaults to False.
blend_mode (str, optional): The blend mode. Defaults to None.
gradient (str, optional): The gradient. Defaults to None.
pattern (str, optional): The pattern. Defaults to None.
visible (bool, optional): Whether the frame is visible. Defaults to True.
min_width (float, optional): The minimum width. Defaults to 0.
min_height (float, optional): The minimum height. Defaults to 0.
min_radius (float, optional): The minimum radius. Defaults to 0.
"""
frame_shape: FrameShape = (
"rectangle" # default value cannot be FrameShape.RECTANGLE!
)
line_width: float = 1
line_dash_array: list = None
line_color: Color = colors.black
back_color: Color = colors.white
fill: bool = False
stroke: bool = True
double: bool = False
double_distance: float = 2
inner_sep: float = 10
outer_sep: float = 10
smooth: bool = False
rounded_corners: bool = False
fillet_radius: float = 10
draw_fillets: bool = False
blend_mode: str = None
gradient: str = None
pattern: str = None
visible: bool = True
min_width: float = 0
min_height: float = 0
min_radius: float = 0
def __post_init__(self):
"""Initialize the FrameSketch object."""
self.type = Types.SKETCH
self.subtype = Types.FRAME_SKETCH
common_properties(self)
[docs]
@dataclass
class RectSketch:
"""RectSketch is a dataclass for creating a rectangle sketch object.
Attributes:
pos (Point): The position of the rectangle.
width (float): The width of the rectangle.
height (float): The height of the rectangle.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
"""
pos: Point
width: float
height: float
xform_matrix: ndarray = None
def __post_init__(self):
"""Initialize the RectSketch object.
Args:
pos (Point): The position of the rectangle.
width (float): The width of the rectangle.
height (float): The height of the rectangle.
xform_matrix (ndarray, optional): The transformation matrix. Defaults to None.
"""
self.type = Types.SKETCH
self.subtype = Types.RECT_SKETCH
if self.xform_matrix is None:
self.xform_matrix = identity_matrix()
pos = self.pos
else:
pos = homogenize([self.pos])
pos = (pos @ self.xform_matrix).tolist()[0][:2]
self.pos = pos
h2 = self.height / 2
w2 = self.width / 2
self.vertices = [
(pos[0] - w2, pos[1] - h2),
(pos[0] + w2, pos[1] - h2),
(pos[0] + w2, pos[1] + h2),
(pos[0] - w2, pos[1] + h2),
]
self.closed = True