nodeeditor-qtpy 0.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. nodeeditor/__init__.py +93 -0
  2. nodeeditor/node_colors_config.py +343 -0
  3. nodeeditor/node_content_widget.py +129 -0
  4. nodeeditor/node_decorators.py +65 -0
  5. nodeeditor/node_edge.py +518 -0
  6. nodeeditor/node_edge_dragging.py +185 -0
  7. nodeeditor/node_edge_intersect.py +219 -0
  8. nodeeditor/node_edge_rerouting.py +216 -0
  9. nodeeditor/node_edge_snapping.py +95 -0
  10. nodeeditor/node_edge_validators.py +82 -0
  11. nodeeditor/node_editor_widget.py +257 -0
  12. nodeeditor/node_editor_window.py +448 -0
  13. nodeeditor/node_graph.py +583 -0
  14. nodeeditor/node_graphics_cutline.py +57 -0
  15. nodeeditor/node_graphics_edge.py +375 -0
  16. nodeeditor/node_graphics_edge_path.py +226 -0
  17. nodeeditor/node_graphics_node.py +693 -0
  18. nodeeditor/node_graphics_scene.py +369 -0
  19. nodeeditor/node_graphics_socket.py +262 -0
  20. nodeeditor/node_graphics_view.py +907 -0
  21. nodeeditor/node_group.py +1286 -0
  22. nodeeditor/node_group_node.py +5 -0
  23. nodeeditor/node_icon_content_widget.py +176 -0
  24. nodeeditor/node_icon_graphics_node.py +524 -0
  25. nodeeditor/node_multi_input_node.py +450 -0
  26. nodeeditor/node_node.py +848 -0
  27. nodeeditor/node_scene.py +1106 -0
  28. nodeeditor/node_scene_clipboard.py +314 -0
  29. nodeeditor/node_scene_history.py +556 -0
  30. nodeeditor/node_serializable.py +43 -0
  31. nodeeditor/node_socket.py +445 -0
  32. nodeeditor/node_socket_shapes.py +261 -0
  33. nodeeditor/py.typed +0 -0
  34. nodeeditor/qss/nodestyle.qss +3 -0
  35. nodeeditor/utils.py +108 -0
  36. nodeeditor/utils_no_qt.py +21 -0
  37. nodeeditor_qtpy-0.8.1.dist-info/METADATA +165 -0
  38. nodeeditor_qtpy-0.8.1.dist-info/RECORD +40 -0
  39. nodeeditor_qtpy-0.8.1.dist-info/WHEEL +4 -0
  40. nodeeditor_qtpy-0.8.1.dist-info/licenses/LICENCE +23 -0
@@ -0,0 +1,314 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A module containing all code for working with Clipboard
4
+ """
5
+ from collections import OrderedDict
6
+ from nodeeditor.node_graphics_edge import QDMGraphicsEdge
7
+ from nodeeditor.node_edge import Edge
8
+
9
+ from typing import TYPE_CHECKING, List, Optional, Tuple, Any, Callable
10
+
11
+
12
+ if TYPE_CHECKING:
13
+ from nodeeditor.node_graphics_view import QDMGraphicsView
14
+ from nodeeditor.node_socket import Socket
15
+ from nodeeditor.node_scene import Scene
16
+ from nodeeditor.node_node import Node
17
+
18
+
19
+ DEBUG = False
20
+ DEBUG_PASTING = False
21
+
22
+
23
+ class SceneClipboard():
24
+ """
25
+ Class contains all the code for serialization/deserialization from Clipboard
26
+ """
27
+
28
+ def __init__(self, scene: 'Scene') -> None:
29
+ """
30
+ :param scene: Reference to the :class:`~nodeeditor.node_scene.Scene`
31
+ :type scene: :class:`~nodeeditor.node_scene.Scene`
32
+
33
+ :Instance Attributes:
34
+
35
+ - **scene** - reference to the :class:`~nodeeditor.node_scene.Scene`
36
+ """
37
+ self.scene = scene
38
+
39
+ def serializeSelected(self, delete: bool = False) -> OrderedDict:
40
+ """
41
+ Serializes selected items in the Scene into ``OrderedDict``
42
+
43
+ :param delete: True if you want to delete selected items after serialization. Useful for Cut operation
44
+ :type delete: ``bool``
45
+ :return: Serialized data of current selection in NodeEditor :class:`~nodeeditor.node_scene.Scene`
46
+ """
47
+ if DEBUG:
48
+ print("-- COPY TO CLIPBOARD ---")
49
+
50
+ sel_nodes, sel_edges, sel_sockets = [], [], {}
51
+ sel_node_ids = set()
52
+ sel_groups = []
53
+
54
+ try:
55
+ from nodeeditor.node_group import Group
56
+ except Exception:
57
+ Group = None # type: ignore
58
+
59
+ # sort edges, nodes, and groups (Group has neither .node nor .edge)
60
+ for item in self.scene.grScene.selectedItems():
61
+ try:
62
+ if Group is not None and isinstance(item, Group):
63
+ if item not in sel_groups:
64
+ sel_groups.append(item)
65
+ continue
66
+ except Exception:
67
+ pass
68
+ if hasattr(item, 'node'):
69
+ try:
70
+ if getattr(item.node, 'id', None) in sel_node_ids:
71
+ continue
72
+ sel_nodes.append(item.node.serialize())
73
+ sel_node_ids.add(item.node.id)
74
+ for socket in (item.node.inputs + item.node.outputs):
75
+ sel_sockets[socket.id] = socket
76
+ except Exception:
77
+ continue
78
+ elif isinstance(item, QDMGraphicsEdge):
79
+ sel_edges.append(item.edge)
80
+
81
+ # wholesale: header-selected group pulls in all children + internal edges
82
+ try:
83
+ for grp in list(sel_groups):
84
+ try:
85
+ for node in list(getattr(grp, 'child_nodes', [])):
86
+ try:
87
+ if getattr(node, 'id', None) in sel_node_ids:
88
+ continue
89
+ sel_nodes.append(node.serialize())
90
+ sel_node_ids.add(node.id)
91
+ for socket in (node.inputs + node.outputs):
92
+ sel_sockets[socket.id] = socket
93
+ except Exception:
94
+ continue
95
+ except Exception:
96
+ continue
97
+ # auto-include internal edges of selected groups (both ends now present)
98
+ seen_edge_ids = set(id(e) for e in sel_edges)
99
+ for grp in list(sel_groups):
100
+ try:
101
+ _, _, internal = grp._classifyEdges()
102
+ except Exception:
103
+ internal = []
104
+ for e in list(internal or []):
105
+ try:
106
+ if id(e) not in seen_edge_ids:
107
+ sel_edges.append(e)
108
+ seen_edge_ids.add(id(e))
109
+ except Exception:
110
+ continue
111
+ except Exception:
112
+ pass
113
+
114
+ # debug
115
+ if DEBUG:
116
+ print(" NODES\n ", sel_nodes)
117
+ print(" EDGES\n ", sel_edges)
118
+ print(" SOCKETS\n ", sel_sockets)
119
+
120
+ # remove all edges which are not connected to a nodeeditor in our list
121
+ edges_to_remove = []
122
+ for edge in sel_edges:
123
+ if edge.start_socket.id in sel_sockets and edge.end_socket.id in sel_sockets:
124
+ # if DEBUG: print(" edge is ok, connected with both sides")
125
+ pass
126
+ else:
127
+ if DEBUG:
128
+ print("edge", edge, "is not connected with both sides")
129
+ edges_to_remove.append(edge)
130
+ for edge in edges_to_remove:
131
+ sel_edges.remove(edge)
132
+
133
+ # make final list of edges
134
+ edges_final = []
135
+ for edge in sel_edges:
136
+ edges_final.append(edge.serialize())
137
+
138
+ if DEBUG:
139
+ print("our final edge list:", edges_final)
140
+
141
+ # groups: header-selected groups always included (children auto-pulled above);
142
+ # otherwise include a group iff ALL its children are selected (rubber-band case).
143
+ groups_final = []
144
+ try:
145
+ included = set()
146
+ for grp in list(sel_groups):
147
+ try:
148
+ groups_final.append(grp.serialize())
149
+ included.add(id(grp))
150
+ except Exception:
151
+ continue
152
+ for grp in list(getattr(self.scene, 'groups', [])):
153
+ try:
154
+ if id(grp) in included:
155
+ continue
156
+ children = list(getattr(grp, 'child_nodes', []))
157
+ if not children:
158
+ continue
159
+ if all(getattr(n, 'id', None) in sel_node_ids for n in children):
160
+ groups_final.append(grp.serialize())
161
+ except Exception:
162
+ continue
163
+ except Exception:
164
+ pass
165
+
166
+ data = OrderedDict([
167
+ ('nodes', sel_nodes),
168
+ ('edges', edges_final),
169
+ ('groups', groups_final),
170
+ ])
171
+
172
+ # if CUT (aka delete) remove selected items
173
+ if delete:
174
+ self.scene.getView().deleteSelected()
175
+ # store our history
176
+ self.scene.history.storeHistory(
177
+ "Cut out elements from scene", setModified=True)
178
+
179
+ return data
180
+
181
+ def deserializeFromClipboard(self, data: dict, *args, **kwargs) -> List['Node']:
182
+ """
183
+ Deserializes data from Clipboard.
184
+
185
+ :param data: ``dict`` data for deserialization to the :class:`nodeeditor.node_scene.Scene`.
186
+ :type data: ``dict``
187
+ """
188
+
189
+ hashmap: dict = {}
190
+
191
+ # calculate mouse pointer - scene position
192
+ view = self.scene.getView()
193
+ mouse_scene_pos = view.last_scene_mouse_position
194
+
195
+ # calculate selected objects bbox and center
196
+ minx, max_x, min_y, max_y = 10000000, -10000000, 10000000, -10000000
197
+ for node_data in data['nodes']:
198
+ if 'pos_x' in node_data and 'pos_y' in node_data:
199
+ x, y = node_data['pos_x'], node_data['pos_y']
200
+ else:
201
+ # added support if node pos serializes into `pos` instead of `pos_x` and `pos_y`
202
+ x, y = node_data['pos']
203
+ if x < minx:
204
+ minx = x
205
+ if x > max_x:
206
+ max_x = x
207
+ if y < min_y:
208
+ min_y = y
209
+ if y > max_y:
210
+ max_y = y
211
+
212
+ # add width and height of a node
213
+ max_x -= 180
214
+ max_y += 100
215
+
216
+ relbboxcenterx = (minx + max_x) / 2 - minx
217
+ relbboxcentery = (min_y + max_y) / 2 - min_y
218
+
219
+ if DEBUG_PASTING:
220
+ print(" *** PASTA:")
221
+ print("Copied boudaries:\n\tX:", minx,
222
+ max_x, " Y:", min_y, max_y)
223
+ print("\tbbox_center:", relbboxcenterx, relbboxcentery)
224
+
225
+ # calculate the offset of the newly creating nodes
226
+ mouse_x, mouse_y = mouse_scene_pos.x(), mouse_scene_pos.y()
227
+
228
+ # create each node
229
+ created_nodes: List['Node'] = []
230
+
231
+ self.scene.setSilentSelectionEvents()
232
+
233
+ self.scene.doDeselectItems()
234
+
235
+ node_id_map = {} # old node id -> new Node (for group relink)
236
+ for node_data in data.get('nodes', []):
237
+ try:
238
+ old_id = node_data.get('id', None)
239
+ except Exception:
240
+ old_id = None
241
+ new_node = self.scene.getNodeClassFromData(node_data)(self.scene)
242
+ new_node.deserialize(node_data, hashmap, False, *args, **kwargs)
243
+ created_nodes.append(new_node)
244
+ if old_id is not None:
245
+ try:
246
+ node_id_map[old_id] = new_node
247
+ except Exception:
248
+ pass
249
+
250
+ # readjust the new nodeeditor's position
251
+
252
+ # new node's current position
253
+ pos_x, pos_y = new_node.pos.x(), new_node.pos.y()
254
+ new_x, new_y = mouse_x + pos_x - minx, mouse_y + pos_y - min_y
255
+
256
+ new_node.setPos(new_x, new_y)
257
+
258
+ new_node.doSelect()
259
+
260
+ if DEBUG_PASTING:
261
+ print("** PASTA SUM:")
262
+ print("\tMouse pos:", mouse_x, mouse_y)
263
+ print("\tnew node pos:", pos_x, pos_y)
264
+ print("\tFINAL:", new_x, new_y)
265
+
266
+ # create each edge
267
+ if 'edges' in data:
268
+ for edge_data in data['edges']:
269
+ new_edge = Edge(self.scene)
270
+ # kwargs_copy = kwargs.copy()
271
+ # if 'restore_id' in kwargs_copy:
272
+ # del kwargs_copy['restore_id']
273
+ new_edge.deserialize(edge_data, hashmap,
274
+ False, *args, **kwargs)
275
+
276
+ # create each group (v2 "children", old "child_node_ids" best-effort)
277
+ if 'groups' in data:
278
+ try:
279
+ from nodeeditor.node_group import Group
280
+ for group_data in list(data.get('groups', []) or []):
281
+ try:
282
+ title = group_data.get('title', 'Group')
283
+ grp = Group(self.scene, title=title)
284
+ # link remapped children
285
+ child_ids = group_data.get('children', group_data.get('child_node_ids', []))
286
+ for cid in list(child_ids or []):
287
+ nn = node_id_map.get(cid, None)
288
+ if nn is not None:
289
+ grp.addNode(nn)
290
+ grp.updateBounds()
291
+ # preserve collapsed flag (old payloads forced expanded in Group.deserialize,
292
+ # but clipboard groups are always v2 so collapsed is honored)
293
+ try:
294
+ want_collapsed = bool(group_data.get('collapsed', group_data.get('is_collapsed', False)))
295
+ except Exception:
296
+ want_collapsed = False
297
+ if want_collapsed and grp.getChildNodes():
298
+ grp.collapse()
299
+ try:
300
+ grp.setSelected(True)
301
+ except Exception:
302
+ pass
303
+ except Exception:
304
+ continue
305
+ except Exception:
306
+ pass
307
+
308
+ self.scene.setSilentSelectionEvents(False)
309
+
310
+ # store history
311
+ self.scene.history.storeHistory(
312
+ "Pasted elements in scene", setModified=True)
313
+
314
+ return created_nodes