controller.py (24261B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\nodegraph\client\ui\controller.py 2 from __future__ import print_function 3 from copy import deepcopy 4 import blue 5 import signals 6 import threadutils 7 from utillib import KeyVal 8 import uthread2 9 import uuid 10 import eveformat 11 from nodegraph.common.nodedata import get_node_graph_data, reload_node_graph_data, get_node_type, get_remote_graph_id_from_data, get_scope_from_data 12 from nodegraph.common.atomdata import get_atom_data 13 from nodegraph.client.graph import ClientNodeGraph 14 from nodegraph.client.nodes import get_node_class 15 from nodegraph.client.custom_graph import get_custom_node_graph, get_custom_node_graph_authored_id, get_custom_node_graphs, save_custom_node_graphs, delete_custom_node_graph, customize_fsd_graph, save_fsd_node_graph, update_parameter, update_parameter_connection, get_new_node_data, copy_graph_to_clipboard, convert_to_dict_nodes, convert_from_clipboard 16 17 def open_node_graph(node_graph_id): 18 from nodegraph.client.ui.window import NodeGraphEditorWindow 19 NodeGraphEditorWindow.Open(node_graph_id=node_graph_id) 20 21 22 class NodeGraphController(object): 23 24 def __init__(self, node_graph_id): 25 self.node_graph_id = node_graph_id 26 self.node_graph = None 27 self.selected_nodes = set() 28 self._settings = None 29 self._node_controllers = {} 30 self.on_update = signals.Signal('on_update') 31 self.on_selected_nodes = signals.Signal('on_selected_nodes') 32 self.on_focus_node = signals.Signal('on_focus_node') 33 self.on_highlight_node = signals.Signal('on_highlight_node') 34 sm.RegisterForNotifyEvent(self, 'OnNodeGraphStarted') 35 sm.RegisterForNotifyEvent(self, 'OnNodeGraphStopped') 36 self._initialize() 37 38 def _initialize(self): 39 from .controllers import get_node_graph_settings 40 self._settings = get_node_graph_settings(self.node_graph_id) 41 42 def close(self): 43 sm.UnregisterForNotifyEvent(self, 'OnNodeGraphStarted') 44 sm.UnregisterForNotifyEvent(self, 'OnNodeGraphStopped') 45 46 def OnNodeGraphStarted(self, instance_id, node_graph_id): 47 node_graph = self.service.get_active_node_graph(instance_id) or self.service.log_graphs.get(instance_id) 48 if node_graph and node_graph.graph_id == self.node_graph_id: 49 if hasattr(self.node_graph, 'instance_id'): 50 self.on_update() 51 else: 52 open_node_graph(instance_id) 53 54 def OnNodeGraphStopped(self, instance_id, node_graph_id): 55 if node_graph_id == self.node_graph_id: 56 self.on_update() 57 58 @property 59 def service(self): 60 return sm.GetService('node_graph') 61 62 @property 63 def name(self): 64 return self.node_graph.name 65 66 @property 67 def description(self): 68 return self.node_graph.description 69 70 @property 71 def nodes_data(self): 72 return self.node_graph.nodes_data 73 74 @property 75 def parent_graph_id(self): 76 return self.node_graph.parent_graph_id 77 78 @property 79 def tags(self): 80 return self.node_graph.tags 81 82 @property 83 def is_server_graph(self): 84 return 'server' in self.tags 85 86 def get_node_data(self, node_id): 87 return self.nodes_data[node_id] 88 89 def get_node_controller(self, node_id): 90 from nodegraph.client.ui.controllers.node_controller import NodeViewController 91 if node_id not in self._node_controllers: 92 self._node_controllers[node_id] = NodeViewController(node_id=node_id, graph_controller=self) 93 return self._node_controllers[node_id] 94 95 def get_node_context_menu(self, node_id): 96 node_data = self.nodes_data[node_id] 97 nodeTypeID = node_data.nodeType 98 node_type_name = get_node_type(nodeTypeID).name 99 m = [['Copy nodeID: %s' % node_id, lambda *args: self._copy_value(node_id)], ['Copy nodeTypeID: %s (%s)' % (node_type_name, nodeTypeID), lambda *args: self._copy_value(nodeTypeID)]] 100 if 'atom_id' in node_data.nodeParameters: 101 atom_id = node_data.nodeParameters['atom_id'].value 102 m += [['Copy atomID: %s' % atom_id, lambda *args: self._copy_value(atom_id)]] 103 return m 104 105 def _copy_value(self, copyValue, *args): 106 return blue.pyos.SetClipboardData(unicode(copyValue)) 107 108 def get_node_class_title(self, node_id): 109 node_data = self.nodes_data[node_id] 110 node_class = get_node_class(node_data.nodeType) 111 if node_class: 112 return node_class.get_title(node_data) 113 return self._get_atom_or_node_type(node_id).name 114 115 def get_node_class_subtitle(self, node_id): 116 node_data = self.nodes_data[node_id] 117 node_class = get_node_class(node_data.nodeType) 118 if node_class: 119 return node_class.get_subtitle(node_data) 120 return '' 121 122 def get_node_description(self, node_id): 123 return self._get_atom_or_node_type(node_id).description or '' 124 125 def get_node_color(self, node_id): 126 return get_node_type(self.nodes_data[node_id].nodeType).color[:3] 127 128 def get_node_tags(self, node_id): 129 node_data = self.nodes_data[node_id] 130 if 'atom_id' in node_data.nodeParameters: 131 return get_atom_data(node_data.nodeParameters['atom_id'].value).tags 132 return [] 133 134 def get_node_output_description(self, node_id): 135 ouput_parameters = self.get_node_output_parameters(node_id) 136 outputs = [] 137 for parameter in ouput_parameters: 138 outputs.append('{} ({})'.format(parameter.parameterKey, parameter.parameterType)) 139 140 return '\n'.join(outputs) 141 142 def get_node_output_parameters(self, node_id): 143 return self._get_atom_or_node_type(node_id).parameters.outputs 144 145 def get_node_input_parameters(self, node_id): 146 input_parameters = [] 147 node_type = get_node_type(self.nodes_data[node_id].nodeType) 148 input_parameters.extend([ p for p in node_type.parameters.inputs if p.parameterKey != 'atom_id' ]) 149 if 'atom_id' in self.nodes_data[node_id].nodeParameters: 150 atom_data = get_atom_data(self.nodes_data[node_id].nodeParameters['atom_id'].value) 151 input_parameters.extend(atom_data.parameters.inputs) 152 return input_parameters 153 154 def _get_atom_or_node_type(self, node_id): 155 node_data = self.nodes_data[node_id] 156 if 'atom_id' in node_data.nodeParameters: 157 return get_atom_data(node_data.nodeParameters['atom_id'].value) 158 return get_node_type(node_data.nodeType) 159 160 @property 161 def can_edit(self): 162 return False 163 164 @property 165 def zoom_level(self): 166 return self._settings.zoom_level 167 168 @zoom_level.setter 169 def zoom_level(self, value): 170 self._settings.zoom_level = value 171 172 @property 173 def pan_left(self): 174 return self._settings.pan_left 175 176 @pan_left.setter 177 def pan_left(self, value): 178 self._settings.pan_left = value 179 180 @property 181 def pan_top(self): 182 return self._settings.pan_top 183 184 @pan_top.setter 185 def pan_top(self, value): 186 self._settings.pan_top = value 187 188 def open_graph(self, graph_id): 189 open_node_graph(graph_id) 190 191 def open_fsd_page(self, node_graph_id = None): 192 import webbrowser 193 webbrowser.open_new('http://localhost:8000/nodegraphs/{}/'.format(node_graph_id or self.node_graph_id)) 194 195 def toggle_selection(self, node_id): 196 if node_id in self.selected_nodes: 197 self.selected_nodes.remove(node_id) 198 else: 199 self.selected_nodes.add(node_id) 200 self.on_selected_nodes(self.selected_nodes) 201 202 def add_to_selection(self, node_id): 203 self.add_selection([node_id]) 204 205 def add_selection(self, node_ids): 206 for node_id in node_ids: 207 if node_id in self.selected_nodes: 208 continue 209 self.selected_nodes.add(node_id) 210 211 self.on_selected_nodes(self.selected_nodes) 212 213 def select_nodes(self, node_ids): 214 self.selected_nodes = set(node_ids) 215 self.on_selected_nodes(self.selected_nodes) 216 217 def select_all_nodes(self): 218 self.selected_nodes = set(self.nodes_data.keys()) 219 self.on_selected_nodes(self.selected_nodes) 220 221 def clear_selection(self): 222 self.selected_nodes.clear() 223 self.on_selected_nodes(self.selected_nodes) 224 225 def highlight_node(self, node_id): 226 self.on_highlight_node(node_id, True) 227 228 def unhighlight_node(self, node_id): 229 self.on_highlight_node(node_id, False) 230 231 def focus_node(self, node_id): 232 self.on_focus_node(node_id) 233 234 235 class ActiveNodeGraphController(NodeGraphController): 236 237 def __init__(self, node_graph_id): 238 node_graph = self.service.get_active_node_graph(node_graph_id) 239 self.node_graph_id = node_graph.graph_id 240 super(ActiveNodeGraphController, self).__init__(self.node_graph_id) 241 self.node_graph = node_graph 242 243 def stop_node_graph(self, *args, **kwargs): 244 self.service.stop_node_graph(self.node_graph.instance_id) 245 246 def stop_active_nodes(self, *args, **kwargs): 247 self.node_graph.stop_active_nodes() 248 249 def get_node_context_menu(self, node_id): 250 m = super(ActiveNodeGraphController, self).get_node_context_menu(node_id) 251 if not self.node_graph.is_active: 252 return m 253 return [('Start node', lambda : self.node_graph.start_node(node_id)), ('Stop node', lambda : self.node_graph.stop_node(node_id)), None] + m 254 255 def go_to_graph(self): 256 open_node_graph(self.node_graph_id) 257 258 259 class LogNodeGraphController(NodeGraphController): 260 261 def __init__(self, node_graph_id): 262 node_graph = self.service.log_graphs.get(node_graph_id) 263 self.node_graph_id = node_graph.graph_id 264 super(LogNodeGraphController, self).__init__(self.node_graph_id) 265 self.node_graph = node_graph 266 267 def stop_node_graph(self, *args, **kwargs): 268 self.node_graph.stop_node_graph() 269 270 def stop_active_nodes(self, *args, **kwargs): 271 self.node_graph.stop_active_nodes() 272 273 def get_node_context_menu(self, node_id): 274 m = super(LogNodeGraphController, self).get_node_context_menu(node_id) 275 if not self.node_graph.is_active: 276 return m 277 return [('Start node', lambda : self.node_graph.start_node(node_id)), ('Stop node', lambda : self.node_graph.stop_node(node_id)), None] + m 278 279 def go_to_graph(self): 280 open_node_graph(self.node_graph_id) 281 282 283 class AuthoredNodeGraphController(NodeGraphController): 284 285 def __init__(self, node_graph_id): 286 super(AuthoredNodeGraphController, self).__init__(node_graph_id) 287 self._load_from_fsd() 288 self._is_building = False 289 290 def _load_from_fsd(self): 291 fsd_data = get_node_graph_data(self.node_graph_id) 292 self.node_graph = KeyVal(name=fsd_data.name, description=fsd_data.description, nodes_data=fsd_data.nodes, tags=fsd_data.tags, parent_graph_id=fsd_data.parentGraph, remote_graph_id=get_remote_graph_id_from_data(fsd_data), scope=get_scope_from_data(fsd_data)) 293 294 def start_node_graph(self, *args, **kwargs): 295 if self.is_server_graph: 296 self.service.qa_start_node_graph(self.node_graph_id) 297 else: 298 self.service.start_node_graph(self.node_graph_id) 299 300 def reload_from_fsd(self, *args, **kwargs): 301 reload_node_graph_data() 302 self._load_from_fsd() 303 self.on_update() 304 305 @property 306 def is_building(self): 307 return self._is_building 308 309 @property 310 def has_custom_graph(self): 311 custom_node_graph_id = 'custom_{}'.format(self.node_graph_id) 312 return bool(get_custom_node_graph(custom_node_graph_id)) 313 314 def customize_graph(self, *args, **kwargs): 315 custom_node_graph_id = customize_fsd_graph(self.node_graph_id) 316 open_node_graph(custom_node_graph_id) 317 318 319 class CustomNodeGraphController(NodeGraphController): 320 321 def __init__(self, node_graph_id): 322 super(CustomNodeGraphController, self).__init__(node_graph_id) 323 authored_id = get_custom_node_graph_authored_id(node_graph_id) 324 if authored_id: 325 fsd_data = get_node_graph_data(authored_id) 326 name = fsd_data.name 327 description = fsd_data.description 328 tags = fsd_data.tags 329 parent_graph_id = fsd_data.parentGraph 330 remote_graph_id = get_remote_graph_id_from_data(fsd_data) 331 scope = get_scope_from_data(fsd_data) 332 else: 333 name = node_graph_id 334 description = '' 335 tags = [] 336 parent_graph_id = None 337 remote_graph_id = None 338 scope = None 339 self.node_graph = KeyVal(name=name, description=description, nodes_data=get_custom_node_graph(node_graph_id, as_object=True), tags=tags, parent_graph_id=parent_graph_id, remote_graph_id=remote_graph_id, scope=scope) 340 self.selected_connection_point = None 341 self.on_selected_connection_point = signals.Signal('on_selected_connection_point') 342 343 def _initialize(self): 344 from .controllers import get_node_graph_settings 345 self._settings = get_node_graph_settings(self.get_authored_id() or self.node_graph_id) 346 347 @property 348 def can_edit(self): 349 return True 350 351 def open_fsd_page(self, node_graph_id = None): 352 node_graph_id = self.get_authored_id() 353 if node_graph_id: 354 super(CustomNodeGraphController, self).open_fsd_page(node_graph_id) 355 356 def start_node_graph(self, *args, **kwargs): 357 if self.is_server_graph: 358 return 359 node_graph = ClientNodeGraph(graph_id=self.node_graph_id, graph_data=KeyVal(name=self.name, description=self.description, nodes=self.nodes_data, tags=self.tags)) 360 self.service._active_graphs[node_graph.instance_id] = node_graph 361 open_node_graph(node_graph.instance_id) 362 node_graph.start_node_graph() 363 364 def get_authored_id(self): 365 return get_custom_node_graph_authored_id(self.node_graph_id) 366 367 def copy_graph_to_clipboard(self): 368 copy_graph_to_clipboard(self.node_graph_id) 369 370 def copy_nodes_to_clipboard(self, node_ids): 371 copy_graph_to_clipboard(self.node_graph_id, node_ids=node_ids) 372 373 def clear_selected_connection_point(self): 374 if self.selected_connection_point: 375 self.selected_connection_point = None 376 self.on_selected_connection_point(self.selected_connection_point) 377 378 def node_connection_clicked(self, connection_point): 379 self.clear_selection() 380 if not self.selected_connection_point: 381 self.selected_connection_point = connection_point 382 self.on_selected_connection_point(self.selected_connection_point) 383 return 384 input_point = None 385 output_point = None 386 if 'input' in connection_point.connection_type: 387 input_point = connection_point 388 else: 389 output_point = connection_point 390 if 'input' in self.selected_connection_point.connection_type: 391 input_point = self.selected_connection_point 392 else: 393 output_point = self.selected_connection_point 394 if not input_point or not output_point: 395 print('cannot connect of same group - input->input or output->output') 396 self.clear_selected_connection_point() 397 return 398 if output_point.node_id == input_point.node_id: 399 print('cannot connect the same node - input->output or output->input') 400 self.clear_selected_connection_point() 401 return 402 if 'variable' in output_point.connection_type and 'variable' in input_point.connection_type: 403 self._add_variable_connection(output_point.node_id, output_point.connection_id, input_point.node_id, input_point.connection_id) 404 elif 'variable' not in output_point.connection_type and 'variable' not in input_point.connection_type: 405 self.add_node_connection(output_point.node_id, input_point.node_id, output_point.connection_id) 406 else: 407 print('cannot connect variable to non-variable port') 408 self.clear_selected_connection_point() 409 410 def get_node_context_menu(self, node_id): 411 m = super(CustomNodeGraphController, self).get_node_context_menu(node_id) 412 if self.selected_nodes: 413 result = [('MULTIPLE NODES SELECTED', None), 414 ('Copy to clipboard', lambda : self.copy_nodes_to_clipboard(self.selected_nodes)), 415 (eveformat.color('! Remove nodes', (1, 0, 0)), lambda : self.remove_nodes(self.selected_nodes)), 416 None] + m 417 else: 418 result = [('Duplicate node', lambda : self.duplicate_node(node_id)), 419 ('Copy to clipboard', lambda : self.copy_nodes_to_clipboard([node_id])), 420 None, 421 (eveformat.color('! Remove node', (1, 0, 0)), lambda : self.remove_nodes([node_id])), 422 None] 423 return result + m 424 425 def add_node(self, node_type_id, position, atom_type_id): 426 node_id = self._get_new_node_id() 427 self.nodes_data[node_id] = get_new_node_data(node_type_id, position, atom_type_id) 428 if self.selected_connection_point: 429 if self.selected_connection_point.connection_type == 'output': 430 self.add_node_connection(self.selected_connection_point.node_id, node_id, self.selected_connection_point.connection_id) 431 self.clear_selected_connection_point() 432 self.save_graph() 433 return node_id 434 435 def duplicate_node(self, node_id): 436 node_data = KeyVal(deepcopy(self.nodes_data[node_id].__dict__)) 437 node_data['connections'] = {connection_id:[] for connection_id in node_data['connections']} 438 for parameter_id, parameter in node_data['nodeParameters'].items(): 439 if parameter.connection: 440 del node_data['nodeParameters'][parameter_id] 441 442 for parameter_id, parameter in node_data.get('atomParameters', {}).items(): 443 if parameter.connection: 444 del node_data['atomParameters'][parameter_id] 445 446 node_data['position'] = [node_data['position'][0] + 3, node_data['position'][1] + 1] 447 new_node_id = self._get_new_node_id() 448 self.nodes_data[new_node_id] = node_data 449 self.save_graph() 450 451 def remove_nodes(self, node_ids): 452 for node_id in node_ids: 453 for from_node_id in self.nodes_data: 454 if from_node_id == node_id: 455 continue 456 connections = self.nodes_data[from_node_id]['connections'] 457 for connection_id in connections: 458 try: 459 connections[connection_id].remove(node_id) 460 except Exception: 461 pass 462 463 for parameter_id, parameter in self.nodes_data[from_node_id]['nodeParameters'].items(): 464 if parameter.connection and parameter.connection.node == node_id: 465 del self.nodes_data[from_node_id]['nodeParameters'][parameter_id] 466 467 for parameter_id, parameter in self.nodes_data[from_node_id].get('atomParameters', {}).items(): 468 if parameter.connection and parameter.connection.node == node_id: 469 del self.nodes_data[from_node_id]['atomParameters'][parameter_id] 470 471 self.nodes_data.pop(node_id, None) 472 473 self.save_graph() 474 475 def add_node_connection(self, from_node_id, to_node_id, connection_id): 476 if 'input' not in get_node_type(self.nodes_data[to_node_id].nodeType).ports.inPorts: 477 return 478 if connection_id not in self.nodes_data[from_node_id]['connections']: 479 self.nodes_data[from_node_id]['connections'][connection_id] = [] 480 connections = self.nodes_data[from_node_id]['connections'][connection_id] 481 if to_node_id not in connections: 482 connections.append(to_node_id) 483 self.save_graph() 484 485 def remove_all_connection_lines(self, connection_lines): 486 for connection_line in connection_lines: 487 self.remove_connection_line(connection_line) 488 489 def remove_connection_line(self, connection_line): 490 if connection_line.point_b.connection_type == 'input_variable': 491 self.remove_variable_connection(connection_line.point_b.node_id, connection_line.point_b.connection_id) 492 else: 493 self.remove_node_connection(connection_line.point_a.node_id, connection_line.point_b.node_id, connection_line.point_a.connection_id) 494 495 def remove_all_node_connections(self, node_id, connection_id): 496 self.nodes_data[node_id]['connections'][connection_id] = [] 497 self.save_graph() 498 499 def remove_node_connection(self, from_node_id, to_node_id, connection_id): 500 connections = self.nodes_data[from_node_id]['connections'][connection_id] 501 if to_node_id in connections: 502 connections.remove(to_node_id) 503 self.save_graph() 504 505 def remove_variable_connection(self, node_id, parameter_id): 506 update_parameter_connection(self.nodes_data[node_id], parameter_id, None, None) 507 self.save_graph() 508 509 def paste_from_clipboard(self, position, clipboard_data): 510 import yaml 511 nodes_data = convert_from_clipboard(yaml.safe_load(clipboard_data or '{}'), position) 512 if nodes_data: 513 self.nodes_data.update(nodes_data) 514 self.save_graph() 515 516 def move_node(self, node_id, position): 517 self.nodes_data[node_id]['position'] = position 518 self.save_graph() 519 520 def move_nodes(self, node_ids, main_node_id, new_position): 521 current_position = self.nodes_data[main_node_id]['position'] 522 pos_diff_x = new_position[0] - current_position[0] 523 pos_diff_y = new_position[1] - current_position[1] 524 adjust_x = 0 525 adjust_y = 0 526 for node_id in node_ids: 527 position = self.nodes_data[node_id]['position'] 528 new_pos_x = position[0] + pos_diff_x 529 new_pos_y = position[1] + pos_diff_y 530 if new_pos_x < 0: 531 adjust_x = min(adjust_x, new_pos_x) 532 if new_pos_y < 0: 533 adjust_y = min(adjust_y, new_pos_y) 534 535 pos_diff_x -= adjust_x 536 pos_diff_y -= adjust_y 537 for node_id in node_ids: 538 position = self.nodes_data[node_id]['position'] 539 new_pos_x = position[0] + pos_diff_x 540 new_pos_y = position[1] + pos_diff_y 541 self.nodes_data[node_id]['position'] = (new_pos_x, new_pos_y) 542 543 self.save_graph() 544 545 def edit_nodes_data(self, data): 546 self.node_graph.nodes_data = data 547 self.save_graph() 548 549 def update_node_parameter(self, node_id, parameter_id, value): 550 update_parameter(self.nodes_data[node_id], parameter_id, value) 551 self.save_graph() 552 553 def update_node_comment(self, node_id, comment): 554 if not comment: 555 comment = None 556 if self.nodes_data[node_id].comment != comment: 557 self.nodes_data[node_id].comment = comment 558 self.save_graph() 559 560 @uthread2.debounce(wait=0.1) 561 def save_graph(self): 562 self.clear_selection() 563 custom_node_graphs = get_custom_node_graphs() 564 data = convert_to_dict_nodes(self.nodes_data) 565 custom_node_graphs[self.node_graph_id] = data 566 save_custom_node_graphs(custom_node_graphs) 567 self.node_graph.nodes_data = get_custom_node_graph(self.node_graph_id, as_object=True) 568 self.on_update() 569 570 @threadutils.threaded 571 def save_to_fsd(self): 572 authored_id = self.get_authored_id() 573 if not authored_id: 574 return 575 open_node_graph(authored_id) 576 save_fsd_node_graph(authored_id, get_custom_node_graph(self.node_graph_id)) 577 578 def delete_graph(self, *args, **kwargs): 579 authored_id = self.get_authored_id() 580 delete_custom_node_graph(self.node_graph_id) 581 open_node_graph(authored_id) 582 583 def go_to_authored_graph(self): 584 authored_id = self.get_authored_id() 585 if authored_id: 586 open_node_graph(authored_id) 587 588 def _get_new_node_id(self): 589 return str(uuid.uuid4()) 590 591 def _add_variable_connection(self, from_node_id, from_key, to_node_id, to_key): 592 update_parameter_connection(self.nodes_data[to_node_id], to_key, from_node_id, from_key) 593 self.save_graph()