LineDream 0.3.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- LineDream/__init__.py +37 -0
- LineDream/__main__.py +7 -0
- LineDream/__version__.py +26 -0
- LineDream/environment/Canvas.py +145 -0
- LineDream/environment/Tweaks.py +9 -0
- LineDream/environment/__init__.py +0 -0
- LineDream/helpers/CircleMath.py +30 -0
- LineDream/helpers/__init__.py +0 -0
- LineDream/primitives/BaseShape.py +332 -0
- LineDream/primitives/Ellipse.py +150 -0
- LineDream/primitives/Group.py +34 -0
- LineDream/primitives/Line.py +11 -0
- LineDream/primitives/Rectangle.py +68 -0
- LineDream/primitives/Text.py +28 -0
- LineDream/primitives/TextLine.py +125 -0
- LineDream/primitives/__init__.py +0 -0
- LineDream/primitives/hershey/__init__.py +0 -0
- LineDream/primitives/hershey/aux_hershey/__init__.py +0 -0
- LineDream/primitives/hershey/aux_hershey/hersheydata.py +334 -0
- LineDream/primitives/hershey/aux_hershey/inkex.py +371 -0
- LineDream/primitives/hershey/aux_hershey/simplestyle.py +0 -0
- LineDream/primitives/hershey/hershey.py +264 -0
- LineDream/primitives/hershey/hershey_v3.py +1912 -0
- linedream-0.3.15.dist-info/METADATA +187 -0
- linedream-0.3.15.dist-info/RECORD +27 -0
- linedream-0.3.15.dist-info/WHEEL +5 -0
- linedream-0.3.15.dist-info/top_level.txt +1 -0
LineDream/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from .primitives.Line import Line
|
|
2
|
+
from .primitives.BaseShape import BaseShape
|
|
3
|
+
from .primitives.Ellipse import Circle, Ellipse, Point, Arc
|
|
4
|
+
from .primitives.Rectangle import Rectangle, Square
|
|
5
|
+
from .primitives.Group import Group
|
|
6
|
+
from .primitives.Text import Text
|
|
7
|
+
from .environment.Canvas import _Canvas
|
|
8
|
+
from .helpers.CircleMath import CircleMath
|
|
9
|
+
# from .primitives.TextLine import TextLine
|
|
10
|
+
# from .environment.Tweaks import Tweaks
|
|
11
|
+
|
|
12
|
+
Canvas = _Canvas
|
|
13
|
+
"""
|
|
14
|
+
Canvas Singleton Object. This controls the output 'canvas', and rendering of the draw loop.
|
|
15
|
+
|
|
16
|
+
### Attributes
|
|
17
|
+
- width
|
|
18
|
+
- height
|
|
19
|
+
"""
|
|
20
|
+
# Note, I had to do this Canvas=_Canvas thing so these ^ doc strings would work.
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
'Line',
|
|
24
|
+
'Circle',
|
|
25
|
+
'Ellipse',
|
|
26
|
+
'Arc',
|
|
27
|
+
'Point',
|
|
28
|
+
'Rectangle',
|
|
29
|
+
'Square',
|
|
30
|
+
'Group',
|
|
31
|
+
'CircleMath',
|
|
32
|
+
'BaseShape',
|
|
33
|
+
'Canvas',
|
|
34
|
+
'Text'
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
LineDream/__main__.py
ADDED
LineDream/__version__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Part of LineDream: A Python package for creative coding
|
|
2
|
+
# Copyright (C) 2020- Marc Leonard
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful, but
|
|
10
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12
|
+
# General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
__title__ = 'LineDream'
|
|
19
|
+
__description__ = 'A creative coding library...'
|
|
20
|
+
__url__ = 'https://linedream.marcrleonard.com/'
|
|
21
|
+
# __url__ = 'https://github.com/marcrleonard/LineDream'
|
|
22
|
+
__version__ = '0.3.14'
|
|
23
|
+
__author__ = 'Marc Leonard'
|
|
24
|
+
__author_email__ = 'marc.r.leonard@gmail.com'
|
|
25
|
+
__license__ = 'MIT'
|
|
26
|
+
__copyright__ = 'Copyright (C) 2023 - Marc Leonard'
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import drawsvg
|
|
2
|
+
from PIL import Image
|
|
3
|
+
import io
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
'''
|
|
7
|
+
SHAPES TO ADD:
|
|
8
|
+
- Rectangle/Square?
|
|
9
|
+
- Mesh?
|
|
10
|
+
- Curves (via paths)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
'''
|
|
14
|
+
|
|
15
|
+
class BaseCanvas(object):
|
|
16
|
+
'''The canvas object controls all the render controls.'''
|
|
17
|
+
def __init__(self, x=1200, y=800, units=None):
|
|
18
|
+
self.width = x
|
|
19
|
+
self.height = y
|
|
20
|
+
|
|
21
|
+
self.units=units
|
|
22
|
+
|
|
23
|
+
self.background_color= None
|
|
24
|
+
|
|
25
|
+
self.draw_queue = []
|
|
26
|
+
|
|
27
|
+
self._frame_index = 0
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def frame_index(self):
|
|
31
|
+
return self._frame_index
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def save(self, filename, open_viewer=False, flush=True, as_string=False):
|
|
35
|
+
|
|
36
|
+
bg = {}
|
|
37
|
+
if self.background_color:
|
|
38
|
+
bg = {
|
|
39
|
+
"style": f'background-color:{self.background_color}'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
svg_canvas = drawsvg.Drawing(
|
|
43
|
+
self.width,
|
|
44
|
+
self.height,
|
|
45
|
+
origin=(0, 0),
|
|
46
|
+
**bg,
|
|
47
|
+
xmlns__inkscape = "http://www.inkscape.org/namespaces/inkscape"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# There may be a better way to do this through the init above, but I found it confusing.
|
|
51
|
+
# it was much easier to just hardcode it.
|
|
52
|
+
svg_canvas.viewBox = (0, 0, self.width, self.height)
|
|
53
|
+
|
|
54
|
+
if self.units:
|
|
55
|
+
svg_canvas.width = f'{self.width}{self.units}'
|
|
56
|
+
svg_canvas.height = f'{self.height}{self.units}'
|
|
57
|
+
|
|
58
|
+
for shape in self.draw_queue:
|
|
59
|
+
|
|
60
|
+
if shape.is_text:
|
|
61
|
+
|
|
62
|
+
for line in shape.text_lines:
|
|
63
|
+
|
|
64
|
+
if len(line) <= 1:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
if len(line) == 2:
|
|
68
|
+
svg_obj = drawsvg.Line(line[0].real, line[0].imag, line[1].real, line[1].imag, fill=shape.fill_color, stroke=shape.stroke_color,
|
|
69
|
+
stroke_width=shape.stroke_width, fill_opacity=shape.fill_opacity)
|
|
70
|
+
else:
|
|
71
|
+
|
|
72
|
+
start_x = line[0].real
|
|
73
|
+
start_y = line[0].imag
|
|
74
|
+
|
|
75
|
+
addl_points = []
|
|
76
|
+
for c in line[1:]:
|
|
77
|
+
addl_points.append(c.real)
|
|
78
|
+
addl_points.append(c.imag)
|
|
79
|
+
#*(c.real, c.imag) for c in line[1:]
|
|
80
|
+
|
|
81
|
+
svg_obj = drawsvg.Lines(start_x, start_y, *addl_points,
|
|
82
|
+
fill=shape.fill_color, stroke=shape.stroke_color,
|
|
83
|
+
stroke_width=shape.stroke_width, fill_opacity=shape.fill_opacity
|
|
84
|
+
)
|
|
85
|
+
# else:
|
|
86
|
+
# path = drawsvg.Lines((c.real, c.imag) for c in line)
|
|
87
|
+
|
|
88
|
+
svg_canvas.append(svg_obj)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
elif shape.is_group:
|
|
92
|
+
g = drawsvg.Group(
|
|
93
|
+
id=shape.id,
|
|
94
|
+
inkscape__label=shape.label,
|
|
95
|
+
inkscape__groupmode="layer"
|
|
96
|
+
)
|
|
97
|
+
for shape in shape.items:
|
|
98
|
+
svg_obj = shape.svg_object
|
|
99
|
+
if svg_obj:
|
|
100
|
+
g.append(svg_obj)
|
|
101
|
+
svg_canvas.append(g)
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
svg_obj = shape.svg_object
|
|
105
|
+
if svg_obj:
|
|
106
|
+
svg_canvas.append(svg_obj)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if open_viewer:
|
|
110
|
+
c = svg_canvas.rasterize()
|
|
111
|
+
image_stream = io.BytesIO(c.png_data)
|
|
112
|
+
# Open the image using PIL from the BytesIO object
|
|
113
|
+
im = Image.open(image_stream)
|
|
114
|
+
|
|
115
|
+
# Display the image
|
|
116
|
+
im.show()
|
|
117
|
+
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
if as_string:
|
|
121
|
+
return svg_canvas.as_svg(header='')
|
|
122
|
+
|
|
123
|
+
else:
|
|
124
|
+
|
|
125
|
+
svg_canvas.save_svg(filename)
|
|
126
|
+
|
|
127
|
+
if flush:
|
|
128
|
+
self.flush()
|
|
129
|
+
|
|
130
|
+
def flush(self):
|
|
131
|
+
'''Clear the render queue'''
|
|
132
|
+
self.draw_queue=[]
|
|
133
|
+
|
|
134
|
+
def remove_by_id(self, id):
|
|
135
|
+
'''This function is used to remove an item from teh draw queue by its id. It is not particularly fast
|
|
136
|
+
since it loops through the render queue. The reason why I added this is to remove an object after it's been
|
|
137
|
+
added to a group. I haven't fully thought through a better way to implement groups, so this will have to do for now.
|
|
138
|
+
Another idea would be to add a dict of id:obj for each, then when we delete the key, it somehow deletes the object too.
|
|
139
|
+
I dunno. I'll think about it more.'''
|
|
140
|
+
for idx, item in enumerate(self.draw_queue):
|
|
141
|
+
if item.id == id:
|
|
142
|
+
del self.draw_queue[idx]
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
_Canvas = BaseCanvas()
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class CircleMath:
|
|
5
|
+
'''
|
|
6
|
+
A class with helper methods for circles
|
|
7
|
+
'''
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def distance_to_coords(degrees, distance):
|
|
11
|
+
angle_radians = degrees * math.pi / 180
|
|
12
|
+
x = math.cos(angle_radians) * distance
|
|
13
|
+
y = math.sin(angle_radians) * distance
|
|
14
|
+
return x, y
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def rotate(p, origin=(0, 0), degrees=0):
|
|
18
|
+
# https://stackoverflow.com/a/58781388/6775715
|
|
19
|
+
angle = np.deg2rad(degrees)
|
|
20
|
+
R = np.array([[np.cos(angle), -np.sin(angle)],
|
|
21
|
+
[np.sin(angle), np.cos(angle)]])
|
|
22
|
+
o = np.atleast_2d(origin)
|
|
23
|
+
p = np.atleast_2d(p)
|
|
24
|
+
rv = np.squeeze((R @ (p.T - o.T) + o.T).T)
|
|
25
|
+
#todo: remove this once native numpy arrays are supported
|
|
26
|
+
return rv.tolist()
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def degrees_to_radians(degrees):
|
|
30
|
+
return (math.radians(degrees / math.pi))
|
|
File without changes
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# this should mirror what is available in an svg
|
|
2
|
+
import math
|
|
3
|
+
import sys
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import drawsvg
|
|
8
|
+
|
|
9
|
+
from ..environment.Canvas import _Canvas
|
|
10
|
+
from ..helpers.CircleMath import CircleMath
|
|
11
|
+
|
|
12
|
+
class BaseShape(object):
|
|
13
|
+
'''All provided primitives inherit from this class. If a user wants to make their own primitive, its recommended to
|
|
14
|
+
inherit from this class so the new primative is added to the draw queue.'''
|
|
15
|
+
def __init__(self,**kwargs):
|
|
16
|
+
self._vertices = np.array([])
|
|
17
|
+
self._fill_color='none'
|
|
18
|
+
self._stroke_color='black'
|
|
19
|
+
self._stroke_width=1
|
|
20
|
+
self._close_path = False
|
|
21
|
+
self._fill_opacity = 1.0
|
|
22
|
+
|
|
23
|
+
self.is_circle=False
|
|
24
|
+
self.is_arc=False
|
|
25
|
+
self.is_group = False
|
|
26
|
+
self.is_text = False
|
|
27
|
+
|
|
28
|
+
self.id = str(uuid.uuid4())
|
|
29
|
+
|
|
30
|
+
for k,v in kwargs.items():
|
|
31
|
+
if hasattr(self, k):
|
|
32
|
+
setattr(self, k, v)
|
|
33
|
+
else:
|
|
34
|
+
raise AttributeError(f'Invalid parameter "{k}" for {self.__class__.__name__}. Attribute does not exist.')
|
|
35
|
+
|
|
36
|
+
_Canvas.draw_queue.append(self)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def fill_color(self):
|
|
40
|
+
'''The fill color of the shape'''
|
|
41
|
+
return self._fill_color
|
|
42
|
+
|
|
43
|
+
@fill_color.setter
|
|
44
|
+
def fill_color(self, v):
|
|
45
|
+
self._fill_color = v
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def fill_opacity(self):
|
|
50
|
+
'''The fill opacity of the shape'''
|
|
51
|
+
return self._fill_opacity
|
|
52
|
+
|
|
53
|
+
@fill_opacity.setter
|
|
54
|
+
def fill_opacity(self, v):
|
|
55
|
+
self._fill_opacity = v
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def close_path(self):
|
|
59
|
+
'''Upon rendering the shape, the last vertex will connect to the first vertex'''
|
|
60
|
+
return self._close_path
|
|
61
|
+
|
|
62
|
+
@close_path.setter
|
|
63
|
+
def close_path(self, v:bool):
|
|
64
|
+
self._close_path = v
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def stroke_color(self):
|
|
68
|
+
'''The stroke color of the shape'''
|
|
69
|
+
return self._stroke_color
|
|
70
|
+
|
|
71
|
+
@stroke_color.setter
|
|
72
|
+
def stroke_color(self, v):
|
|
73
|
+
self._stroke_color = v
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def stroke_width(self):
|
|
77
|
+
'''The stroke width of the shape'''
|
|
78
|
+
return self._stroke_width
|
|
79
|
+
|
|
80
|
+
@stroke_width.setter
|
|
81
|
+
def stroke_width(self, v):
|
|
82
|
+
self._stroke_width = v
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def vertices(self):
|
|
86
|
+
'''The list of vertices'''
|
|
87
|
+
return self._vertices
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def first_vertex(self):
|
|
91
|
+
'''The first vertex of the shape'''
|
|
92
|
+
rv = (None, None)
|
|
93
|
+
if self.vertices.size > 0:
|
|
94
|
+
_rv = self.vertices[0]
|
|
95
|
+
rv = (_rv[0], _rv[1])
|
|
96
|
+
|
|
97
|
+
return rv
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def min_x(self):
|
|
101
|
+
'''The smallest x value of all the vertices'''
|
|
102
|
+
rv = self.first_vertex[0]
|
|
103
|
+
|
|
104
|
+
_v = self.vertices.tolist()
|
|
105
|
+
|
|
106
|
+
for vertex in _v:
|
|
107
|
+
x = vertex[0]
|
|
108
|
+
if x < rv:
|
|
109
|
+
rv = x
|
|
110
|
+
return rv
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def max_x(self):
|
|
114
|
+
'''The largest x value of all the vertices'''
|
|
115
|
+
rv = self.first_vertex[0]
|
|
116
|
+
|
|
117
|
+
_v = self.vertices.tolist()
|
|
118
|
+
|
|
119
|
+
for vertex in _v:
|
|
120
|
+
x = vertex[0]
|
|
121
|
+
if x > rv:
|
|
122
|
+
rv = x
|
|
123
|
+
return rv
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def min_y(self):
|
|
127
|
+
'''The smallest y value of all the vertices'''
|
|
128
|
+
rv = self.first_vertex[1]
|
|
129
|
+
|
|
130
|
+
_v = self.vertices.tolist()
|
|
131
|
+
|
|
132
|
+
for vertex in _v:
|
|
133
|
+
y = vertex[1]
|
|
134
|
+
if y < rv:
|
|
135
|
+
rv = y
|
|
136
|
+
return rv
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def max_y(self):
|
|
140
|
+
'''The largest y value of all the vertices'''
|
|
141
|
+
rv = self.first_vertex[1]
|
|
142
|
+
|
|
143
|
+
_v = self.vertices.tolist()
|
|
144
|
+
|
|
145
|
+
for vertex in _v:
|
|
146
|
+
y = vertex[1]
|
|
147
|
+
if y > rv:
|
|
148
|
+
rv = y
|
|
149
|
+
return rv
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def center(self):
|
|
153
|
+
'''The center of the shape based on the min/max x/y'''
|
|
154
|
+
x = ((self.max_x - self.min_x)/2) + self.min_x
|
|
155
|
+
y = ((self.max_y - self.min_y)/2) + self.min_y
|
|
156
|
+
return (x,y)
|
|
157
|
+
|
|
158
|
+
def add_vertex(self, x, y, z=0):
|
|
159
|
+
'''Append a set of vertexes to the primitive shape'''
|
|
160
|
+
|
|
161
|
+
l = self._vertices.tolist()
|
|
162
|
+
l.append([x,y,z,1])
|
|
163
|
+
|
|
164
|
+
self._vertices = np.array(l)
|
|
165
|
+
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def transform(self, x, y, z=0):
|
|
170
|
+
'''Transform the vertices based on x/y coords'''
|
|
171
|
+
|
|
172
|
+
translate_matrix = np.identity(4)
|
|
173
|
+
translate_matrix[0, -1] = x
|
|
174
|
+
translate_matrix[1, -1] = y
|
|
175
|
+
translate_matrix[2, -1] = z
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
self._vertices = np.dot(self._vertices, translate_matrix.T)[:, :4]
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def rotate(self, theta, origin=None, axis=np.array([0, 0, 1])):
|
|
183
|
+
"""Rotate the shape by the given angle along the given axis.
|
|
184
|
+
|
|
185
|
+
:param theta: The angle by which to rotate (in degrees)
|
|
186
|
+
:type theta: float
|
|
187
|
+
|
|
188
|
+
:param axis: The axis along which to rotate (defaults to the z-axis)
|
|
189
|
+
:type axis: np.ndarray or list
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
# Convert degrees to radians
|
|
194
|
+
theta = theta * math.pi / 180
|
|
195
|
+
|
|
196
|
+
axis = np.array(axis[:])
|
|
197
|
+
|
|
198
|
+
x = axis[0]
|
|
199
|
+
y = axis[1]
|
|
200
|
+
z = axis[2]
|
|
201
|
+
|
|
202
|
+
s = np.sin(theta)
|
|
203
|
+
c = np.cos(theta)
|
|
204
|
+
c1 = 1 - c
|
|
205
|
+
|
|
206
|
+
rotation = np.identity(4)
|
|
207
|
+
|
|
208
|
+
rotation[0, 0] = x * x * c1 + c
|
|
209
|
+
rotation[0, 1] = x * y * c1 - z * s
|
|
210
|
+
rotation[0, 2] = x * z * c1 + y * s
|
|
211
|
+
rotation[1, 0] = y * x * c1 + z * s
|
|
212
|
+
rotation[1, 1] = y * y * c1 + c
|
|
213
|
+
rotation[1, 2] = y * z * c1 - x * s
|
|
214
|
+
rotation[2, 0] = x * z * c1 - y * s
|
|
215
|
+
rotation[2, 1] = y * z * c1 + x * s
|
|
216
|
+
rotation[2, 2] = z * z * c1 + c
|
|
217
|
+
|
|
218
|
+
if origin == None:
|
|
219
|
+
x_c,y_c = self.center
|
|
220
|
+
else:
|
|
221
|
+
x_c = origin[0]
|
|
222
|
+
y_c = origin[1]
|
|
223
|
+
|
|
224
|
+
self.transform(-x_c, -y_c)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
rotation[0, -1] = x_c
|
|
228
|
+
rotation[1, -1] = y_c
|
|
229
|
+
rotation[2, -1] = 1
|
|
230
|
+
|
|
231
|
+
self._vertices = np.dot(self._vertices, rotation.T)[:, :4]
|
|
232
|
+
|
|
233
|
+
return self
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def rotate_x(self, theta):
|
|
237
|
+
"""This is a convenience function to rotate the shape along the x axis.
|
|
238
|
+
|
|
239
|
+
:param theta: The angle by which to rotate (in degrees)
|
|
240
|
+
:type theta: float
|
|
241
|
+
|
|
242
|
+
:returns: The rotation matrix used to apply the transformation.
|
|
243
|
+
:rtype: np.ndarray
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
self.rotate(theta, axis=np.array([1, 0, 0]))
|
|
247
|
+
return self
|
|
248
|
+
|
|
249
|
+
def rotate_y(self, theta):
|
|
250
|
+
"""This is a convenience function to rotate the shape along the y axis.
|
|
251
|
+
|
|
252
|
+
:param theta: The angle by which to rotate (in degrees)
|
|
253
|
+
:type theta: float
|
|
254
|
+
|
|
255
|
+
:returns: The rotation matrix used to apply the transformation.
|
|
256
|
+
:rtype: np.ndarray
|
|
257
|
+
|
|
258
|
+
"""
|
|
259
|
+
self.rotate(theta, axis=np.array([0, 1, 0]))
|
|
260
|
+
return self
|
|
261
|
+
|
|
262
|
+
def rotate_z(self, theta):
|
|
263
|
+
"""This is a convenience function to rotate the shape along the z axis.
|
|
264
|
+
|
|
265
|
+
:param theta:The angle by which to rotate (in degrees)
|
|
266
|
+
:type theta: float
|
|
267
|
+
|
|
268
|
+
:returns: The rotation matrix used to apply the transformation.
|
|
269
|
+
:rtype: np.ndarray
|
|
270
|
+
|
|
271
|
+
"""
|
|
272
|
+
self.rotate(theta, axis=np.array([0, 0, 1]))
|
|
273
|
+
return self
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def scale(self, amt, amt_y=None, origin=None):
|
|
277
|
+
"""Scale the shape by the given amount.
|
|
278
|
+
|
|
279
|
+
:param amt: Scale factor for x-axis (2.0 = twice as big)
|
|
280
|
+
:type amt: float
|
|
281
|
+
:param amt_y: Scale factor for y-axis (defaults to amt for uniform scaling)
|
|
282
|
+
:type amt_y: float
|
|
283
|
+
:param origin: Origin point for scaling (defaults to shape center)
|
|
284
|
+
:type origin: tuple
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
if amt_y == None:
|
|
288
|
+
amt_y = amt
|
|
289
|
+
|
|
290
|
+
scale_matrix = np.identity(4)
|
|
291
|
+
scale_matrix[0, 0] = amt
|
|
292
|
+
scale_matrix[1, 1] = amt_y
|
|
293
|
+
scale_matrix[2, 2] = 1 # default for z
|
|
294
|
+
|
|
295
|
+
self._vertices = self._vertices.dot(scale_matrix)
|
|
296
|
+
|
|
297
|
+
return self
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def svg_object(self):
|
|
302
|
+
|
|
303
|
+
if len(self.vertices) > 0:
|
|
304
|
+
|
|
305
|
+
verts = self.vertices
|
|
306
|
+
|
|
307
|
+
verts = verts.tolist()
|
|
308
|
+
|
|
309
|
+
# # verts = vertices.tolist()
|
|
310
|
+
if len(verts)== 0 :
|
|
311
|
+
print(f'verts contains one item of {[[0.0, 0.0, 0.0]]} ... continuing.')
|
|
312
|
+
return None
|
|
313
|
+
|
|
314
|
+
start_l = verts.pop(0)
|
|
315
|
+
start_x = start_l[0]
|
|
316
|
+
start_y = start_l[1]
|
|
317
|
+
|
|
318
|
+
other_verts = []
|
|
319
|
+
for o_v in verts:
|
|
320
|
+
x = o_v[0]
|
|
321
|
+
|
|
322
|
+
y = o_v[1]
|
|
323
|
+
|
|
324
|
+
# z = o_v[2]
|
|
325
|
+
other_verts.append(x)
|
|
326
|
+
other_verts.append(y)
|
|
327
|
+
|
|
328
|
+
return drawsvg.Lines(start_x, start_y, *other_verts, fill=self.fill_color, stroke=self.stroke_color,
|
|
329
|
+
stroke_width=self.stroke_width, fill_opacity=self.fill_opacity, close=self.close_path)
|
|
330
|
+
|
|
331
|
+
else:
|
|
332
|
+
raise Exception("This does not have vertices or a custom svg_object, so it cannot be rendered.")
|