├── LICENSE
├── README.md
├── examples
├── 3DExample
│ ├── ctk_spinbox.py
│ ├── cube.json
│ ├── pyramid.json
│ ├── render_engine.py
│ ├── requirements.txt
│ └── threeDapp.py
├── advanced_example.py
├── maths_with_tknodes.py
└── simple_example.py
└── tknodesystem
├── __init__.py
├── grid_images
├── grid_dot.png
├── grid_lines.png
└── no_grid.png
├── node.py
├── node_args.py
├── node_canvas.py
├── node_menu.py
├── node_socket.py
├── node_types.py
└── node_wire.py
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Akash Bora
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **Advanced Node System (DAG) in tkinter python!**
4 |
5 | ## Feature
6 | - Lightweight library
7 | - Easy to install and use
8 | - Multiple nodes and inputs
9 | - **Save/Load** node trees
10 | - Canvas with **drag and zoom** ability
11 | - Customizable nodes and options
12 | - Built-in **Node menu**
13 |
14 | ## Installation
15 | ```
16 | pip install tknodesystem
17 | ```
18 | ### [
](https://github.com/Akascape/TkNodeSystem/archive/refs/heads/main.zip)
19 |
20 | [](https://pypi.org/project/tknodesystem)
21 | [](https://pepy.tech/project/tknodesystem)
22 | 
23 |
24 | ### Requirements
25 | - tkinter
26 | - customtkinter _(for the node menu)_
27 |
28 | ## Overview
29 |
30 | - Node Types
31 |
32 | 
33 |
34 | - Node Menu
35 |
36 | 
37 |
38 | - Node Canvas
39 |
40 | 
41 |
42 | ## Documentation
43 | Full documentation can be found in the **Wiki** page
44 |
45 | [
](https://github.com/Akascape/TkNodeSystem/wiki)
46 |
47 | ## Examples
48 | Examples can be found in the [`examples`](https://github.com/Akascape/TkNodeSystem/tree/main/examples) folder.
49 |
50 |
51 |
52 | ### Level: Beginner
53 | Maths with tknodes
54 |
55 | 
56 |
57 | ### Level: Intermediate
58 | Image manipulation with PIL
59 |
60 | 
61 |
62 | ### Level: Advanced
63 | 3D Viewer
64 |
65 | 
66 | 
67 |
68 |
69 |
70 | ### Bug Fixes
71 | This library is at **experimental stage**, so there must be some bugs which can appear randomly.
72 |
73 | **So, please report the bugs at the issues/discussions tab. A pull request is always welcomed :)**
74 |
--------------------------------------------------------------------------------
/examples/3DExample/ctk_spinbox.py:
--------------------------------------------------------------------------------
1 | # Author: Akash Bora (Akascape)
2 | # License: MIT
3 |
4 | import customtkinter
5 | import tkinter
6 | from typing import Union, Callable
7 |
8 | class CTkSpinbox(customtkinter.CTkFrame):
9 |
10 | def __init__(self, *args,
11 | width: int = 100,
12 | height: int = 32,
13 | number_of_steps: int = 1,
14 | from_: int = 0,
15 | to: int = 1000,
16 | value: int = None,
17 | button_color: str = None,
18 | button_width: int = 25,
19 | button_hover_color: str = None,
20 | entry_color: str = None,
21 | text_color: str = None,
22 | border_color: str = None,
23 | command: Callable = None,
24 | border_width: int = 0,
25 | corner_radius: int = 5,
26 | font = None,
27 | **kwargs):
28 |
29 | super().__init__(*args, height=height, **kwargs)
30 |
31 | self.step_size = number_of_steps
32 | self.max_value = to
33 | self.min_value = from_
34 | self.command = command
35 | self.validation = self.register(self.only_numbers)
36 | self.value = value
37 |
38 | self.grid_columnconfigure((0, 2), weight=0)
39 | self.grid_columnconfigure(1, weight=1)
40 |
41 | self.button_color = customtkinter.ThemeManager.theme["CTkButton"]["fg_color"] if button_color is None else button_color
42 | self.button_hover = customtkinter.ThemeManager.theme["CTkButton"]["hover_color"] if button_hover_color is None else button_hover_color
43 | self.entry_color = customtkinter.ThemeManager.theme["CTkEntry"]["fg_color"] if entry_color is None else entry_color
44 | self.border_color = customtkinter.ThemeManager.theme["CTkEntry"]["border_color"] if border_color is None else border_color
45 | self.text_color = customtkinter.ThemeManager.theme["CTkEntry"]["text_color"] if text_color is None else text_color
46 | self.border_width = border_width
47 | self.corner_radius = corner_radius
48 | self.button_width = button_width
49 | self.font = font
50 |
51 | super().configure(border_color=self.border_color)
52 |
53 | self.subtract_button = customtkinter.CTkButton(self, text="-", width=self.button_width, height=height-6, corner_radius=self.corner_radius,
54 | border_color=self.border_color, text_color=self.text_color, font=self.font,
55 | command=self.subtract_button_callback, fg_color=self.button_color,
56 | hover_color=self.button_hover, border_width=self.border_width)
57 | self.subtract_button.grid(row=0, column=0, padx=(3, 0), pady=3)
58 |
59 | self.text = tkinter.StringVar()
60 | self.text.set(self.min_value)
61 |
62 | self.entry = customtkinter.CTkEntry(self, width=width, height=height-6, textvariable=self.text, font=self.font,
63 | fg_color=self.entry_color, border_width=self.border_width+2,
64 | placeholder_text=str(self.min_value), justify="center", validate='key',
65 | border_color=self.border_color, corner_radius=self.corner_radius,
66 | validatecommand=(self.validation, '%P'), text_color=self.text_color)
67 | self.entry.grid(row=0, column=1, columnspan=1, padx=3, pady=3, sticky="ew")
68 |
69 | self.add_button = customtkinter.CTkButton(self, text="+", width=self.button_width, height=height-6, corner_radius=self.corner_radius,
70 | border_color=self.border_color, text_color=self.text_color, font=self.font,
71 | command=self.add_button_callback, fg_color=self.button_color,
72 | hover_color=self.button_hover, border_width=self.border_width)
73 | self.add_button.grid(row=0, column=2, padx=(0, 3), pady=3)
74 | self.entry.bind("", self.on_mouse_wheel)
75 |
76 | if self.value is None:
77 | self.set(self.min_value)
78 | else:
79 | self.set(self.value)
80 |
81 | def on_mouse_wheel(self, event):
82 | if event.delta > 0:
83 | self.add_button_callback()
84 | else:
85 | self.subtract_button_callback()
86 |
87 | def add_button_callback(self):
88 | if self.entry.get()=="":
89 | self.set(0)
90 | return
91 |
92 | self.set(int(self.entry.get()) + self.step_size)
93 |
94 | if self.command is not None:
95 | self.command()
96 |
97 | def subtract_button_callback(self):
98 | if self.entry.get()=="":
99 | self.set(0)
100 | return
101 |
102 | self.set(int(self.entry.get()) - self.step_size)
103 |
104 | if self.command is not None:
105 | self.command()
106 |
107 | def configure(self, **kwargs):
108 |
109 | if "state" in kwargs:
110 | if kwargs["state"]=="disabled":
111 | self.entry.unbind("")
112 | else:
113 | self.entry.bind("", self.on_mouse_wheel)
114 | super().configure(state=kwargs["state"])
115 |
116 | if "width" in kwargs:
117 | self.entry.configure(width=kwargs.pop("width"))
118 | if "fg_color" in kwargs:
119 | super().configure(fg_color=kwargs.pop("fg_color"))
120 | if "corner_radius" in kwargs:
121 | self.corner_radius = kwargs["corner_radius"]
122 | if "border_width" in kwargs:
123 | self.border_width = kwargs.pop("border_width")
124 | self.entry.configure(border_width=self.border_width+2)
125 | self.add_button.configure(border_width=self.border_width)
126 | self.subtract_button.configure(border_width=self.border_width)
127 | if "button_width" in kwargs:
128 | self.button_width = kwargs.pop("button_width")
129 | self.add_button.configure(width=self.button_width)
130 | self.subtract_button.configure(width=self.button_width)
131 | if "border_color" in kwargs:
132 | self.border_color = kwargs["border_color"]
133 | if "button_color" in kwargs:
134 | self.button_color = kwargs.pop("button_color")
135 | self.add_button.configure(fg_color=self.button_color)
136 | self.subtract_button.configure(fg_color=self.button_color)
137 | if "button_hover_color" in kwargs:
138 | self.button_hover = kwargs.pop("button_hover_color")
139 | self.add_button.configure(hover_color=self.button_hover)
140 | self.subtract_button.configure(hover_color=self.button_hover)
141 | if "entry_color" in kwargs:
142 | self.entry_color = kwargs.pop("entry_color")
143 | self.entry.configure(fg_color=self.entry_color)
144 | if "text_color" in kwargs:
145 | self.text_color = kwargs["text_color"]
146 | if "value" in kwargs:
147 | self.values = kwargs.pop("value")
148 | self.set(self.values)
149 | if "from_" in kwargs:
150 | self.min_value = kwargs.pop("from_")
151 | if int(self.value)self.max_value:
156 | self.set(self.max_value)
157 | if "number_of_steps" in kwargs:
158 | self.step_size = kwargs.pop("number_of_steps")
159 | if "command" in kwargs:
160 | self.command = kwargs.pop("command")
161 | if "font" in kwargs:
162 | self.font = kwargs["font"]
163 |
164 | self.entry.configure(**kwargs)
165 | self.add_button.configure(**kwargs)
166 | self.subtract_button.configure(**kwargs)
167 |
168 | def cget(self, param):
169 | if param=="width":
170 | return self.entry.winfo_width()
171 | if param=="height":
172 | return super().winfo_height()
173 | if param=="corner_radius":
174 | return self.corner_radius
175 | if param=="border_width":
176 | return self.border_width
177 | if param=="button_width":
178 | return self.button_width
179 | if param=="border_color":
180 | return self.border_color
181 | if param=="button_color":
182 | return self.button_color
183 | if param=="button_hover_color":
184 | return self.button_hover
185 | if param=="entry_color":
186 | return self.entry_color
187 | if param=="text_color":
188 | return self.text_color
189 | if param=="value":
190 | return int(self.entry.get())
191 | if param=="from_":
192 | return self.min_value
193 | if param=="to":
194 | return self.max_value
195 | if param=="number_of_steps":
196 | return self.step_size
197 | if param=="font":
198 | return self.font
199 | return super().cget(param)
200 |
201 | def only_numbers(self, char):
202 | if (char.isdigit() or (char=="")):
203 | if (len(str(self.max_value))-1)==str(self.max_value).count("0"):
204 | if (len(char)<=len(str(self.max_value))-1) or int(char)==self.max_value:
205 | return True
206 | else:
207 | return False
208 | else:
209 | if (len(char)<=len(str(self.max_value))):
210 | return True
211 | else:
212 | return False
213 | else:
214 | return False
215 |
216 | def get(self) -> Union[int, None]:
217 | if self.entry.get()=="":
218 | return 0
219 | try:
220 | return int(self.text.get())
221 | except ValueError:
222 | return None
223 |
224 | def set(self, value: int):
225 | if int(value)>self.max_value:
226 | self.set(self.max_value)
227 | return
228 | if int(value)self.d:
114 | self.d = distance
115 | self.d*=50
116 |
117 | def set_view_point(self, view_point = None, reference_point = None):
118 | if view_point == None and reference_point == None:
119 | self.a = m.cos(self.beeta) * m.sin(self.alpha)
120 | self.b = m.sin(self.beeta)
121 | self.c = m.cos(self.beeta) * m.cos(self.alpha)
122 |
123 | reference_point = self.rotate_zaxis((0,1,0), theeta = self.gaama)
124 | self.set_virtual_axis(reference_point)
125 | else:
126 | self.a, self.b, self.c = view_point
127 | self.set_virtual_axis(reference_point)
128 |
129 | def distinct_points(self):
130 | points = []
131 | for surface in self.coords:
132 | for polygon in surface:
133 | for point in polygon[:-1]:
134 | if point not in points:
135 | points.append(point)
136 | return points
137 |
138 | def set_canvas_size(self):
139 | self.csize = 0
140 | points = self.distinct_points()
141 | for point in points:
142 | distance = self.dist(point,(0,0,0))
143 | if distance>self.csize:
144 | self.csize = distance
145 | self.csize = int(self.csize*2*self.unit_pixels)+50
146 | #self.canvas.config(width = self.csize, height = self.csize)
147 |
148 | @staticmethod
149 | def plane_equation(point1, point2, point3):
150 | x1,y1,z1 = point1
151 | x2,y2,z2 = point2
152 | x3,y3,z3 = point3
153 |
154 | a = (y2-y1)*(z3-z1)-(y3-y1)*(z2-z1)
155 | b = (x3-x1)*(z2-z1)-(x2-x1)*(z3-z1)
156 | c = (x2-x1)*(y3-y1)-(x3-x1)*(y2-y1)
157 | d = a*x1 + b*y1 + c*z1
158 |
159 | return [a,b,c,d]
160 |
161 | def set_surface_equations(self):
162 | self.s_equations = []
163 | for surface in self.coords:
164 | point1 = surface[0][0]
165 | point2 = surface[0][1]
166 | point3 = surface[0][2]
167 |
168 | self.s_equations.append(self.plane_equation(point1,point2,point3))
169 |
170 | for polygon in surface:
171 | for point in polygon[:-1]:
172 | x,y,z = point
173 | a,b,c,d = self.s_equations[-1]
174 | if a*x + b*y + c*z != d:
175 | return 0
176 |
177 | return 1
178 |
179 | def display_list(self):
180 | l = []
181 |
182 | for equation in self.s_equations:
183 | A,B,C,D = equation
184 | x,y,z = self.d*self.a,self.d*self.b,self.d*self.c
185 | l.append(A*x + B*y + C*z >= D)
186 |
187 | return l
188 |
189 |
190 |
191 | def display_surfaces(self,coords):
192 | d_list = self.display_list()
193 | d_surface = []
194 |
195 | for i in range(len(d_list)):
196 | if d_list[i]:
197 | d_surface.append(coords[i])
198 |
199 | return d_surface
200 |
201 | def threeD_to_twoD(self):
202 | return_coords = []
203 | for surface in self.coords:
204 | return_surface = []
205 | for polygon in surface:
206 | return_polygon = []
207 | for point in polygon[:-1]:
208 | x,y,z = point
209 | a,b,c = self.a, self.b, self.c
210 |
211 | X = x*(b**2+c**2) - y*(a*b) - z*(a*c)
212 | Y = y*(a**2+c**2) - z*(b*c) - x*(a*b)
213 | Z = z*(a**2+b**2) - y*(b*c) - x*(a*c)
214 |
215 | lamda = m.sqrt(b**2+c**2)
216 | v = m.sqrt(a**2+b**2+c**2)
217 | if lamda == 0:
218 | lamdax = 1
219 | c=1
220 | else:
221 | lamdax = lamda
222 |
223 | X,Y,Z = self.rotate_xaxis((X,Y,Z), cos_val = c/lamdax, sin_val = b/lamdax)
224 | X,Y,Z = self.rotate_yaxis((X,Y,Z), cos_val = lamda/v, sin_val = -a/v)
225 |
226 | new_vxaxis = self.rotate_xaxis(self.vxaxis, cos_val = c/lamdax, sin_val = b/lamdax)
227 | new_vxaxis = self.rotate_yaxis(new_vxaxis, cos_val = lamda/v, sin_val = -a/v)
228 |
229 | new_referencepoint = self.rotate_xaxis(self.reference_point, cos_val = c/lamdax, sin_val = b/lamdax)
230 | new_referencepoint = self.rotate_yaxis(new_referencepoint, cos_val = lamda/v, sin_val = -a/v)
231 |
232 | if new_vxaxis[1]>=0 and new_referencepoint[1]>=0:
233 | gaama = m.asin(new_vxaxis[1])
234 | elif new_referencepoint[1]<=0:
235 | gaama = m.pi - m.asin(new_vxaxis[1])
236 | else:
237 | gaama = 2*m.pi + m.asin(new_vxaxis[1])
238 |
239 | X,Y,Z = self.rotate_zaxis((X,Y,Z),theeta = -gaama)
240 | X = X*self.unit_pixels + self.csize/2
241 | Y = self.csize/2 - Y*self.unit_pixels
242 | return_polygon.append((X,Y))
243 | return_polygon.append('#%02x%02x%02x' % polygon[-1])
244 |
245 | return_surface.append(return_polygon)
246 | return_coords.append(return_surface)
247 |
248 | return return_coords
249 |
250 | def delete_polygon(self):
251 | for polygon in self.printed_polygons:
252 | self.canvas.delete(polygon)
253 |
254 | self.printed_polygons = []
255 |
256 | def print_object(self , during_animation = 0):
257 | self.delete_polygon()
258 | twoD_coords = self.display_surfaces(self.threeD_to_twoD())
259 | self.dynamic_colors()
260 | for surface in twoD_coords:
261 | for polygon in surface:
262 | self.printed_polygons.append(self.canvas.create_polygon(polygon[:-1], fill = polygon[-1]))
263 | self.canvas.update()
264 |
265 | if during_animation:
266 | t.sleep(1/self.frame_rate)
267 |
268 | def change_angles(self, change_alpha, change_beeta, change_gaama):
269 | self.alpha += change_alpha
270 | self.beeta += change_beeta
271 | self.gaama += change_gaama
272 | self.set_view_point()
273 |
274 | def set_angles(self, alpha = None, beeta = None, gaama = None):
275 | if alpha == None and beeta == None and gaama ==None:
276 | pass
277 | else:
278 | self.alpha = alpha
279 | self.beeta = beeta
280 | self.gaama = gaama
281 | self.set_view_point()
282 |
283 | @staticmethod
284 | def rotate_xaxis(point, theeta = None, cos_val = None, sin_val = None):
285 | if cos_val == None:
286 | cos_val = m.cos(theeta)
287 | if sin_val == None:
288 | sin_val = m.sin(theeta)
289 |
290 | x,y,z = point
291 | Y = y*cos_val - z*sin_val
292 | Z = y*sin_val + z*cos_val
293 |
294 | return (x,Y,Z)
295 |
296 | @staticmethod
297 | def rotate_yaxis(point, theeta = None, cos_val = None, sin_val = None):
298 | if cos_val == None:
299 | cos_val = m.cos(theeta)
300 | if sin_val == None:
301 | sin_val = m.sin(theeta)
302 |
303 | x,y,z = point
304 | X = x*cos_val + z*sin_val
305 | Z = -x*sin_val + z*cos_val
306 |
307 | return (X,y,Z)
308 |
309 | @staticmethod
310 | def rotate_zaxis(point, theeta = None, cos_val = None, sin_val = None):
311 | if cos_val == None:
312 | cos_val = m.cos(theeta)
313 | if sin_val == None:
314 | sin_val = m.sin(theeta)
315 |
316 | x,y,z = point
317 | X = x*cos_val - y*sin_val
318 | Y = x*sin_val + y*cos_val
319 |
320 | return (X,Y,z)
321 |
322 | def rotate_point_about_line(self, point, angle, line_vector):
323 | a,b,c = line_vector
324 | lamda = m.sqrt(b**2+c**2)
325 | v = m.sqrt(a**2+b**2+c**2)
326 | if lamda == 0:
327 | lamdax = 1
328 | c=1
329 | else:
330 | lamdax = lamda
331 |
332 | p = self.rotate_xaxis(point, cos_val = c/lamdax, sin_val = b/lamdax)
333 | p = self.rotate_yaxis(p, cos_val = lamda/v, sin_val = -a/v)
334 | p = self.rotate_zaxis(p, theeta = angle)
335 | p = self.rotate_yaxis(p, cos_val = lamda/v, sin_val = a/v)
336 | p = self.rotate_xaxis(p, cos_val = c/lamdax, sin_val = -b/lamdax)
337 |
338 | return p
339 |
340 | def set_virtual_axis(self, reference_point = (0,1,0)):
341 | self.reference_point = reference_point
342 | x1,y1,z1 = reference_point
343 | x2,y2,z2 = self.a,self.b,self.c
344 | self.vxaxis = (y1*z2-y2*z1, x2*z1-x1*z2, x1*y2-x2*y1)
345 |
346 | def set_first_click(self, event):
347 | self.mouse_loc = (event.x, event.y)
348 |
349 | def change_view_angle(self, event):
350 | self.canvas.unbind('', self.move)
351 | x_diff = event.x - self.mouse_loc[0]
352 | y_diff = event.y - self.mouse_loc[1]
353 | const = m.pi/(self.unit_pixels*4)
354 | alpha_change = -x_diff * const
355 | beeta_change = y_diff * const
356 |
357 | new_viewpoint = self.rotate_point_about_line((self.a,self.b,self.c),alpha_change,self.reference_point)
358 | new_viewpoint = self.rotate_point_about_line(new_viewpoint,-beeta_change,self.vxaxis)
359 | new_referencepoint = self.rotate_point_about_line(self.reference_point,-beeta_change,self.vxaxis)
360 |
361 | self.set_view_point(new_viewpoint,new_referencepoint)
362 |
363 | self.print_object(1)
364 |
365 | self.mouse_loc = (event.x, event.y)
366 | self.move = self.canvas.bind('', self.change_view_angle)
367 |
368 | def dynamic_movement(self):
369 | self.start_move = self.canvas.bind('', self.set_first_click)
370 | self.move = self.canvas.bind('', self.change_view_angle)
371 |
372 | def stop_dynamic_movement(self):
373 | self.canvas.unbind('', self.start_move)
374 | self.canvas.unbind('',self.move)
375 |
376 | def change_color(self, colors):
377 | for i in range(len(self.coords)):
378 | for j in range(len(self.coords[i])):
379 | self.colors[i][j][-1] = colors[i][j]
380 |
381 | def set_color(self, colors = None):
382 | if colors == None:
383 | self.colors = []
384 | for surface in self.coords:
385 | s = []
386 | for polygon in surface:
387 | s.append(polygon[-1])
388 | self.colors.append(s)
389 | else:
390 | self.colors = colors
391 |
392 | def dynamic_colors(self):
393 |
394 | a1,b1,c1 = self.a,self.b,self.c
395 |
396 | for i in range(len(self.coords)):
397 | a2,b2,c2 = self.s_equations[i][0],self.s_equations[i][1],self.s_equations[i][2]
398 | d = self.dist((a2,b2,c2),(0,0,0))
399 | a2,b2,c2 = a2/d,b2/d,c2/d
400 |
401 | cos_angle = a1*a2+b1*b2+c1*c2
402 | if cos_angle>=0:
403 | for j in range(len(self.coords[i])):
404 | r,g,b = self.colors[i][j]
405 | r,g,b = r*cos_angle + r/3*(1-cos_angle),g*cos_angle + g/3*(1-cos_angle),b*cos_angle + b/3*(1-cos_angle)
406 | self.coords[i][j][-1] = (int(r),int(g),int(b))
407 |
408 |
--------------------------------------------------------------------------------
/examples/3DExample/requirements.txt:
--------------------------------------------------------------------------------
1 | sv-ttk
2 | customtkinter
3 | tknodesystem
--------------------------------------------------------------------------------
/examples/3DExample/threeDapp.py:
--------------------------------------------------------------------------------
1 | # Author: Akash Bora
2 | # License: MIT
3 |
4 | import customtkinter
5 | from tknodesystem import *
6 | from ctk_spinbox import CTkSpinbox
7 | from tkinter.colorchooser import askcolor
8 | from render_engine import ThreeDFrame
9 | import sv_ttk
10 | from tkinter import ttk, filedialog
11 | import math
12 |
13 | customtkinter.set_appearance_mode("Dark")
14 |
15 | def hex_to_rgb(value):
16 | value = value.lstrip('#')
17 | lv = len(value)
18 | return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
19 |
20 | def to_points(node, x):
21 | for i in x:
22 | if len(i)!=3:
23 | node.toggle(1)
24 | if type(i) is list:
25 | x[x.index(i)] = tuple(i)
26 | node.configure(text=f"SIDE \npoints: {len(x)}")
27 | return x, hex_to_rgb(node.node_color)
28 |
29 | def angle(v1, v2):
30 | dot = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
31 | mag1 = math.sqrt(v1[0] ** 2 + v1[1] ** 2 + v1[2] ** 2)
32 | mag2 = math.sqrt(v2[0] ** 2 + v2[1] ** 2 + v2[2] ** 2)
33 |
34 | return math.acos(dot / (mag1 * mag2))
35 |
36 | def sort_points(points):
37 | # sort angles based on the right hand rule
38 | origin = points[0]
39 | ref = (1, 0, 0)
40 | angles = []
41 | for point in points[1:]:
42 | vector = (point[0] - origin[0], point[1] - origin[1], point[2] - origin[2])
43 | theta = angle(vector, ref)
44 | angles.append((point, theta))
45 | angles.sort(key=lambda x: x[1])
46 | sorted_points = [origin]
47 | for i in angles:
48 | sorted_points.append(i[0])
49 | return sorted_points
50 |
51 | def join_coords(values):
52 |
53 | geo_data = []
54 | for i in values:
55 | geo_data.append(i[0])
56 |
57 | coords_data = []
58 | all_points = set()
59 |
60 | for side in geo_data:
61 | for points in side:
62 | all_points.add(points)
63 |
64 | num = 0
65 |
66 | coords_data.append(list(all_points))
67 | for side in geo_data:
68 |
69 | side = sort_points(side)
70 | anti_side = side[::-1]
71 |
72 | connections = []
73 |
74 | for i in side:
75 | connections.append(list(all_points).index(i))
76 |
77 | connections.append(values[num][1])
78 |
79 | coords_data.append([connections])
80 |
81 | connections = []
82 |
83 | for i in anti_side:
84 | connections.append(list(all_points).index(i))
85 |
86 | connections.append(values[num][1])
87 |
88 | coords_data.append([connections])
89 | num +=1
90 |
91 | for i in coords_data:
92 | if i==[]:
93 | return
94 |
95 | try:
96 | geo[notebook.index(notebook.select())+1].configure(coords=coords_data)
97 | except:
98 | pass
99 |
100 | def add_content(tab, tab_num):
101 | global dialog_box
102 |
103 | def open_color_dialog(node):
104 | global global_side_color
105 | color = askcolor(title="Choose Side Color")
106 | if color[1]:
107 | node.configure(fg_color=color[1], text_color="black")
108 | global_side_color = color[1]
109 | node.update()
110 |
111 | def open_config_dialog(node):
112 | global dialog_box
113 |
114 | if dialog_box:
115 | dialog_box.destroy()
116 |
117 | def configure_value_node():
118 | value = (x.get(), y.get(), z.get())
119 | node.configure(text=str(value), value=value)
120 |
121 | dialog_box = customtkinter.CTkToplevel(root)
122 | dialog_box.resizable(False, False)
123 | dialog_box.transient(root)
124 | dialog_box.attributes("-alpha", 0.9)
125 | dialog_box.title("Configure XYZ")
126 |
127 | customtkinter.CTkLabel(dialog_box, text="X Coordinate").pack(padx=5, pady=10)
128 | x = CTkSpinbox(dialog_box, from_=-20, to=20, command=configure_value_node)
129 | x.pack(expand=True, fill="x", padx=5)
130 | x.set(node.get()[0])
131 |
132 | customtkinter.CTkLabel(dialog_box, text="Y Coordinate").pack(padx=5, pady=10)
133 | y = CTkSpinbox(dialog_box, from_=-20, to=20, command=configure_value_node)
134 | y.pack(expand=True, fill="x", padx=5)
135 | y.set(node.get()[1])
136 |
137 | customtkinter.CTkLabel(dialog_box, text="Z Coordinate").pack(padx=5, pady=10)
138 | z = CTkSpinbox(dialog_box, from_=-20, to=20, command=configure_value_node)
139 | z.pack(expand=True, fill="x", padx=5, pady=(0,10))
140 | z.set(node.get()[2])
141 |
142 | spawn_x = int(root.winfo_width() * .5 + root.winfo_x() - .5 * dialog_box.winfo_width() + 7)
143 | spawn_y = int(root.winfo_height() * .5 + root.winfo_y() - .5 * dialog_box.winfo_height() + 20)
144 | dialog_box.geometry(f"+{spawn_x}+{spawn_y}")
145 |
146 | def add_point():
147 | point_node = NodeValue(canvas, text=f"(0, 0, 0)", value=(0,0,0))
148 | point_node.bind("", lambda e: open_config_dialog(point_node))
149 |
150 | def add_side():
151 | side_node = NodeOperation(canvas, text=f"SIDE", inputs=1, multiple_connection=True,
152 | command=to_points, pass_node_id=True)
153 | side_node.bind("", lambda e: open_color_dialog(side_node))
154 | if global_side_color:
155 | side_node.configure(fg_color=global_side_color, text_color="black")
156 |
157 | def load_canvas():
158 | if open_file := filedialog.askopenfilename(filetypes=[
159 | ("JSON", ["*.json"]),
160 | ("All Files", "*.*")]):
161 | canvas.load(open_file)
162 |
163 | for i in canvas.node_list:
164 | if type(i) is NodeOperation:
165 | i.bind("", lambda e, n=i: open_color_dialog(n))
166 | elif type(i) is NodeValue:
167 | i.bind("", lambda e, n=i: open_config_dialog(n))
168 |
169 | def save_canvas():
170 | save_file = filedialog.asksaveasfilename(defaultextension=".json",
171 | filetypes=[('json', ['*.json']),('All Files', '*.*')])
172 | if save_file:
173 | canvas.save(save_file)
174 |
175 | frame_left = customtkinter.CTkFrame(tab)
176 | frame_left.pack(expand=True, fill="both", padx=10, pady=10, side="left")
177 |
178 | frame_right = customtkinter.CTkFrame(tab, width=500)
179 | frame_right.pack(fill="y", padx=(0,10), pady=10, side="right")
180 |
181 | geo[tab_num] = ThreeDFrame(frame_right, bg=frame_right.cget("fg_color")[1],
182 | coords=default_pyramid)
183 | geo[tab_num].pack(fill="both", expand=True)
184 |
185 | label = customtkinter.CTkLabel(frame_right, text="3D Viewer")
186 | label.pack(fill="x", pady=0)
187 |
188 | scale = customtkinter.CTkSlider(frame_right,
189 | from_=0,
190 | to=100,
191 | command=lambda e: geo[tab_num].configure(size=int(e)))
192 | scale.pack(padx=5, fill="x")
193 | scale.set(100)
194 |
195 | open_button = customtkinter.CTkButton(frame_right, text="OPEN", width=200, command=load_canvas)
196 | open_button.pack(fill="x", padx=(10,5), pady=10, side="left")
197 |
198 | save_button = customtkinter.CTkButton(frame_right, text="SAVE", width=200, command=save_canvas)
199 | save_button.pack(fill="x", padx=(5,10), pady=10, side="left")
200 |
201 | canvas = NodeCanvas(frame_left, bg=frame_left._fg_color[1], width=800, height=500)
202 | canvas.pack(expand=True, fill="both", padx=5, pady=5)
203 |
204 | comp = NodeCompile(canvas, fixed=True, text="Render",
205 | multiple_connection=True, show_value=False,
206 | command=join_coords)
207 |
208 | canvas.rowconfigure(0, weight=1)
209 |
210 | button_1 = customtkinter.CTkButton(canvas, text="Add Point", width=80, command=add_point)
211 | button_1.grid(pady=10, padx=10, sticky="se")
212 |
213 | button_2 = customtkinter.CTkButton(canvas, text="Add Side", width=80, command=add_side)
214 | button_2.grid(pady=10, padx=10, sticky="se")
215 |
216 | def add_tab():
217 | global tab_num
218 | if tab_num>9:
219 | add_tab_button.configure(state="disabled")
220 | return
221 | tab_num +=1
222 | tab = ttk.Frame(notebook, takefocus=0)
223 | notebook.add(tab, text=f"Tab {tab_num}")
224 | add_content(tab, tab_num)
225 |
226 | tab_num = 1
227 | root = customtkinter.CTk()
228 | root.title("3D Geometry Viewer")
229 | root.geometry("1200x550")
230 | customtkinter.deactivate_automatic_dpi_awareness()
231 | root.resizable(False, False)
232 |
233 | geo = {}
234 | dialog_box = None
235 | global_side_color = "#ffffff"
236 | notebook = ttk.Notebook(root, takefocus=False)
237 | notebook.pack(expand=True, fill="both")
238 |
239 | default_pyramid = [[(0, 1, 0), (-1, -1, -1), (1, -1, 1), (-1, -1, 1), (1, -1, -1)],
240 | [[2, 4, 1, 3, (255, 255, 255)]], [[3, 1, 4, 2, (255, 255, 255)]],
241 | [[2, 0, 3, (255, 255, 255)]], [[3, 0, 2, (255, 255, 255)]],
242 | [[2, 4, 0, (255, 255, 255)]], [[0, 4, 2, (255, 255, 255)]],
243 | [[1, 4, 0, (255, 255, 255)]], [[0, 4, 1, (255, 255, 255)]],
244 | [[3, 0, 1, (255, 255, 255)]], [[1, 0, 3, (255, 255, 255)]]]
245 |
246 | tab_1 = ttk.Frame(notebook, takefocus=0)
247 | notebook.add(tab_1, text=f"Tab 1")
248 |
249 | add_content(tab_1, 1)
250 |
251 | style = ttk.Style()
252 |
253 | style.layout("Tab", [('Notebook.tab', {'sticky': 'nswe', 'children':
254 | [('Notebook.padding', {'side': 'top', 'sticky': 'nswe', 'children':
255 | [('Notebook.label', {'side': 'top', 'sticky': ''})],
256 | })],
257 | })]
258 | )
259 |
260 | add_tab_button = customtkinter.CTkButton(notebook, width=30, text="+",
261 | bg_color="#2f2f2f", command=add_tab)
262 | add_tab_button.pack(anchor="ne", pady=5, padx=10)
263 | sv_ttk.set_theme("dark")
264 | root.mainloop()
265 |
--------------------------------------------------------------------------------
/examples/advanced_example.py:
--------------------------------------------------------------------------------
1 | # Advanced Example of TkNodeSystem
2 | # Author: Akash Bora
3 | # License: MIT
4 |
5 | from PIL import ImageEnhance, Image # pip install pillow
6 | import customtkinter
7 | from tknodesystem import *
8 | import tkinter
9 |
10 | root = customtkinter.CTk()
11 | root.title("Image Enhancement")
12 |
13 | frame_left = customtkinter.CTkFrame(root)
14 | frame_left.pack(expand=True, fill="both", padx=10, pady=10, side="left")
15 |
16 | frame_right = customtkinter.CTkFrame(root)
17 | frame_right.pack(expand=True, fill="both", padx=10, pady=10, side="right")
18 |
19 | image_label = customtkinter.CTkLabel(frame_right, corner_radius=20, width=600, height=400, text="")
20 | image_label.pack(expand=True, fill="both", padx=5, pady=10)
21 |
22 | label = customtkinter.CTkLabel(frame_right, text="Image Viewer")
23 | label.pack(expand=True, fill="both", padx=5, pady=0)
24 |
25 | canvas = NodeCanvas(frame_left, bg=frame_left._fg_color[1], width=800, height=500)
26 | canvas.pack(expand=True, fill="both", padx=5, pady=5)
27 |
28 | sliders = {}
29 | x = 1
30 | img = None
31 |
32 | def show_current_slider(node, num):
33 | """ command when a node is clicked, places only the required slider """
34 | for i in sliders.values():
35 | i.pack_forget()
36 |
37 | sliders[num].pack(expand=True, fill="x", padx=20, pady=5, side="bottom")
38 | label.configure(text=node.text)
39 |
40 | def add_slider(num, node):
41 | """ add some sliders when a new node is placed """
42 | def update(e):
43 | node.update()
44 | label.configure(text=f"{node.text}: {round(e,2)}")
45 |
46 | for i in sliders.values():
47 | i.pack_forget()
48 |
49 | sliders[num] = customtkinter.CTkSlider(frame_right, from_=0, to=10, command=lambda e: update(e))
50 | sliders[num].pack(expand=True, fill="x", padx=20, pady=5, side="bottom")
51 | sliders[num].set(1)
52 |
53 | def input_media():
54 | """ input node which contains a file data """
55 | file = tkinter.filedialog.askopenfilename(filetypes =[('Images', ['*.png','*.jpg','*.jpeg','*.bmp','*webp']),('All Files', '*.*')])
56 | if file:
57 | NodeValue(canvas, value=Image.open(file), text="MediaIn")
58 |
59 | def add_effect(effect):
60 | """ node operations """
61 | global x
62 | def image_modify(img, value):
63 | try:
64 | if effect=="Brightness":
65 | mode = ImageEnhance.Brightness(img)
66 | elif effect=="Contrast":
67 | mode = ImageEnhance.Contrast(img)
68 | elif effect=="Sharpness":
69 | mode = ImageEnhance.Sharpness(img)
70 | elif effect=="Color":
71 | mode = ImageEnhance.Color(img)
72 |
73 | return mode.enhance(sliders[value].get())
74 | except AttributeError: None
75 |
76 | value = x
77 | node = NodeOperation(canvas, inputs=1, text=effect, command=lambda img: image_modify(img, value),
78 | click_command=lambda: show_current_slider(node, value))
79 | label.configure(text=node.text)
80 | add_slider(value, node)
81 | x += 1
82 |
83 | def show_image(output):
84 | """ compile operation: shows the output image """
85 |
86 | global img
87 | label_height = frame_right.winfo_height()
88 | ratio = output.size[1]/output.size[0]
89 | if ratio>1.5: ratio = 1.5
90 | if ratio<0.5: ratio = 0.5
91 | img = customtkinter.CTkImage(output, size=(label_height, label_height*ratio))
92 | image_label.configure(image=img)
93 |
94 | menu = NodeMenu(canvas) # right click or press to spawn the node menu
95 | menu.add_node(label="Media Import", command=input_media)
96 | menu.add_node(label="Media Out", command=lambda: NodeCompile(canvas, text="MediaOut", show_value=None, command=show_image))
97 | menu.add_node(label="Brightness", command=lambda: add_effect("Brightness"))
98 | menu.add_node(label="Contrast", command=lambda: add_effect("Contrast"))
99 | menu.add_node(label="Color", command=lambda: add_effect("Color"))
100 | menu.add_node(label="Sharpness", command=lambda: add_effect("Sharpness"))
101 |
102 | root.mainloop()
103 |
--------------------------------------------------------------------------------
/examples/maths_with_tknodes.py:
--------------------------------------------------------------------------------
1 | # Example of TkNodeSystem
2 | # Author: Akash Bora
3 | # License: MIT
4 |
5 | import customtkinter
6 | from tknodesystem import *
7 |
8 | root = customtkinter.CTk()
9 | root.geometry("800x500")
10 | root.title("Maths with TkNodes")
11 |
12 | canvas = NodeCanvas(root)
13 | canvas.pack(fill="both", expand=True)
14 |
15 | canvas.rowconfigure(0, weight=1)
16 | button_1 = customtkinter.CTkButton(canvas, text="save", width=50, command=lambda: canvas.save("canvas.json"))
17 | button_1.grid(pady=10, padx=10, sticky="se")
18 |
19 | button_2 = customtkinter.CTkButton(canvas, text="load", width=50, command=lambda: canvas.load("canvas.json"))
20 | button_2.grid(pady=10, padx=10, sticky="se")
21 |
22 | def add_value():
23 | dialog = customtkinter.CTkInputDialog(text="Type in a number:", title="Value Node")
24 | text = dialog.get_input()
25 | if text is not None:
26 | if text.isdigit():
27 | NodeValue(canvas, text=f"Value {text}", value=int(text))
28 |
29 | def add(x,y):
30 | return x+y
31 |
32 | def power(x,y):
33 | return x**y
34 |
35 | def sub(x,y):
36 | return x-y
37 |
38 | def div(x,y):
39 | if y!=0:
40 | return x/y
41 | else:
42 | return None
43 |
44 | def mul(x,y):
45 | return x*y
46 |
47 | def mod(x):
48 | return abs(x)
49 |
50 | menu = NodeMenu(canvas) # right click or press to spawn the node menu
51 | menu.add_node(label="Value", command=add_value)
52 | menu.add_node(label="Output", command=lambda: NodeCompile(canvas))
53 | menu.add_node(label="Add/Sum", command=lambda: NodeOperation(canvas, text="Add", command=add))
54 | menu.add_node(label="Subtract", command=lambda: NodeOperation(canvas, text="Sub", command=sub))
55 | menu.add_node(label="Divide", command=lambda: NodeOperation(canvas, text="Div", command=div))
56 | menu.add_node(label="Mod", command=lambda: NodeOperation(canvas, inputs=1, text="Mod", command=mod))
57 | menu.add_node(label="Multiply", command=lambda: NodeOperation(canvas, text="Mul", command=mul))
58 | menu.add_node(label="Power", command=lambda: NodeOperation(canvas, text="Power", command=power))
59 | root.mainloop()
60 |
--------------------------------------------------------------------------------
/examples/simple_example.py:
--------------------------------------------------------------------------------
1 | from tknodesystem import *
2 | import tkinter
3 |
4 | root = tkinter.Tk()
5 |
6 | # Node Canvas
7 | canvas = NodeCanvas(root)
8 | canvas.pack(fill="both", expand=True)
9 |
10 | # Node Types
11 | NodeValue(canvas, x=0, y=10)
12 | NodeOperation(canvas, x=150, y=1)
13 | NodeCompile(canvas, x=300, y=10)
14 |
15 | # Node Menu
16 | menu = NodeMenu(canvas) # right click or press to spawn the node menu
17 | menu.add_node(label="NodeValue", command=lambda: NodeValue(canvas))
18 | menu.add_node(label="NodeOperation", command=lambda: NodeOperation(canvas))
19 | menu.add_node(label="NodeCompile", command=lambda: NodeCompile(canvas))
20 |
21 | root.mainloop()
22 |
--------------------------------------------------------------------------------
/tknodesystem/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Node System in Tkinter
3 | Author: Akash Bora
4 | Homepage: https://github.com/Akascape/TkNodeSystem
5 | License: MIT
6 | """
7 |
8 | __version__ = '0.9'
9 |
10 | from .node_types import NodeValue, NodeOperation, NodeCompile
11 | from .node_canvas import NodeCanvas
12 | from .node_menu import NodeMenu
13 |
--------------------------------------------------------------------------------
/tknodesystem/grid_images/grid_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akascape/TkNodeSystem/a9e9c587c47facda46bd4c06bfc3b2db1fe74606/tknodesystem/grid_images/grid_dot.png
--------------------------------------------------------------------------------
/tknodesystem/grid_images/grid_lines.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akascape/TkNodeSystem/a9e9c587c47facda46bd4c06bfc3b2db1fe74606/tknodesystem/grid_images/grid_lines.png
--------------------------------------------------------------------------------
/tknodesystem/grid_images/no_grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akascape/TkNodeSystem/a9e9c587c47facda46bd4c06bfc3b2db1fe74606/tknodesystem/grid_images/no_grid.png
--------------------------------------------------------------------------------
/tknodesystem/node.py:
--------------------------------------------------------------------------------
1 | class Node:
2 | def __init__(self, canvas, width=50, height=50, border_color='white', border_width=0, justify="center",
3 | fg_color='#37373D', center=(100,50), text='', text_color='white', corner_radius=25,
4 | font=("",10), click_command=None, highlightcolor='#52d66c', hover=True):
5 |
6 | self.canvas = canvas
7 | self.width = width
8 | self.height = height
9 | self.node_outline_color = border_color
10 | self.node_outline_thickness = border_width
11 | self.node_color = fg_color
12 | self.text_color = text_color
13 | self.corner_radius = corner_radius
14 | self.font = font
15 | self.justify = justify
16 | self.text = text
17 | self.center = center
18 | self.click_command = click_command
19 | self.auxlist = []
20 | self.signal = False
21 | self.hover = hover
22 | self.hover_color = highlightcolor
23 | self.create()
24 | self.canvas.node_list.add(self)
25 |
26 | def create(self):
27 | """ create round rectangular frame with text """
28 |
29 | self.ID = self.create_round_rectangle(self.center[0]-self.width*0.5, self.center[1]-self.height*0.5,
30 | self.center[0]+self.width*0.5, self.center[1]+self.height*0.5,
31 | radius=self.corner_radius, outline=self.node_outline_color, fill=self.node_color,
32 | width=self.node_outline_thickness)
33 |
34 | self.IDtext = self.canvas.create_text(self.center, fill=self.text_color, justify=self.justify, font=self.font, text=self.text)
35 | self.allIDs = [self.ID, self.IDtext]
36 | self.auxlist = [self.ID, self.IDtext]
37 |
38 | for i in self.auxlist:
39 | self.canvas.tag_bind(i, "", self.getpos, add="+")
40 | self.canvas.tag_bind(i, '', self.enter_node)
41 | self.canvas.tag_bind(i, '', self.leave_node)
42 |
43 | def create_round_rectangle(self, x1, y1, x2, y2, radius=25, **kwargs):
44 | points = [x1+radius, y1, x1+radius, y1, x2-radius, y1, x2-radius, y1, x2, y1, x2,
45 | y1+radius, x2, y1+radius, x2, y2-radius, x2, y2-radius, x2, y2,
46 | x2-radius, y2, x2-radius, y2, x1+radius, y2, x1+radius, y2, x1, y2, x1,
47 | y2-radius, x1, y2-radius, x1, y1+radius, x1, y1+radius, x1, y1]
48 |
49 | return self.canvas.create_polygon(points, **kwargs, smooth=True)
50 |
51 | def getpos(self, event):
52 | """ bind click command and raise the node"""
53 | self.xy_set = (event.x, event.y)
54 |
55 | if self.click_command: self.click_command()
56 |
57 | for id_ in self.allIDs:
58 | self.canvas.tag_raise(id_)
59 |
60 | def bind_all_to_movement(self):
61 | """ bind the node items to motion """
62 |
63 | for id_ in self.auxlist:
64 | self.canvas.tag_bind(id_, '', self.mouse_mov)
65 |
66 | def mouse_mov(self, event):
67 | """ place the node items based one the motion of mouse """
68 |
69 | for id_ in self.allIDs:
70 | self.canvas.move(id_, event.x-self.xy_set[0], event.y-self.xy_set[1])
71 | self.canvas.tag_raise(id_)
72 |
73 | self.xy_set = (event.x, event.y)
74 |
75 | self.update_sockets()
76 |
77 | def update_sockets(self):
78 | """ update the coordinates of sockets and lines """
79 | self.output_.update()
80 | try:
81 | self.input_1.update()
82 | self.input_2.update()
83 | self.input_3.update()
84 | self.input_4.update()
85 | self.input_5.update()
86 | except AttributeError: None
87 |
88 | for i in self.canvas.line_ids:
89 | i.update()
90 |
91 | deleted = set()
92 | for i in self.canvas.line_ids:
93 | if not i.connected:
94 | deleted.add(i)
95 | self.canvas.line_ids = self.canvas.line_ids.difference(deleted)
96 |
97 | def move(self, x, y):
98 | for id_ in self.allIDs:
99 | self.canvas.move(id_, x, y)
100 | self.canvas.tag_raise(id_)
101 | self.update_sockets()
102 |
103 | def enter_node(self, event):
104 | if self.hover:
105 | self.canvas.itemconfigure(self.ID, outline=self.hover_color, width=self.node_outline_thickness+1)
106 | self.signal = True
107 |
108 | def leave_node(self, event):
109 | if self.hover:
110 | self.canvas.itemconfigure(self.ID, outline=self.node_outline_color, width=self.node_outline_thickness)
111 | self.signal = False
112 |
113 | def destroy(self):
114 | self.canvas.delete(self.ID, self.IDtext)
115 | self.canvas.node_list.remove(self)
116 | for i in self.canvas.line_ids:
117 | i.update()
118 |
119 | def bind(self, binding, command, add="+"):
120 | for i in self.auxlist:
121 | self.canvas.tag_bind(i, binding, command, add)
122 |
123 | def configure(self, **kwargs):
124 | """ configure options """
125 |
126 | if "fg_color" in kwargs:
127 | self.node_color = kwargs.pop("fg_color")
128 | self.canvas.itemconfig(self.ID, fill=self.node_color)
129 | if "highlightcolor" in kwargs:
130 | self.hover_color = kwargs.pop("highlightcolor")
131 | if "hover" in kwargs:
132 | self.hover = kwargs.pop("hover")
133 | if "text" in kwargs:
134 | self.text = kwargs.pop("text")
135 | self.canvas.itemconfig(self.IDtext, text=self.text)
136 | if "text_color" in kwargs:
137 | self.text_color = kwargs.pop("text_color")
138 | self.canvas.itemconfig(self.IDtext, fill=self.text_color)
139 | if "font" in kwargs:
140 | self.font = kwargs.pop("font")
141 | self.canvas.itemconfig(self.IDtext, font=self.font)
142 | if len(kwargs)>0:
143 | raise ValueError("This option is not configurable:" + list(kwargs.keys())[0])
144 |
--------------------------------------------------------------------------------
/tknodesystem/node_args.py:
--------------------------------------------------------------------------------
1 | class Args():
2 | """ This class is used to remove default arguments in order to reduce the size of the export file """
3 |
4 | def value_args(args):
5 | default_args = {'width': 100, 'height': 50, 'value': 0, 'border_color': 'white', 'text': None,
6 | 'corner_radius': 25, 'border_width': 0, 'fg_color': '#37373D', 'text_color': 'white',
7 | 'font': ('', 10), 'socket_radius': 8, 'socket_hover': True, 'socket_color': 'green',
8 | 'socket_hover_color': 'grey50', 'highlightcolor': '#52d66c', 'hover': True, 'fixed': False,
9 | 'click_command': None, 'side': 'right', 'x': None, 'y': None, 'num': None, 'justify': 'center'}
10 |
11 | args.pop("canvas")
12 | args.pop("self")
13 | args.pop("__class__")
14 | args.pop("x")
15 | args.pop("y")
16 | args.pop("click_command")
17 | args.pop("num")
18 | new_args = {}
19 |
20 | for i in args.keys():
21 | if default_args.get(i)!=args.get(i):
22 | new_args.update({i:args.get(i)})
23 |
24 | return new_args
25 |
26 | def func_args(args):
27 | default_args = {'width': 100, 'height': 80, 'inputs': 2, 'border_color': '#37373D', 'text': None, 'socket_radius': 8,
28 | 'corner_radius': 25, 'border_width': 0, 'fg_color': '#37373D', 'text_color': 'white', 'font': ('', 10),
29 | 'highlightcolor': '#52d66c', 'hover': True, 'socket_color': 'green', 'socket_hover_color': 'grey50', 'pass_node_id': False,
30 | 'x': None, 'y': None, 'multiside': False, 'output_socket_color': 'green', 'click_command': None, 'fixed': False,
31 | 'socket_hover': True, 'num': None, 'none_inputs': False, 'justify': 'center', 'hover_text': None, 'multiple_connection': False}
32 |
33 | args.pop("canvas")
34 | args.pop("self")
35 | args.pop("__class__")
36 | args.pop("command")
37 | args.pop("x")
38 | args.pop("y")
39 | args.pop("click_command")
40 | args.pop("num")
41 | new_args = {}
42 |
43 | for i in args.keys():
44 | if default_args.get(i)!=args.get(i):
45 | new_args.update({i:args.get(i)})
46 |
47 | return new_args
48 |
49 | def compile_args(args):
50 | default_args = {'width': 100, 'height': 50, 'border_color': '#37373D', 'text': 'Compile', 'socket_radius': 8, 'justify': 'center', 'pass_node_id': False,
51 | 'corner_radius': 25, 'x': None, 'y': None, 'border_width': 0, 'fg_color': '#37373D', 'text_color': 'white', 'multiple_connection': False,
52 | 'font': ('', 10), 'highlightcolor': '#52d66c', 'hover': True, 'socket_hover': True, 'socket_color': 'green', 'fixed': False,
53 | 'socket_hover_color': 'grey50', 'show_value': True, 'command': None, 'click_command': None, 'side': 'left', 'num': None}
54 |
55 | args.pop("canvas")
56 | args.pop("self")
57 | args.pop("__class__")
58 | args.pop("command")
59 | args.pop("x")
60 | args.pop("y")
61 | args.pop("click_command")
62 | args.pop("num")
63 | new_args = {}
64 |
65 | for i in args.keys():
66 | if default_args.get(i)!=args.get(i):
67 | new_args.update({i:args.get(i)})
68 |
69 | return new_args
70 |
--------------------------------------------------------------------------------
/tknodesystem/node_canvas.py:
--------------------------------------------------------------------------------
1 | import tkinter
2 | import platform
3 | import os
4 | import json
5 | import sys
6 | from .node_wire import NodeWire
7 | from .node_types import NodeValue, NodeOperation, NodeCompile
8 |
9 | class NodeCanvas(tkinter.Canvas):
10 | def __init__(self, master, bg="grey10", width=500, height=500, wire_color="white", wire_width=3,
11 | grid_image="lines", zoom=True, wire_dash=True, move=True, wire_hover_color="red", **kwargs):
12 |
13 | super().__init__(master, bg=bg, width=width, height=height, bd=0, highlightthickness=0, **kwargs)
14 |
15 | self.wire_color = wire_color
16 | self.wire_width = wire_width
17 | self.wire_hover_color = wire_hover_color
18 | self.dash = wire_dash
19 | self.bg = bg
20 | self.inputcell = None
21 | self.outputcell = None
22 | self.clickcount = 0
23 | self.IDc = None
24 | self.operation_num = 0
25 | self.input_num = 0
26 | self.compile_num = 0
27 | self.socket_num = 0
28 | self.connect_wire = True
29 | self.line_list = set()
30 | self.obj_list = set()
31 | self.line_ids = set()
32 | self.node_list = set()
33 | self.gain_in = 1
34 | self.gain_out = 1
35 | self.set_grid_image(grid_image)
36 |
37 | if move:
38 | if sys.platform.startswith("darwin"):
39 | self.tag_bind(self.grid_bg, '', lambda e: self.getpos(e, 1))
40 | self.tag_bind(self.grid_bg, '', lambda e: self.getpos(e, 0))
41 | self.tag_bind(self.grid_bg, "", self.move_grid)
42 | else:
43 | self.tag_bind(self.grid_bg, '', lambda e: self.getpos(e, 1))
44 | self.tag_bind(self.grid_bg, '', lambda e: self.getpos(e, 0))
45 | self.tag_bind(self.grid_bg, "", self.move_grid)
46 |
47 | if zoom:
48 | self.bind("", self.do_zoom)
49 | self.tag_bind(self.grid_bg, "", lambda e: self.do_zoom(e, 120))
50 | self.tag_bind(self.grid_bg, "", lambda e: self.do_zoom(e, -120))
51 |
52 | def set_grid_image(self, grid_image):
53 | """ set the grid image for the canvas """
54 |
55 | # default grids: ['dots', 'lines', None]
56 | base_path = os.path.dirname(os.path.abspath(__file__))
57 | if grid_image=="dots":
58 | self.image = tkinter.PhotoImage(file=os.path.join(base_path, "grid_images", "grid_dot.png"))
59 | elif grid_image=="lines":
60 | self.image = tkinter.PhotoImage(file=os.path.join(base_path, "grid_images", "grid_lines.png"))
61 | elif not grid_image:
62 | self.image = tkinter.PhotoImage(file=os.path.join(base_path, "grid_images", "no_grid.png"))
63 | else:
64 | self.image = tkinter.PhotoImage(file=grid_image)
65 |
66 | self.image = self.image.subsample(1,1)
67 | try:
68 | self.delete(self.grid_bg)
69 | except: None
70 | self.grid_bg = self.create_image(self.image.width()/2, self.image.height()/2, image = self.image)
71 | self.tag_lower(self.grid_bg)
72 |
73 | def getpos(self, event, cursor):
74 | """ get the mouse position and change cursor style """
75 |
76 | self.xy_set = (event.x, event.y)
77 |
78 | if cursor:
79 | self.config(cursor="fleur", width=self.winfo_reqwidth(), height=self.winfo_reqheight())
80 | else:
81 | self.config(cursor="arrow", width=self.winfo_reqwidth(), height=self.winfo_reqheight())
82 |
83 | def move_grid(self, event):
84 | """ move the contents of the canvas except the grid image """
85 |
86 | self.all_items = list(self.find_all())
87 | self.all_items.pop(self.all_items.index(self.grid_bg))
88 |
89 | for i in self.all_items:
90 | self.move(i, event.x-self.xy_set[0], event.y-self.xy_set[1])
91 | self.xy_set = (event.x, event.y)
92 |
93 | for i in self.node_list:
94 | i.update_sockets()
95 |
96 | def do_zoom(self, event, delta=None):
97 | """ zoom in/out the canvas by changing the coordinates of all canvas items """
98 |
99 | self.all_items = list(self.find_all())
100 | self.all_items.pop(self.all_items.index(self.grid_bg))
101 |
102 | if not delta:
103 | delta = event.delta
104 |
105 | if delta>0:
106 | for i in self.all_items:
107 | self.scale(i, event.x, event.y, 1.1, 1.1)
108 | self.gain_in +=1
109 | else:
110 | for i in self.all_items:
111 | self.scale(i, event.x, event.y, 0.9, 0.9)
112 | self.gain_out +=1
113 |
114 | for i in self.node_list:
115 | i.update_sockets()
116 |
117 | def conectcells(self):
118 | """ connection data for the inputs of any operation node """
119 |
120 | if self.IDc == 'input1': self.inputcell.cellinput1 = self.outputcell
121 | if self.IDc == 'input2': self.inputcell.cellinput2 = self.outputcell
122 | if self.IDc == 'input3': self.inputcell.cellinput3 = self.outputcell
123 | if self.IDc == 'input4': self.inputcell.cellinput4 = self.outputcell
124 | if self.IDc == 'input5': self.inputcell.cellinput5 = self.outputcell
125 |
126 | if self.inputcell is None or self.outputcell is None:
127 | return
128 | if self.inputcell.ID!=self.outputcell.ID:
129 | self.line = NodeWire(self, self.outputcell, self.inputcell, wire_color=self.wire_color,
130 | wire_width=self.wire_width, dash=self.dash, wire_hover_color=self.wire_hover_color)
131 | self.inputcell.update()
132 |
133 | def clear(self):
134 | """ clear the canvas except the grid image """
135 |
136 | self.all_items = list(self.find_all())
137 | self.all_items.pop(self.all_items.index(self.grid_bg))
138 |
139 | for i in self.all_items:
140 | self.delete(i)
141 |
142 | self.node_num = 0
143 | self.input_num = 0
144 | self.compile_num = 0
145 | self.socket_num = 0
146 | self.line_list = set()
147 | self.obj_list = set()
148 | self.line_ids = set()
149 | self.node_list = set()
150 |
151 | def configure(self, **kwargs):
152 | """ configure options """
153 |
154 | if "wire_color" in kwargs:
155 | self.wire_color = kwargs.pop("wire_color")
156 | for i in self.line_ids:
157 | i.configure(wire_color=self.wire_color)
158 | if "wire_width" in kwargs:
159 | self.wire_width = kwargs.pop("wire_width")
160 | for i in self.line_ids:
161 | i.configure(wire_width=self.wire_width)
162 | if "wire_dash" in kwargs:
163 | self.dash = kwargs.pop("wire_dash")
164 | for i in self.line_ids:
165 | i.configure(dash=self.dash)
166 | if "wire_hover_color" in kwargs:
167 | self.wire_hover_color = kwargs.pop("wire_hover_color")
168 | for i in self.line_ids:
169 | i.configure(wire_hover_color=self.wire_hover_color)
170 | if "grid_image" in kwargs:
171 | self.set_grid_image(kwargs.pop("grid_image"))
172 |
173 | super().config(**kwargs)
174 |
175 | def save(self, filename):
176 | """ save the node tree to a file """
177 | if os.path.exists(filename):
178 | os.remove(filename)
179 |
180 | x = []
181 | sorted_obj_list = []
182 | for i in self.obj_list:
183 | x.append(i.ID)
184 |
185 | for i in sorted(x):
186 | for x in self.obj_list:
187 | if i==x.ID:
188 | sorted_obj_list.append(x)
189 |
190 | with open(filename, 'w') as file:
191 | obj_dict = {f'{obj.type} {id}': (obj.args, round(obj.output_.center[0]-obj.width,2),
192 | round(obj.output_.center[1]-obj.height,2), obj.socket_nums) for id, obj in enumerate(sorted_obj_list)}
193 | obj_dict.update({"Lines" : list(self.line_list)})
194 | json.dump(obj_dict, file)
195 |
196 | def load(self, filename):
197 | """ load the node tree back to the canvas """
198 |
199 | if not os.path.exists(filename):
200 | raise FileNotFoundError("No such file found: " + str(filename))
201 |
202 | self.clear()
203 | self.connect_wire = False
204 |
205 | obj_type_dict = {'NodeValue': NodeValue,
206 | 'NodeOperation': NodeOperation,
207 | 'NodeCompile': NodeCompile}
208 |
209 | with open(filename) as file:
210 | obj_dict = json.load(file)
211 | value_nodes = []
212 | func_nodes = []
213 | comp_nodes = []
214 | self.obj_list = set()
215 |
216 | for obj_type, attributes in obj_dict.items():
217 | if obj_type.split()[0]=="Lines":
218 | line_list = attributes
219 | continue
220 |
221 | obj = obj_type_dict[obj_type.split()[0]](self, **attributes[0], x=attributes[1], y=attributes[2], num=attributes[3])
222 | if obj_type.split()[0]=="NodeValue":
223 | value_nodes.append(obj)
224 | elif obj_type.split()[0]=="NodeOperation":
225 | func_nodes.append(obj)
226 | elif obj_type.split()[0]=="NodeCompile":
227 | comp_nodes.append(obj)
228 | self.obj_list.add(obj)
229 |
230 | for nodes in [value_nodes, func_nodes, comp_nodes]:
231 | for i in nodes:
232 | i.connect_output(None)
233 | for j in func_nodes:
234 | try:
235 | if [self.outputcell.output_.socket_num, j.input_1.socket_num] in line_list:
236 | self.clickcount = 1
237 | j.connect_input(j.line1, 'input1')
238 | if [self.outputcell.output_.socket_num, j.input_2.socket_num] in line_list:
239 | self.clickcount = 1
240 | j.connect_input(j.line2, 'input2')
241 | if [self.outputcell.output_.socket_num, j.input_3.socket_num] in line_list:
242 | self.clickcount = 1
243 | j.connect_input(j.line3, 'input3')
244 | if [self.outputcell.output_.socket_num, j.input_4.socket_num] in line_list:
245 | self.clickcount = 1
246 | j.connect_input(j.line4, 'input4')
247 | if [self.outputcell.output_.socket_num, j.input_5.socket_num] in line_list:
248 | self.clickcount = 1
249 | j.connect_input(j.line5, 'input5')
250 | except AttributeError: None
251 | for j in comp_nodes:
252 | if [self.outputcell.output_.socket_num, j.input_1.socket_num] in line_list:
253 | self.clickcount = 1
254 | j.connect_input(None)
255 |
256 |
257 | self.connect_wire = True
258 |
259 |
--------------------------------------------------------------------------------
/tknodesystem/node_menu.py:
--------------------------------------------------------------------------------
1 | import customtkinter
2 | import sys
3 | import time
4 |
5 | customtkinter.set_appearance_mode("Dark")
6 |
7 | class NodeMenu(customtkinter.CTkToplevel):
8 |
9 | def __init__(self, attach, button_color=None, height: int = 300, width: int = 250, text_color=None,
10 | fg_color=None, button_height: int = 30, justify="center", scrollbar_button_color=None,
11 | scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2,
12 | command=None, alpha: float = 0.97, frame_corner_radius=20, label="Search Nodes",
13 | resize=True, border_color=None, **kwargs):
14 |
15 | super().__init__(takefocus=1)
16 |
17 | self.focus()
18 | self.alpha = alpha
19 | self.attributes('-alpha', 0)
20 | self.corner = frame_corner_radius
21 |
22 | self.attach = attach
23 | self.height = height
24 | self.width = width
25 | self.command = command
26 | self.fade = False
27 | self.resize = resize
28 | self.button_num = 0
29 | self.node = {}
30 | self.padding = 0
31 | self.focus_something = False
32 |
33 | self.withdraw()
34 | if sys.platform.startswith("win"):
35 | self.overrideredirect(True)
36 | self.transparent_color = self._apply_appearance_mode(self._fg_color)
37 | self.attributes("-transparentcolor", self.transparent_color)
38 | self.attach.bind("", self.popup, add="+")
39 | self.bind("", lambda e: self._withdraw())
40 | elif sys.platform.startswith("darwin"):
41 | self.overrideredirect(True)
42 | self.transparent_color = 'systemTransparent'
43 | self.attributes("-transparent", True)
44 | self.transient(self.master)
45 | self.attach.bind("", lambda e: self._withdraw(), add="+")
46 | self.attach.bind("", self.popup, add="+")
47 | self.focus_something = True
48 | else:
49 | self.attributes("-type", "splash")
50 | self.transparent_color = '#000001'
51 | self.corner = 0
52 | self.padding = 20
53 | self.attach.bind("", self.popup, add="+")
54 | self.bind("", lambda e: self._withdraw())
55 |
56 | self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color
57 | self.scroll_button_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_color"] if scrollbar_button_color is None else scrollbar_button_color
58 | self.scroll_hover_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if scrollbar_button_hover_color is None else scrollbar_button_hover_color
59 | self.border_color = customtkinter.ThemeManager.theme["CTkFrame"]["border_color"] if border_color is None else border_color
60 | self.button_color = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if button_color is None else button_color
61 | self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color
62 |
63 | if scrollbar is False:
64 | self.scroll_button_color = self.fg_color
65 | self.scroll_hover_color = self.fg_color
66 |
67 | self.frame_top = customtkinter.CTkFrame(self, bg_color=self.transparent_color, fg_color=self.fg_color,
68 | corner_radius=self.corner, border_width=frame_border_width,
69 | border_color=self.border_color)
70 |
71 | self.frame_top.pack(expand=True, fill="both")
72 |
73 | self.var = customtkinter.StringVar()
74 | self.var.trace_add('write', self.search)
75 |
76 | self.label = customtkinter.CTkLabel(self.frame_top, text=label)
77 | self.label.pack(fill="x", pady=10, padx=20)
78 |
79 | self.search_entry = customtkinter.CTkEntry(self.frame_top, fg_color=self.button_color, border_color=self.border_color,
80 | textvariable=self.var, **kwargs)
81 |
82 | self.search_entry.pack(fill="x", pady=0, padx=20)
83 | self.search_entry.bind("", lambda e: self.attach.unbind_all(""))
84 |
85 | self.frame = customtkinter.CTkScrollableFrame(self.frame_top, fg_color=self.fg_color,
86 | scrollbar_button_hover_color=self.scroll_hover_color,
87 | corner_radius=self.corner, scrollbar_button_color=self.scroll_button_color,)
88 |
89 | self.frame._scrollbar.grid_configure(padx=3)
90 |
91 | if self.padding:
92 | frame_padding = 10
93 | else:
94 | frame_padding = (0,6)
95 |
96 | self.frame.pack(expand=True, fill="both", padx=8, pady=frame_padding)
97 | self.no_match = customtkinter.CTkLabel(self.frame, text="No Match")
98 |
99 | if justify.lower()=="left":
100 | self.justify = "w"
101 | elif justify.lower()=="right":
102 | self.justify = "e"
103 | else:
104 | self.justify = "c"
105 |
106 | self.button_height = button_height
107 |
108 | self.resizable(width=False, height=False)
109 | self.disable = False
110 |
111 | self.attach.bind_all("", self.popup)
112 |
113 | self.update_idletasks()
114 | self.attach.focus_set()
115 |
116 | def _withdraw(self):
117 | if not self.disable:
118 | self.withdraw()
119 | self.attach.bind_all("", self.popup)
120 |
121 | def fade_out(self):
122 | for i in range(100,0,-10):
123 | if not self.winfo_exists():
124 | break
125 | self.attributes("-alpha", i/100)
126 | self.update()
127 | time.sleep(1/100)
128 |
129 | def fade_in(self):
130 | for i in range(0,100,10):
131 | if not self.winfo_exists():
132 | break
133 | self.attributes("-alpha", i/100)
134 | self.update()
135 | time.sleep(1/100)
136 |
137 | def search(self, a,b,c):
138 | self.live_update(self.var.get())
139 |
140 | def add_node(self, label, command, **button_kwargs):
141 | self.node[self.button_num] = customtkinter.CTkButton(self.frame,
142 | text=label,
143 | text_color=self.text_color,
144 | height=self.button_height,
145 | fg_color=self.button_color,
146 | anchor=self.justify,
147 | command=lambda: self._attach_key_press(command), **button_kwargs)
148 | self.node[self.button_num].pack(fill="x", pady=5, padx=(self.padding,0))
149 | self.button_num +=1
150 |
151 | def destroy_popup(self):
152 | self.destroy()
153 | self.disable = True
154 |
155 | def place_dropdown(self, x=None, y=None):
156 | self.geometry('{}x{}+{}+{}'.format(self.width, self.height, x, y))
157 | self.fade_in()
158 | self.attributes('-alpha', self.alpha)
159 |
160 | def _iconify(self, x=None, y=None):
161 | self.focus_set()
162 | self._deiconify()
163 | if self.focus_something: self.node[0].focus_set()
164 | self.search_entry.focus_set()
165 | self.place_dropdown(x,y)
166 |
167 | def _attach_key_press(self, command):
168 | self.fade_out()
169 | self.withdraw()
170 | command()
171 |
172 | def live_update(self, string=None):
173 | if self.disable: return
174 | if self.fade: return
175 | if string:
176 | i=1
177 | for key in self.node.keys():
178 | s = self.node[key].cget("text").lower()
179 | if not s.startswith(string.lower()):
180 | self.node[key].pack_forget()
181 | else:
182 | self.node[key].pack(fill="x", pady=5, padx=(self.padding,0))
183 | i+=1
184 |
185 | if i==1:
186 | self.no_match.pack(fill="x", pady=2, padx=(self.padding,0))
187 | else:
188 | self.no_match.pack_forget()
189 | self.button_num = i
190 |
191 | else:
192 | self.no_match.pack_forget()
193 | for key in self.node.keys():
194 | self.node[key].pack(fill="x", pady=5, padx=(self.padding,0))
195 | self.frame._parent_canvas.yview_moveto(0.0)
196 |
197 | def _deiconify(self):
198 | if self.button_num>0:
199 | self.deiconify()
200 |
201 | def popup(self, event):
202 | if self.disable: return
203 | self._iconify(event.x_root, event.y_root)
204 |
205 | def configure(self, **kwargs):
206 | if "height" in kwargs:
207 | self.height = kwargs.pop("height")
208 |
209 | if "alpha" in kwargs:
210 | self.alpha = kwargs.pop("alpha")
211 |
212 | if "width" in kwargs:
213 | self.width = kwargs.pop("width")
214 |
215 | if "fg_color" in kwargs:
216 | self.frame.configure(fg_color=kwargs.pop("fg_color"))
217 |
218 | if "button_color" in kwargs:
219 | for key in self.node.keys():
220 | self.node[key].configure(fg_color=kwargs.pop("button_color"))
221 |
222 | for key in self.node.keys():
223 | self.node[key].configure(**kwargs)
224 |
--------------------------------------------------------------------------------
/tknodesystem/node_socket.py:
--------------------------------------------------------------------------------
1 | import tkinter
2 |
3 | class NodeSocket:
4 | def __init__(self, canvas, radius=10, center=(50,50), value=0, border_color='white',
5 | border_width=1, fg_color='green', hover_color='red', hover=True, socket_num=None):
6 |
7 | self.canvas = canvas
8 | self.radius = radius
9 | self.center = center
10 | self.value = value
11 | self.fg_color = fg_color
12 | self.hover_color = hover_color
13 | self.hover = hover
14 | self.hover_message = False
15 | self.live_connection = False
16 |
17 | self.create(border_color, border_width, self.fg_color)
18 | self.canvas.socket_num +=1
19 | if socket_num:
20 | self.socket_num = socket_num
21 | self.canvas.socket_num = socket_num
22 | else:
23 | self.socket_num = self.canvas.socket_num
24 |
25 | self.msg = tkinter.StringVar()
26 | self.hover_text = tkinter.Message(self.canvas, textvariable=self.msg, aspect=1000,
27 | highlightthickness=0, borderwidth=0, bg="grey20", fg="white")
28 |
29 | self.update()
30 |
31 | def create(self, border_color, border_width, connecter_color):
32 | """ create a circle which will act as a node socket """
33 |
34 | self.ID = self.canvas.create_oval(
35 | (self.center[0]-self.radius, self.center[1]-self.radius),
36 | (self.center[0]+self.radius, self.center[1]+self.radius),
37 | outline=border_color, width=border_width, fill=connecter_color)
38 |
39 | self.canvas.tag_bind(self.ID, '', self.enter_socket)
40 | self.canvas.tag_bind(self.ID, '', self.leave_socket)
41 |
42 | def update(self):
43 | """ update the coordinates of socket """
44 | try:
45 | self.cords = self.canvas.coords(self.ID)
46 | self.center = (self.cords[0]+self.cords[2])/2, (self.cords[1]+self.cords[3])/2
47 | except: None
48 |
49 | def enter_socket(self, event):
50 | if self.hover: self.canvas.itemconfigure(self.ID, fill=self.hover_color)
51 | if self.hover_message:
52 | x_pos = self.cords[0]-self.hover_text.winfo_reqwidth()-3
53 | y_pos = self.center[1]-self.hover_text.winfo_reqheight()/2
54 | self.hover_text.place(x=x_pos, y=y_pos)
55 |
56 | def leave_socket(self, event):
57 | if self.hover: self.canvas.itemconfigure(self.ID, fill=self.fg_color)
58 | if self.hover_message: self.hover_text.place_forget()
59 |
60 | def hide(self):
61 | self.canvas.itemconfigure(self.ID, state="hidden")
62 |
63 | def show(self):
64 | self.canvas.itemconfigure(self.ID, state="normal")
65 |
66 | def connect_wire(self):
67 | """ make a dummy wire """
68 | if self.live_connection: return
69 |
70 | self.x1, self.y1 = self.center
71 | self.x2, self.y2 = self.center
72 | self.wire_exist = True
73 | self.wireID = self.canvas.create_line(self.x1, self.y1, self.x2, self.y2, dash=self.canvas.dash,
74 | width=self.canvas.wire_width, fill=self.canvas.wire_color)
75 | self.canvas.tag_lower(self.wireID)
76 | self.canvas.tag_lower(self.canvas.grid_bg)
77 | self.canvas.tag_bind(self.ID, "", lambda e: self.mouse_move(), add="+")
78 | self.canvas.tag_bind(self.wireID, "", lambda e: self.delete_wire(), add="+")
79 | if self.canvas.connect_wire: self.mouse_move()
80 |
81 | def delete_wire(self):
82 | """ delete the dummy wire """
83 | self.canvas.delete(self.wireID)
84 | self.wire_exist = False
85 | self.live_connection = False
86 |
87 | def mouse_move(self):
88 | """ connect the dummy wire with mouse """
89 | if self.ID not in self.canvas.find_all():
90 | self.delete_wire()
91 | self.canvas.connect_wire = False
92 | if (self.x1, self.y1)!=self.center:
93 | self.delete_wire()
94 | x = self.canvas.master.winfo_pointerx()
95 | y = self.canvas.master.winfo_pointery()
96 | abs_coord_x = self.canvas.master.winfo_pointerx() - self.canvas.winfo_rootx()
97 | abs_coord_y = self.canvas.master.winfo_pointery() - self.canvas.winfo_rooty()
98 |
99 | self.canvas.coords(self.wireID, self.x1, self.y1, abs_coord_x, abs_coord_y)
100 | if self.wire_exist:
101 | self.canvas.after(50, self.mouse_move)
102 | self.live_connection = True
103 | else:
104 | self.live_connection = False
105 | self.canvas.connect_wire = True
106 |
107 | def configure(self, **kwargs):
108 | """ configure options """
109 |
110 | if "fg_color" in kwargs:
111 | self.fg_color = kwargs.pop("fg_color")
112 | self.canvas.itemconfig(self.ID, fill=self.fg_color)
113 | if "hover_color" in kwargs:
114 | self.hover_color = kwargs.pop("hover_color")
115 | if "hover" in kwargs:
116 | self.hover = kwargs.pop("hover")
117 |
118 | if len(kwargs)>0:
119 | raise ValueError("This option is not configurable:" + list(kwargs.keys())[0])
120 |
121 |
--------------------------------------------------------------------------------
/tknodesystem/node_types.py:
--------------------------------------------------------------------------------
1 | import __main__
2 | from .node import Node
3 | from .node_socket import NodeSocket
4 | from .node_args import Args
5 | import warnings
6 |
7 | class NodeValue(Node):
8 | def __init__(self, canvas, width=100, height=50, value=0, border_color='white', text=None, corner_radius=25,
9 | border_width=0, fg_color='#37373D', text_color='white', font=("",10), socket_radius=8, socket_hover=True,
10 | socket_color="green", socket_hover_color="grey50", highlightcolor='#52d66c', hover=True, justify="center",
11 | click_command=None, side="right", x=0, y=0, num=None, fixed=False):
12 |
13 | self.text = text
14 | self.canvas = canvas
15 |
16 | self.args = Args.value_args(locals())
17 |
18 | if click_command:
19 | if click_command!="":
20 | if type(click_command) is str:
21 | click_command = getattr(__main__, click_command)
22 | self.args.update({"click_command": click_command.__name__})
23 | else:
24 | click_command = None
25 | warnings.warn("Warning: currently cannot be saved and loaded, please use any local function instead")
26 |
27 | if self.text is None:
28 | self.text = f"Input{self.canvas.input_num}"
29 |
30 | self.canvas.input_num +=1
31 | self.connected_func = set()
32 | self.value = value
33 | self.type = 'NodeValue'
34 |
35 | if border_width==0:
36 | border_color = fg_color
37 |
38 |
39 | super().__init__(canvas=canvas, width=width, height=height, center=(width,height), text=str(self.text),
40 | border_width=border_width, border_color=border_color, fg_color=fg_color, corner_radius=corner_radius,
41 | text_color=text_color, font=font, highlightcolor=highlightcolor, hover=hover, justify=justify,
42 | click_command=click_command)
43 |
44 | if side=="left":
45 | center = (width-(width/2),height)
46 | else:
47 | center = (width+(width/2),height)
48 |
49 | self.output_ = NodeSocket(canvas, value=value, radius=socket_radius, center=center,
50 | fg_color=socket_color, hover_color=socket_hover_color, border_width=border_width,
51 | border_color=border_color, hover=socket_hover, socket_num=num)
52 |
53 | self.allIDs = self.allIDs + [self.output_.ID]
54 |
55 | self.bind_all_to_movement()
56 | self.canvas.tag_bind(self.output_.ID, '', self.connect_output)
57 | if not fixed:
58 | self.canvas.bind_all("", lambda e: self.destroy() if self.signal else None, add="+")
59 | self.socket_nums = self.output_.socket_num
60 |
61 | for j in range(self.canvas.gain_in):
62 | for i in self.allIDs:
63 | self.canvas.scale(i, 0, 0, 1.1, 1.1)
64 |
65 | for j in range(abs(self.canvas.gain_out)):
66 | for i in self.allIDs:
67 | self.canvas.scale(i, 0, 0, 0.9, 0.9)
68 |
69 | if x or y:
70 | super().move(x,y)
71 |
72 | self.canvas.obj_list.add(self)
73 |
74 | def get(self):
75 | """ get the current value of node """
76 | return self.output_.value
77 |
78 | def connect_output(self, event):
79 | """ connect output socket """
80 |
81 | self.canvas.clickcount += 1
82 | self.canvas.outputcell = self
83 |
84 | if self.canvas.clickcount == 2:
85 | self.canvas.clickcount = 0
86 |
87 | self.output_.connect_wire()
88 |
89 | def destroy(self):
90 | if self.ID not in self.canvas.find_all(): return
91 |
92 | self.output_.value = None
93 | for i in self.allIDs:
94 | self.canvas.delete(i)
95 |
96 | self.canvas.obj_list.remove(self)
97 | super().destroy()
98 |
99 | for i in self.connected_func:
100 | i.update()
101 |
102 | def exists(self):
103 | if self.ID in self.canvas.find_all():
104 | return True
105 | else:
106 | return False
107 |
108 | def configure(self, **kwargs):
109 | """ configure options """
110 | self.args.update(kwargs)
111 | if "value" in kwargs:
112 | self.output_.value = kwargs.pop("value")
113 | if not self.text:
114 | super().configure(text=kwargs.pop("text"))
115 | for i in self.connected_func:
116 | i.update()
117 | if "text" in kwargs:
118 | self.text = kwargs.pop("text")
119 | super().configure(text=self.text)
120 | if "fg_color" in kwargs:
121 | super().configure(fg_color=kwargs.pop("fg_color"))
122 | if "text_color" in kwargs:
123 | super().configure(text_color=kwargs.pop("text_color"))
124 | if "font" in kwargs:
125 | super().configure(font=kwargs.pop("font"))
126 | if "highlightcolor" in kwargs:
127 | super().configure(highlightcolor=kwargs.pop("highlightcolor"))
128 | if "socket_color" in kwargs:
129 | self.output_.configure(fg_color=kwargs.pop("socket_color"))
130 | if "socket_hover_color" in kwargs:
131 | self.output_.configure(hover_color=kwargs.pop("socket_hover_color"))
132 | if "hover" in kwargs:
133 | super().configure(hover=kwargs.pop("hover"))
134 | if "socket_hover" in kwargs:
135 | self.output_.configure(hover=kwargs.pop("socket_hover"))
136 |
137 | if len(kwargs)>0:
138 | raise ValueError("This option is not configurable:" + list(kwargs.keys())[0])
139 |
140 | class NodeOperation(Node):
141 | def __init__(self, canvas, width=100, height=None, inputs=2, border_color='white', text=None, justify="center", hover_text=None,
142 | socket_radius=8, corner_radius=25, border_width=0, fg_color='#37373D', text_color='white', font=("",10), multiple_connection=False,
143 | highlightcolor='#52d66c', hover=True, socket_color="green", socket_hover_color="grey50", x=0, y=0, none_inputs=False, pass_node_id=False,
144 | multiside=False, command=None, output_socket_color="green", click_command=None, socket_hover=True, num=None, fixed=False):
145 |
146 | self.text = text
147 | self.canvas = canvas
148 | self.type = 'NodeOperation'
149 | self.hover_text = {} if hover_text is None else hover_text
150 |
151 | if self.text is None:
152 | self.text = f"Function{self.canvas.operation_num}"
153 |
154 | self.canvas.operation_num +=1
155 |
156 | if border_width==0:
157 | border_color = fg_color
158 |
159 | args = locals()
160 | args['hover_text'] = self.hover_text
161 | self.args = Args.func_args(args)
162 |
163 | self.pass_node = pass_node_id
164 |
165 |
166 | if command:
167 | if command!="":
168 | if type(command) is str:
169 | command = getattr(__main__, command)
170 | self.args.update({"command": command.__name__})
171 | else:
172 | command = None
173 | warnings.warn("Warning: currently cannot be saved and loaded, please use any local function instead")
174 | if click_command:
175 | if click_command!="":
176 | if type(click_command) is str:
177 | click_command = getattr(__main__, click_command)
178 | self.args.update({"click_command": click_command.__name__})
179 | else:
180 | click_command = None
181 | warnings.warn("Warning: currently cannot be saved and loaded, please use any local function instead")
182 |
183 | self.command = command
184 |
185 | if height is None:
186 | height = 50 + (inputs*10)
187 |
188 | super().__init__(canvas=canvas, width=width, height=height, center=(width,height), justify=justify,
189 | border_width=border_width, fg_color=fg_color, border_color=border_color,
190 | text_color=text_color, font=font, click_command=click_command, corner_radius=corner_radius,
191 | highlightcolor=highlightcolor, text=str(self.text), hover=hover)
192 |
193 | self.line1 = None
194 | self.line2 = None
195 | self.line3 = None
196 | self.line4 = None
197 | self.line5 = None
198 | self.socket_colors = []
199 | self.connected_node = set()
200 | self.connected_node_first = None
201 | self.none_values = none_inputs
202 |
203 | x_pos = x
204 | y_pos = y
205 |
206 | if type(socket_color) is list:
207 | self.socket_colors = socket_color
208 | else:
209 | for i in range(5):
210 | self.socket_colors.append(socket_color)
211 |
212 | self.inputs = inputs
213 |
214 | if self.inputs>3 and self.height<=80:
215 | multiside=True
216 |
217 | if multiside:
218 | z = self.inputs-1
219 | else:
220 | z = self.inputs+1
221 |
222 | if z==0:
223 | z = 2
224 |
225 | y = height/z
226 | x = 1
227 | if self.inputs==2 and multiside:
228 | x = 1/2
229 |
230 | self.cellinput1 = None
231 | self.cellinput2 = None
232 | self.cellinput3 = None
233 | self.cellinput4 = None
234 | self.cellinput5 = None
235 | self.celloutput = None
236 | self.socket_nums = []
237 | self.values_args = []
238 | self.multiple = multiple_connection
239 |
240 | self.connected_inputs1 = list()
241 | self.connected_inputs2 = list()
242 | self.connected_inputs3 = list()
243 | self.connected_inputs4 = list()
244 | self.connected_inputs5 = list()
245 |
246 | self.output_ = NodeSocket(canvas, radius=socket_radius, center=(width+(width/2),height),
247 | fg_color=output_socket_color, hover_color=socket_hover_color, socket_num=num[0] if num else None,
248 | border_width=border_width, border_color=border_color, hover=socket_hover)
249 | self.canvas.tag_bind(self.output_.ID, '', self.connect_output)
250 | self.socket_nums.append(self.output_.socket_num)
251 |
252 | center = (width/2, y * x + height/2)
253 | self.input_1 = NodeSocket(canvas, radius=socket_radius, center=center, fg_color=self.socket_colors[0],
254 | hover_color=socket_hover_color, border_width=border_width, hover=socket_hover,
255 | border_color=border_color, socket_num=num[1] if num else None)
256 |
257 | id_list = [self.output_.ID, self.input_1.ID]
258 | self.canvas.tag_bind(self.input_1.ID, '', lambda e: self.connect_input(self.line1,'input1'))
259 | self.socket_nums.append(self.input_1.socket_num)
260 |
261 | if self.inputs>=2:
262 | if not multiside:
263 | x+=1
264 | center = (width/2,y * x +height/2)
265 | else:
266 | center = (width, height/2)
267 |
268 | self.input_2 = NodeSocket(canvas, radius=socket_radius, center=center, fg_color=self.socket_colors[1],
269 | hover_color=socket_hover_color, border_width=border_width, hover=socket_hover,
270 | border_color=border_color, socket_num=num[2] if num else None)
271 | id_list.append(self.input_2.ID)
272 | self.canvas.tag_bind(self.input_2.ID, '', lambda e: self.connect_input(self.line2,'input2'))
273 | self.socket_nums.append(self.input_2.socket_num)
274 |
275 | if self.inputs>=3:
276 | if not multiside:
277 | x+=1
278 | center = (width/2,y * x +height/2)
279 | else:
280 | center = (width, height*1.5)
281 | self.input_3 = NodeSocket(canvas, radius=socket_radius, center=center, fg_color=self.socket_colors[2],
282 | hover_color=socket_hover_color, border_width=border_width, hover=socket_hover,
283 | border_color=border_color, socket_num=num[3] if num else None)
284 | self.canvas.tag_bind(self.input_3.ID, '', lambda e: self.connect_input(self.line3,'input3'))
285 | id_list.append(self.input_3.ID)
286 | self.socket_nums.append(self.input_3.socket_num)
287 |
288 | if self.inputs>=4:
289 | x+=1
290 | center = (width/2,y * x +height/2)
291 | self.input_4 = NodeSocket(canvas, radius=socket_radius, center=center, fg_color=self.socket_colors[3],
292 | hover_color=socket_hover_color, border_width=border_width, hover=socket_hover,
293 | border_color=border_color, socket_num=num[4] if num else None)
294 | self.canvas.tag_bind(self.input_4.ID, '', lambda e: self.connect_input(self.line4,'input4'))
295 | id_list.append(self.input_4.ID)
296 | self.socket_nums.append(self.input_4.socket_num)
297 |
298 | if self.inputs>=5:
299 | x+=1
300 | center = (width/2,y * x +height/2)
301 | self.input_5 = NodeSocket(canvas, radius=socket_radius, center=center, fg_color=self.socket_colors[4],
302 | hover_color=socket_hover_color, border_width=border_width, hover=socket_hover,
303 | border_color=border_color, socket_num=num[5] if num else None)
304 | self.canvas.tag_bind(self.input_5.ID, '', lambda e: self.connect_input(self.line5, 'input5'))
305 | id_list.append(self.input_5.ID)
306 | self.socket_nums.append(self.input_5.socket_num)
307 |
308 | self.allIDs = self.allIDs + id_list
309 | self.bind_all_to_movement()
310 | self.id_list = id_list
311 | if not fixed:
312 | self.canvas.bind_all("", lambda e: self.destroy() if self.signal else None, add="+")
313 |
314 | for i in self.hover_text:
315 | self.config_socket(**self.hover_text[i])
316 |
317 | for j in range(self.canvas.gain_in):
318 | for i in self.allIDs:
319 | self.canvas.scale(i, 0, 0, 1.1, 1.1)
320 |
321 | for j in range(abs(self.canvas.gain_out)):
322 | for i in self.allIDs:
323 | self.canvas.scale(i, 0, 0, 0.9, 0.9)
324 |
325 | if x_pos or y_pos:
326 | super().move(x_pos,y_pos)
327 |
328 | self.canvas.obj_list.add(self)
329 |
330 | def connect_output(self, event):
331 | """ connect output socket """
332 |
333 | self.canvas.clickcount += 1
334 | self.canvas.outputcell = self
335 |
336 | if self.canvas.clickcount == 2:
337 | self.canvas.clickcount = 0
338 |
339 | self.output_.connect_wire()
340 |
341 | def connect_input(self, line_id, input_id):
342 | """ connect input sockets """
343 | if self.canvas.outputcell is None:
344 | return
345 | if self.canvas.outputcell in self.connected_node:
346 | return
347 | if self.canvas.outputcell==self:
348 | return
349 |
350 | m = self.connected_node_first
351 |
352 | for i in range(self.canvas.operation_num):
353 | if m is None:
354 | break
355 | if m.type=="NodeOperation":
356 | if self.canvas.outputcell in m.connected_node:
357 | return
358 | m = m.connected_node_first
359 |
360 | if not self.multiple:
361 | try: self.canvas.delete(line_id.ID)
362 | except: None
363 |
364 | self.canvas.clickcount += 1
365 | self.canvas.IDc = input_id
366 | self.canvas.inputcell = self
367 |
368 | if self.canvas.clickcount == 2:
369 | self.canvas.clickcount = 0
370 | self.canvas.conectcells()
371 | if self.canvas.outputcell.type=="NodeValue":
372 | self.canvas.outputcell.connected_func.add(self)
373 | elif self.canvas.outputcell.type=="NodeOperation":
374 | self.canvas.outputcell.connected_node.add(self)
375 | self.canvas.outputcell.connected_node_first = self
376 | try:
377 | if input_id=="input1":
378 | if not self.multiple:
379 | for x in self.canvas.line_list:
380 | if x[1]==self.input_1.socket_num:
381 | self.canvas.line_list.remove(x)
382 | break
383 | else:
384 | if self.canvas.outputcell not in self.connected_inputs1:
385 | self.connected_inputs1.append(self.canvas.outputcell)
386 | l = (self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_1.socket_num)
387 | self.canvas.line_list.add(l)
388 | if input_id=="input2":
389 | if not self.multiple:
390 | for x in self.canvas.line_list:
391 | if x[1]==self.input_2.socket_num:
392 | self.canvas.line_list.remove(x)
393 | break
394 | else:
395 | if self.canvas.outputcell not in self.connected_inputs2:
396 | self.connected_inputs2.append(self.canvas.outputcell)
397 | l = (self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_2.socket_num)
398 | self.canvas.line_list.add(l)
399 | if input_id=="input3":
400 | if not self.multiple:
401 | for x in self.canvas.line_list:
402 | if x[1]==self.input_3.socket_num:
403 | self.canvas.line_list.remove(x)
404 | break
405 | else:
406 | if self.canvas.outputcell not in self.connected_inputs3:
407 | self.connected_inputs3.append(self.canvas.outputcell)
408 | l = (self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_3.socket_num)
409 | self.canvas.line_list.add(l)
410 | if input_id=="input4":
411 | if not self.multiple:
412 | for x in self.canvas.line_list:
413 | if x[1]==self.input_4.socket_num:
414 | self.canvas.line_list.remove(x)
415 | break
416 | else:
417 | if self.canvas.outputcell not in self.connected_inputs4:
418 | self.connected_inputs4.append(self.canvas.outputcell)
419 | l = (self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_4.socket_num)
420 | self.canvas.line_list.add(l)
421 | if input_id=="input5":
422 | if not self.multiple:
423 | for x in self.canvas.line_list:
424 | if x[1]==self.input_5.socket_num:
425 | self.canvas.line_list.remove(x)
426 | break
427 | else:
428 | if self.canvas.outputcell not in self.connected_inputs5:
429 | self.connected_inputs5.append(self.canvas.outputcell)
430 | l = (self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_5.socket_num)
431 | self.canvas.line_list.add(l)
432 | except AttributeError: None
433 |
434 | else:
435 | if self.canvas.outputcell.type=="NodeValue":
436 | try: self.canvas.outputcell.connected_func.remove(self)
437 | except KeyError: None
438 | elif self.canvas.outputcell.type=="NodeOperation":
439 | try:
440 | self.canvas.outputcell.connected_node.remove(self)
441 | self.canvas.outputcell.connected_node_first = None
442 | except KeyError: None
443 | try:
444 | if input_id=="input1":
445 | self.cellinput1 = None
446 | self.canvas.line_list.remove((self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_1.socket_num))
447 | if self.multiple:
448 | self.connected_inputs1.remove(self.canvas.outputcell)
449 | if input_id=="input2":
450 | self.cellinput2 = None
451 | self.canvas.line_list.remove((self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_2.socket_num))
452 | if self.multiple:
453 | self.connected_inputs2.remove(self.canvas.outputcell)
454 | if input_id=="input3":
455 | self.cellinput3 = None
456 | self.canvas.line_list.remove((self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_3.socket_num))
457 | if self.multiple:
458 | self.connected_inputs3.remove(self.canvas.outputcell)
459 | if input_id=="input4":
460 | self.cellinput4 = None
461 | self.canvas.line_list.remove((self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_4.socket_num))
462 | if self.multiple:
463 | self.connected_inputs4.remove(self.canvas.outputcell)
464 | if input_id=="input5":
465 | self.cellinput5 = None
466 | self.canvas.line_list.remove((self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_5.socket_num))
467 | if multiple:
468 | self.connected_inputs5.remove(self.canvas.outputcell)
469 | except AttributeError: None
470 | except KeyError: None
471 |
472 | if self.multiple:
473 | for i in self.canvas.line_ids:
474 | if (i.firstcell==self.canvas.outputcell) and (i.secondcell==self) and (i.inputs==input_id):
475 | i.delete_line(i.inputs)
476 | break
477 | self.update()
478 |
479 | def toggle(self, input_num: int):
480 | line_num = eval(f"self.line{input_num}")
481 | input_num = f"input{input_num}"
482 | self.connect_input(line_num, input_num)
483 |
484 | def update(self):
485 | """ update the output values """
486 |
487 | if self.multiple:
488 | cells = [self.connected_inputs1,
489 | self.connected_inputs2,
490 | self.connected_inputs3,
491 | self.connected_inputs4,
492 | self.connected_inputs5]
493 | self.values_args = []
494 |
495 | if self.command:
496 | for i in cells[0:self.inputs]:
497 | v = []
498 | for j in i:
499 | if j.output_.value is not None:
500 | v.append(j.output_.value)
501 | self.values_args.append(v)
502 |
503 | if not self.none_values:
504 | for i in self.values_args:
505 | if len(i)==0:
506 | self.output_.value = None
507 | break
508 | else:
509 | self.output_.value = 1
510 | else:
511 | self.output_.value = 1
512 |
513 | if self.output_.value:
514 | if self.pass_node:
515 | self.output_.value = self.command(self, *self.values_args[0:self.inputs])
516 | else:
517 | self.output_.value = self.command(*self.values_args[0:self.inputs])
518 | else:
519 | self.output_.value = []
520 |
521 | else:
522 | arguments = [self.cellinput1,
523 | self.cellinput2,
524 | self.cellinput3,
525 | self.cellinput4,
526 | self.cellinput5]
527 | self.values_args = []
528 | if self.command:
529 | for i in arguments[0:self.inputs]:
530 | if i is None:
531 | self.values_args.append(None)
532 | else:
533 | self.values_args.append(i.output_.value)
534 |
535 | if not self.none_values:
536 | for i in self.values_args:
537 | if i is None:
538 | self.output_.value = None
539 | break
540 | else:
541 | self.output_.value = 1
542 | else:
543 | self.output_.value = 1
544 |
545 | if self.output_.value:
546 | if self.pass_node:
547 | self.output_.value = self.command(self, *self.values_args[0:self.inputs])
548 | else:
549 | self.output_.value = self.command(*self.values_args[0:self.inputs])
550 | else:
551 | self.output_.value = None
552 |
553 | if len(self.connected_node)>0:
554 | for i in self.connected_node:
555 | i.update()
556 |
557 | def get(self):
558 | """ get the current value of node """
559 | return self.output_.value
560 |
561 | def get_inputs(self):
562 | return self.values_args
563 |
564 | def destroy(self):
565 | if self.ID not in self.canvas.find_all(): return
566 | self.output_.value = None
567 | for i in self.id_list:
568 | self.canvas.delete(i)
569 |
570 | self.canvas.obj_list.remove(self)
571 | super().destroy()
572 |
573 | if len(self.connected_node)>0:
574 | for i in self.connected_node:
575 | i.update()
576 |
577 | def exists(self):
578 | if self.ID in self.canvas.find_all():
579 | return True
580 | else:
581 | return False
582 |
583 | def config_socket(self, index: int, hover_text: str=None, hover_text_color=None, hover_bg=None, **kwargs):
584 | if index==1:
585 | socket = self.input_1
586 | elif index==2:
587 | socket = self.input_2
588 | elif index==3:
589 | socket = self.input_3
590 | elif index==4:
591 | socket = self.input_4
592 | elif index==5:
593 | socket = self.input_5
594 | else:
595 | return
596 |
597 | kwarg_args = {}
598 | for i in kwargs:
599 | kwarg_args = {i: kwargs[i]}
600 |
601 | self.hover_text[str(index)] = {'index': index, 'hover_text': hover_text, 'hover_text_color': hover_text_color, 'hover_bg': hover_bg, **kwarg_args}
602 |
603 | if hover_text:
604 | socket.hover_message = True
605 | socket.msg.set(hover_text)
606 | else:
607 | socket.hover_message = False
608 |
609 | if hover_text_color:
610 | socket.hover_text.configure(fg=hover_text_color)
611 |
612 | if hover_bg:
613 | socket.hover_text.configure(bg=hover_bg)
614 |
615 | if "socket_color" in kwargs:
616 | socket.configure(fg_color=kwargs.pop("socket_color"))
617 |
618 | self.configure(**kwargs)
619 |
620 | def configure(self, **kwargs):
621 | """ configure options """
622 | self.args.update(kwargs)
623 | if "text" in kwargs:
624 | self.text = kwargs.pop("text")
625 | super().configure(text=self.text)
626 | if "fg_color" in kwargs:
627 | super().configure(fg_color=kwargs.pop("fg_color"))
628 | if "text_color" in kwargs:
629 | super().configure(text_color=kwargs.pop("text_color"))
630 | if "font" in kwargs:
631 | super().configure(font=kwargs.pop("font"))
632 | if "highlightcolor" in kwargs:
633 | super().configure(highlightcolor=kwargs.pop("highlightcolor"))
634 | if "socket_color" in kwargs:
635 | socket_color = kwargs.pop("socket_color")
636 | if type(socket_color) is list:
637 | try:
638 | self.input_1.configure(fg_color=socket_color[0])
639 | self.input_2.configure(fg_color=socket_color[1])
640 | self.input_3.configure(fg_color=socket_color[2])
641 | self.input_4.configure(fg_color=socket_color[3])
642 | self.input_5.configure(fg_color=socket_color[4])
643 | except: None
644 | else:
645 | try:
646 | self.input_1.configure(fg_color=socket_color)
647 | self.input_2.configure(fg_color=socket_color)
648 | self.input_3.configure(fg_color=socket_color)
649 | self.input_4.configure(fg_color=socket_color)
650 | self.input_5.configure(fg_color=socket_color)
651 | except: None
652 |
653 | if "socket_hover_color" in kwargs:
654 | socket_hover_color = kwargs.pop("socket_hover_color")
655 | self.output_.configure(hover_color=socket_hover_color)
656 | try:
657 | self.input_1.configure(hover_color=socket_hover_color)
658 | self.input_2.configure(hover_color=socket_hover_color)
659 | self.input_3.configure(hover_color=socket_hover_color)
660 | self.input_4.configure(hover_color=socket_hover_color)
661 | self.input_5.configure(hover_color=socket_hover_color)
662 | except: None
663 |
664 | if "output_socket_color" in kwargs:
665 | self.output_.configure(fg_color=kwargs.pop("output_socket_color"))
666 | if "hover" in kwargs:
667 | super().configure(hover=kwargs.pop("hover"))
668 | if "socket_hover" in kwargs:
669 | hover = kwargs.pop("socket_hover")
670 | self.output_.configure(hover=hover)
671 | try:
672 | self.input_1.configure(hover=hover)
673 | self.input_2.configure(hover=hover)
674 | self.input_3.configure(hover=hover)
675 | self.input_4.configure(hover=hover)
676 | self.input_5.configure(hover=hover)
677 | except: None
678 |
679 | if len(kwargs)>0:
680 | raise ValueError("This option is not configurable:" + list(kwargs.keys())[0])
681 |
682 | class NodeCompile(Node):
683 | def __init__(self, canvas, width=100, height=50, border_color='white', text="Compile", socket_radius=8, corner_radius=25, x=0, y=0, justify="center",
684 | border_width=0, fg_color='#37373D',text_color='white', font=("",10), highlightcolor='#52d66c', hover=True, socket_hover=True, fixed=False,
685 | socket_color="green", socket_hover_color="grey50", show_value=True, command=None, click_command=None, side="left", num=None,
686 | multiple_connection=False, pass_node_id=False):
687 |
688 | self.canvas = canvas
689 | self.text = text
690 | self.type = 'NodeCompile'
691 |
692 | if border_width==0:
693 | border_color = fg_color
694 |
695 | self.canvas.compile_num +=1
696 |
697 | self.args = Args.compile_args(locals())
698 |
699 | self.pass_node = pass_node_id
700 |
701 | if command:
702 | if command!="":
703 | if type(command) is str:
704 | command = getattr(__main__, command)
705 | self.args.update({"command": command.__name__})
706 | else:
707 | command = None
708 | warnings.warn("Warning: currently cannot be saved and loaded, please use any local function instead")
709 | if click_command:
710 | if click_command!="":
711 | if type(click_command) is str:
712 | click_command = getattr(__main__, click_command)
713 | self.args.update({"click_command": click_command.__name__})
714 | else:
715 | click_command = None
716 | warnings.warn("Warning: currently cannot be saved and loaded, please use any local function instead")
717 |
718 | super().__init__(canvas=canvas, width=width, height=height, center=(width,height), text=str(text), corner_radius=corner_radius,
719 | border_width=border_width, fg_color=fg_color, border_color=border_color, hover=hover, justify=justify,
720 | text_color=text_color, font=font, click_command=click_command, highlightcolor=highlightcolor)
721 |
722 | self.line1 = None
723 | self.cellinput1 = None
724 | self.celloutput = None
725 | self.show_value = show_value
726 | self.previous_value = None
727 | self.command = command
728 | self.connected_inputs = list()
729 | self.multiple = multiple_connection
730 | self.multilines = {}
731 |
732 | if side=="left":
733 | center = (width-(width/2),height)
734 | else:
735 | center = (width+(width/2),height)
736 |
737 | self.input_1 = NodeSocket(canvas, radius=socket_radius, center=center, border_width=border_width,
738 | fg_color=socket_color, hover_color=socket_hover_color, border_color=border_color,
739 | hover=socket_hover, socket_num=num[0] if num else None)
740 |
741 | self.output_ = NodeSocket(canvas, radius=socket_radius, center=(width+(width/2),height),
742 | fg_color=socket_color, hover_color=socket_hover_color, border_width=border_width,
743 | border_color=border_color, hover=socket_hover, socket_num=num[1] if num else None)
744 |
745 | self.socket_nums = [self.input_1.socket_num, self.output_.socket_num]
746 | self.allIDs = self.allIDs + [self.output_.ID, self.input_1.ID]
747 | self.fixed = True
748 | self.bind_all_to_movement()
749 | self.canvas.tag_bind(self.input_1.ID, '', self.connect_input)
750 |
751 | if not fixed:
752 | self.canvas.bind_all("", lambda e: self.destroy() if self.signal else None, add="+")
753 |
754 | self.output_.hide()
755 |
756 | for j in range(self.canvas.gain_in):
757 | for i in self.allIDs:
758 | self.canvas.scale(i, 0, 0, 1.1, 1.1)
759 |
760 | for j in range(abs(self.canvas.gain_out)):
761 | for i in self.allIDs:
762 | self.canvas.scale(i, 0, 0, 0.9, 0.9)
763 |
764 | if x or y:
765 | super().move(x,y)
766 |
767 | self.canvas.obj_list.add(self)
768 |
769 | def connect_output(self, event):
770 | """ connect output socket """
771 |
772 | self.canvas.clickcount += 1
773 | self.canvas.outputcell = self
774 |
775 | if self.canvas.clickcount == 2:
776 | self.canvas.clickcount = 0
777 |
778 | self.output_.connect_wire()
779 |
780 | def connect_input(self, event):
781 | """ connect input sockets """
782 |
783 | if not self.multiple:
784 | try: self.canvas.delete(self.line1.ID)
785 | except: None
786 | else:
787 | if self.canvas.outputcell in list(self.multilines.keys()):
788 | if self.multilines[self.canvas.outputcell] is not None:
789 | self.canvas.delete(self.multilines[self.canvas.outputcell])
790 | del self.multilines[self.canvas.outputcell]
791 |
792 | self.canvas.clickcount += 1
793 | self.canvas.IDc = 'input1'
794 | self.canvas.inputcell = self
795 |
796 | if self.canvas.clickcount == 2:
797 | self.canvas.clickcount = 0
798 | self.canvas.conectcells()
799 | self.fixed = False
800 | try:
801 | if self.canvas.outputcell.type=="NodeValue":
802 | self.canvas.outputcell.connected_func.add(self)
803 | if not self.multiple:
804 | for x in self.canvas.line_list:
805 | if x[1]==self.input_1.socket_num:
806 | self.canvas.line_list.remove(x)
807 | break
808 | else:
809 | if self.canvas.outputcell not in self.connected_inputs:
810 | self.connected_inputs.append(self.canvas.outputcell)
811 | self.canvas.line_list.add((self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_1.socket_num))
812 | except AttributeError: None
813 | else:
814 | self.fixed = True
815 | try:
816 | if self.canvas.outputcell.type=="NodeValue":
817 | try: self.canvas.outputcell.connected_func.remove(self)
818 | except KeyError: None
819 | self.canvas.line_list.remove((self.canvas.outputcell.output_.socket_num, self.canvas.inputcell.input_1.socket_num))
820 | if self.multiple:
821 | self.connected_inputs.remove(self.canvas.outputcell)
822 | self.canvas.delete(self.line1.ID)
823 | except AttributeError: None
824 | except KeyError: None
825 | if self.multiple:
826 | try:
827 | self.multilines.update({self.canvas.outputcell:self.line1.ID})
828 | except:
829 | self.multilines.update({self.canvas.outputcell:None})
830 |
831 | def get(self):
832 | """ get the current value of node """
833 | return self.output_.value
834 |
835 | def toggle(self):
836 | self.connect_input(0)
837 |
838 | def update(self):
839 | """ update the output values """
840 |
841 | if self.ID not in self.canvas.find_all():
842 | return
843 | if self.multiple:
844 | output = []
845 | for i in self.connected_inputs:
846 | if i.output_.value is not None:
847 | output.append(i.output_.value)
848 | else:
849 | output = self.cellinput1.output_.value if not self.fixed else None
850 |
851 | self.output_.value = output
852 | if self.previous_value!=self.output_.value:
853 | if self.show_value:
854 | self.canvas.itemconfigure(self.IDtext, text=str(self.output_.value))
855 | if self.output_.value is not None:
856 | if self.command:
857 | if self.pass_node:
858 | self.command(self, self.output_.value)
859 | else:
860 | self.command(self.output_.value)
861 | self.previous_value = self.output_.value
862 |
863 | self.canvas.after(50, self.update)
864 |
865 | def destroy(self):
866 | if self.ID not in self.canvas.find_all(): return
867 | self.output_.value = None
868 |
869 | for i in self.allIDs:
870 | self.canvas.delete(i)
871 |
872 | self.canvas.obj_list.remove(self)
873 | super().destroy()
874 |
875 | def exists(self):
876 | if self.ID in self.canvas.find_all():
877 | return True
878 | else:
879 | return False
880 |
881 | def configure(self, **kwargs):
882 | """ configure options """
883 | self.args.update(kwargs)
884 | if "text" in kwargs:
885 | self.text = kwargs.pop("text")
886 | super().configure(text=self.text)
887 | if "fg_color" in kwargs:
888 | super().configure(fg_color=kwargs.pop("fg_color"))
889 | if "text_color" in kwargs:
890 | super().configure(text_color=kwargs.pop("text_color"))
891 | if "font" in kwargs:
892 | super().configure(font=kwargs.pop("font"))
893 | if "highlightcolor" in kwargs:
894 | super().configure(highlightcolor=kwargs.pop("highlightcolor"))
895 | if "socket_color" in kwargs:
896 | self.input_1.configure(fg_color=kwargs.pop("socket_color"))
897 | if "socket_hover_color" in kwargs:
898 | self.input_1.configure(hover_color=kwargs.pop("socket_hover_color"))
899 | if "hover" in kwargs:
900 | super().configure(hover=kwargs.pop("hover"))
901 | if "socket_hover" in kwargs:
902 | self.input_1.configure(hover=kwargs.pop("socket_hover"))
903 | if "show_value" in kwargs:
904 | self.show_value = kwargs.pop("show_value")
905 |
906 | if len(kwargs)>0:
907 | raise ValueError("This option is not configurable:" + list(kwargs.keys())[0])
908 |
--------------------------------------------------------------------------------
/tknodesystem/node_wire.py:
--------------------------------------------------------------------------------
1 | class NodeWire():
2 | def __init__(self, canvas, firstcell, secondcell, wire_color='white', wire_width=3,
3 | dash=True, wire_hover_color="red"):
4 |
5 | self.canvas = canvas
6 | self.firstcell = firstcell # output socket
7 | self.secondcell = secondcell # input socket
8 | self.IDc = self.canvas.IDc
9 | self.wire_color = wire_color
10 | self.wire_width = wire_width
11 | self.canvas.line_ids.add(self)
12 | self.dash = (2,4) if dash else ()
13 | self.hover_color = wire_hover_color
14 | self.connected = True
15 |
16 | if self.firstcell:
17 | self.create()
18 | self.update()
19 |
20 | def create(self):
21 | """ creates the line from output-->input sockets """
22 |
23 | self.firstcell.line = self
24 | self.x1, self.y1 = self.firstcell.output_.center
25 |
26 | if self.canvas.IDc == 'input1':
27 | self.input_num = self.secondcell.input_1
28 | self.secondcell.line1 = self
29 |
30 | if self.canvas.IDc == 'input2':
31 | self.input_num = self.secondcell.input_2
32 | self.secondcell.line2 = self
33 |
34 | if self.canvas.IDc == 'input3':
35 | self.input_num = self.secondcell.input_3
36 | self.secondcell.line3 = self
37 |
38 | if self.canvas.IDc == 'input4':
39 | self.input_num = self.secondcell.input_4
40 | self.secondcell.line4 = self
41 |
42 | if self.canvas.IDc == 'input5':
43 | self.input_num = self.secondcell.input_5
44 | self.secondcell.line5 = self
45 |
46 | self.x2,self.y2 = self.input_num.center
47 | self.firstcell.output_.delete_wire()
48 | self.ID = self.canvas.create_line(self.x1, self.y1, self.x2, self.y2, dash=self.dash, width=self.wire_width,
49 | activefill=self.hover_color, fill=self.wire_color, activewidth=self.wire_width)
50 | self.canvas.tag_lower(self.ID)
51 | self.canvas.tag_lower(self.canvas.grid_bg)
52 | self.inputs = self.canvas.IDc
53 | self.canvas.tag_bind(self.ID, "", lambda e: self.delete_line(self.inputs))
54 |
55 | def delete_line(self, input_num):
56 | """ delete the line when disconnected """
57 | self.canvas.delete(self.ID)
58 | self.canvas.line_ids.remove(self)
59 | self.firstcell.output_.live_connection = True
60 | self.firstcell.connect_output(None)
61 | self.firstcell.output_.live_connection = False
62 | self.canvas.clickcount = 0
63 | if self.secondcell.type=="NodeOperation":
64 | self.secondcell.connect_input(self.ID, input_num)
65 | else:
66 | self.secondcell.connect_input(None)
67 |
68 | def update(self):
69 | """ update the coordinates of line based on the socket position """
70 | all_items = self.canvas.find_all()
71 | if self.firstcell.ID not in all_items or self.secondcell.ID not in all_items:
72 | self.canvas.delete(self.ID)
73 | self.connected = False
74 | if self.ID not in all_items: return
75 | self.x1, self.y1 = self.firstcell.output_.center
76 | if self.IDc == 'input1': self.x2, self.y2 = self.secondcell.input_1.center
77 | if self.IDc == 'input2': self.x2, self.y2 = self.secondcell.input_2.center
78 | if self.IDc == 'input3': self.x2, self.y2 = self.secondcell.input_3.center
79 | if self.IDc == 'input4': self.x2, self.y2 = self.secondcell.input_4.center
80 | if self.IDc == 'input5': self.x2, self.y2 = self.secondcell.input_5.center
81 | self.canvas.coords(self.ID, self.x1, self.y1, self.x2, self.y2)
82 |
83 | def configure(self, **kwargs):
84 | if "wire_color" in kwargs:
85 | self.canvas.itemconfig(self.ID, fill=kwargs.pop("wire_color"))
86 | if "wire_width" in kwargs:
87 | self.canvas.itemconfig(self.ID, width=kwargs.pop("wire_width"))
88 | if "dash" in kwargs:
89 | self.dash = kwargs.pop("dash")
90 | self.canvas.itemconfig(self.ID, dash=self.dash if self.dash else ())
91 | if "wire_hover_color" in kwargs:
92 | self.canvas.itemconfig(self.ID, activefill=kwargs.pop("wire_hover_color"))
93 |
94 | if len(kwargs)>0:
95 | raise ValueError("This option is not configurable:" + list(kwargs.keys())[0])
96 |
97 |
--------------------------------------------------------------------------------