This is the API documentation of LineDream. This was auto generated from the current release.

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.Rectangle import Rectangle, Square
from .primitives.Group import Group
from .primitives.Text import Text
from .enviornment.Canvas import _Canvas
from .helpers.CircleMath import CircleMath
# from .primitives.TextLine import TextLine
# from .enviornment.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:
			for (x,y) in vertices:
				self.add_vertex(x,y,0)

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:
			for (x,y) in vertices:
				self.add_vertex(x,y,0)
# 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, percent, _percent_y=0):
		self.radius_x = self.radius_x * (percent/100)
		self.radius_y = self.radius_y * (percent/100)
		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, percent, _percent_y=0):
View Source
	def scale(self, percent, _percent_y=0):
		self.radius_x = self.radius_x * (percent/100)
		self.radius_y = self.radius_y * (percent/100)
		return self
# 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, percent_x, percent_y=100, **kwargs):
		self.radius_x = self.radius_x * (percent_x/100)
		self.radius_y = self.radius_y * (percent_y/100)
		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, percent_x, percent_y=100, **kwargs):
View Source
	def scale(self, percent_x, percent_y=100, **kwargs):
		self.radius_x = self.radius_x * (percent_x/100)
		self.radius_y = self.radius_y * (percent_y/100)
		return self
# 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, percent, _percent_y=0, origin=None):
		self.radius = self.radius * (percent/100)
		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, percent, _percent_y=0, origin=None):
View Source
	def scale(self, percent, _percent_y=0, origin=None):
		self.radius = self.radius * (percent/100)
		return self
# 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, percent_x=1, _percent_y=1):
		raise Exception('Cannot scale a Point')

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, percent_x=1, _percent_y=1):
View Source
	def scale(self, percent_x=1, _percent_y=1):
		raise Exception('Cannot scale a Point')
# 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)

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

# 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:
				sys.stderr.write(f'attr "{k}" does not exist.')
				# print(f'attr "{k}" does not exist.')

		_Canvas.draw_queue.append(self)


	# def __str__(self):
	# 	return f'<{__class__} fill_color: {self.fill_color}>'
	#
	# def __repr__(self):
	# 	return f'<{__class__} fill_color: {self.fill_color}>'

	@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'''
		# or do you just raise an exception?
		rv = (None, None)
		if  self.vertices.size > 0:
			_rv = self.vertices[0]
			rv = (_rv[0], _rv[1])

		return rv

	# Not sure why this was here, but it was not used so I'm commenting out.
	# @property
	# def last_vertex(self):
	# 	# or do you just raise an exception?
	# 	rv = (None, None)
	# 	if  self.vertices.size > 0:
	# 		_rv = self.vertices[-1]
	# 		rv = (_rv[0], _rv[1])
	#
	# 	return rv

	# @property
	# def length(self):
	# 	x = np.array(xcoordinates)
	# 	y = np.array(ycoordinates)
	#
	# 	dist_array = (x[:-1] - x[1:]) ** 2 + (y[:-1] - y[1:]) ** 2
	#
	# 	np.sum(np.sqrt(dist_array))

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

		_v = self.vertices.tolist()

		for x,y, z, _ in _v:
			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 x,y, z, _ in _v:
			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 x,y, z, _ in _v:
			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 x,y, z, _ in _v:
			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 rotate(self, degrees, origin=None):
	#
	# 	if not origin:
	# 		x = [p[0] for p in self.vertices]
	# 		y = [p[1] for p in self.vertices]
	# 		origin = (max(x) + min(x)) / 2, (max(y) + min(y)) / 2
	#
	# 	self._vertices = CircleMath.rotate(self.vertices, origin=origin, degrees=degrees)

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

		l = self._vertices.tolist()
		l.append([x,y,z,1])

		self._vertices = np.array(l)

		return self


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

		# THIS IS DEFAULT BEHAVIOR IF IT IS NOT OVERRIDEN IN THE DERIVED CLASS.
		# This will work for shapes/objects that user vertex's.. but not for things like Circles
		#
		# for idx, (o_x,o_y) in enumerate(self._vertices):
		# 	o_x = o_x + x
		# 	o_y = o_y + y
		#
		# 	self._vertices[idx] = (o_x, o_y)

		# tmat = matrix.translation_matrix(x, y, z)

		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[:])

		#

		# tmat = matrix.rotation_matrix(axis, theta)

		#

		# NOTE: THIS ALL MIGHT NEED TO BE RADIANS
		# This might be the wrong interpretatino...
		x = axis[0]
		y = axis[1]
		z = axis[2]

		# x, y, z = _normalize(axis)

		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]

		# self.transform(x_c, y_c)
		# self._vertices = self._vertices.dot(rotation)
		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):

		sys.stderr.write("Scale is not fully implemented yet.\n")

		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)

		# raise Exception('Inherited class should implement')
		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:
				sys.stderr.write(f'attr "{k}" does not exist.')
				# print(f'attr "{k}" 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'''

		l = self._vertices.tolist()
		l.append([x,y,z,1])

		self._vertices = np.array(l)

		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'''

		# THIS IS DEFAULT BEHAVIOR IF IT IS NOT OVERRIDEN IN THE DERIVED CLASS.
		# This will work for shapes/objects that user vertex's.. but not for things like Circles
		#
		# for idx, (o_x,o_y) in enumerate(self._vertices):
		# 	o_x = o_x + x
		# 	o_y = o_y + y
		#
		# 	self._vertices[idx] = (o_x, o_y)

		# tmat = matrix.translation_matrix(x, y, z)

		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[:])

		#

		# tmat = matrix.rotation_matrix(axis, theta)

		#

		# NOTE: THIS ALL MIGHT NEED TO BE RADIANS
		# This might be the wrong interpretatino...
		x = axis[0]
		y = axis[1]
		z = axis[2]

		# x, y, z = _normalize(axis)

		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]

		# self.transform(x_c, y_c)
		# self._vertices = self._vertices.dot(rotation)
		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):

		sys.stderr.write("Scale is not fully implemented yet.\n")

		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)

		# raise Exception('Inherited class should implement')
		return self
# svg_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):
	'''This is how to create an ellipse'''
	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

		# This is a unique data structure to vpype. The canvas render has a different way to render them.
		self.a = text_block(text, width, font_name, font_size, align, 1.0)
		self.a.translate(x, y)
		self.text_lines = self.a.lines

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

	def transform(self, *coords):
		self.a.translate(*coords)

This is how to create an ellipse

# 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

		# This is a unique data structure to vpype. The canvas render has a different way to render them.
		self.a = text_block(text, width, font_name, font_size, align, 1.0)
		self.a.translate(x, y)
		self.text_lines = self.a.lines
# 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, *coords):
View Source
	def transform(self, *coords):
		self.a.translate(*coords)

Transform the vertices based on x/y coords