API documentation

Every class LineDream exports, generated from the docstrings of the current release (v0.4.0).

LineDream

View Source
from .primitives.Line import Line
from .primitives.BaseShape import BaseShape
from .primitives.Ellipse import Circle, Ellipse, Point, Arc
from .primitives.Rectangle import Rectangle, Square
from .primitives.Group import Group
from .primitives.Text import Text
from .environment.Canvas import _Canvas
from .helpers.CircleMath import CircleMath
# from .primitives.TextLine import TextLine
# from .environment.Tweaks import Tweaks

Canvas = _Canvas
"""
Canvas Singleton Object. This controls the output 'canvas', and rendering of the draw loop.

### Attributes
- width
- height
"""
# Note, I had to do this Canvas=_Canvas thing so these ^ doc strings would work.

__all__ = [
	'Line',
	'Circle',
	'Ellipse',
	'Arc',
	'Point',
	'Rectangle',
	'Square',
	'Group',
	'CircleMath',
	'BaseShape',
	'Canvas',
	'Text'
]
# class Line(LineDream.BaseShape):
View Source
class Line(BaseShape):
	'''This is how you create a line with two or more vertices'''
	def __init__(self, vertices:List[Tuple]=None, **kwargs):
		super().__init__(**kwargs)
		if vertices:
			self._set_vertices(vertices)

	@classmethod
	def from_array(cls, xy, **kwargs):
		'''Create a line from an (N, 2) or (N, 3) array-like of x, y[, z] coordinates.
		Builds the vertex array in one step, so it is fast for long polylines.'''
		line = cls(**kwargs)
		line._set_vertices(xy)
		return line

	def _set_vertices(self, xy):
		xy = np.asarray(xy, dtype=float)
		if xy.ndim != 2 or xy.shape[1] not in (2, 3):
			raise ValueError(f'Expected an (N, 2) or (N, 3) array of vertices, got shape {xy.shape}')
		z = xy[:, 2] if xy.shape[1] == 3 else np.zeros(len(xy))
		self._vertices = np.column_stack([xy[:, 0], xy[:, 1], z, np.ones(len(xy))])

This is how you create a line with two or more vertices

# Line(vertices: List[Tuple] = None, **kwargs)
View Source
	def __init__(self, vertices:List[Tuple]=None, **kwargs):
		super().__init__(**kwargs)
		if vertices:
			self._set_vertices(vertices)
#
@classmethod
def from_array(cls, xy, **kwargs):
View Source
	@classmethod
	def from_array(cls, xy, **kwargs):
		'''Create a line from an (N, 2) or (N, 3) array-like of x, y[, z] coordinates.
		Builds the vertex array in one step, so it is fast for long polylines.'''
		line = cls(**kwargs)
		line._set_vertices(xy)
		return line

Create a line from an (N, 2) or (N, 3) array-like of x, y[, z] coordinates. Builds the vertex array in one step, so it is fast for long polylines.

# class Circle(LineDream.Ellipse):
View Source
class Circle(Ellipse):
	def __init__(self,x,y, radius, **kwargs):
		super().__init__(x,y, radius, radius, **kwargs)

	def scale(self, amt, _amt_y=None):
		"""Scale the circle by the given amount.

		:param amt: Scale factor (2.0 = twice as big)
		"""
		self.radius_x = self.radius_x * amt
		self.radius_y = self.radius_y * amt
		return self

This is how to create an ellipse

# Circle(x, y, radius, **kwargs)
View Source
	def __init__(self,x,y, radius, **kwargs):
		super().__init__(x,y, radius, radius, **kwargs)

This is the init for creating an __init__ :param x: x coord of center :param y: y coord of center :param radius_x: radius x :param radius_y: radius y :param kwargs: style kwargs

.. todo:: THIS NEEDS WORK

# def scale(self, amt, _amt_y=None):
View Source
	def scale(self, amt, _amt_y=None):
		"""Scale the circle by the given amount.

		:param amt: Scale factor (2.0 = twice as big)
		"""
		self.radius_x = self.radius_x * amt
		self.radius_y = self.radius_y * amt
		return self

Scale the circle by the given amount.

:param amt: Scale factor (2.0 = twice as big)

# class Ellipse(LineDream.BaseShape):
View Source
class Ellipse(BaseShape):
	'''This is how to create an ellipse'''
	def __init__(self,x,y, radius_x, radius_y,   **kwargs):
		'''This is the init for creating an __init__
		:param x: x coord of center
		:param y: y coord of center
		:param radius_x: radius x
		:param radius_y: radius y
		:param kwargs: style kwargs


		.. todo:: THIS NEEDS WORK
		'''
		super().__init__(**kwargs)
		self.x = x
		""" x coord of center"""
		self.y = y
		self.radius_x = radius_x
		self.radius_y = radius_y

		self.is_circle=True

	def add_vertex(self, *coords):
		raise Exception("Ellipses do not have vertexes")

	def transform(self, x=0, y=0, z=0):
		'''
		Translate the ellipse by x/y/z
		:param x:
		:param y:
		:param z:
		:return:
		'''

		self.x = self.x + x
		self.y = self.y + y
		return self

	def scale(self, amt_x, amt_y=None, **kwargs):
		"""Scale the ellipse by the given amount.

		:param amt_x: Scale factor for x-axis (2.0 = twice as big)
		:param amt_y: Scale factor for y-axis (defaults to amt_x for uniform scaling)
		"""
		if amt_y is None:
			amt_y = amt_x
		self.radius_x = self.radius_x * amt_x
		self.radius_y = self.radius_y * amt_y
		return self

	@property
	def svg_object(self):
		return drawsvg.Ellipse(self.x, self.y, self.radius_x, self.radius_y,
							   fill=self.fill_color, stroke=self.stroke_color,
							   stroke_width=self.stroke_width, fill_opacity=self.fill_opacity)

This is how to create an ellipse

# Ellipse(x, y, radius_x, radius_y, **kwargs)
View Source
	def __init__(self,x,y, radius_x, radius_y,   **kwargs):
		'''This is the init for creating an __init__
		:param x: x coord of center
		:param y: y coord of center
		:param radius_x: radius x
		:param radius_y: radius y
		:param kwargs: style kwargs


		.. todo:: THIS NEEDS WORK
		'''
		super().__init__(**kwargs)
		self.x = x
		""" x coord of center"""
		self.y = y
		self.radius_x = radius_x
		self.radius_y = radius_y

		self.is_circle=True

This is the init for creating an __init__ :param x: x coord of center :param y: y coord of center :param radius_x: radius x :param radius_y: radius y :param kwargs: style kwargs

.. todo:: THIS NEEDS WORK

# x

x coord of center

# def add_vertex(self, *coords):
View Source
	def add_vertex(self, *coords):
		raise Exception("Ellipses do not have vertexes")

Append a set of vertexes to the primitive shape

# def transform(self, x=0, y=0, z=0):
View Source
	def transform(self, x=0, y=0, z=0):
		'''
		Translate the ellipse by x/y/z
		:param x:
		:param y:
		:param z:
		:return:
		'''

		self.x = self.x + x
		self.y = self.y + y
		return self

Translate the ellipse by x/y/z :param x: :param y: :param z: :return:

# def scale(self, amt_x, amt_y=None, **kwargs):
View Source
	def scale(self, amt_x, amt_y=None, **kwargs):
		"""Scale the ellipse by the given amount.

		:param amt_x: Scale factor for x-axis (2.0 = twice as big)
		:param amt_y: Scale factor for y-axis (defaults to amt_x for uniform scaling)
		"""
		if amt_y is None:
			amt_y = amt_x
		self.radius_x = self.radius_x * amt_x
		self.radius_y = self.radius_y * amt_y
		return self

Scale the ellipse by the given amount.

:param amt_x: Scale factor for x-axis (2.0 = twice as big) :param amt_y: Scale factor for y-axis (defaults to amt_x for uniform scaling)

# svg_object
# class Arc(LineDream.BaseShape):
View Source
class Arc(BaseShape):
	'''This is how to create an ellipse'''

	def __init__(self, x, y, radius, start_angle, end_angle, x_y_start_coords=False, **kwargs):
		'''This is the init for creating an __init__
		:param x: x coord of center
		:param y: y coord of center
		:param radius_x: radius x
		:param radius_y: radius y
		:param x_y_start_coords: If true, this will use the x,y params as where the 0 degree location is - not the center of the arc. For example:
								If you specify Arc(10, 15, 3, 0, 270) the arc start will be at (10,15).
		:param kwargs: style kwargs


		.. todo:: THIS NEEDS WORK
		'''
		super().__init__(**kwargs)

		if x_y_start_coords:
			d_x, d_y = CircleMath.distance_to_coords(start_angle, radius)
			x = x-d_x
			y = y-d_y

		self.x = x
		self.y = y
		self.radius = radius
		self.start_angle = start_angle
		self.end_angle = end_angle

		self.is_arc = True

	@property
	def start_coords(self):
		'''For the give arc, get the x,y coordinates of the start of the arc'''
		d_x, d_y = CircleMath.distance_to_coords(self.start_angle, self.radius)
		return self.x + d_x, self.y +d_y

	@property
	def end_coords(self):
		'''For the give arc, get the x,y coordinates of the end start of the arc'''
		d_x, d_y = CircleMath.distance_to_coords(self.end_angle, self.radius)
		return self.x + d_x, self.y + d_y

	def scale(self, amt, _amt_y=None, origin=None):
		"""Scale the arc by the given amount.

		:param amt: Scale factor (2.0 = twice as big)
		"""
		self.radius = self.radius * amt
		return self

	def rotate(self, theta, origin=None, axis=None):
		self.start_angle +=theta
		self.end_angle += theta
		return self

	@property
	def svg_object(self):
		return drawsvg.Arc(cx=self.x, cy=self.y, r=self.radius, cw=True,
							  start_deg=self.start_angle, end_deg=self.end_angle,
							  fill=self.fill_color, stroke=self.stroke_color,
							  stroke_width=self.stroke_width, fill_opacity=self.fill_opacity,
							  # close=shape.close_path
							  )

This is how to create an ellipse

# Arc( x, y, radius, start_angle, end_angle, x_y_start_coords=False, **kwargs )
View Source
	def __init__(self, x, y, radius, start_angle, end_angle, x_y_start_coords=False, **kwargs):
		'''This is the init for creating an __init__
		:param x: x coord of center
		:param y: y coord of center
		:param radius_x: radius x
		:param radius_y: radius y
		:param x_y_start_coords: If true, this will use the x,y params as where the 0 degree location is - not the center of the arc. For example:
								If you specify Arc(10, 15, 3, 0, 270) the arc start will be at (10,15).
		:param kwargs: style kwargs


		.. todo:: THIS NEEDS WORK
		'''
		super().__init__(**kwargs)

		if x_y_start_coords:
			d_x, d_y = CircleMath.distance_to_coords(start_angle, radius)
			x = x-d_x
			y = y-d_y

		self.x = x
		self.y = y
		self.radius = radius
		self.start_angle = start_angle
		self.end_angle = end_angle

		self.is_arc = True

This is the init for creating an __init__ :param x: x coord of center :param y: y coord of center :param radius_x: radius x :param radius_y: radius y :param x_y_start_coords: If true, this will use the x,y params as where the 0 degree location is - not the center of the arc. For example: If you specify Arc(10, 15, 3, 0, 270) the arc start will be at (10,15). :param kwargs: style kwargs

.. todo:: THIS NEEDS WORK

# start_coords

For the give arc, get the x,y coordinates of the start of the arc

# end_coords

For the give arc, get the x,y coordinates of the end start of the arc

# def scale(self, amt, _amt_y=None, origin=None):
View Source
	def scale(self, amt, _amt_y=None, origin=None):
		"""Scale the arc by the given amount.

		:param amt: Scale factor (2.0 = twice as big)
		"""
		self.radius = self.radius * amt
		return self

Scale the arc by the given amount.

:param amt: Scale factor (2.0 = twice as big)

# def rotate(self, theta, origin=None, axis=None):
View Source
	def rotate(self, theta, origin=None, axis=None):
		self.start_angle +=theta
		self.end_angle += theta
		return self

Rotate the shape by the given angle along the given axis.

:param theta: The angle by which to rotate (in degrees) :type theta: float

:param axis: The axis along which to rotate (defaults to the z-axis) :type axis: np.ndarray or list

# svg_object
# class Point(LineDream.Circle):
View Source
class Point(Circle):
	def __init__(self,x,y, **kwargs):
		super().__init__(x,y, .5, **kwargs)

	def scale(self, amt=1, _amt_y=None):
		"""Points cannot be scaled. This is a no-op that returns self for method chaining."""
		import sys
		sys.stderr.write("Warning: Points cannot be scaled. Operation ignored.\n")
		return self

This is how to create an ellipse

# Point(x, y, **kwargs)
View Source
	def __init__(self,x,y, **kwargs):
		super().__init__(x,y, .5, **kwargs)

This is the init for creating an __init__ :param x: x coord of center :param y: y coord of center :param radius_x: radius x :param radius_y: radius y :param kwargs: style kwargs

.. todo:: THIS NEEDS WORK

# def scale(self, amt=1, _amt_y=None):
View Source
	def scale(self, amt=1, _amt_y=None):
		"""Points cannot be scaled. This is a no-op that returns self for method chaining."""
		import sys
		sys.stderr.write("Warning: Points cannot be scaled. Operation ignored.\n")
		return self

Points cannot be scaled. This is a no-op that returns self for method chaining.

# class Rectangle(LineDream.BaseShape):
View Source
class Rectangle(BaseShape):
	def __init__(self, c_x, c_y, width, height, **kwargs):
		"""
		Creates a Rectangle Primitive

		Parameters
		----------
		c_x : int
			center of rectangle x coord

		c_y: int
			center of rectangle y coord

		:param width:
		:param height:
		:param kwargs:
		"""

		super().__init__(**kwargs)
		self.c_x = c_x
		self.c_y = c_y
		self.width = width
		self.height = height

		self.close_path = True

		# top left
		t_l_x = c_x - (width/2)
		t_l_y = c_y - (height/2)

		# top right
		t_r_x = c_x + (width/2)
		t_r_y = c_y - (height/2)

		# bottom right
		b_r_x = c_x + (width / 2)
		b_r_y = c_y + (height / 2)

		# bottom left
		b_l_x = c_x - (width/2)
		b_l_y = c_y + (height/2)

		# this order is very important.
		# It adds the vertex's in a clockwise rotation
		self.add_vertex(t_l_x, t_l_y)
		self.add_vertex(t_r_x, t_r_y)
		self.add_vertex(b_r_x, b_r_y)
		self.add_vertex(b_l_x, b_l_y)

All provided primitives inherit from this class. If a user wants to make their own primitive, its recommended to inherit from this class so the new primative is added to the draw queue.

# Rectangle(c_x, c_y, width, height, **kwargs)
View Source
	def __init__(self, c_x, c_y, width, height, **kwargs):
		"""
		Creates a Rectangle Primitive

		Parameters
		----------
		c_x : int
			center of rectangle x coord

		c_y: int
			center of rectangle y coord

		:param width:
		:param height:
		:param kwargs:
		"""

		super().__init__(**kwargs)
		self.c_x = c_x
		self.c_y = c_y
		self.width = width
		self.height = height

		self.close_path = True

		# top left
		t_l_x = c_x - (width/2)
		t_l_y = c_y - (height/2)

		# top right
		t_r_x = c_x + (width/2)
		t_r_y = c_y - (height/2)

		# bottom right
		b_r_x = c_x + (width / 2)
		b_r_y = c_y + (height / 2)

		# bottom left
		b_l_x = c_x - (width/2)
		b_l_y = c_y + (height/2)

		# this order is very important.
		# It adds the vertex's in a clockwise rotation
		self.add_vertex(t_l_x, t_l_y)
		self.add_vertex(t_r_x, t_r_y)
		self.add_vertex(b_r_x, b_r_y)
		self.add_vertex(b_l_x, b_l_y)

Creates a Rectangle Primitive

Parameters
  • c_x (int): center of rectangle x coord
  • c_y (int): center of rectangle y coord
  • :param width:
  • :param height:
  • :param kwargs:
# class Square(LineDream.Rectangle):
View Source
class Square(Rectangle):
	def __init__(self, c_x: int, c_y: int, width, **kwargs):
		"""
		Create a square primitive

		:param c_x: center of square x coordinate
		:param c_y: center of square y coordinate
		:param width: height/width of the square.
		:param kwargs: style dict
		"""

		super().__init__(c_x, c_y, width, width, **kwargs)

All provided primitives inherit from this class. If a user wants to make their own primitive, its recommended to inherit from this class so the new primative is added to the draw queue.

# Square(c_x: int, c_y: int, width, **kwargs)
View Source
	def __init__(self, c_x: int, c_y: int, width, **kwargs):
		"""
		Create a square primitive

		:param c_x: center of square x coordinate
		:param c_y: center of square y coordinate
		:param width: height/width of the square.
		:param kwargs: style dict
		"""

		super().__init__(c_x, c_y, width, width, **kwargs)

Create a square primitive

:param c_x: center of square x coordinate :param c_y: center of square y coordinate :param width: height/width of the square. :param kwargs: style dict

# class Group(LineDream.BaseShape):
View Source
class Group(BaseShape):
    '''This will create a group. It also adds inkscape/axidraw compatible attributes
    :param label: The label of the group
    :param id: The id of the group
    '''
    def __init__(self,label=None,id=None, **kwargs):

        super().__init__(**kwargs)

        self.is_group = True

        if label == None:
            label = str(uuid.uuid4())
        self.label = label

        if id == None:
            id = label
        self.id = id

        self.items = []

    @property
    def svg_label(self):
        return f'inkscape:label="{self.label}"'

    def add_item(self, item):
        '''Add a shape to the group'''
        self.items.append(item)
        _Canvas.remove_by_id(item.id)

    def extend(self, items):
        '''Add many shapes to the group at once. Removes them from the draw queue in a single pass,
        so it stays fast for thousands of items (add_item scans the queue once per item).'''
        items = list(items)
        self.items.extend(items)
        ids = {item.id for item in items}
        _Canvas.draw_queue = [q for q in _Canvas.draw_queue if q.id not in ids]

This will create a group. It also adds inkscape/axidraw compatible attributes :param label: The label of the group :param id: The id of the group

# Group(label=None, id=None, **kwargs)
View Source
    def __init__(self,label=None,id=None, **kwargs):

        super().__init__(**kwargs)

        self.is_group = True

        if label == None:
            label = str(uuid.uuid4())
        self.label = label

        if id == None:
            id = label
        self.id = id

        self.items = []
# svg_label
# def add_item(self, item):
View Source
    def add_item(self, item):
        '''Add a shape to the group'''
        self.items.append(item)
        _Canvas.remove_by_id(item.id)

Add a shape to the group

# def extend(self, items):
View Source
    def extend(self, items):
        '''Add many shapes to the group at once. Removes them from the draw queue in a single pass,
        so it stays fast for thousands of items (add_item scans the queue once per item).'''
        items = list(items)
        self.items.extend(items)
        ids = {item.id for item in items}
        _Canvas.draw_queue = [q for q in _Canvas.draw_queue if q.id not in ids]

Add many shapes to the group at once. Removes them from the draw queue in a single pass, so it stays fast for thousands of items (add_item scans the queue once per item).

# class CircleMath:
View Source
class CircleMath:
	'''
	A class with helper methods for circles
	'''

	@staticmethod
	def distance_to_coords(degrees, distance):
		angle_radians = degrees * math.pi / 180
		x = math.cos(angle_radians) * distance
		y = math.sin(angle_radians) * distance
		return x, y

	@staticmethod
	def rotate(p, origin=(0, 0), degrees=0):
		# https://stackoverflow.com/a/58781388/6775715
		angle = np.deg2rad(degrees)
		R = np.array([[np.cos(angle), -np.sin(angle)],
					  [np.sin(angle), np.cos(angle)]])
		o = np.atleast_2d(origin)
		p = np.atleast_2d(p)
		rv = np.squeeze((R @ (p.T - o.T) + o.T).T)
		#todo: remove this once native numpy arrays are supported
		return rv.tolist()

	@staticmethod
	def degrees_to_radians(degrees):
		return (math.radians(degrees / math.pi))

A class with helper methods for circles

# CircleMath()
#
@staticmethod
def distance_to_coords(degrees, distance):
View Source
	@staticmethod
	def distance_to_coords(degrees, distance):
		angle_radians = degrees * math.pi / 180
		x = math.cos(angle_radians) * distance
		y = math.sin(angle_radians) * distance
		return x, y
#
@staticmethod
def rotate(p, origin=(0, 0), degrees=0):
View Source
	@staticmethod
	def rotate(p, origin=(0, 0), degrees=0):
		# https://stackoverflow.com/a/58781388/6775715
		angle = np.deg2rad(degrees)
		R = np.array([[np.cos(angle), -np.sin(angle)],
					  [np.sin(angle), np.cos(angle)]])
		o = np.atleast_2d(origin)
		p = np.atleast_2d(p)
		rv = np.squeeze((R @ (p.T - o.T) + o.T).T)
		#todo: remove this once native numpy arrays are supported
		return rv.tolist()
#
@staticmethod
def degrees_to_radians(degrees):
View Source
	@staticmethod
	def degrees_to_radians(degrees):
		return (math.radians(degrees / math.pi))
# class BaseShape:
View Source
class BaseShape(object):
	'''All provided primitives inherit from this class. If a user wants to make their own primitive, its recommended to
	inherit from this class so the new primative is added to the draw queue.'''
	def __init__(self,**kwargs):
		self._vertices = np.array([])
		self._fill_color='none'
		self._stroke_color='black'
		self._stroke_width=1
		self._close_path = False
		self._fill_opacity = 1.0

		self.is_circle=False
		self.is_arc=False
		self.is_group = False
		self.is_text = False

		self.id = str(uuid.uuid4())

		for k,v in kwargs.items():
			if hasattr(self, k):
				setattr(self, k, v)
			else:
				raise AttributeError(f'Invalid parameter "{k}" for {self.__class__.__name__}. Attribute does not exist.')

		_Canvas.draw_queue.append(self)

	@property
	def fill_color(self):
		'''The fill color of the shape'''
		return self._fill_color

	@fill_color.setter
	def fill_color(self, v):
		self._fill_color = v


	@property
	def fill_opacity(self):
		'''The fill opacity of the shape'''
		return self._fill_opacity

	@fill_opacity.setter
	def fill_opacity(self, v):
		self._fill_opacity = v

	@property
	def close_path(self):
		'''Upon rendering the shape, the last vertex will connect to the first vertex'''
		return self._close_path

	@close_path.setter
	def close_path(self, v:bool):
		self._close_path = v

	@property
	def stroke_color(self):
		'''The stroke color of the shape'''
		return self._stroke_color

	@stroke_color.setter
	def stroke_color(self, v):
		self._stroke_color = v

	@property
	def stroke_width(self):
		'''The stroke width of the shape'''
		return self._stroke_width

	@stroke_width.setter
	def stroke_width(self, v):
		self._stroke_width = v

	@property
	def vertices(self):
		'''The list of vertices'''
		return self._vertices

	@property
	def first_vertex(self):
		'''The first vertex of the shape'''
		rv = (None, None)
		if  self.vertices.size > 0:
			_rv = self.vertices[0]
			rv = (_rv[0], _rv[1])

		return rv

	@property
	def min_x(self):
		'''The smallest x value of all the vertices'''
		rv = self.first_vertex[0]

		_v = self.vertices.tolist()

		for vertex in _v:
			x = vertex[0]
			if x < rv:
				rv = x
		return rv

	@property
	def max_x(self):
		'''The largest x value of all the vertices'''
		rv = self.first_vertex[0]

		_v = self.vertices.tolist()

		for vertex in _v:
			x = vertex[0]
			if x > rv:
				rv = x
		return rv

	@property
	def min_y(self):
		'''The smallest y value of all the vertices'''
		rv = self.first_vertex[1]

		_v = self.vertices.tolist()

		for vertex in _v:
			y = vertex[1]
			if y < rv:
				rv = y
		return rv

	@property
	def max_y(self):
		'''The largest y value of all the vertices'''
		rv = self.first_vertex[1]

		_v = self.vertices.tolist()

		for vertex in _v:
			y = vertex[1]
			if y > rv:
				rv = y
		return rv

	@property
	def center(self):
		'''The center of the shape based on the min/max x/y'''
		x = ((self.max_x - self.min_x)/2) + self.min_x
		y = ((self.max_y - self.min_y)/2) + self.min_y
		return (x,y)

	def add_vertex(self, x, y, z=0):
		'''Append a set of vertexes to the primitive shape'''

		# vstack copies the existing array once; the old tolist()/np.array() round trip
		# rebuilt every vertex through Python objects on each call
		self._vertices = np.vstack([self._vertices.reshape(-1, 4), [[x, y, z, 1]]])

		return self


	def transform(self, x, y, z=0):
		'''Transform the vertices based on x/y coords'''

		translate_matrix = np.identity(4)
		translate_matrix[0, -1] = x
		translate_matrix[1, -1] = y
		translate_matrix[2, -1] = z


		self._vertices = np.dot(self._vertices, translate_matrix.T)[:, :4]
		return self


	def rotate(self, theta, origin=None, axis=np.array([0, 0, 1])):
		"""Rotate the shape by the given angle along the given axis.

		:param theta: The angle by which to rotate (in degrees)
		:type theta: float

		:param axis: The axis along which to rotate (defaults to the z-axis)
		:type axis: np.ndarray or list

	   """

		# Convert degrees to radians
		theta = theta * math.pi / 180

		axis = np.array(axis[:])

		x = axis[0]
		y = axis[1]
		z = axis[2]

		s = np.sin(theta)
		c = np.cos(theta)
		c1 = 1 - c

		rotation = np.identity(4)

		rotation[0, 0] = x * x * c1 + c
		rotation[0, 1] = x * y * c1 - z * s
		rotation[0, 2] = x * z * c1 + y * s
		rotation[1, 0] = y * x * c1 + z * s
		rotation[1, 1] = y * y * c1 + c
		rotation[1, 2] = y * z * c1 - x * s
		rotation[2, 0] = x * z * c1 - y * s
		rotation[2, 1] = y * z * c1 + x * s
		rotation[2, 2] = z * z * c1 + c

		if origin == None:
			x_c,y_c = self.center
		else:
			x_c = origin[0]
			y_c = origin[1]

		self.transform(-x_c, -y_c)


		rotation[0, -1] = x_c
		rotation[1, -1] = y_c
		rotation[2, -1] = 1

		self._vertices = np.dot(self._vertices, rotation.T)[:, :4]

		return self


	def rotate_x(self, theta):
		"""This is a convenience function to rotate the shape along the x axis.

		:param theta: The angle by which to rotate (in degrees)
		:type theta: float

		:returns: The rotation matrix used to apply the transformation.
		:rtype: np.ndarray

		"""
		self.rotate(theta, axis=np.array([1, 0, 0]))
		return self

	def rotate_y(self, theta):
		"""This is a convenience function to rotate the shape along the y axis.

		:param theta: The angle by which to rotate (in degrees)
		:type theta: float

		:returns: The rotation matrix used to apply the transformation.
		:rtype: np.ndarray

	   """
		self.rotate(theta, axis=np.array([0, 1, 0]))
		return self

	def rotate_z(self, theta):
		"""This is a convenience function to rotate the shape along the z axis.

		:param theta:The angle by which to rotate (in degrees)
		:type theta: float

		:returns: The rotation matrix used to apply the transformation.
		:rtype: np.ndarray

	   """
		self.rotate(theta, axis=np.array([0, 0, 1]))
		return self


	def scale(self, amt, amt_y=None, origin=None):
		"""Scale the shape by the given amount.

		:param amt: Scale factor for x-axis (2.0 = twice as big)
		:type amt: float
		:param amt_y: Scale factor for y-axis (defaults to amt for uniform scaling)
		:type amt_y: float
		:param origin: Origin point for scaling (defaults to shape center)
		:type origin: tuple
		"""

		if amt_y == None:
			amt_y = amt

		scale_matrix = np.identity(4)
		scale_matrix[0, 0] = amt
		scale_matrix[1, 1] = amt_y
		scale_matrix[2, 2] = 1  # default for z

		self._vertices = self._vertices.dot(scale_matrix)

		return self


	@property
	def svg_object(self):

		if len(self.vertices) > 0:

			verts = self.vertices

			verts = verts.tolist()

			# # verts = vertices.tolist()
			if len(verts)== 0 :
				print(f'verts contains one item of {[[0.0, 0.0, 0.0]]} ... continuing.')
				return None

			start_l = verts.pop(0)
			start_x = start_l[0]
			start_y = start_l[1]

			other_verts = []
			for o_v in verts:
				x = o_v[0]

				y = o_v[1]

				# z = o_v[2]
				other_verts.append(x)
				other_verts.append(y)

			return drawsvg.Lines(start_x, start_y, *other_verts, fill=self.fill_color, stroke=self.stroke_color,
								stroke_width=self.stroke_width, fill_opacity=self.fill_opacity, close=self.close_path)

		else:
			raise Exception("This does not have vertices or a custom svg_object, so it cannot be rendered.")

All provided primitives inherit from this class. If a user wants to make their own primitive, its recommended to inherit from this class so the new primative is added to the draw queue.

# BaseShape(**kwargs)
View Source
	def __init__(self,**kwargs):
		self._vertices = np.array([])
		self._fill_color='none'
		self._stroke_color='black'
		self._stroke_width=1
		self._close_path = False
		self._fill_opacity = 1.0

		self.is_circle=False
		self.is_arc=False
		self.is_group = False
		self.is_text = False

		self.id = str(uuid.uuid4())

		for k,v in kwargs.items():
			if hasattr(self, k):
				setattr(self, k, v)
			else:
				raise AttributeError(f'Invalid parameter "{k}" for {self.__class__.__name__}. Attribute does not exist.')

		_Canvas.draw_queue.append(self)
# fill_color

The fill color of the shape

# fill_opacity

The fill opacity of the shape

# close_path

Upon rendering the shape, the last vertex will connect to the first vertex

# stroke_color

The stroke color of the shape

# stroke_width

The stroke width of the shape

# vertices

The list of vertices

# first_vertex

The first vertex of the shape

# min_x

The smallest x value of all the vertices

# max_x

The largest x value of all the vertices

# min_y

The smallest y value of all the vertices

# max_y

The largest y value of all the vertices

# center

The center of the shape based on the min/max x/y

# def add_vertex(self, x, y, z=0):
View Source
	def add_vertex(self, x, y, z=0):
		'''Append a set of vertexes to the primitive shape'''

		# vstack copies the existing array once; the old tolist()/np.array() round trip
		# rebuilt every vertex through Python objects on each call
		self._vertices = np.vstack([self._vertices.reshape(-1, 4), [[x, y, z, 1]]])

		return self

Append a set of vertexes to the primitive shape

# def transform(self, x, y, z=0):
View Source
	def transform(self, x, y, z=0):
		'''Transform the vertices based on x/y coords'''

		translate_matrix = np.identity(4)
		translate_matrix[0, -1] = x
		translate_matrix[1, -1] = y
		translate_matrix[2, -1] = z


		self._vertices = np.dot(self._vertices, translate_matrix.T)[:, :4]
		return self

Transform the vertices based on x/y coords

# def rotate(self, theta, origin=None, axis=array([0, 0, 1])):
View Source
	def rotate(self, theta, origin=None, axis=np.array([0, 0, 1])):
		"""Rotate the shape by the given angle along the given axis.

		:param theta: The angle by which to rotate (in degrees)
		:type theta: float

		:param axis: The axis along which to rotate (defaults to the z-axis)
		:type axis: np.ndarray or list

	   """

		# Convert degrees to radians
		theta = theta * math.pi / 180

		axis = np.array(axis[:])

		x = axis[0]
		y = axis[1]
		z = axis[2]

		s = np.sin(theta)
		c = np.cos(theta)
		c1 = 1 - c

		rotation = np.identity(4)

		rotation[0, 0] = x * x * c1 + c
		rotation[0, 1] = x * y * c1 - z * s
		rotation[0, 2] = x * z * c1 + y * s
		rotation[1, 0] = y * x * c1 + z * s
		rotation[1, 1] = y * y * c1 + c
		rotation[1, 2] = y * z * c1 - x * s
		rotation[2, 0] = x * z * c1 - y * s
		rotation[2, 1] = y * z * c1 + x * s
		rotation[2, 2] = z * z * c1 + c

		if origin == None:
			x_c,y_c = self.center
		else:
			x_c = origin[0]
			y_c = origin[1]

		self.transform(-x_c, -y_c)


		rotation[0, -1] = x_c
		rotation[1, -1] = y_c
		rotation[2, -1] = 1

		self._vertices = np.dot(self._vertices, rotation.T)[:, :4]

		return self

Rotate the shape by the given angle along the given axis.

:param theta: The angle by which to rotate (in degrees) :type theta: float

:param axis: The axis along which to rotate (defaults to the z-axis) :type axis: np.ndarray or list

# def rotate_x(self, theta):
View Source
	def rotate_x(self, theta):
		"""This is a convenience function to rotate the shape along the x axis.

		:param theta: The angle by which to rotate (in degrees)
		:type theta: float

		:returns: The rotation matrix used to apply the transformation.
		:rtype: np.ndarray

		"""
		self.rotate(theta, axis=np.array([1, 0, 0]))
		return self

This is a convenience function to rotate the shape along the x axis.

:param theta: The angle by which to rotate (in degrees) :type theta: float

:returns: The rotation matrix used to apply the transformation. :rtype: np.ndarray

# def rotate_y(self, theta):
View Source
	def rotate_y(self, theta):
		"""This is a convenience function to rotate the shape along the y axis.

		:param theta: The angle by which to rotate (in degrees)
		:type theta: float

		:returns: The rotation matrix used to apply the transformation.
		:rtype: np.ndarray

	   """
		self.rotate(theta, axis=np.array([0, 1, 0]))
		return self

This is a convenience function to rotate the shape along the y axis.

:param theta: The angle by which to rotate (in degrees) :type theta: float

:returns: The rotation matrix used to apply the transformation. :rtype: np.ndarray

# def rotate_z(self, theta):
View Source
	def rotate_z(self, theta):
		"""This is a convenience function to rotate the shape along the z axis.

		:param theta:The angle by which to rotate (in degrees)
		:type theta: float

		:returns: The rotation matrix used to apply the transformation.
		:rtype: np.ndarray

	   """
		self.rotate(theta, axis=np.array([0, 0, 1]))
		return self

This is a convenience function to rotate the shape along the z axis.

:param theta:The angle by which to rotate (in degrees) :type theta: float

:returns: The rotation matrix used to apply the transformation. :rtype: np.ndarray

# def scale(self, amt, amt_y=None, origin=None):
View Source
	def scale(self, amt, amt_y=None, origin=None):
		"""Scale the shape by the given amount.

		:param amt: Scale factor for x-axis (2.0 = twice as big)
		:type amt: float
		:param amt_y: Scale factor for y-axis (defaults to amt for uniform scaling)
		:type amt_y: float
		:param origin: Origin point for scaling (defaults to shape center)
		:type origin: tuple
		"""

		if amt_y == None:
			amt_y = amt

		scale_matrix = np.identity(4)
		scale_matrix[0, 0] = amt
		scale_matrix[1, 1] = amt_y
		scale_matrix[2, 2] = 1  # default for z

		self._vertices = self._vertices.dot(scale_matrix)

		return self

Scale the shape by the given amount.

:param amt: Scale factor for x-axis (2.0 = twice as big) :type amt: float :param amt_y: Scale factor for y-axis (defaults to amt for uniform scaling) :type amt_y: float :param origin: Origin point for scaling (defaults to shape center) :type origin: tuple

# svg_object
# Canvas = <LineDream.environment.Canvas.BaseCanvas object>

Canvas Singleton Object. This controls the output 'canvas', and rendering of the draw loop.

Attributes

  • width
  • height
# class Text(LineDream.BaseShape):
View Source
class Text(BaseShape):
	'''A block of single-stroke Hershey text, wrapped to `width`, with its top-left corner at (x, y).
	See hershey_text.FONT_NAMES for the available fonts.'''
	def __init__(self, text, x,y, width=200, font_size=18.0, align="left", line_spacing=1.0, font_name='futural',   **kwargs):
		''''''
		super().__init__(**kwargs)

		self.x = x
		self.y = y
		self.text = text

		self.is_text=True

		# a list of complex arrays (x + iy), one per pen stroke; the canvas renders these directly
		self.text_lines = text_block(text, width, font_name, font_size, align, line_spacing)
		self.transform(x, y)

	def add_vertex(self, *coords):
		raise Exception("Cannot accept additional vertexes")

	def transform(self, x, y, z=0):
		c = complex(x, y)
		for line in self.text_lines:
			line += c
		return self

A block of single-stroke Hershey text, wrapped to width, with its top-left corner at (x, y). See hershey_text.FONT_NAMES for the available fonts.

# Text( text, x, y, width=200, font_size=18.0, align='left', line_spacing=1.0, font_name='futural', **kwargs )
View Source
	def __init__(self, text, x,y, width=200, font_size=18.0, align="left", line_spacing=1.0, font_name='futural',   **kwargs):
		''''''
		super().__init__(**kwargs)

		self.x = x
		self.y = y
		self.text = text

		self.is_text=True

		# a list of complex arrays (x + iy), one per pen stroke; the canvas renders these directly
		self.text_lines = text_block(text, width, font_name, font_size, align, line_spacing)
		self.transform(x, y)
# def add_vertex(self, *coords):
View Source
	def add_vertex(self, *coords):
		raise Exception("Cannot accept additional vertexes")

Append a set of vertexes to the primitive shape

# def transform(self, x, y, z=0):
View Source
	def transform(self, x, y, z=0):
		c = complex(x, y)
		for line in self.text_lines:
			line += c
		return self

Transform the vertices based on x/y coords