def __init__(self, config): super(MainWindow, self).__init__() QFontDatabase.addApplicationFont('fonts/poppins/Poppins-Medium.ttf') QFontDatabase.addApplicationFont('fonts/source code pro/SourceCodePro-Regular.ttf') self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.splitter.setSizes([120, 800]) self.setWindowTitle('pyScript') self.setWindowIcon(QIcon('stuff/pics/program_icon.png')) self.load_stylesheet('dark') self.ui.scripts_tab_widget.removeTab(0) self.ui.actionImport_Nodes.triggered.connect(self.on_import_nodes_triggered) self.ui.actionSave_Project.triggered.connect(self.on_save_project_triggered) self.ui.actionDesignDark_Std.triggered.connect(self.on_dark_std_design_triggered) self.ui.actionDesignDark_Tron.triggered.connect(self.on_dark_tron_design_triggered) self.ui.actionEnableDebugging.triggered.connect(self.on_enable_debugging_triggered) self.ui.actionDisableDebugging.triggered.connect(self.on_disable_debugging_triggered) self.ui.actionSave_Pic_Viewport.triggered.connect(self.on_save_scene_pic_viewport_triggered) self.ui.actionSave_Pic_Whole_Scene_scaled.triggered.connect(self.on_save_scene_pic_whole_triggered) # Shortcuts save_shortcut = QShortcut(QKeySequence.Save, self) save_shortcut.activated.connect(self.on_save_project_triggered) self.custom_nodes = [] self.all_nodes = [SetVariable_Node(), GetVariable_Node()] # holds NI subCLASSES for imported nodes: self.all_node_instance_classes = { self.all_nodes[0]: SetVar_NodeInstance, self.all_nodes[1]: GetVar_NodeInstance } # (key: node obj, val: NI subclass) (used in Flow) # {node : {str: PortInstanceWidget-subclass}} (used in PortInstance) self.custom_node_input_widget_classes = {} # clear temp folder for f in os.listdir('temp'): os.remove('temp/'+f) self.scripts = [] self.scripts_list_widget = ScriptsListWidget(self, self.scripts) self.ui.scripts_scrollArea.setWidget(self.scripts_list_widget) self.ui.add_new_script_pushButton.clicked.connect(self.create_new_script_button_pressed) self.ui.new_script_name_lineEdit.returnPressed.connect(self.create_new_script_le_return_pressed) self.design_style = 'dark std' if config['config'] == 'create plain new project': self.try_to_create_new_script() elif config['config'] == 'open project': self.import_required_packages(config['required packages']) self.parse_project(config['content']) self.resize(1500, 800)
def __init__(self, config): super(MainWindow, self).__init__() QFontDatabase.addApplicationFont( 'resources/fonts/poppins/Poppins-Medium.ttf') QFontDatabase.addApplicationFont( 'resources/fonts/source code pro/SourceCodePro-Regular.ttf') self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.splitter.setSizes([120, 800]) self.setWindowTitle('Ryven') self.setWindowIcon(QIcon('resources/pics/program_icon2.png')) self.load_stylesheet('dark') self.ui.scripts_tab_widget.removeTab(0) # menu actions self.flow_design_actions = [] self.setup_menu_actions() # shortcuts save_shortcut = QShortcut(QKeySequence.Save, self) save_shortcut.activated.connect(self.on_save_project_triggered) import_nodes_shortcut = QShortcut(QKeySequence('Ctrl+i'), self) import_nodes_shortcut.activated.connect(self.on_import_nodes_triggered) # clear temp folder if not os.path.exists('temp'): os.mkdir('temp') for f in os.listdir('temp'): os.remove('temp/' + f) # GENERAL ATTRIBUTES self.scripts = [] self.custom_nodes = [] self.all_nodes = [ SetVariable_Node(), GetVariable_Node(), Val_Node(), Result_Node() ] self.package_names = [] # holds NI subCLASSES for imported nodes: self.all_node_instance_classes = { self.all_nodes[0]: SetVar_NodeInstance, self.all_nodes[1]: GetVar_NodeInstance, self.all_nodes[2]: Val_NodeInstance, self.all_nodes[3]: Result_NodeInstance } # (key: node obj, val: NI subclass) (used in Flow) # custom subclasses for input widgets # {node : {str: PortInstanceWidget-subclass}} (used in PortInstance) self.custom_node_input_widget_classes = {} # UI self.scripts_list_widget = ScriptsListWidget(self, self.scripts) self.ui.scripts_scrollArea.setWidget(self.scripts_list_widget) self.ui.add_new_script_pushButton.clicked.connect( self.create_new_script_button_pressed) self.ui.new_script_name_lineEdit.returnPressed.connect( self.create_new_script_LE_return_pressed) if config['config'] == 'create plain new project': self.try_to_create_new_script() elif config['config'] == 'open project': print('importing packages...') self.import_packages(config['required packages']) print('loading project...') self.parse_project(config['content']) print('finished') print(''' CONTROLS placing nodes: right mouse selecting components: left mouse panning: middle mouse saving: ctrl+s ''') Design.set_flow_theme() Design.set_flow_theme() # temporary # the double call is just a temporary fix for an issue I will address in a future release. # Problem: because the signal emitted when setting a flow theme is directly connected to the according slots # in NodeInstance as well as NodeInstance_TitleLabel, the NodeInstance's slot (which starts an animation which # uses the title label's current and theme dependent color) could get called before the title # label's slot has been called to reinitialize this color. This results in wrong color end points for the # title label when activating animations. # This is pretty nasty since I cannot think of a nice fix for this issue other that not letting the slot # methods be called directly from the emitted signal but instead through a defined procedure like before. # maybe this will be necessary due to scheduling issues when loading flows # for s in self.scripts: # s.flow.viewport().update() self.resize(1500, 800)
class MainWindow(QMainWindow): def __init__(self, config): super(MainWindow, self).__init__() QFontDatabase.addApplicationFont( 'resources/fonts/poppins/Poppins-Medium.ttf') QFontDatabase.addApplicationFont( 'resources/fonts/source code pro/SourceCodePro-Regular.ttf') self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.splitter.setSizes([120, 800]) self.setWindowTitle('Ryven') self.setWindowIcon(QIcon('resources/pics/program_icon2.png')) self.load_stylesheet('dark') self.ui.scripts_tab_widget.removeTab(0) # menu actions self.flow_design_actions = [] self.setup_menu_actions() # shortcuts save_shortcut = QShortcut(QKeySequence.Save, self) save_shortcut.activated.connect(self.on_save_project_triggered) import_nodes_shortcut = QShortcut(QKeySequence('Ctrl+i'), self) import_nodes_shortcut.activated.connect(self.on_import_nodes_triggered) # clear temp folder if not os.path.exists('temp'): os.mkdir('temp') for f in os.listdir('temp'): os.remove('temp/' + f) # GENERAL ATTRIBUTES self.scripts = [] self.custom_nodes = [] self.all_nodes = [ SetVariable_Node(), GetVariable_Node(), Val_Node(), Result_Node() ] self.package_names = [] # holds NI subCLASSES for imported nodes: self.all_node_instance_classes = { self.all_nodes[0]: SetVar_NodeInstance, self.all_nodes[1]: GetVar_NodeInstance, self.all_nodes[2]: Val_NodeInstance, self.all_nodes[3]: Result_NodeInstance } # (key: node obj, val: NI subclass) (used in Flow) # custom subclasses for input widgets # {node : {str: PortInstanceWidget-subclass}} (used in PortInstance) self.custom_node_input_widget_classes = {} # UI self.scripts_list_widget = ScriptsListWidget(self, self.scripts) self.ui.scripts_scrollArea.setWidget(self.scripts_list_widget) self.ui.add_new_script_pushButton.clicked.connect( self.create_new_script_button_pressed) self.ui.new_script_name_lineEdit.returnPressed.connect( self.create_new_script_LE_return_pressed) if config['config'] == 'create plain new project': self.try_to_create_new_script() elif config['config'] == 'open project': print('importing packages...') self.import_packages(config['required packages']) print('loading project...') self.parse_project(config['content']) print('finished') print(''' CONTROLS placing nodes: right mouse selecting components: left mouse panning: middle mouse saving: ctrl+s ''') Design.set_flow_theme() Design.set_flow_theme() # temporary # the double call is just a temporary fix for an issue I will address in a future release. # Problem: because the signal emitted when setting a flow theme is directly connected to the according slots # in NodeInstance as well as NodeInstance_TitleLabel, the NodeInstance's slot (which starts an animation which # uses the title label's current and theme dependent color) could get called before the title # label's slot has been called to reinitialize this color. This results in wrong color end points for the # title label when activating animations. # This is pretty nasty since I cannot think of a nice fix for this issue other that not letting the slot # methods be called directly from the emitted signal but instead through a defined procedure like before. # maybe this will be necessary due to scheduling issues when loading flows # for s in self.scripts: # s.flow.viewport().update() self.resize(1500, 800) def setup_menu_actions(self): # flow designs for d in Design.flow_themes: design_action = QAction(d, self) self.ui.menuFlow_Design_Style.addAction(design_action) design_action.triggered.connect(self.on_design_action_triggered) self.flow_design_actions.append(design_action) self.ui.actionImport_Nodes.triggered.connect( self.on_import_nodes_triggered) self.ui.actionSave_Project.triggered.connect( self.on_save_project_triggered) self.ui.actionEnableDebugging.triggered.connect( self.on_enable_debugging_triggered) self.ui.actionDisableDebugging.triggered.connect( self.on_disable_debugging_triggered) self.ui.actionSave_Pic_Viewport.triggered.connect( self.on_save_scene_pic_viewport_triggered) self.ui.actionSave_Pic_Whole_Scene_scaled.triggered.connect( self.on_save_scene_pic_whole_triggered) # performance mode self.action_set_performance_mode_fast = QAction('Fast', self) self.action_set_performance_mode_fast.setCheckable(True) self.action_set_performance_mode_pretty = QAction('Pretty', self) self.action_set_performance_mode_pretty.setCheckable(True) performance_mode_AG = QActionGroup(self) performance_mode_AG.addAction(self.action_set_performance_mode_fast) performance_mode_AG.addAction(self.action_set_performance_mode_pretty) self.action_set_performance_mode_fast.setChecked(True) performance_mode_AG.triggered.connect(self.on_performance_mode_changed) performance_menu = QMenu('Performance Mode', self) performance_menu.addAction(self.action_set_performance_mode_fast) performance_menu.addAction(self.action_set_performance_mode_pretty) self.ui.menuView.addMenu(performance_menu) # animations self.action_set_animation_active = QAction('Enabled', self) self.action_set_animation_active.setCheckable(True) self.action_set_animations_inactive = QAction('Disabled', self) self.action_set_animations_inactive.setCheckable(True) animation_enabled_AG = QActionGroup(self) animation_enabled_AG.addAction(self.action_set_animation_active) animation_enabled_AG.addAction(self.action_set_animations_inactive) self.action_set_animation_active.setChecked(True) animation_enabled_AG.triggered.connect( self.on_animation_enabling_changed) animations_menu = QMenu('Animations', self) animations_menu.addAction(self.action_set_animation_active) animations_menu.addAction(self.action_set_animations_inactive) self.ui.menuView.addMenu(animations_menu) def load_stylesheet(self, ss): ss_content = '' try: f = open('resources/stylesheets/' + ss + '.txt') ss_content = f.read() f.close() finally: Design.ryven_stylesheet = ss_content self.setStyleSheet(ss_content) def on_performance_mode_changed(self, action): if action == self.action_set_performance_mode_fast: Design.set_performance_mode('fast') else: Design.set_performance_mode('pretty') def on_animation_enabling_changed(self, action): if action == self.action_set_animation_active: Design.animations_enabled = True else: Design.animations_enabled = False def on_design_action_triggered(self): index = self.flow_design_actions.index(self.sender()) Design.set_flow_theme(Design.flow_themes[index]) def on_enable_debugging_triggered(self): Debugger.enable() def on_disable_debugging_triggered(self): Debugger.disable() def on_save_scene_pic_viewport_triggered(self): """Saves a picture of the currently visible viewport.""" if len(self.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] img = self.scripts[ self.ui.scripts_tab_widget.currentIndex()].flow.get_viewport_img() img.save(file_path) def on_save_scene_pic_whole_triggered(self): """Saves a picture of the whole currently visible scene.""" if len(self.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] img = self.scripts[self.ui.scripts_tab_widget.currentIndex( )].flow.get_whole_scene_img() img.save(file_path) def create_new_script_button_pressed(self): self.try_to_create_new_script( name=self.ui.new_script_name_lineEdit.text()) def create_new_script_LE_return_pressed(self): self.try_to_create_new_script( name=self.ui.new_script_name_lineEdit.text()) def try_to_create_new_script(self, name='fancy script', config=None): """Tries to create a new script with a given name. If the name is already used or '', it fails.""" if len(name) == 0: return for s in self.scripts: if s.name == name: return new_script = Script(self, name, config) new_script.name_changed.connect(self.rename_script) self.ui.scripts_tab_widget.addTab(new_script.widget, new_script.name) self.scripts.append(new_script) self.scripts_list_widget.recreate_ui() def rename_script(self, script, new_name): self.ui.scripts_tab_widget.setTabText(self.scripts.index(script), new_name) script.name = new_name def delete_script(self, script): index = self.scripts.index(script) self.ui.scripts_tab_widget.removeTab(index) del self.scripts[index] def on_import_nodes_triggered(self): file_path = QFileDialog.getOpenFileName( self, 'select nodes file', '../packages', 'Ryven Packages(*.rpc)', )[0] if file_path != '': self.import_nodes_package(file_path) def import_packages(self, packages_list): for p in packages_list: self.import_nodes_package(p) 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 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 parse_node(self, j_node, package_name, package_path): new_node = Node() # loading the node's specifications which get finally set below after importing the classes 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 info: 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') # IMPORT CUSTOM INPUT WIDGETS # 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 # --------------------------------------------------------------------------------------------------- j_n_inputs = j_node['inputs'] inputs = [] num_inputs = len(j_n_inputs) for ii in range(num_inputs): # loading info j_input = j_n_inputs[ii] i_type = j_input['type'] i_label = j_input['label'] i_has_widget = None i_widget_name = '' i_widget_pos = None if i_type == 'data': i_has_widget = j_input['has widget'] if i_has_widget: i_widget_name = j_input['widget name'] i_widget_pos = j_input['widget position'] # creating port new_input = NodePort() new_input.type_ = i_type new_input.label = i_label if i_has_widget: new_input.widget_name = i_widget_name new_input.widget_pos = i_widget_pos inputs.append(new_input) j_n_outputs = j_node['outputs'] outputs = [] num_outputs = len(j_n_outputs) for oi in range(num_outputs): # loading info j_output = j_n_outputs[oi] o_type = j_output['type'] o_label = j_output['label'] # creating port new_output = NodePort() new_output.type_ = o_type new_output.label = o_label outputs.append(new_output) # setting the Node's attributes 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) def get_class_from_file(self, file_path, file_name, class_name): """Returns a class with a given name from a file for instantiation by importing the module. Used for all the dynamically imported classes: - NodeInstances - A NodeInstance's main widget - A NodeInstance's custom input widgets """ # Debugger.debug(file_path) # Debugger.debug(file_name) # Debugger.debug(class_name) sys.path.append(file_path) try: new_module = __import__(file_name, fromlist=[class_name]) except ModuleNotFoundError as e: QMessageBox.warning(self, 'Missing Python module', str(e)) sys.exit() new_class = getattr(new_module, class_name) return new_class def parse_project(self, j_obj): if j_obj['general info']['type'] != 'Ryven project file': return for s in j_obj['scripts']: # fill flows self.try_to_create_new_script(config=s) def on_save_project_triggered(self): file_name = QFileDialog.getSaveFileName( self, 'select location and give file name', '../saves', 'Ryven Project(*.rpo)')[0] if file_name != '': self.save_project(file_name) 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()
class MainWindow(QMainWindow): """MainWindow still lacks cleanup and documentation, sorry.""" def __init__(self, config): super(MainWindow, self).__init__() QFontDatabase.addApplicationFont('fonts/poppins/Poppins-Medium.ttf') QFontDatabase.addApplicationFont('fonts/source code pro/SourceCodePro-Regular.ttf') self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.splitter.setSizes([120, 800]) self.setWindowTitle('pyScript') self.setWindowIcon(QIcon('stuff/pics/program_icon.png')) self.load_stylesheet('dark') self.ui.scripts_tab_widget.removeTab(0) self.ui.actionImport_Nodes.triggered.connect(self.on_import_nodes_triggered) self.ui.actionSave_Project.triggered.connect(self.on_save_project_triggered) self.ui.actionDesignDark_Std.triggered.connect(self.on_dark_std_design_triggered) self.ui.actionDesignDark_Tron.triggered.connect(self.on_dark_tron_design_triggered) self.ui.actionEnableDebugging.triggered.connect(self.on_enable_debugging_triggered) self.ui.actionDisableDebugging.triggered.connect(self.on_disable_debugging_triggered) self.ui.actionSave_Pic_Viewport.triggered.connect(self.on_save_scene_pic_viewport_triggered) self.ui.actionSave_Pic_Whole_Scene_scaled.triggered.connect(self.on_save_scene_pic_whole_triggered) # Shortcuts save_shortcut = QShortcut(QKeySequence.Save, self) save_shortcut.activated.connect(self.on_save_project_triggered) self.custom_nodes = [] self.all_nodes = [SetVariable_Node(), GetVariable_Node()] # holds NI subCLASSES for imported nodes: self.all_node_instance_classes = { self.all_nodes[0]: SetVar_NodeInstance, self.all_nodes[1]: GetVar_NodeInstance } # (key: node obj, val: NI subclass) (used in Flow) # {node : {str: PortInstanceWidget-subclass}} (used in PortInstance) self.custom_node_input_widget_classes = {} # clear temp folder for f in os.listdir('temp'): os.remove('temp/'+f) self.scripts = [] self.scripts_list_widget = ScriptsListWidget(self, self.scripts) self.ui.scripts_scrollArea.setWidget(self.scripts_list_widget) self.ui.add_new_script_pushButton.clicked.connect(self.create_new_script_button_pressed) self.ui.new_script_name_lineEdit.returnPressed.connect(self.create_new_script_le_return_pressed) self.design_style = 'dark std' if config['config'] == 'create plain new project': self.try_to_create_new_script() elif config['config'] == 'open project': self.import_required_packages(config['required packages']) self.parse_project(config['content']) self.resize(1500, 800) def load_stylesheet(self, ss): ss_content = '' try: f = open('stuff/stylesheets/'+ss+'.txt') ss_content = f.read() f.close() finally: self.setStyleSheet(ss_content) def on_dark_std_design_triggered(self): self.set_design('dark std') def on_dark_tron_design_triggered(self): self.set_design('dark tron') def set_design(self, new_design): Design.flow_style = new_design self.design_style = new_design for script in self.scripts: script.flow.design_style_changed() def on_enable_debugging_triggered(self): Debugger.enable() def on_disable_debugging_triggered(self): Debugger.disable() def on_save_scene_pic_viewport_triggered(self): if len(self.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] img = self.scripts[self.ui.scripts_tab_widget.currentIndex()].flow.get_viewport_img() img.save(file_path) def on_save_scene_pic_whole_triggered(self): if len(self.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] img = self.scripts[self.ui.scripts_tab_widget.currentIndex()].flow.get_whole_scene_img() img.save(file_path) def create_new_script_button_pressed(self): self.try_to_create_new_script(name=self.ui.new_script_name_lineEdit.text()) def create_new_script_le_return_pressed(self): self.try_to_create_new_script(name=self.ui.new_script_name_lineEdit.text()) def try_to_create_new_script(self, name='fancy script', config=None): if len(name) == 0: return for s in self.scripts: if s.name == name: return new_script = Script(self, name, config) new_script.name_changed.connect(self.rename_script) self.ui.scripts_tab_widget.addTab(new_script.widget, new_script.name) self.scripts.append(new_script) self.scripts_list_widget.recreate_ui() def rename_script(self, script, new_name): self.ui.scripts_tab_widget.setTabText(self.scripts.index(script), new_name) script.name = new_name def delete_script(self, script): index = self.scripts.index(script) self.ui.scripts_tab_widget.removeTab(index) del self.scripts[index] def on_import_nodes_triggered(self): file_path = QFileDialog.getOpenFileName(self, 'select nodes file', '../packages', 'PyScript Packages(*.pypac)',)[0] if file_path != '': self.import_nodes_package_from_file(file_path) def import_required_packages(self, packages_list): for p in packages_list: self.import_nodes_package_from_file(p) 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 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 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 parse_project(self, j_obj): if j_obj['general info']['type'] != 'pyScriptFP project file': return for s in j_obj['scripts']: # fill flows self.try_to_create_new_script(config=s) def on_save_project_triggered(self): file_name = '' file_name = QFileDialog.getSaveFileName(self, 'select location and give file name', '../saves', 'PyScript Project(*.pypro)')[0] if file_name != '': self.save_project(file_name) def save_project(self, file_name): import json file = None try: file = open(file_name, 'w') except FileNotFoundError: Debugger.debug('couldn\'t open file') return general_project_info_dict = {'type': 'pyScriptFP 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 __init__(self, config): super(MainWindow, self).__init__() QFontDatabase.addApplicationFont( 'resources/fonts/poppins/Poppins-Medium.ttf') QFontDatabase.addApplicationFont( 'resources/fonts/source code pro/SourceCodePro-Regular.ttf') self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.splitter.setSizes([120, 800]) self.setWindowTitle('Ryven') self.setWindowIcon(QIcon('resources/pics/program_icon2.png')) self.load_stylesheet('dark') self.ui.scripts_tab_widget.removeTab(0) # menu actions self.setup_menu_actions() # shortcuts save_shortcut = QShortcut(QKeySequence.Save, self) save_shortcut.activated.connect(self.on_save_project_triggered) # clear temp folder if not os.path.exists('temp'): os.mkdir('temp') for f in os.listdir('temp'): os.remove('temp/' + f) # GENERAL ATTRIBUTES self.scripts = [] self.custom_nodes = [] self.all_nodes = [ SetVariable_Node(), GetVariable_Node(), Val_Node(), Result_Node() ] self.package_names = [] # holds NI subCLASSES for imported nodes: self.all_node_instance_classes = { self.all_nodes[0]: SetVar_NodeInstance, self.all_nodes[1]: GetVar_NodeInstance, self.all_nodes[2]: Val_NodeInstance, self.all_nodes[3]: Result_NodeInstance } # (key: node obj, val: NI subclass) (used in Flow) # custom subclasses for input widgets # {node : {str: PortInstanceWidget-subclass}} (used in PortInstance) self.custom_node_input_widget_classes = {} # UI self.scripts_list_widget = ScriptsListWidget(self, self.scripts) self.ui.scripts_scrollArea.setWidget(self.scripts_list_widget) self.ui.add_new_script_pushButton.clicked.connect( self.create_new_script_button_pressed) self.ui.new_script_name_lineEdit.returnPressed.connect( self.create_new_script_LE_return_pressed) if config['config'] == 'create plain new project': self.try_to_create_new_script() elif config['config'] == 'open project': print('importing packages...') self.import_packages(config['required packages']) print('loading project...') self.parse_project(config['content']) print('finished') print(''' CONTROLS placing nodes: right mouse selecting components: left mouse panning: middle mouse saving: ctrl+s ''') self.set_flow_design('dark std') self.resize(1500, 800)