Excalidraw-Interface 0.0.1__tar.gz

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.
@@ -0,0 +1,9 @@
1
+ Modified by Robert Nies
2
+
3
+ Copyright (c) 2023 Aditya Ganeshan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,6 @@
1
+ from Excalidraw_Interface.sketch_builder import SketchBuilder
2
+ from Excalidraw_Interface.primitives import Group
3
+
4
+ #TODO - write readme
5
+ #TODO - publish to pypi
6
+ #TODO - tests (check against specific keys from json)
@@ -0,0 +1,54 @@
1
+ """Specify the default values for excalidraw primitives."""
2
+ import os
3
+
4
+ # Font family for estimating font size
5
+ current_file_dir = os.path.dirname(os.path.abspath(__file__))
6
+ _fonts = {
7
+ 1: "fonts/Virgil.ttf",
8
+ 2: "fonts/Assistant-VariableFont.ttf",
9
+ 3: "fonts/CascadiaCode.ttf",
10
+ }
11
+ FONT_FAMILY = {x: os.path.join(current_file_dir, y) for x, y in _fonts.items()}
12
+
13
+ # Settings for Rectangle, Ellipse, Diamond
14
+ BOX_DEFAULTS = dict(
15
+ angle=0,
16
+ strokeColor="#000000",
17
+ backgroundColor="transparent",
18
+ fillStyle="hachure",
19
+ strokeStyle="solid",
20
+ strokeWidth=1,
21
+ roughness=0,
22
+ opacity=100,
23
+ roundness={"type": 3},
24
+ version=1,
25
+ versionNonce=0,
26
+ isDeleted=False,
27
+ boundElements=[],
28
+ updated=0,
29
+ link=None,
30
+ locked=False,
31
+ )
32
+
33
+ # Extra properties for Text
34
+ TEXT_DEFAULTS = dict(
35
+ fontSize=20,
36
+ fontFamily=3,
37
+ textAlign="left",
38
+ verticalAlign="top",
39
+ baseline=18,
40
+ containerId=None,
41
+ originalText="default text",
42
+ roundness=None,
43
+ )
44
+
45
+
46
+ # Extra properties for Line
47
+ LINE_DEFAULTS = dict(
48
+ lastCommitedPoint=None,
49
+ startBinding=None,
50
+ endBinding=None,
51
+ startArrowhead=None,
52
+ endArrowhead=None,
53
+ roundess={"type": 2}, # It has only two configurations None and type 2
54
+ )
@@ -0,0 +1,16 @@
1
+ from Excalidraw_Interface import SketchBuilder, Group
2
+
3
+ sb = SketchBuilder()
4
+
5
+ tb_1 = sb.TextBox('Start Here', 0.5, 0.5)
6
+ tb_2 = sb.TextBox('End Here', 100, 100)
7
+
8
+ group_1 = Group([tb_1, tb_2])
9
+ bound_1 = sb.create_bounding_element(group_1)
10
+
11
+ hcb = sb.HeaderContentBox('Header', 'Some Extremely Long Content', 200, 200)
12
+
13
+ group_2 = Group([bound_1, hcb])
14
+ sb.create_bounding_element(group_2, strokeColor="#FF0000",)
15
+
16
+ sb.export_to_file('out.excalidraw')
@@ -0,0 +1,20 @@
1
+ from Excalidraw_Interface import SketchBuilder
2
+
3
+ flowchart_items = ['First Step', 'Second Step', 'Third Step']
4
+
5
+ sb = SketchBuilder() # Create a Sketch
6
+
7
+ prev_item = sb.TextBox("Start Here", x = 0, y = 0) # Create a Text Box
8
+ for index, item in enumerate(flowchart_items):
9
+ new_item = sb.TextBox(item, x = 0, y = (index+1) * 150) # Create a Text Box
10
+ sb.create_binding_arrows(prev_item, new_item) # Create arrows between boxes
11
+ prev_item = new_item
12
+
13
+ hcb = sb.HeaderContentBox("Header", "Content", x = -200, y = 400) # Create a multiline text box
14
+ circle = sb.Ellipse(200, 400, width=50, height=50, backgroundColor = 'red',
15
+ roughness=1) # Create a red circle in hand drawn style
16
+
17
+ sb.create_binding_arrows(prev_item, hcb, sb.DoubleArrow) # Create a double headed arrow
18
+ sb.create_binding_arrows(prev_item, circle, strokeColor = 'blue') # Create an blue arrow
19
+
20
+ sb.export_to_file('out.excalidraw')
@@ -0,0 +1,206 @@
1
+ from Excalidraw_Interface.defaults import FONT_FAMILY
2
+ from PIL import ImageFont
3
+ import random
4
+ import uuid
5
+ import math
6
+ import copy
7
+
8
+ class ExcaliDrawPrimitive:
9
+ """
10
+ Base class for all excalidraw primitives
11
+ """
12
+
13
+ def __init__(self, excal_type: str, default_config: dict,
14
+ x: float, y: float, width: float, height: float, **kwargs):
15
+ """
16
+ Initialize the excalidraw primitive
17
+ :param excal_type: type of the excalidraw primitive
18
+ :param default_config: default values
19
+ :param x: top left x
20
+ :param y: top left y
21
+ :param width: width
22
+ :param height: height
23
+ :param kwargs: additional config to override defaults
24
+ """
25
+ self.excal_type = excal_type
26
+ self.excal_id = str(uuid.uuid4())
27
+ self.excal_seed = random.randint(0, 100000)
28
+
29
+ self.x = x
30
+ self.y = y
31
+ self.width = width
32
+ self.height = height
33
+ self._groupIds = []
34
+ self.apply_recursive_groups = []
35
+
36
+ self.config = copy.deepcopy(default_config)
37
+ for key, value in kwargs.items():
38
+ if key not in self.config:
39
+ raise Exception(f"Unexpected key for shape {excal_type}: {key}")
40
+ self.config[key] = value
41
+
42
+ def export(self):
43
+ """ Convert the excalidraw primitive specifications to a dictionary """
44
+ export_dict = {'id': self.excal_id, 'type': self.excal_type, 'seed': self.excal_seed,
45
+ 'x': self.x, 'y': self.y, 'width': self.width, 'height': self.height,
46
+ 'groupIds': self._groupIds}
47
+ export_dict.update(self.config)
48
+ return export_dict
49
+
50
+ def add_to_group(self, group_id):
51
+ self._groupIds.append(group_id)
52
+ for elem in self.apply_recursive_groups:
53
+ elem.add_to_group(group_id)
54
+
55
+ @property
56
+ def center(self):
57
+ """ Return the center of the excalidraw primitive """
58
+ return [self.x + self.width / 2, self.y + self.height / 2]
59
+
60
+ def get_edge_midpoint(self, theta, padding=5):
61
+ # given angle theta measured from the positive x-axis, return the boundary mapped to the center of the edges
62
+ theta_lim = math.atan2(self.height, self.width)
63
+ if abs(theta) < theta_lim or abs(theta) > math.pi - theta_lim:
64
+ sign_indicator = (abs(theta) < math.pi / 2)
65
+ x = self.x + self.width * sign_indicator + \
66
+ padding * (-1 + 2 * sign_indicator)
67
+ y = self.y + (self.height / 2)
68
+ else:
69
+ sign_indicator = (theta > 0)
70
+ x = self.x + self.width / 2
71
+ y = self.y + self.height * sign_indicator + \
72
+ padding * (-1 + 2 * sign_indicator)
73
+ return [x, y]
74
+
75
+
76
+ class Shape(ExcaliDrawPrimitive):
77
+ def __init__(self, shape: str, default_config, x, y, width, height, **kwargs):
78
+ """
79
+ Creates a rectangle, ellipse, or diamond
80
+ :param shape: "rectangle", "ellipse", "diamond"
81
+ :param default_config: default values
82
+ :param x: center x
83
+ :param y: center y
84
+ :param width: width
85
+ :param height: height
86
+ :param kwargs: additional config to override defaults
87
+ """
88
+
89
+ x -= width/2
90
+ y -= height/2
91
+
92
+ super().__init__(shape, default_config, x=x, y=y, width=width, height=height, **kwargs)
93
+
94
+ #TODO - fix fonts
95
+ class Text(ExcaliDrawPrimitive):
96
+ def __init__(self, text: str, default_config, x, y, **kwargs):
97
+ """
98
+ Creates text item, width and height set automatically
99
+ :param text: text
100
+ :param default_config: default values
101
+ :param x: center x
102
+ :param y: center y
103
+ :param kwargs: additional config to override defaults
104
+ """
105
+
106
+ super().__init__('text', default_config, x, y, 0, 0, **kwargs)
107
+ self.text = text
108
+
109
+ d = super().export()
110
+ _font_file = FONT_FAMILY[d['fontFamily']]
111
+ _font = ImageFont.truetype(_font_file, d['fontSize'])
112
+ left, top, right, bottom = _font.getbbox(self.text)
113
+
114
+ self.width = right - left
115
+ self.height = bottom - top
116
+ self.x -= self.width / 2
117
+ self.y -= self.height / 2
118
+
119
+ def export(self):
120
+ d = super().export()
121
+ d['text'] = self.text
122
+ return d
123
+
124
+ class Line(ExcaliDrawPrimitive):
125
+ def __init__(self, excal_type, default_config, start_pt: tuple, end_pt: tuple, **kwargs):
126
+ """
127
+ Creates a line or arrow
128
+ :param excal_type: 'line' or 'arrow'
129
+ :param default_config: default values
130
+ :param start_pt: start pt | tuple[float, float]
131
+ :param end_pt: end pt | tuple[float, float]
132
+ :param kwargs: additional config to override defaults
133
+ """
134
+
135
+ start_x = start_pt[0]
136
+ start_y = start_pt[1]
137
+ end_x = end_pt[0]
138
+ end_y = end_pt[1]
139
+ width = abs(end_x - start_x)
140
+ height = abs(end_y - start_y)
141
+ self.points = [[0, 0], [end_x - start_x, end_y - start_y]]
142
+
143
+ super().__init__(excal_type, default_config, x=start_x, y=start_y, width=width, height=height, **kwargs)
144
+
145
+ def export(self):
146
+ d = super().export()
147
+ d['points'] = self.points
148
+ return d
149
+
150
+ def set_start_binding(self, element: ExcaliDrawPrimitive, padding=10):
151
+ """ Set binding in both the line as well as the element."""
152
+ self.config["startBinding"] = {
153
+ "elementId": element.excal_id,
154
+ "focus": 0,
155
+ "gap": padding
156
+ }
157
+ bound_e = element.config.get('boundElements', [])
158
+ bound_e.append({
159
+ "id": self.excal_id,
160
+ "type": self.excal_type
161
+ })
162
+ element.config['boundElements'] = bound_e
163
+
164
+ def set_end_binding(self, element, padding=10):
165
+ """ Set binding in both the line as well as the element."""
166
+ self.config["endBinding"] = {
167
+ "elementId": element.excal_id,
168
+ "focus": 0,
169
+ "gap": padding
170
+ }
171
+ bound_e = element.config.get('boundElements', [])
172
+ bound_e.append({
173
+ "id": self.excal_id,
174
+ "type": self.excal_type
175
+ })
176
+ element.config['boundElements'] = bound_e
177
+
178
+ class Group(ExcaliDrawPrimitive):
179
+ def __init__(self, elems: list[ExcaliDrawPrimitive], first_is_group = False):
180
+ """
181
+ Create a group (a fake element for handling group logic)
182
+ :param elems: list of excalidraw objects to group
183
+ :param first_is_group: treat the first element of the list as representing the whole group
184
+ """
185
+ group_id = str(uuid.uuid4())
186
+
187
+ left_x = min([elem.x for elem in elems])
188
+ top_y = min([elem.y for elem in elems])
189
+ width = max([elem.x + elem.width - left_x for elem in elems])
190
+ height = max([elem.y + elem.height - top_y for elem in elems])
191
+
192
+ for elem in elems:
193
+ elem.add_to_group(group_id)
194
+
195
+ super().__init__('group', {}, left_x, top_y, width, height)
196
+ self.elems = elems
197
+
198
+ if first_is_group:
199
+ elems[0].apply_recursive_groups.extend(elems[1:])
200
+
201
+ def export(self):
202
+ raise Exception("Group should not be exported - a group was incorrectly added to sketch.")
203
+
204
+ def add_to_group(self, group_id):
205
+ for elem in self.elems:
206
+ elem.add_to_group(group_id)
@@ -0,0 +1,241 @@
1
+ from Excalidraw_Interface import primitives, defaults
2
+ import json
3
+ import math
4
+ import copy
5
+
6
+ def none_to_empty_dict(x):
7
+ if x is None:
8
+ return {}
9
+ return x
10
+
11
+ class SketchBuilder:
12
+ def __init__(self, **custom_defaults):
13
+ """
14
+ Create a sketch
15
+ :param custom_defaults: overrides normal defaults
16
+ """
17
+ self._draw_objs: list[primitives.ExcaliDrawPrimitive] = []
18
+
19
+ self.BOX_DEFAULTS = copy.deepcopy(defaults.BOX_DEFAULTS)
20
+
21
+ self.LINE_DEFAULTS = copy.deepcopy(defaults.BOX_DEFAULTS)
22
+ self.LINE_DEFAULTS.update(copy.deepcopy(defaults.LINE_DEFAULTS))
23
+
24
+ self.TEXT_DEFAULTS = copy.deepcopy(defaults.BOX_DEFAULTS)
25
+ self.TEXT_DEFAULTS.update(copy.deepcopy(defaults.TEXT_DEFAULTS))
26
+
27
+ for key, value in custom_defaults.items():
28
+ used = False
29
+
30
+ if key in self.BOX_DEFAULTS:
31
+ self.BOX_DEFAULTS[key] = value
32
+ used = True
33
+
34
+ if key in self.LINE_DEFAULTS:
35
+ self.BOX_DEFAULTS[key] = value
36
+ used = True
37
+
38
+ if key in self.TEXT_DEFAULTS:
39
+ self.BOX_DEFAULTS[key] = value
40
+ used = True
41
+
42
+ if not used:
43
+ raise Exception(f"Key <{key}> not used.")
44
+
45
+ def add_element(self, element: primitives.ExcaliDrawPrimitive):
46
+ self._draw_objs.append(element)
47
+ return element
48
+
49
+ def Rectangle(self, x: float, y: float, width: float = 100, height: float = 100, **kwargs):
50
+ """
51
+ Creates a rectangle
52
+ :param x: center x
53
+ :param y: center y
54
+ :param width: width
55
+ :param height: height
56
+ :param kwargs: additional config to override defaults
57
+ """
58
+ return self.add_element(primitives.Shape('rectangle', self.BOX_DEFAULTS, x, y, width, height, **kwargs))
59
+
60
+ def Diamond(self, x: float, y: float, width: float = 100, height: float = 100, **kwargs):
61
+ """
62
+ Creates a diamond
63
+ :param x: center x
64
+ :param y: center y
65
+ :param width: width
66
+ :param height: height
67
+ :param kwargs: additional config to override defaults
68
+ """
69
+ return self.add_element(primitives.Shape('diamond', self.BOX_DEFAULTS, x, y, width, height, **kwargs))
70
+
71
+ def Ellipse(self, x: float, y: float, width: float = 100, height: float = 100, **kwargs):
72
+ """
73
+ Creates a ellipse
74
+ :param x: center x
75
+ :param y: center y
76
+ :param width: width
77
+ :param height: height
78
+ :param kwargs: additional config to override defaults
79
+ """
80
+ return self.add_element(primitives.Shape('ellipse', self.BOX_DEFAULTS, x, y, width, height, **kwargs))
81
+
82
+ def Text(self, text: str, x: float, y: float, **kwargs):
83
+ """
84
+ Creates text item, width and height set automatically
85
+ :param text: text
86
+ :param x: center x
87
+ :param y: center y
88
+ :param kwargs: additional config to override defaults
89
+ """
90
+ return self.add_element(primitives.Text(text, self.TEXT_DEFAULTS, x, y, **kwargs))
91
+
92
+ def Line(self, start_pt: tuple, end_pt: tuple, **kwargs):
93
+ """
94
+ Creates a line
95
+ :param start_pt: start pt | tuple[float, float]
96
+ :param end_pt: end pt | tuple[float, float]
97
+ :param kwargs: additional config to override defaults
98
+ """
99
+ return self.add_element(primitives.Line('line', self.LINE_DEFAULTS, start_pt, end_pt, **kwargs))
100
+
101
+ def Arrow(self, start_pt: tuple, end_pt: tuple, **kwargs):
102
+ """
103
+ Creates an arrow
104
+ :param start_pt: start pt | tuple[float, float]
105
+ :param end_pt: end pt | tuple[float, float]
106
+ :param kwargs: additional config to override defaults
107
+ """
108
+ kwargs['endArrowhead'] = 'arrow'
109
+ arrow = primitives.Line('arrow', self.LINE_DEFAULTS, start_pt, end_pt, **kwargs)
110
+ self.add_element(arrow)
111
+ return arrow
112
+
113
+ def DoubleArrow(self, start_pt: tuple, end_pt: tuple, **kwargs):
114
+ """
115
+ Creates a bi-directional arrow
116
+ :param start_pt: start pt | tuple[float, float]
117
+ :param end_pt: end pt | tuple[float, float]
118
+ :param kwargs: additional config to override defaults
119
+ """
120
+ kwargs['startArrowhead'] = 'arrow'
121
+ return self.Arrow(start_pt, end_pt, **kwargs)
122
+
123
+ def export_to_file(self, save_path):
124
+ """Export the sketch to a excalidraw file."""
125
+ data = {
126
+ "type": "excalidraw",
127
+ "version": 1,
128
+ "source": "Excalidraw.py",
129
+ "elements": [],
130
+ "appState": {
131
+ "viewBackgroundColor": "#ffffff",
132
+ "gridSize": None,
133
+ }
134
+ }
135
+
136
+ for element in self._draw_objs:
137
+ json_obj = element.export()
138
+ data['elements'].append(json_obj)
139
+
140
+ if save_path.split(".")[-1] != "excalidraw":
141
+ save_path += ".excalidraw"
142
+
143
+ with open(save_path, "w") as file_reader:
144
+ json.dump(data, file_reader, indent=4)
145
+
146
+ def create_binding_arrows(self, start: primitives.ExcaliDrawPrimitive, end: primitives.ExcaliDrawPrimitive,
147
+ arrow = None, padding:int=10, **kwargs):
148
+ """
149
+ Create a binding arrow between two elements.
150
+ :param start: primitives.ExcaliDrawPrimitives
151
+ :param end: primitives.ExcaliDrawPrimitives
152
+ :param arrow: self.Arrow or self.DoubleArrow
153
+ :param padding: defaults to 10
154
+ :param kwargs: passed to arrow
155
+ """
156
+
157
+ if arrow is None:
158
+ arrow = self.Arrow
159
+
160
+ center_start = start.center
161
+ center_end = end.center
162
+ dx = center_end[0] - center_start[0]
163
+ dy = center_end[1] - center_start[1]
164
+ theta = math.atan2(dy, dx)
165
+ theta = (theta + math.pi) % (2*math.pi) - math.pi
166
+ inverted_theta = theta % (2*math.pi) - math.pi
167
+ start_pt = start.get_edge_midpoint(theta, padding=padding)
168
+ end_pt = end.get_edge_midpoint(inverted_theta, padding=padding)
169
+
170
+ line = arrow(start_pt, end_pt, **kwargs)
171
+ line.set_start_binding(start, padding=padding)
172
+ line.set_end_binding(end, padding=padding)
173
+
174
+ def create_bounding_element(self, element: primitives.ExcaliDrawPrimitive, function = None, padding = 10, **kwargs):
175
+ """
176
+ Create a bounding box around the given element/group.
177
+
178
+ :param element: primitives.ExcaliDrawPrimitives
179
+ :param function: self.Rectangle or similar, defaults to self.Rectangle
180
+ :param padding: defaults to 10
181
+ :param kwargs: passed to arrow
182
+ """
183
+
184
+ if function is None:
185
+ function = self.Rectangle
186
+
187
+ element_center = element.center
188
+ new_x = element_center[0]
189
+ new_y = element_center[1]
190
+
191
+ new_width = element.width + 2 * padding
192
+ new_height = element.height + 2 * padding
193
+
194
+ bounding_elem = function(x=new_x, y=new_y, width=new_width, height=new_height, **kwargs)
195
+ primitives.Group([bounding_elem, element], first_is_group=True)
196
+
197
+ return bounding_elem
198
+
199
+ def TextBox(self, text: str, x: float, y: float, txt_kwargs = None, rect_kwargs = None):
200
+ """
201
+ Creates a text box
202
+ :param text: text
203
+ :param x: center x
204
+ :param y: center y
205
+ :param txt_kwargs: other properties for text
206
+ :param rect_kwargs: other properties for rect
207
+ """
208
+ txt_kwargs = none_to_empty_dict(txt_kwargs)
209
+ rect_kwargs = none_to_empty_dict(rect_kwargs)
210
+
211
+ txt = self.Text(text, x, y, **txt_kwargs)
212
+ return self.create_bounding_element(txt, self.Rectangle, **rect_kwargs)
213
+
214
+ def HeaderContentBox(self, header: str, content: str, x: float, y: float, padding = 10,
215
+ header_kwargs = None, content_kwargs = None, rect_kwargs = None):
216
+ """
217
+ Creates a multiline text box
218
+ :param header: text
219
+ :param content: text
220
+ :param x: center x
221
+ :param y: center y
222
+ :param padding: defaults to 10
223
+ :param header_kwargs: other properties for text
224
+ :param content_kwargs: other properties for text
225
+ :param rect_kwargs: other properties for rect
226
+ """
227
+ header_kwargs = none_to_empty_dict(header_kwargs)
228
+ content_kwargs = none_to_empty_dict(content_kwargs)
229
+ rect_kwargs = none_to_empty_dict(rect_kwargs)
230
+
231
+ fontSize = int(self.TEXT_DEFAULTS['fontSize'] * 2/3)
232
+
233
+ header_item = self.Text(header, x, y, **header_kwargs)
234
+ content_item = self.Text(content, x, y, fontSize=fontSize, **content_kwargs)
235
+ header_item.y -= header_item.height/2 + padding/2
236
+ content_item.y += content_item.height/2 + padding/2
237
+
238
+ return self.create_bounding_element(primitives.Group([header_item, content_item]),
239
+ self.Rectangle, **rect_kwargs)
240
+
241
+
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.1
2
+ Name: Excalidraw_Interface
3
+ Version: 0.0.1
4
+ Summary: Create excalidraw drawings with python
5
+ Home-page: https://github.com/RobertJN64/Excalidraw_Interface
6
+ Author: Robert Nies
7
+ Author-email: robertjnies@gmail.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pillow
21
+
22
+ # Excalidraw_Interface
23
+
24
+ ![Tests Badge](https://github.com/RobertJN64/Excalidraw_Interface/actions/workflows/tests.yml/badge.svg)
25
+ ![Python Version Badge](https://img.shields.io/pypi/pyversions/Excalidraw_Interface)
26
+ ![License Badge](https://img.shields.io/github/license/RobertJN64/Excalidraw_Interface)
27
+
28
+ A pythonic interface for creating diagrams in Excalidraw.
29
+
30
+ Based on https://github.com/BardOfCodes/procXD by Aditya Ganeshan (MIT License) and updated with modern python support
31
+ and API improvements.
32
+
33
+ ## Example: Flowchart
34
+
35
+ [flowchart.py](Excalidraw_Interface/examples/flowchart.py)
36
+
37
+ ![flowchart image](images/flowchart.png)
38
+
39
+ ```python
40
+ from Excalidraw_Interface import SketchBuilder
41
+
42
+ flowchart_items = ['First Step', 'Second Step', 'Third Step']
43
+
44
+ sb = SketchBuilder() # Create a Sketch
45
+
46
+ prev_item = sb.TextBox("Start Here", x = 0, y = 0) # Create a Text Box
47
+ for index, item in enumerate(flowchart_items):
48
+ new_item = sb.TextBox(item, x = 0, y = (index+1) * 150) # Create a Text Box
49
+ sb.create_binding_arrows(prev_item, new_item) # Create arrows between boxes
50
+ prev_item = new_item
51
+
52
+ hcb = sb.HeaderContentBox("Header", "Content", x = -200, y = 400) # Create a multiline text box
53
+ circle = sb.Ellipse(200, 400, width=50, height=50, backgroundColor = 'red',
54
+ roughness=1) # Create a red circle in hand drawn style
55
+
56
+ sb.create_binding_arrows(prev_item, hcb, sb.DoubleArrow) # Create a double headed arrow
57
+ sb.create_binding_arrows(prev_item, circle, strokeColor = 'blue') # Create an blue arrow
58
+
59
+ sb.export_to_file('out.excalidraw')
60
+ ```
@@ -0,0 +1,21 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.cfg
6
+ setup.py
7
+ Excalidraw_Interface/LICENSE
8
+ Excalidraw_Interface/__init__.py
9
+ Excalidraw_Interface/defaults.py
10
+ Excalidraw_Interface/primitives.py
11
+ Excalidraw_Interface/sketch_builder.py
12
+ Excalidraw_Interface.egg-info/PKG-INFO
13
+ Excalidraw_Interface.egg-info/SOURCES.txt
14
+ Excalidraw_Interface.egg-info/dependency_links.txt
15
+ Excalidraw_Interface.egg-info/requires.txt
16
+ Excalidraw_Interface.egg-info/top_level.txt
17
+ Excalidraw_Interface/examples/advanced_groups.py
18
+ Excalidraw_Interface/examples/flowchart.py
19
+ Excalidraw_Interface/fonts/Assistant-VariableFont.ttf
20
+ Excalidraw_Interface/fonts/CascadiaCode.ttf
21
+ Excalidraw_Interface/fonts/Virgil.ttf
@@ -0,0 +1 @@
1
+ Excalidraw_Interface
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2024 Robert Nies
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ include pyproject.toml
2
+
3
+ # Include the README
4
+ include *.md
5
+
6
+ # Include the license file
7
+ include LICENSE
8
+
9
+ # Include setup.py
10
+ include setup.py
11
+
12
+ # Include the data files
13
+ recursive-include Excalidraw_Interface *
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.1
2
+ Name: Excalidraw_Interface
3
+ Version: 0.0.1
4
+ Summary: Create excalidraw drawings with python
5
+ Home-page: https://github.com/RobertJN64/Excalidraw_Interface
6
+ Author: Robert Nies
7
+ Author-email: robertjnies@gmail.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pillow
21
+
22
+ # Excalidraw_Interface
23
+
24
+ ![Tests Badge](https://github.com/RobertJN64/Excalidraw_Interface/actions/workflows/tests.yml/badge.svg)
25
+ ![Python Version Badge](https://img.shields.io/pypi/pyversions/Excalidraw_Interface)
26
+ ![License Badge](https://img.shields.io/github/license/RobertJN64/Excalidraw_Interface)
27
+
28
+ A pythonic interface for creating diagrams in Excalidraw.
29
+
30
+ Based on https://github.com/BardOfCodes/procXD by Aditya Ganeshan (MIT License) and updated with modern python support
31
+ and API improvements.
32
+
33
+ ## Example: Flowchart
34
+
35
+ [flowchart.py](Excalidraw_Interface/examples/flowchart.py)
36
+
37
+ ![flowchart image](images/flowchart.png)
38
+
39
+ ```python
40
+ from Excalidraw_Interface import SketchBuilder
41
+
42
+ flowchart_items = ['First Step', 'Second Step', 'Third Step']
43
+
44
+ sb = SketchBuilder() # Create a Sketch
45
+
46
+ prev_item = sb.TextBox("Start Here", x = 0, y = 0) # Create a Text Box
47
+ for index, item in enumerate(flowchart_items):
48
+ new_item = sb.TextBox(item, x = 0, y = (index+1) * 150) # Create a Text Box
49
+ sb.create_binding_arrows(prev_item, new_item) # Create arrows between boxes
50
+ prev_item = new_item
51
+
52
+ hcb = sb.HeaderContentBox("Header", "Content", x = -200, y = 400) # Create a multiline text box
53
+ circle = sb.Ellipse(200, 400, width=50, height=50, backgroundColor = 'red',
54
+ roughness=1) # Create a red circle in hand drawn style
55
+
56
+ sb.create_binding_arrows(prev_item, hcb, sb.DoubleArrow) # Create a double headed arrow
57
+ sb.create_binding_arrows(prev_item, circle, strokeColor = 'blue') # Create an blue arrow
58
+
59
+ sb.export_to_file('out.excalidraw')
60
+ ```
@@ -0,0 +1,39 @@
1
+ # Excalidraw_Interface
2
+
3
+ ![Tests Badge](https://github.com/RobertJN64/Excalidraw_Interface/actions/workflows/tests.yml/badge.svg)
4
+ ![Python Version Badge](https://img.shields.io/pypi/pyversions/Excalidraw_Interface)
5
+ ![License Badge](https://img.shields.io/github/license/RobertJN64/Excalidraw_Interface)
6
+
7
+ A pythonic interface for creating diagrams in Excalidraw.
8
+
9
+ Based on https://github.com/BardOfCodes/procXD by Aditya Ganeshan (MIT License) and updated with modern python support
10
+ and API improvements.
11
+
12
+ ## Example: Flowchart
13
+
14
+ [flowchart.py](Excalidraw_Interface/examples/flowchart.py)
15
+
16
+ ![flowchart image](images/flowchart.png)
17
+
18
+ ```python
19
+ from Excalidraw_Interface import SketchBuilder
20
+
21
+ flowchart_items = ['First Step', 'Second Step', 'Third Step']
22
+
23
+ sb = SketchBuilder() # Create a Sketch
24
+
25
+ prev_item = sb.TextBox("Start Here", x = 0, y = 0) # Create a Text Box
26
+ for index, item in enumerate(flowchart_items):
27
+ new_item = sb.TextBox(item, x = 0, y = (index+1) * 150) # Create a Text Box
28
+ sb.create_binding_arrows(prev_item, new_item) # Create arrows between boxes
29
+ prev_item = new_item
30
+
31
+ hcb = sb.HeaderContentBox("Header", "Content", x = -200, y = 400) # Create a multiline text box
32
+ circle = sb.Ellipse(200, 400, width=50, height=50, backgroundColor = 'red',
33
+ roughness=1) # Create a red circle in hand drawn style
34
+
35
+ sb.create_binding_arrows(prev_item, hcb, sb.DoubleArrow) # Create a double headed arrow
36
+ sb.create_binding_arrows(prev_item, circle, strokeColor = 'blue') # Create an blue arrow
37
+
38
+ sb.export_to_file('out.excalidraw')
39
+ ```
@@ -0,0 +1,5 @@
1
+ [build-system]
2
+ # These are the assumed default build requirements from pip:
3
+ # https://pip.pypa.io/en/stable/reference/pip/#pep-517-and-518-support
4
+ requires = ["setuptools>=40.8.0", "wheel"]
5
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,32 @@
1
+ [metadata]
2
+ name = Excalidraw_Interface
3
+ description = Create excalidraw drawings with python
4
+ author = Robert Nies
5
+ license = MIT
6
+ license_file = LICENSE
7
+ url = https://github.com/RobertJN64/Excalidraw_Interface
8
+ author_email = robertjnies@gmail.com
9
+ version = 0.0.1
10
+ long_description = file: README.md
11
+ long_description_content_type = text/markdown
12
+ classifiers =
13
+ Programming Language :: Python
14
+ Programming Language :: Python :: 3
15
+ Programming Language :: Python :: 3 :: Only
16
+ Programming Language :: Python :: 3.8
17
+ Programming Language :: Python :: 3.9
18
+ Programming Language :: Python :: 3.10
19
+ Programming Language :: Python :: 3.11
20
+ Programming Language :: Python :: 3.12
21
+
22
+ [options]
23
+ packages = Excalidraw_Interface
24
+ python_requires = >=3.8
25
+ include_package_data = True
26
+ install_requires =
27
+ pillow
28
+
29
+ [egg_info]
30
+ tag_build =
31
+ tag_date = 0
32
+
@@ -0,0 +1,6 @@
1
+ #This is a barebones setup.py file, because we use setup.cfg instead!
2
+ #setup.cfg is declarative, and therefore better for security reasons,
3
+ from setuptools import setup
4
+
5
+ if __name__ == "__main__":
6
+ setup()