def update(self, input_called=-1, output_called=-1): Debugger.debug('update in', self.parent_node.title, 'on input', input_called) try: self.update_event(input_called) except Exception as e: Debugger.debug('EXCEPTION IN', self.parent_node.title, 'NI:', e)
def get_var(self, name): Debugger.debug('getting variable with name:', name) for v in self.variables: if v.name == name: return v return None
def remove_node_instance(self, ni): ni.about_to_remove_from_scene() # to stop running threads self.scene().removeItem(ni) Debugger.debug('calling ni removed') self.all_node_instances.remove(ni)
def create_new_input(self, type_, label, widget_type='', widget_name='', widget_pos='under', pos=-1): """Creates and adds a new input. Handy for subclasses.""" Debugger.debug('create_new_input called with widget pos:', widget_pos) pi = PortInstance(self, 'input', type_, label, widget_type=widget_type, widget_name=widget_name, widget_pos=widget_pos) if pos == -1: self.inputs.append(pi) else: if pos == -1: self.inputs.insert(0, pi) else: self.inputs.insert(pos, pi) if self.scene(): self.add_input_to_scene(pi) if not self.initializing: self.update_shape()
def import_nodes_package(self, file_path): j_str = '' try: f = open(file_path) j_str = f.read() f.close() except FileExistsError or FileNotFoundError: Debugger.debug('couldn\'t open file') return # don't import a package twice if it already has been imported filename = os.path.splitext(os.path.basename(file_path)) if filename in self.package_names: return # Important: translate the package first (metacore files -> src code files) PackageTranslator = self.get_class_from_file( file_path='../Ryven_PackageTranslator', file_name='Ryven_PackageTranslator', class_name='PackageTranslator') package_translator = PackageTranslator( os.path.dirname(os.path.abspath(file_path))) self.parse_nodes(j_str, package_path=os.path.dirname(file_path), package_name=os.path.splitext( os.path.basename(file_path))[0]) # self.package_names.append(filename)
def input(self, index): """Returns the value of a data input. If the input is connected, the value of the connected output is used: If not, the value of the widget is used.""" Debugger.debug('input called in', self.parent_node.title, 'NI:', index) return self.inputs[index].get_val()
def create_new_input(self, type_, label, widget_name=None, widget_pos='under', pos=-1, config=None): """Creates and adds a new input. Handy for subclasses.""" Debugger.debug('create_new_input called') pi = InputPortInstance(self, type_, label, config_data=config, widget_name=widget_name, widget_pos=widget_pos) if pos < -1: pos += len(self.inputs) if pos == -1: self.inputs.append(pi) self.add_input_to_layout(pi) else: self.inputs.insert(pos, pi) self.insert_input_into_layout(pos, pi) if not self.initializing: self.update_shape() self.update()
def parse_nodes(self, j_str, package_path, package_name): """Parses the nodes from a node package in JSON format. Here, all the classes get imported and for every node a Node object with specific attribute values gets created.""" import json # strict=False is necessary to allow 'control characters' like '\n' for newline when loading the json j_obj = json.loads(j_str, strict=False) Debugger.debug(j_obj['type']) if j_obj['type'] != 'Ryven nodes package' and j_obj[ 'type'] != 'vyScriptFP nodes package': # old syntax return # package_title = j_obj['title'] # package_description = j_obj['description'] j_nodes_list = j_obj['nodes'] num_nodes = len(j_nodes_list) for ni in range(num_nodes): j_node = j_nodes_list[ni] self.parse_node(j_node, package_name, package_path) Debugger.debug(len(self.custom_nodes), 'nodes imported')
def save_project(self, file_name): import json file = None try: if os.path.exists(file_name): os.remove(file_name) file = open(file_name, 'w') except FileNotFoundError: Debugger.debug('couldn\'t open file') return general_project_info_dict = {'type': 'Ryven project file'} scripts_data = [] for script in self.scripts: scripts_data.append(script.get_json_data()) whole_project_dict = { 'general info': general_project_info_dict, 'scripts': scripts_data } json_str = json.dumps(whole_project_dict) Debugger.debug(json_str) file.write(json_str) file.close()
def data_outputs_updated(self): """(outdated!) Sends update signals to all data outputs causing connected NIs to update.""" Debugger.debug('updating data outputs in', self.parent_node.title) for o in self.outputs: if o.type_ == 'data': o.updated_val() Debugger.debug('data outputs in', self.parent_node.title, 'updated')
def tabletEvent(self, event): """tabletEvent gets called by stylus operations. LeftButton: std, no button pressed RightButton: upper button pressed""" # if in edit mode and not panning or starting a pan, pass on to std mouseEvent handlers above if self.stylus_mode == 'edit' and not self.panning and not \ (event.type() == QTabletEvent.TabletPress and event.button() == Qt.RightButton): return # let the mousePress/Move/Release-Events handle it scaled_event_pos: QPointF = event.posF() / self.current_scale if event.type() == QTabletEvent.TabletPress: self.ignore_mouse_event = True if event.button() == Qt.LeftButton: if self.stylus_mode == 'comment': view_pos = self.mapToScene(self.viewport().pos()) new_drawing = self.create_and_place_drawing__cmd( view_pos + scaled_event_pos, config={ **self.stylus_modes_widget.get_pen_settings(), 'viewport pos': view_pos }) self.current_drawing = new_drawing self.drawing = True elif event.button() == Qt.RightButton: self.panning = True self.pan_last_x = event.x() self.pan_last_y = event.y() elif event.type() == QTabletEvent.TabletMove: self.ignore_mouse_event = True if self.panning: self.pan(event.pos()) elif event.pointerType() == QTabletEvent.Eraser: if self.stylus_mode == 'comment': for i in self.items(event.pos()): if find_type_in_object(i, DrawingObject): self.remove_drawing(i) break elif self.stylus_mode == 'comment' and self.drawing: if self.current_drawing.append_point(scaled_event_pos): self.current_drawing.stroke_weights.append( event.pressure()) self.current_drawing.update() self.viewport().update() elif event.type() == QTabletEvent.TabletRelease: if self.panning: self.panning = False if self.stylus_mode == 'comment' and self.drawing: Debugger.debug('drawing obj finished') self.current_drawing.finished() self.current_drawing = None self.drawing = False
def tabletEvent(self, event): """tabletEvent gets called by stylus operations. LeftButton: std, no button pressed RightButton: upper button pressed""" # if in edit mode and not panning or starting a pan, pass on to std mouseEvent handlers above if self.stylus_mode == 'edit' and not self.panning and not \ (event.type() == QTabletEvent.TabletPress and event.button() == Qt.RightButton): return # let the mousePress/Move/Release-Events handle it if event.type() == QTabletEvent.TabletPress: self.tablet_press_pos = event.pos() self.ignore_mouse_event = True if event.button() == Qt.LeftButton: if self.stylus_mode == 'comment': new_drawing = self.create_and_place_drawing__cmd(self.mapToScene(self.tablet_press_pos), config=self.stylus_modes_widget.get_pen_settings()) self.current_drawing = new_drawing self.drawing = True elif event.button() == Qt.RightButton: self.panning = True self.pan_last_x = event.x() self.pan_last_y = event.y() elif event.type() == QTabletEvent.TabletMove: self.ignore_mouse_event = True if self.panning: self.pan(event.pos()) elif event.pointerType() == QTabletEvent.Eraser: if self.stylus_mode == 'comment': for i in self.items(event.pos()): if find_type_in_object(i, DrawingObject): self.remove_drawing(i) break elif self.stylus_mode == 'comment' and self.drawing: mapped = self.mapToScene(QPoint(event.posF().x(), event.posF().y())) # rest = QPointF(event.posF().x()%1, event.posF().y()%1) # exact = QPointF(mapped.x()+rest.x()%1, mapped.y()+rest.y()%1) # TODO: use exact position (event.posF() ). Problem: mapToScene() only uses QPoint, not QPointF. The # calculation above didn't work if self.current_drawing.try_to_append_point(mapped): self.current_drawing.stroke_weights.append(event.pressure()) self.current_drawing.update() self.viewport().update() elif event.type() == QTabletEvent.TabletRelease: if self.panning: self.panning = False if self.stylus_mode == 'comment' and self.drawing: Debugger.debug('drawing obj finished') self.current_drawing.finished() self.current_drawing = None self.drawing = False
def get_sorted_dict_matching_search(self, items_dict, text): indices_dict = {} for item, name in items_dict.items( ): # the strings are already lowered here Debugger.debug(item, name, text) if name.__contains__(text): index = name.index(text) indices_dict[item] = index return { k: v for k, v in sorted(indices_dict.items(), key=lambda i: i[1]) }
def set_val(self, val): """applies on INPUT; called NI internally""" Debugger.debug('setting value of', self.direction, 'port of', self.parent_node_instance.parent_node.title, 'NodeInstance to', val) # note that val COULD be of object type and therefore already changed (because the original object did) self.val = val # if gen_data_on_request is set to true, all data will be required instead of actively forward propagated if not Algorithm.gen_data_on_request: self.updated_val()
def set_val(self, val): """applies on OUTPUT; called NI internally""" Debugger.debug('setting value of', self.direction, 'port of', self.parent_node_instance.parent_node.title, 'NodeInstance to', val) # note that val COULD be of object type and therefore already changed (because the original object did) self.val = val # if algorithm mode would be exec flow, all data will be required instead of actively forward propagated if self.parent_node_instance.flow.algorithm_mode.mode_data_flow and \ not self.parent_node_instance.initializing: self.updated_val()
def update(self, input_called=-1, output_called=-1): """This is the method used to activate a NodeInstance. Note that this signature shadows the update() method from QGraphicsItem used to graphically update a QGraphicsItem which can be accessed via QGraphicsItem.update(self).""" if Design.animations_enabled: self.animator.start() Debugger.debug('update in', self.parent_node.title, 'on input', input_called) try: self.update_event(input_called) except Exception as e: Debugger.debugerr('EXCEPTION IN', self.parent_node.title, 'NI:', e)
def add_package_button_clicked(self): file_names = \ QFileDialog.getOpenFileNames(self, 'select package files', '../packages', 'Ryven Package(*.rpc)')[0] for file_name in file_names: try: f = open(file_name) f.close() self.file_paths.append(file_name) except FileNotFoundError: Debugger.debug('couldn\'t open file') self.rebuild_selected_packages_list_widget()
def set_val(self, val): """applies on INPUT; called NI internally""" Debugger.debug('setting value of', self.direction, 'port of', self.parent_node_instance.parent_node.title, 'NodeInstance to', val) if self.val is val: # no update if value didn't change return self.val = val self.gate.setToolTip(str(val)) self.gate.update() self.updated_val()
def dropEvent(self, event): text = event.mimeData().text() item: QListWidgetItem = event.mimeData() Debugger.debug('drop received in Flow:', text) j_obj = None type = '' try: j_obj = json.loads(text) type = j_obj['type'] except Exception: return if type == 'variable': self.show_node_choice_widget(event.pos(), # only show get_var and set_var nodes [n for n in self.all_nodes if find_type_in_object(n, GetVariable_Node) or find_type_in_object(n, SetVariable_Node)])
def load_project_button_clicked(self): self.editor_startup_configuration['config'] = 'open project' import json file_name = QFileDialog.getOpenFileName( self, 'select project file', '../saves', 'Ryven Project(*.rpo *.rypo)')[0] j_str = '' try: f = open(file_name) j_str = f.read() f.close() except FileNotFoundError: Debugger.debug('couldn\'t open file') return # strict=False has to be to allow 'control characters' like '\n' for newline when loading the json j_obj = json.loads(j_str, strict=False) if j_obj['general info']['type'] != 'Ryven project file': return # scan for all required packages packages = [] package_file_paths = [] scripts = j_obj['scripts'] for script in scripts: flow = script['flow'] for n in flow['nodes']: package = n['parent node package'] if package != 'built in' and not packages.__contains__( package): packages.append(package) if len(packages) > 0: select_packages_dialog = SelectPackages_Dialog(self, packages) select_packages_dialog.exec_() package_file_paths = select_packages_dialog.file_paths self.editor_startup_configuration[ 'required packages'] = package_file_paths self.editor_startup_configuration['content'] = j_obj self.accept()
def import_nodes_package_from_file(self, file_path): j_str = '' try: f = open(file_path) j_str = f.read() f.close() except FileExistsError or FileNotFoundError: Debugger.debug('couldn\'t open file') return # Important: translate the package first (metacore files -> src code files) PackageTranslator = self.get_class_from_file(file_path='../pyScript_PackageTranslator', file_name='pyScript_PackageTranslator', class_name='PackageTranslator') package_translator = PackageTranslator(os.path.dirname(os.path.abspath(file_path))) self.parse_nodes(j_str, os.path.dirname(file_path), os.path.splitext(os.path.basename(file_path))[0])
def mousePressEvent(self, event): Debugger.debug('mouse press event received, point:', event.pos()) # to catch tablet events (for some reason, it results in a mousePrEv too) if self.ignore_mouse_event: self.ignore_mouse_event = False return # there might be a proxy widget meant to receive the event instead of the flow QGraphicsView.mousePressEvent(self, event) # to catch any Proxy that received the event. Checking for event.isAccepted() or what is returned by # QGraphicsView.mousePressEvent(...) both didn't work so far, so I do it manually if self.ignore_mouse_event: self.ignore_mouse_event = False return if event.button() == Qt.LeftButton: if self.node_choice_proxy.isVisible(): self.hide_node_choice_widget() else: if find_type_in_object(self.itemAt(event.pos()), PortInstanceGate): self.gate_selected = self.itemAt(event.pos()) self.dragging_connection = True self.left_mouse_pressed_in_flow = True elif event.button() == Qt.RightButton: if len(self.items(event.pos())) == 0: self.node_choice_widget.reset_list() self.show_node_choice_widget(event.pos()) elif event.button() == Qt.MidButton: self.panning = True self.pan_last_x = event.x() self.pan_last_y = event.y() event.accept() self.mouse_press_pos = self.mapToScene(event.pos())
def get_class_from_file(self, file_path, file_name, class_name): Debugger.debug(file_path) Debugger.debug(file_name) Debugger.debug(class_name) sys.path.append(file_path) new_module = __import__(file_name, fromlist=[class_name]) new_class = getattr(new_module, class_name) return new_class
def get_val(self): """applies on DATA; called NI internally AND externally""" Debugger.debug( 'get value in', self.direction, 'port instance', self.parent_node_instance.inputs.index(self) if self.direction == 'input' else self.parent_node_instance.outputs.index(self), 'of', self.parent_node_instance.parent_node.title) Debugger.debug('my value is', self.val) if self.direction == 'input': if len(self.connected_port_instances) == 0: if self.widget: return self.widget.get_val() else: return None else: Debugger.debug('calling connected port for val') return self.connected_port_instances[0].get_val() elif self.direction == 'output': Debugger.debug('returning val directly') if self.parent_node_instance.gen_data_on_request: self.parent_node_instance.update() return self.val
def parse_nodes(self, j_str, package_path, package_name): import json # strict=False is necessary to allow 'control characters' like '\n' for newline when loading the json j_obj = json.loads(j_str, strict=False) Debugger.debug(j_obj['type']) if j_obj['type'] != 'vyScriptFP nodes package': return # package_title = j_obj['title'] # package_description = j_obj['description'] j_nodes_list = j_obj['nodes'] num_nodes = len(j_nodes_list) for ni in range(num_nodes): # new node j_node = j_nodes_list[ni] new_node = Node() node_title = j_node['title'] node_class_name = j_node['class name'] node_description = j_node['description'] node_type = j_node['type'] node_has_main_widget = j_node['has main widget'] node_main_widget_pos = j_node['widget position'] if node_has_main_widget else None node_design_style = j_node['design style'] node_color = j_node['color'] # every node has a custom module name which differs from it's name to prevent import issues when using # multiple (different) Nodes with same titles # FOR FURTHER EXPLANATION: see node manager node_module_name = j_node['module name'] module_name_separator = '___' # CUSTOM CLASS IMPORTS ---------------------------------------------------------------------------- # creating all the necessary path variables here for all potentially imported classes # IMPORT NODE INSTANCE SUBCLASS node_instance_class_file_path = package_path+'/nodes/'+node_module_name+'/' node_instance_widgets_file_path = node_instance_class_file_path+'/widgets' node_instance_filename = node_module_name # the NI file's name is just the 'module name' new_node_instance_class = self.get_class_from_file(file_path=node_instance_class_file_path, file_name=node_instance_filename, class_name=node_class_name+'_NodeInstance') self.all_node_instance_classes[new_node] = new_node_instance_class # IMPORT MAIN WIDGET if node_has_main_widget: main_widget_filename = node_module_name+module_name_separator+'main_widget' new_node.main_widget_class = self.get_class_from_file(file_path=node_instance_widgets_file_path, file_name=main_widget_filename, class_name=node_class_name+'_NodeInstance_MainWidget') # I need to create the dict for the node's potential custom input widgets already here self.custom_node_input_widget_classes[new_node] = {} for w_name in j_node['custom input widgets']: input_widget_filename = node_module_name+module_name_separator+w_name custom_widget_class = self.get_class_from_file(file_path=node_instance_widgets_file_path, file_name=input_widget_filename, class_name=w_name+'_PortInstanceWidget') self.custom_node_input_widget_classes[new_node][w_name] = custom_widget_class # note: the input widget classes get imported below in the loop # --------------------------------------------------------------------------------------------------- j_n_inputs = j_node['inputs'] inputs = [] num_inputs = len(j_n_inputs) for ii in range(num_inputs): j_input = j_n_inputs[ii] i_type = j_input['type'] i_label = j_input['label'] i_has_widget = None i_widget_type = '' i_widget_name = '' i_widget_pos = None if i_type == 'data': i_has_widget = j_input['has widget'] if i_has_widget: i_widget_type = j_input['widget type'] i_widget_pos = j_input['widget position'] if i_widget_type == 'custom widget': i_widget_name = j_input['widget name'] new_input = NodePort() new_input.type_ = i_type new_input.label = i_label if i_has_widget: new_input.widget_type = i_widget_type new_input.widget_name = i_widget_name if i_widget_pos: new_input.widget_pos = i_widget_pos else: new_input.widget_type = 'None' inputs.append(new_input) j_n_outputs = j_node['outputs'] outputs = [] num_outputs = len(j_n_outputs) for oi in range(num_outputs): j_output = j_n_outputs[oi] o_type = j_output['type'] o_label = j_output['label'] new_output = NodePort() new_output.type_ = o_type new_output.label = o_label outputs.append(new_output) new_node.title = node_title new_node.description = node_description new_node.type_ = node_type new_node.package = package_name new_node.has_main_widget = node_has_main_widget if node_has_main_widget: new_node.main_widget_pos = node_main_widget_pos new_node.design_style = node_design_style new_node.color = QColor(node_color) new_node.inputs = inputs new_node.outputs = outputs self.custom_nodes.append(new_node) self.all_nodes.append(new_node) Debugger.debug(len(self.custom_nodes), 'nodes imported')
def dropEvent(self, event): text = event.mimeData().text() item: QListWidgetItem = event.mimeData() Debugger.debug('drop received in Flow:', text)