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 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
@@ -0,0 +1,7 @@
1
+ from .primitives.Line import Line
2
+ from .primitives.Ellipse import Circle, Ellipse, Point
3
+ from .primitives.Rectangle import Rectangle, Square
4
+ from .environment.Canvas import _Canvas
5
+
6
+ def main():
7
+ print('entry point')
@@ -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()
@@ -0,0 +1,9 @@
1
+ class TweaksBase:
2
+ def __init__(self):
3
+ pass
4
+
5
+ def add_tweak(self, t):
6
+
7
+ return t
8
+
9
+ Tweaks = TweaksBase()
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.")