diff --git a/README.md b/README.md index 54e2598c881274dab7b365dc47549d8652a3f257..08342a5d26d710bcf9e7cf5db47f71de1a8c083d 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ ## Как запустить? -`pip install dearpygui numpy` +`pip install dearpygui numpy paramiko scp python-dotenv` `git clone http://tesla.parallel.ru/kuzmichovamary/gui.git` `cd gui` -`python prerun.py` -- один раз (тут собирается `config-parser` ;) +`python prerun.py` — один раз (тут собирается `config-parser` ;) `python app.py` \ No newline at end of file diff --git a/app.py b/app.py index a76068abaa898a781f10dd1a7059e02ed521b5c3..5c4fc7cc06032bf637f538620d804de8722425c7 100644 --- a/app.py +++ b/app.py @@ -4,403 +4,28 @@ import sys import dearpygui.dearpygui as dpg import numpy as np +from dotenv import load_dotenv -from styles import GuiStyle, INDENT, SPACE, FONT_SIZE, ICON_PATH -from config import load_config, dump_config -from generator import LCZ -from utils import * +from static_ui import construct_ui +from constants import ICON_PATH -ctypes.windll.shcore.SetProcessDpiAwareness(1) +load_dotenv() -TABS_UI = 'tabs_ui' -COLLAPSING_HEADERS_UI = 'collapsing_headers_ui' -WIDGET_WIDTH = 300 -XOFFSET = 300 -LCZS = ['compact_high_rise', 'compact_low_rise', 'compact_mid_rise', 'heavy_industry', 'large_low_rise', 'lightweight_low_rise', 'open_high_rise', 'open_low_rise', 'open_mid_rise', 'sparsley_build'] - -UIS = [TABS_UI, COLLAPSING_HEADERS_UI] - -class ConfigEditor: - def __init__(self): - self.config = dict() - self.config_file_path = str() - - self.height_map = None - self.height_map_path = str() - - self.current_action = None - self.prev_coords = None - self.drawing = False - - dpg.create_context() - dpg.create_viewport( - title='config file editor', - small_icon=ICON_PATH, - large_icon=ICON_PATH - ) - dpg.setup_dearpygui() - dpg.show_viewport() - - self.styles = GuiStyle() - - self.construct_main_window_ui() - if self.config_file_path: - self.construct_config_ui() - - def update_texture(self): - if not self.height_map_path: - return - max_height = np.max(self.height_map) - normalized_map = self.height_map / max_height - - texture_data = normalized_map.flatten() - texture_data = np.tile(texture_data, (4, 1)).T.flatten() - - texture_data.resize(800 * 800 * 4) - - width, height = self.height_map.shape - - dpg.set_value('heatmap_texture', texture_data) - - def map_edit_window_on_close(self): - self.height_map = None - self.height_map_path = str() - - self.current_action = None - self.prev_coords = None - self.drawing = False - - def delete_config_ui_and_clear_aliases(self): - if dpg.does_item_exist(self.config_file_path): - if dpg.get_item_type(self.config_file_path) == 'mvAppItemType::mvChildWindow': - uuid = dpg.get_item_parent(dpg.get_item_parent(self.config_file_path)) - dpg.delete_item(uuid) - else: - dpg.delete_item(self.config_file_path) - - if dpg.does_alias_exist(self.config_file_path): - dpg.remove_alias(self.config_file_path) - for full_path in self.config: - for path in path_iter(full_path, prefix=self.config_file_path): - if dpg.does_alias_exist(path): - dpg.remove_alias(path) - - def update_config_values(self): - for path in self.config: - if dpg.does_item_exist(f'{self.config_file_path}.{path}'): - self.config[path].value = dpg.get_value(f'{self.config_file_path}.{path}') - - def manage_file(self, sender, sender_data, user_data): - file_path = sender_data['file_path_name'] - - if not file_path: - return - - if user_data == 'open_config': - self.delete_config_ui_and_clear_aliases() - self.config_file_path = file_path - self.config = load_config(file_path) - self.construct_config_ui(dpg.get_item_user_data('ui')) - elif user_data == 'open_map': - self.height_map_path = file_path - self.height_map = load_height_map(file_path) - dpg.show_item('map_window') - self.update_texture() - elif user_data == 'save_as' or user_data == 'save': - self.update_config_values() - dump_config(self.config, file_path) - dpg.show_item('file_save_popup') - elif user_data == 'save_map_as' or user_data == 'save_map': - dump_map(self.height_map, file_path) - dpg.show_item('file_save_popup') - - def generate_map(self, sender, sender_data, user_data): - self.height_map_path = 'lcz.txt' - self.height_map = LCZ(config_path=f'configs/{sender}.json').to_height_map() - dpg.show_item('map_window') - self.update_texture() - - def set_file_path(self, sender, sender_data, user_data): - file_path = sender_data['file_path_name'] - dpg.set_value(user_data, file_path) - - def open_file_dialog(self, sender, sender_data, user_data): - dpg.show_item('file_dialog') - dpg.set_item_user_data('file_dialog', user_data) - - def open_folder_dialog(self, sender, sender_data, user_data): - dpg.show_item('folder_dialog') - dpg.set_item_user_data('folder_dialog', user_data) - - def set_action(self, action): - self.current_action = action - - def check_coords(self, x, y, w, h): - mx, my = dpg.get_item_pos('map') - mw, mh = dpg.get_item_width('map'), dpg.get_item_height('map') - - return mx <= x <= x + w <= mx + mw and my <= y <= y + h <= my + mh - - def mouse_down_callback(self): - if not self.height_map_path: - return - if not self.drawing: - self.prev_coords = dpg.get_mouse_pos() - self.drawing = True - else: - x, y = dpg.get_mouse_pos() - px, py = self.prev_coords - y += dpg.get_text_size('edit map')[1] + 2 * SPACE + dpg.get_y_scroll('map_window') - py += dpg.get_text_size('edit map')[1] + 2 * SPACE + dpg.get_y_scroll('map_window') - x += dpg.get_x_scroll('map_window') - px += dpg.get_x_scroll('map_window') - w, h = abs(x - px), abs(y - py) - x, y = min(x, px), min(y, py) - - if w > 0 and h > 0 and self.check_coords(x, y, w, h): - dpg.set_item_pos('drawing_frame', [min(x, px), min(y, py)]) - dpg.set_item_width('drawing_frame', w) - dpg.set_item_height('drawing_frame', h) - dpg.show_item('drawing_frame') - - def mouse_release_callback(self): - coords = dpg.get_mouse_pos() - - if self.current_action == 'erase': - self.draw_rectangle(coords, 0) - elif self.current_action == 'draw_rect': - height = dpg.get_value('height_input') - self.draw_rectangle(coords, np.max(self.height_map) * height) - - self.update_texture() - - self.drawing = False - dpg.hide_item('drawing_frame') - - def draw_rectangle(self, coords, height): - y1, y2, x1, x2 = get_rect(self.prev_coords, coords) - self.height_map[y1:y2, x1:x2] = height - - def run_on_lab_server(self): - pass - - def construct_config_ui(self, ui): - if not self.config_file_path: - return - - self.update_config_values() - self.delete_config_ui_and_clear_aliases() - if ui == TABS_UI: - self.construct_config_ui_tabs() - else: - self.construct_config_ui_collapsing_headers() - - def construct_main_window_ui(self): - dpg.bind_font(self.styles.montserrat_font) - self.styles.apply_theme('orange') - - with dpg.window(label='config file editor', tag='main'): - with dpg.menu_bar(): - with dpg.menu(label='config'): - dpg.add_menu_item(label='open', callback=self.open_file_dialog, tag='open_btn', user_data='open_config') - dpg.add_menu_item(label='save', callback=lambda: self.manage_file('', {'file_path_name': self.config_file_path}, 'save')) - dpg.add_menu_item(label='save as', callback=self.open_file_dialog, tag='save_as_btn', user_data='save_as') - - with dpg.menu(label='map'): - dpg.add_menu_item(label='open map', callback=self.open_file_dialog, tag='open_map_btn', user_data='open_map') - with dpg.menu(label='generate map'): - for lcz_type in LCZS: - dpg.add_menu_item(label=lcz_type, check=False, callback=combo_menu(self.generate_map), tag=lcz_type) - - with dpg.menu(label='run'): - dpg.add_menu_item(label='run on lab server', callback=self.run_on_lab_server) - - with dpg.menu(label='view'): - with dpg.menu(label='ui', tag='ui', user_data=COLLAPSING_HEADERS_UI): - for ui in UIS: - dpg.add_menu_item(label=ui, check=True, default_value=False, callback=combo_menu(lambda s, a, u: self.construct_config_ui(s)), tag=ui) - dpg.set_value(UIS[1], True) - - with dpg.menu(label='theme'): - for theme in self.styles.themes: - dpg.add_menu_item(label=theme, check=True, default_value=False, callback=combo_menu(lambda s, a, u: self.styles.apply_theme(s)), tag=theme) - dpg.set_value('orange', True) - - dpg.add_menu_item(label='show theme editor', callback=dpg.show_style_editor) - - with dpg.menu(label='settings'): - dpg.add_menu_item(label='show mouse coords', check=True, callback=lambda _, v: dpg.show_item('mouse') if v else dpg.hide_item('mouse'), default_value=False) - - dpg.add_text('', tag='mouse', show=False) - - with dpg.file_dialog(directory_selector=False, show=False, callback=self.manage_file, tag='file_dialog', width=600, height=400): - dpg.add_file_extension('.txt', color=(0, 255, 0, 255)) - dpg.add_file_extension('.*', color=(255, 255, 255, 255)) - - with dpg.file_dialog(directory_selector=True, show=False, callback=self.set_file_path, tag='folder_dialog', width=500, height=400): - dpg.add_file_extension('.*', color=(255, 255, 255, 255)) - - with dpg.window(label='', autosize=True, no_resize=True, modal=True, tag='file_save_popup', show=False, pos=(40, 40)): - dpg.add_text('file saved successfully:)') - - with dpg.item_handler_registry(tag='main_handler'): - dpg.add_item_resize_handler(callback=lambda: self.change_height(self.config_file_path)) - - dpg.bind_item_handler_registry('main', 'main_handler') - dpg.set_viewport_resize_callback(lambda: self.change_height(self.config_file_path)) - - with dpg.texture_registry(): - dpg.add_dynamic_texture(800, 800, np.zeros((800, 800, 4), dtype=np.uint8), tag='heatmap_texture') - - with dpg.theme() as item_theme: - with dpg.theme_component(dpg.mvAll): - dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (0, 0, 0, 0), category=dpg.mvThemeCat_Core) - dpg.add_theme_style(dpg.mvStyleVar_ChildRounding, 0, category=dpg.mvThemeCat_Core) - - dpg.add_colormap_registry(label="colormap registry", tag="colormap_registry") - dpg.add_colormap(list(map(apply_tint, [[0, 0, 0], [100, 100, 100], [255, 255, 255]])), False, tag="colormap", parent="colormap_registry") - - with dpg.window(label='edit map', tag='map_window', horizontal_scrollbar=True, show=False, width=600, height=400, pos=[20, 60], on_close=self.map_edit_window_on_close): - with dpg.menu_bar(): - with dpg.menu(label='file'): - dpg.add_menu_item(label='save', callback=lambda: self.manage_file('', {'file_path_name': self.height_map_path}, 'save_map')) - dpg.add_menu_item(label='save as', callback=self.open_file_dialog, tag='save_map_as_btn', user_data='save_map_as') - - with dpg.group(horizontal=True): - with dpg.group(width=200, tag='tools'): - dpg.add_button(label='erase', callback=lambda: self.set_action('erase')) - dpg.add_button(label='draw rectangle', callback=lambda: self.set_action('draw_rect')) - dpg.add_colormap_slider(tag='height_input') - dpg.bind_colormap(dpg.last_item(), "colormap") - dpg.add_image('heatmap_texture', tint_color=(0, 119, 200, 255), tag='map') - - dpg.add_child_window(tag='drawing_frame', show=False, width=0, height=0) - - dpg.bind_item_theme('drawing_frame', item_theme) - - def clb(s, a, u): - dpg.set_value('mouse', str(a)) - - with dpg.handler_registry(): - dpg.add_mouse_move_handler(callback=clb) - dpg.add_mouse_down_handler(callback=self.mouse_down_callback) - dpg.add_mouse_release_handler(callback=self.mouse_release_callback) - - def construct_config_ui_collapsing_headers(self): - dpg.add_collapsing_header(label=self.config_file_path, parent='main', tag=self.config_file_path, indent=0, default_open=True) - - for full_path in self.config: - namespaces = full_path.split('.') - parent = self.config_file_path - for level in range(len(namespaces) - 1): - path = self.config_file_path + '.' + '.'.join(namespaces[:level+1]) - - if not dpg.does_item_exist(path): - dpg.add_collapsing_header(label=namespaces[level], parent=parent, tag=path, indent=INDENT) - - parent = path - - self.create_widget(full_path) - - def construct_config_ui_tabs(self): - tab_bar_exists, tab_bar_uuid = does_tab_bar_child_exist('main') - if not tab_bar_exists: - tab_bar_uuid = dpg.add_tab_bar(parent='main') - tab_uuid = dpg.add_tab(label=self.config_file_path, parent=tab_bar_uuid) - dpg.add_child_window(parent=tab_uuid, tag=self.config_file_path, border=True, autosize_y=False, autosize_x=True) - dpg.set_item_user_data(self.config_file_path, 2 * SPACE) - - for full_path in self.config: - namespaces = full_path.split('.') - parent = self.config_file_path - for level in range(len(namespaces) - 1): - path = self.config_file_path + '.' + '.'.join(namespaces[:level+1]) - - tab_bar_exists, tab_bar_uuid = does_tab_bar_child_exist(parent) - - if not tab_bar_exists: - tab_bar_uuid = dpg.add_tab_bar(parent=parent, callback=lambda s, a: self.change_height(a)) - dpg.set_item_user_data(parent, dpg.get_item_user_data(parent) + 4 * SPACE + FONT_SIZE) - - if not dpg.does_item_exist(path): - tab_uuid = dpg.add_tab(label=namespaces[level], parent=tab_bar_uuid) - dpg.add_child_window(parent=tab_uuid, tag=path, border=True, autosize_y=False, autosize_x=True) - dpg.set_item_user_data(path, 2 * SPACE) - - parent = path - - self.create_widget(full_path) - - def change_height(self, item): - if not dpg.does_item_exist(item): - return - if dpg.get_item_type(item) == 'mvAppItemType::mvChildWindow': - uuid = dpg.get_item_parent(item) - new_height = dpg.get_viewport_client_height() - get_absolute_y(uuid) - SPACE * get_item_level(item) - - dpg.set_item_height(item, max(new_height, dpg.get_item_user_data(item))) - - for child in dpg.get_item_children(item, 1): - self.change_height(child) - - def create_widget(self, full_path): - tag = self.config_file_path + '.' + full_path - parent = '.'.join([self.config_file_path] + full_path.split('.')[:-1]) - - value = self.config[full_path].value - value_type = self.config[full_path].value_type - key = self.config[full_path].key - comment = self.config[full_path].comment - - if dpg.get_value('use_tabs_ui') and parent != self.config_file_path and full_path != key: - dpg.set_item_user_data(parent, dpg.get_item_user_data(parent) + 2 * SPACE + FONT_SIZE + SPACE) - - indent = INDENT - if dpg.get_value('use_tabs_ui'): - indent = 0 - - with dpg.group(horizontal=True, parent=parent, indent=indent, xoffset=XOFFSET): - if not comment: - dpg.add_text(key) - if value_type == 'BOOLEAN': - dpg.add_checkbox(default_value=value, tag=tag) - elif value_type == 'DOUBLE': - dpg.add_input_double(default_value=value, tag=tag, format='%.10f', width=WIDGET_WIDTH) - elif value_type == 'INT': - dpg.add_input_int(default_value=value, tag=tag, width=WIDGET_WIDTH) - elif 'dir' in key.lower() or 'file' in key.lower(): - with dpg.group(horizontal=True): - dpg.add_input_text(default_value=value, tag=tag, width=WIDGET_WIDTH) - dpg.add_button(label='...', callback=self.open_folder_dialog, user_data=tag) - else: - dpg.add_input_text(default_value=value, tag=tag, width=WIDGET_WIDTH) - else: - dpg.add_text(key) - with dpg.group(horizontal=True, xoffset=WIDGET_WIDTH + 100): - if value_type == 'BOOLEAN': - dpg.add_checkbox(default_value=value, tag=tag) - elif value_type == 'DOUBLE': - dpg.add_input_double(default_value=value, tag=tag, format='%.10f', width=WIDGET_WIDTH) - elif value_type == 'INT': - dpg.add_input_int(default_value=value, tag=tag, width=WIDGET_WIDTH) - elif 'dir' in key.lower() or 'file' in key.lower(): - with dpg.group(horizontal=True): - dpg.add_input_text(default_value=value, tag=tag, width=WIDGET_WIDTH - 3 * SPACE - dpg.get_text_size('...')[0]) - dpg.add_button(label='...', callback=self.open_folder_dialog, user_data=tag) - else: - dpg.add_input_text(default_value=value, tag=tag, width=WIDGET_WIDTH) - dpg.add_text(comment, color=(255, 255, 255, 150)) - - def run(self): - dpg.set_primary_window('main', True) - dpg.start_dearpygui() - dpg.destroy_context() +if sys.platform.startswith('win'): + ctypes.windll.shcore.SetProcessDpiAwareness(1) if __name__ == '__main__': - app = ConfigEditor() - - if len(sys.argv) > 1: - app.config_file_path = sys.argv[1] - app.config = load_config(sys.argv[1]) - - app.run() \ No newline at end of file + dpg.create_context() + dpg.create_viewport( + title='config file editor', + small_icon=ICON_PATH, + large_icon=ICON_PATH + ) + dpg.setup_dearpygui() + dpg.show_viewport() + + construct_ui() + + dpg.set_primary_window('main', True) + dpg.start_dearpygui() + dpg.destroy_context() \ No newline at end of file diff --git a/callbacks.py b/callbacks.py new file mode 100644 index 0000000000000000000000000000000000000000..34417fce0099b38a0b05b475ebb94d9b71b3aa6d --- /dev/null +++ b/callbacks.py @@ -0,0 +1,128 @@ +import dearpygui.dearpygui as dpg + +from utils import * +from remote_run import start_remote_execution +from styles import get_accent_color, apply_theme +from generator import LCZ +from config import load_config, dump_config +from dynamic_ui import construct_config_ui + +def map_window_on_close_clb(s, a, u): + dpg.set_item_user_data('map_window', {'height_map': None, 'height_map_file_path': None, 'drawing': False, 'prev_coords': None, 'current_action': None}) + +def construct_config_ui_clb(s, a, u): + if not get_config_file_path(): + return + delete_item_children_and_clear_aliases() + update_config_values() + construct_config_ui(get_config_file_path(), get_config()) + +def map_open_clb(s, a, u): + height_map_file_path = a['file_path_name'] + height_map = load_height_map(height_map_file_path) + + set_map_window_value('height_map', height_map) + set_map_window_value('height_map_file_path', height_map_file_path) + + update_texture(height_map) + dpg.show_item('map_window') + +def generate_map_clb(s, a, u): + height_map = LCZ(config_path=f'configs/{s}.json').to_height_map() + + set_map_window_value('height_map', height_map) + set_map_window_value('height_map_file_path', 'lcz.txt') + + update_texture(height_map) + dpg.show_item('map_window') + +def map_save_clb(s, a, u): + dump_map(get_height_map(), get_height_map_file_path()) + dpg.show_item('message_popup') + +def map_save_as_clb(s, a, u): + dump_map(get_height_map(), a['file_path_name']) + dpg.show_item('message_popup') + +def open_map_save_as_dialog_clb(s, a, u): + dpg.show_item('map_save_as') + +def open_map_open_dialog_clb(s, a, u): + dpg.show_item('map_open') + +def open_config_open_dialog_clb(s, a, u): + dpg.show_item('config_open') + +def search_clb(s, data, u): + if not get_config_file_path(): + return + + if not data: + return + + for path in get_config(): + if data.lower() in path.split('.')[-1]: + dpg.set_y_scroll('main', dpg.get_item_pos('.'.join([get_config_file_path(), path.split('.')[0]]))[1]) + for tag in path_iter(path, prefix=get_config_file_path()): + if dpg.get_value(COLLAPSING_HEADERS_UI) == True: + dpg.set_value(tag, True) + else: + dpg.set_value(dpg.get_item_parent(dpg.get_item_parent(tag)), dpg.get_item_parent(tag)) + change_color_temporarily('.'.join([get_config_file_path(), path, 'text']), color=get_accent_color()) + +def remote_run_clb(s, a, u): + if not get_config_file_path(): + show_status_text('no config.txt specified. cannot run.') + return + + update_config_values() + dump_config(get_config(), 'tmp.txt') + + dpg.show_item('login_modal') + +def apply_theme_clb(s, a, u): + apply_theme(s) + +def set_file_path_clb(s, a, u): + file_path = a['file_path_name'] + dpg.set_value(u, file_path) + +def show_config_search_window_clb(s, a, u): + dpg.show_item('search_popup') + +def start_remote_execution_clb(s, a, u): + start_remote_execution(dpg.get_value('server_username'), dpg.get_value('server_password'), dpg.get_value('gitlab_username'), dpg.get_value('gitlab_password')) + +def update_config_values(): + config = get_config() + for path in config: + if dpg.does_item_exist(f'{get_config_file_path()}.{path}'): + config[path].value = dpg.get_value(f'{get_config_file_path()}.{path}') + set_main_window_value('config', config) + +def config_save_as_clb(s, a, u): + update_config_values() + dump_config(get_config(), a['file_path_name']) + dpg.show_item('message_popup') + +def config_open_clb(s, a, u): + delete_item_children_and_clear_aliases() + config_file_path = a['file_path_name'] + config = load_config(config_file_path) + set_main_window_value('config', config) + set_main_window_value('config_file_path', config_file_path) + construct_config_ui(a['file_path_name'], config) + +def config_save_clb(s, a, u): + update_config_values() + dump_config(get_config(), get_config_file_path()) + dpg.show_item('message_popup') + +def set_action_clb(s, a, u): + set_map_window_value('current_action', s) + +def set_action_none_clb(s, a, u): + set_map_window_value('current_action', None) + +def open_config_save_as_dialog_clb(s, a, u): + dpg.show_item('config_open') \ No newline at end of file diff --git a/constants.py b/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..dbeadabb6c937362ac09b06c30a3a8d0d1e9f313 --- /dev/null +++ b/constants.py @@ -0,0 +1,45 @@ +import os + +TABS_UI = 'tabs_ui' +COLLAPSING_HEADERS_UI = 'collapsing_headers_ui' +WIDGET_WIDTH = 300 +XOFFSET = 300 +LCZS = ['compact_high_rise', 'compact_low_rise', 'compact_mid_rise', 'heavy_industry', 'large_low_rise', 'lightweight_low_rise', 'open_high_rise', 'open_low_rise', 'open_mid_rise', 'sparsley_build'] + +UIS = [TABS_UI, COLLAPSING_HEADERS_UI] + +FONT_PATH = os.path.join('fonts', 'Montserrat-Medium.ttf') +ICON_PATH = os.path.join('icons', 'icon.ico') +FONT_SIZE = 24 +ACCENT_COLOR = (0, 119, 200, 100) +ROUNDING = 10 +SPACE = 10 +INDENT = 40 +BORDER = 3 + +TEXT = (0, 1) +ACCENT = (5, 18, 19, 20, 15, 16, 35, 37, 34, 23, 22, 25, 26, 27, 28, 29, 10, 11, 12, 30, 31, 32, 49, 50) +PRIMARY = (2, 3, 39) +SECONDARY = (36, 33, 21, 24, 4, 7, 8, 9, 13, 14) +APPLY_ALPHA = (1, 22, 25, 31, 34, 16) +COLOR_TYPE_TO_DPG_ITEM = {'TEXT': (0, 1), 'PRIMARY': (2, 3, 39), 'SECONDARY': (36, 33, 21, 24, 4, 7, 8, 9, 13, 14), 'ACCENT': (5, 18, 19, 20, 15, 16, 35, 37, 34, 23, 22, 25, 26, 27, 28, 29, 10, 11, 12, 30, 31, 32, 49, 50)} +COLOR_TO_COLOR_TYPE = {0: 'TEXT', 1: 'TEXT', 2: 'PRIMARY', 3: 'PRIMARY', 39: 'PRIMARY', 36: 'SECONDARY', 21: 'SECONDARY', 24: 'SECONDARY', 4: 'SECONDARY', 7: 'SECONDARY', 8: 'SECONDARY', 9: 'SECONDARY', 13: 'SECONDARY', 33: 'SECONDARY', 14: 'SECONDARY', 35: 'ACCENT', 20: 'ACCENT', 37: 'ACCENT', 22: 'ACCENT', 23: 'ACCENT', 5: 'ACCENT', 25: 'ACCENT', 26: 'ACCENT', 27: 'ACCENT', 28: 'ACCENT', 29: 'ACCENT', 10: 'ACCENT', 30: 'ACCENT', 11: 'ACCENT', 31: 'ACCENT', 12: 'ACCENT', 32: 'ACCENT', 49: 'ACCENT', 34: 'ACCENT', 15: 'ACCENT', 50: 'ACCENT', 16: 'ACCENT', 18: 'ACCENT', 19: 'ACCENT', 1: 'TEXT'} + +BLUE = { + 'TEXT': (255, 255, 255, 255), + 'PRIMARY': (37, 37, 38, 255), + 'SECONDARY': (51, 51, 55, 255), + 'ACCENT': (0, 119, 200, 153) +} + +ORANGE = { + 'TEXT': (255, 255, 255, 255), + 'PRIMARY': (37, 37, 38, 255), + 'SECONDARY': (51, 51, 55, 255), + 'ACCENT': (251, 133, 0, 153) +} + +THEMES = { + 'blue': BLUE, + 'orange': ORANGE +} \ No newline at end of file diff --git a/dynamic_ui.py b/dynamic_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..3b6ab2e49e6f460b7f3ea8778ad097a82f2248a6 --- /dev/null +++ b/dynamic_ui.py @@ -0,0 +1,110 @@ +import dearpygui.dearpygui as dpg + +from utils import * +from constants import * + +def open_folder_dialog_clb(s, a, u): + dpg.show_item('set_folder_dialog') + dpg.set_item_user_data('set_folder_dialog', u) + +def change_tab_bar_height_clb(s, a, u): + change_height(a) + +def construct_config_ui(config_file_path, config): + if get_current_ui_type() == TABS_UI: + construct_config_ui_tabs(config_file_path, config) + else: + construct_config_ui_collapsing_headers(config_file_path, config) + +def construct_config_ui_collapsing_headers(config_file_path, config): + dpg.add_collapsing_header(label=config_file_path, parent='main', tag=config_file_path, indent=0, default_open=True) + + for full_path in config: + namespaces = full_path.split('.') + parent = config_file_path + for level in range(len(namespaces) - 1): + path = config_file_path + '.' + '.'.join(namespaces[:level+1]) + + if not dpg.does_item_exist(path): + dpg.add_collapsing_header(label=namespaces[level], parent=parent, tag=path, indent=INDENT) + + parent = path + + create_widget(config_file_path, config, full_path) + +def construct_config_ui_tabs(config_file_path, config): + tab_bar_exists, tab_bar_uuid = does_tab_bar_child_exist('main') + if not tab_bar_exists: + tab_bar_uuid = dpg.add_tab_bar(parent='main') + tab_uuid = dpg.add_tab(label=config_file_path, parent=tab_bar_uuid) + dpg.add_child_window(parent=tab_uuid, tag=config_file_path, border=True, autosize_y=False, autosize_x=True) + dpg.set_item_user_data(config_file_path, 2 * SPACE) + + for full_path in config: + namespaces = full_path.split('.') + parent = config_file_path + for level in range(len(namespaces) - 1): + path = config_file_path + '.' + '.'.join(namespaces[:level+1]) + + tab_bar_exists, tab_bar_uuid = does_tab_bar_child_exist(parent) + + if not tab_bar_exists: + tab_bar_uuid = dpg.add_tab_bar(parent=parent, callback=change_tab_bar_height_clb) + dpg.set_item_user_data(parent, dpg.get_item_user_data(parent) + 4 * SPACE + FONT_SIZE) + + if not dpg.does_item_exist(path): + tab_uuid = dpg.add_tab(label=namespaces[level], parent=tab_bar_uuid) + dpg.add_child_window(parent=tab_uuid, tag=path, border=True, autosize_y=False, autosize_x=True) + dpg.set_item_user_data(path, 2 * SPACE) + + parent = path + + create_widget(config_file_path, config, full_path) + +def create_widget(config_file_path, config, full_path): + tag = config_file_path + '.' + full_path + parent = '.'.join([config_file_path] + full_path.split('.')[:-1]) + + value = config[full_path].value + value_type = config[full_path].value_type + key = config[full_path].key + comment = config[full_path].comment + + if dpg.get_value('use_tabs_ui') and parent != config_file_path and full_path != key: + dpg.set_item_user_data(parent, dpg.get_item_user_data(parent) + 2 * SPACE + FONT_SIZE + SPACE) + + indent = INDENT + if dpg.get_value('use_tabs_ui'): + indent = 0 + + with dpg.group(horizontal=True, parent=parent, indent=indent, xoffset=XOFFSET): + if not comment: + dpg.add_text(key, tag=f'{tag}.text') + if value_type == 'BOOLEAN': + dpg.add_checkbox(default_value=value, tag=tag) + elif value_type == 'DOUBLE': + dpg.add_input_double(default_value=value, tag=tag, format='%.10f', width=WIDGET_WIDTH) + elif value_type == 'INT': + dpg.add_input_int(default_value=value, tag=tag, width=WIDGET_WIDTH) + elif 'dir' in key.lower() or 'file' in key.lower(): + with dpg.group(horizontal=True): + dpg.add_input_text(default_value=value, tag=tag, width=WIDGET_WIDTH) + dpg.add_button(label='...', callback=open_folder_dialog_clb, user_data=tag) + else: + dpg.add_input_text(default_value=value, tag=tag, width=WIDGET_WIDTH) + else: + dpg.add_text(key, tag=f'{tag}.text') + with dpg.group(horizontal=True, xoffset=WIDGET_WIDTH + 100): + if value_type == 'BOOLEAN': + dpg.add_checkbox(default_value=value, tag=tag) + elif value_type == 'DOUBLE': + dpg.add_input_double(default_value=value, tag=tag, format='%.10f', width=WIDGET_WIDTH) + elif value_type == 'INT': + dpg.add_input_int(default_value=value, tag=tag, width=WIDGET_WIDTH) + elif 'dir' in key.lower() or 'file' in key.lower(): + with dpg.group(horizontal=True): + dpg.add_input_text(default_value=value, tag=tag, width=WIDGET_WIDTH - 3 * SPACE - dpg.get_text_size('...')[0]) + dpg.add_button(label='...', callback=open_folder_dialog_clb, user_data=tag) + else: + dpg.add_input_text(default_value=value, tag=tag, width=WIDGET_WIDTH) + dpg.add_text(comment, color=(255, 255, 255, 150)) \ No newline at end of file diff --git a/generator.py b/generator.py index 5866d8150f8add6fe3615a9f3c45e5edc7e84b02..df871beacf36e27476dcd2272f25af932b57fdd6 100644 --- a/generator.py +++ b/generator.py @@ -256,8 +256,6 @@ class LCZ: if __name__ == '__main__': lcz_types = ['compact_high_rise', 'compact_mid_rise', 'compact_low_rise', 'open_high_rise', 'open_mid_rise', 'open_low_rise', 'lightweight_low_rise', 'large_low_rise', 'sparsley_build', 'heavy_industry'] - #sys.argv.append('sparsley_build') - fig, axs = plt.subplots(2, 2, figsize=(10, 6)) lcz_type = "open_mid_rise" @@ -281,37 +279,3 @@ if __name__ == '__main__': plt.savefig(f'{out_folder}/height_maps_mid_rise.png') plt.show() - - if False:#len(sys.argv) <= 1: - fig, axs = plt.subplots(2, 5, figsize=(16, 7)) - - for ax, lcz_type in zip(axs.flat, lcz_types): - print(lcz_type) - lcz = LCZ(config_path=f'configs/{lcz_type}.json', output_folder='v2') - - # lcz.to_model_input_building(f'building_{fn}.txt') - # lcz.to_model_input_street(f'street_{fn}.txt') - - # lcz.to_image(f'lcz_{lcz_type}_tree.png') - lcz.check_params() - - height_map = lcz.to_height_map() - im = ax.imshow(height_map, cmap='viridis', origin='lower') - ax.set_title(lcz_type) - fig.colorbar(im, ax=ax, shrink=0.3) - - plt.tight_layout() - plt.savefig('height_maps_main_road.png') - plt.show() - - if len(sys.argv) > 1: - lcz_type = sys.argv[1] - - if lcz_type not in lcz_types: - lcz_type = lcz_types[0] - - lcz = LCZ(config_path=f'configs/{lcz_type}.json', output_folder='v2') - - lcz.to_image(f'lcz_{lcz_type}_tree.png') - lcz.check_params() - diff --git a/handlers.py b/handlers.py new file mode 100644 index 0000000000000000000000000000000000000000..f77165e38a0551385255ba297e23aaabfde57641 --- /dev/null +++ b/handlers.py @@ -0,0 +1,89 @@ +import dearpygui.dearpygui as dpg + +from utils import * +from callbacks import * + +def mouse_down_callback(): + if not get_height_map_path(): + return + if not is_drawing(): + set_map_window_value('prev_coords', dpg.get_mouse_pos()) + set_map_window_value('drawing', True) + else: + x, y = dpg.get_mouse_pos() + px, py = get_prev_coords() + y += dpg.get_text_size('edit map')[1] + 2 * SPACE + dpg.get_y_scroll('map_window') + py += dpg.get_text_size('edit map')[1] + 2 * SPACE + dpg.get_y_scroll('map_window') + x += dpg.get_x_scroll('map_window') + px += dpg.get_x_scroll('map_window') + w, h = abs(x - px), abs(y - py) + x, y = min(x, px), min(y, py) + + if w > 0 and h > 0 and check_coords(x, y, w, h): + dpg.set_item_pos('drawing_frame', [min(x, px), min(y, py)]) + dpg.set_item_width('drawing_frame', w) + dpg.set_item_height('drawing_frame', h) + dpg.show_item('drawing_frame') + +def mouse_release_callback(): + if not get_height_map_path(): + return + coords = dpg.get_mouse_pos() + + if get_current_action() == 'erase': + draw_rectangle(coords, 0) + elif get_current_action() == 'draw_rect': + height = dpg.get_value('height_input') + draw_rectangle(coords, np.max(get_height_map()) * height) + + update_texture(get_height_map()) + + set_map_window_value('drawing', False) + dpg.hide_item('drawing_frame') + +def draw_rectangle(coords, height): + y1, y2, x1, x2 = get_rect(get_prev_coords(), coords) + height_map = get_height_map() + height_map[y1:y2, x1:x2] = height + set_map_window_value('height_map', height_map) + +def on_ctrl_o(sender, app_data): + if dpg.is_key_down(dpg.mvKey_Control) and dpg.is_key_down(dpg.mvKey_O): + dpg.show_item('config_open') + +def on_ctrl_f(sender, app_data): + if dpg.is_key_down(dpg.mvKey_Control) and dpg.is_key_down(dpg.mvKey_F): + dpg.show_item('search_popup') + +def on_ctrl_s(sender, app_data): + if dpg.is_key_down(dpg.mvKey_Control) and dpg.is_key_down(dpg.mvKey_Shift) and dpg.is_key_down(dpg.mvKey_S): + dpg.show_item('config_save_as') + elif dpg.is_key_down(dpg.mvKey_Control) and dpg.is_key_down(dpg.mvKey_S): + config_save_clb() + +def change_height_(): + dpg.set_item_pos('status_text', (dpg.get_item_width('main') - dpg.get_text_size(dpg.get_value('status_text'))[0] - 2 * SPACE, dpg.get_item_pos('status_text')[1])) + + if get_config_file_path(): + change_height(get_config_file_path()) + +def setup_handlers(): + with dpg.handler_registry(): + dpg.add_key_press_handler(key=dpg.mvKey_O, callback=on_ctrl_o) + dpg.add_key_press_handler(key=dpg.mvKey_F, callback=on_ctrl_f) + dpg.add_key_press_handler(key=dpg.mvKey_S, callback=on_ctrl_s) + + with dpg.item_handler_registry(tag='main_handler'): + dpg.add_item_resize_handler(callback=change_height_) + + dpg.bind_item_handler_registry('main', 'main_handler') + dpg.set_viewport_resize_callback(change_height_) + + with dpg.item_handler_registry(tag='map_handler'): + dpg.add_item_resize_handler(callback=set_action_none_clb) + + dpg.bind_item_handler_registry('map_window', 'map_handler') + + with dpg.handler_registry(): + dpg.add_mouse_down_handler(callback=mouse_down_callback) + dpg.add_mouse_release_handler(callback=mouse_release_callback) \ No newline at end of file diff --git a/remote_run.py b/remote_run.py new file mode 100644 index 0000000000000000000000000000000000000000..721b75eae44a08c2d3664f7457cbe078692c1540 --- /dev/null +++ b/remote_run.py @@ -0,0 +1,93 @@ +import threading +import os +import re +import time + +from scp import SCPClient +import dearpygui.dearpygui as dpg +import paramiko + +from utils import show_status_text + + +HOST = "geophyslab.srcc.msu.ru" +REPOS = ( + "git clone http://{username}:{password}@tesla.parallel.ru/emortikov/nselibx-common.git", + "git clone http://{username}:{password}@tesla.parallel.ru/emortikov/nselibx-wstgrid.git", + "git clone http://{username}:{password}@tesla.parallel.ru/emortikov/nse-gabls1-urban-les.git" +) + +def run_on_remote_server(username, password, gitlab_username, gitlab_password): + try: + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(HOST, username=username, password=password) + + scp = SCPClient(ssh.get_transport()) + + scp.put("tmp.txt", "./tmp.txt") + + commands = [ + "mkdir run", + "cd ./run" + ] + + # for repo in REPOS: + # commands.append(repo.format(username=gitlab_username, password=gitlab_password)) + + commands += [ + "cd ./nse-gabls1-urban-les/nse-gabls1-urban-les/", + #"chmod +x cpall.sh", + #"./cpall.sh ../../code", + "cd ../../code", + #"make -B MACHINE=local COMPILER=gnu", + "cp ../../tmp.txt ./config.txt", + "./nsenx" + ] + + commands = '; '.join(commands) + + log = '' + + stdin, stdout, stderr = ssh.exec_command(commands) + + while not stdout.channel.exit_status_ready(): + if stdout.channel.recv_ready(): + output = stdout.channel.recv(512).decode() + update_progress(output) + log += '\n\n' + output + time.sleep(1) + + output = stdout.read().decode() + dpg.set_value("progress_bar", 1.) + dpg.hide_item('loading') + + log += '\n\n' + output + + with open("log.txt", "w") as res_file: + res_file.write(output) + + scp.close() + ssh.close() + + show_status_text("success:) log written to log.txt.") + + except Exception as e: + show_status_text(f"error: {str(e)}") + +def update_progress(output): + if not re.findall(r'([0-9]+)%', output): + progress = 0.0 + show_status_text(f"building model") + dpg.show_item('loading') + x, y = dpg.get_item_pos('status_text') + dpg.set_item_pos('loading', (x - 40, y)) + else: + progress = int(re.findall(r'([0-9]+)%', output)[-1]) / 100 + show_status_text('') + dpg.hide_item('loading') + dpg.set_value("progress_bar", progress) + +def start_remote_execution(server_username, server_password, gitlab_username, gitlab_password): + dpg.configure_item("login_modal", show=False) + threading.Thread(target=run_on_remote_server, args=(server_username, server_password, gitlab_username, gitlab_password)).start() diff --git a/static_ui.py b/static_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..051a1302a5c0037413cd4813a234532f0c3422c8 --- /dev/null +++ b/static_ui.py @@ -0,0 +1,132 @@ +import dearpygui.dearpygui as dpg + +from utils import * +from constants import * +from styles import setup_fonts, setup_themes, apply_theme, get_accent_color +from handlers import setup_handlers +from callbacks import * + + +def construct_main_window_ui(): + with dpg.window(label='config file editor', tag='main', user_data={'config': None, 'config_file_path': None}): + with dpg.menu_bar(): + with dpg.menu(label='config'): + dpg.add_menu_item(label='open\t\t', shortcut='ctrl+o', callback=open_config_open_dialog_clb, tag='open_config_open_dialog') + dpg.add_menu_item(label='search\t\t', shortcut='ctrl+f', callback=show_config_search_window_clb, tag='show_config_search_window') + dpg.add_menu_item(label='save\t\t', shortcut='ctrl+s', callback=config_save_clb, tag='save_config') + dpg.add_menu_item(label='save as\t\t', shortcut='ctrl+shift+s', callback=open_config_save_as_dialog_clb, tag='open_config_save_as_dialog') + + with dpg.menu(label='map'): + dpg.add_menu_item(label='open map', callback=open_map_open_dialog_clb, tag='open_map_open_dialog') + with dpg.menu(label='generate map'): + for lcz_type in LCZS: + dpg.add_menu_item(label=lcz_type, check=False, callback=combo_menu(generate_map_clb), tag=lcz_type) + + with dpg.menu(label='run'): + dpg.add_menu_item(label='run on lab server', callback=remote_run_clb, tag='remote_run') + + with dpg.menu(label='view'): + with dpg.menu(label='ui', tag='ui'): + for ui in UIS: + dpg.add_menu_item(label=ui, check=True, default_value=False, callback=combo_menu(construct_config_ui_clb), tag=ui) + dpg.set_value(UIS[1], True) + + with dpg.menu(label='theme', tag='theme'): + for theme in THEMES: + dpg.add_menu_item(label=theme, check=True, default_value=False, callback=combo_menu(apply_theme_clb), tag=theme) + dpg.set_value('orange', True) + + dpg.add_progress_bar(label='progress', default_value=0.0, tag='progress_bar', height=FONT_SIZE, width=200, pos=[dpg.get_viewport_width() // 2 - 100 + SPACE, SPACE]) + dpg.add_text('', tag='status_text', show=True) + dpg.add_loading_indicator(style=1, color=(255, 255, 255, 255), thickness=2, radius=2, tag='loading', show=False) + +def construct_crendentials_window(): + with dpg.window(label='credentials', show=False, tag='login_modal', width=550, height=350): + dpg.add_input_text(label='server username', tag='server_username', password=False, default_value=os.getenv('SERVER_LOGIN', ''), width=300) + dpg.add_input_text(label='server password', tag='server_password', password=True, default_value=os.getenv('SERVER_PASS', ''), width=300) + dpg.add_input_text(label='gitlab username', tag='gitlab_username', password=False, default_value=os.getenv('GITLAB_LOGIN', ''), width=300) + dpg.add_input_text(label='gitlab password', tag='gitlab_password', password=True, default_value=os.getenv('GITLAB_PASS', ''), width=300) + dpg.add_button(label='submit', callback=start_remote_execution_clb) + +def construct_config_open_dialog(): + with dpg.file_dialog(directory_selector=False, show=False, callback=config_open_clb, tag='config_open', width=600, height=400): + dpg.add_file_extension('.txt', color=(0, 255, 0, 255)) + dpg.add_file_extension('.*', color=(255, 255, 255, 255)) + +def construct_config_save_as_dialog(): + with dpg.file_dialog(directory_selector=False, show=False, callback=config_save_as_clb, tag='config_save_as', width=600, height=400): + dpg.add_file_extension('.txt', color=(0, 255, 0, 255)) + dpg.add_file_extension('.*', color=(255, 255, 255, 255)) + +def construct_map_open_dialog(): + with dpg.file_dialog(directory_selector=False, show=False, callback=map_open_clb, tag='map_open', width=600, height=400): + dpg.add_file_extension('.txt', color=(0, 255, 0, 255)) + dpg.add_file_extension('.*', color=(255, 255, 255, 255)) + +def construct_map_save_as_dialog(): + with dpg.file_dialog(directory_selector=False, show=False, callback=map_save_as_clb, tag='map_save_as', width=600, height=400): + dpg.add_file_extension('.txt', color=(0, 255, 0, 255)) + dpg.add_file_extension('.*', color=(255, 255, 255, 255)) + +def construct_set_file_path_dialog(): + with dpg.file_dialog(directory_selector=True, show=False, callback=set_file_path_clb, tag='set_folder_dialog', width=500, height=400): + dpg.add_file_extension('.*', color=(255, 255, 255, 255)) + +def construct_message_window(): + with dpg.window(label='', no_collapse=True, autosize=True, no_resize=True, modal=True, tag='message_popup', show=False, pos=(dpg.get_viewport_width() / 3, dpg.get_viewport_height() / 3)): + dpg.add_text('file saved successfully:)') + +def construct_search_window(): + with dpg.window(label='', no_collapse=True, autosize=True, no_resize=True, tag='search_popup', show=False, pos=(40, 40)): + dpg.add_input_text(hint='search for...', on_enter=True, callback=search_clb, tag='search') + +def construct_map_edit_window(): + with dpg.texture_registry(): + dpg.add_dynamic_texture(800, 800, np.zeros((800, 800, 4), dtype=np.uint8), tag='heatmap_texture') + + with dpg.theme() as item_theme: + with dpg.theme_component(dpg.mvAll): + dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (0, 0, 0, 0), category=dpg.mvThemeCat_Core) + dpg.add_theme_style(dpg.mvStyleVar_ChildRounding, 0, category=dpg.mvThemeCat_Core) + + dpg.add_colormap_registry(label='colormap registry', tag='colormap_registry') + dpg.add_colormap(list(map(apply_tint, [[0, 0, 0], [100, 100, 100], [255, 255, 255]])), False, tag='colormap', parent='colormap_registry') + + with dpg.window(label='edit map', tag='map_window', user_data={'height_map': None, 'height_map_file_path': None, 'drawing': False, 'prev_coords': None, 'current_action': None}, horizontal_scrollbar=True, show=False, width=600, height=400, pos=[20, 60], on_close=map_window_on_close_clb): + with dpg.menu_bar(): + with dpg.menu(label='file'): + dpg.add_menu_item(label='save', callback=map_save_clb, tag='map_save') + dpg.add_menu_item(label='save as', callback=open_map_save_as_dialog_clb, tag='open_map_save_as_dialog') + + with dpg.group(horizontal=True): + with dpg.group(width=200, tag='tools'): + dpg.add_button(label='erase', callback=set_action_clb, tag='erase') + dpg.add_button(label='draw rectangle', callback=set_action_clb, tag='draw_rect') + dpg.add_colormap_slider(tag='height_input') + dpg.bind_colormap(dpg.last_item(), 'colormap') + dpg.add_image('heatmap_texture', tint_color=(0, 119, 200, 255), tag='map') + + dpg.add_child_window(tag='drawing_frame', show=False, width=0, height=0) + + dpg.bind_item_theme('drawing_frame', item_theme) + + +def construct_ui(): + construct_main_window_ui() + + construct_message_window() + + construct_map_open_dialog() + construct_map_save_as_dialog() + construct_map_edit_window() + construct_crendentials_window() + + construct_config_save_as_dialog() + construct_config_open_dialog() + construct_search_window() + + construct_set_file_path_dialog() + + setup_handlers() + setup_fonts() + setup_themes() \ No newline at end of file diff --git a/styles.py b/styles.py index fcb78614a5ec75f64b873bbb00f46d2ffc4cf1dc..d8a8bbd6f4b72a30da77a2218254c5c2a2bab05b 100644 --- a/styles.py +++ b/styles.py @@ -1,37 +1,11 @@ +import os + from enum import Enum import dearpygui.dearpygui as dpg -FONT_PATH = "fonts\\Montserrat-Medium.ttf" -ICON_PATH = "icons\\icon.ico" -FONT_SIZE = 24 -ACCENT_COLOR = (0, 119, 200, 100) -ROUNDING = 10 -SPACE = 10 -INDENT = 40 -BORDER = 3 - -TEXT = (0, 1) -ACCENT = (5, 18, 19, 20, 15, 16, 35, 37, 34, 23, 22, 25, 26, 27, 28, 29, 10, 11, 12, 30, 31, 32, 49, 50) -PRIMARY = (2, 3, 39) -SECONDARY = (36, 33, 21, 24, 4, 7, 8, 9, 13, 14) -APPLY_ALPHA = (1, 22, 25, 31, 34, 16) -COLOR_TYPE_TO_DPG_ITEM = {'TEXT': (0, 1), 'PRIMARY': (2, 3, 39), 'SECONDARY': (36, 33, 21, 24, 4, 7, 8, 9, 13, 14), 'ACCENT': (5, 18, 19, 20, 15, 16, 35, 37, 34, 23, 22, 25, 26, 27, 28, 29, 10, 11, 12, 30, 31, 32, 49, 50)} -COLOR_TO_COLOR_TYPE = {0: 'TEXT', 1: 'TEXT', 2: 'PRIMARY', 3: 'PRIMARY', 39: 'PRIMARY', 36: 'SECONDARY', 21: 'SECONDARY', 24: 'SECONDARY', 4: 'SECONDARY', 7: 'SECONDARY', 8: 'SECONDARY', 9: 'SECONDARY', 13: 'SECONDARY', 33: 'SECONDARY', 14: 'SECONDARY', 35: 'ACCENT', 20: 'ACCENT', 37: 'ACCENT', 22: 'ACCENT', 23: 'ACCENT', 5: 'ACCENT', 25: 'ACCENT', 26: 'ACCENT', 27: 'ACCENT', 28: 'ACCENT', 29: 'ACCENT', 10: 'ACCENT', 30: 'ACCENT', 11: 'ACCENT', 31: 'ACCENT', 12: 'ACCENT', 32: 'ACCENT', 49: 'ACCENT', 34: 'ACCENT', 15: 'ACCENT', 50: 'ACCENT', 16: 'ACCENT', 18: 'ACCENT', 19: 'ACCENT', 1: 'TEXT'} - -BLUE = { - 'TEXT': (255, 255, 255, 255), - 'PRIMARY': (37, 37, 38, 255), - 'SECONDARY': (51, 51, 55, 255), - 'ACCENT': (0, 119, 200, 153) -} - -ORANGE = { - 'TEXT': (255, 255, 255, 255), - 'PRIMARY': (37, 37, 38, 255), - 'SECONDARY': (51, 51, 55, 255), - 'ACCENT': (251, 133, 0, 153) -} +from utils import * +from constants import * def apply_alpha(color, divisor=2): r, g, b, a = color @@ -76,31 +50,19 @@ def add_styles_to_theme(theme): dpg.add_theme_style(dpg.mvStyleVar_FrameBorderSize, 0, category=dpg.mvThemeCat_Core) -class GuiStyle: - def __init__(self): - self.font_registry = dpg.add_font_registry() - self.montserrat_font = dpg.add_font(FONT_PATH, FONT_SIZE, parent=self.font_registry) - - self.fonts = { - 'montserrat': self.montserrat_font - } - - self.themes = { - 'blue': BLUE, - 'orange': ORANGE - } - - self.dpg_themes = dict() +def setup_fonts(): + font_registry = dpg.add_font_registry() + montserrat_font = dpg.add_font(FONT_PATH, FONT_SIZE, parent=font_registry) + dpg.bind_font(montserrat_font) - for theme_name in self.themes: - theme = dpg.add_theme() - add_styles_to_theme(theme) - add_colors_to_theme(self.themes[theme_name], theme) - self.dpg_themes[theme_name] = theme +def apply_theme(theme_name): + theme = dpg.add_theme() + add_styles_to_theme(theme) + add_colors_to_theme(THEMES[theme_name], theme) + dpg.bind_theme(theme) - def apply_font(self, font): - dpg.bind_font(self.fonts[font]) +def setup_themes(): + apply_theme(get_current_theme()) - def apply_theme(self, theme): - print('appling', theme) - dpg.bind_theme(self.dpg_themes[theme]) \ No newline at end of file +def get_accent_color(): + return THEMES[get_current_theme()]['ACCENT'] \ No newline at end of file diff --git a/utils.py b/utils.py index 78dc7629d32c409416ed4852beaad3527114c936..bb18a0a3ef337fa0a44de3e686d86c8157389839 100644 --- a/utils.py +++ b/utils.py @@ -1,14 +1,116 @@ import dearpygui.dearpygui as dpg + import numpy as np +import threading -from styles import SPACE +from constants import * -def dump_map(height_map, file_path): - with open(file_path, "w", encoding="utf-8") as file: - w, h = height_map.shape - file.write(f"{w} {h}\n") - for row in height_map: - file.write(" ".join(list(map(str, row))) + "\n") +def get_current_ui_type(): + for child in dpg.get_item_children('ui', 1): + if dpg.get_value(child): + return dpg.get_item_alias(child) + +def get_current_theme(): + for child in dpg.get_item_children('theme', 1): + if dpg.get_value(child): + return dpg.get_item_alias(child) + +def get_config_file_path(): + return dpg.get_item_user_data('main')['config_file_path'] + +def get_config(): + return dpg.get_item_user_data('main')['config'] + +def set_main_window_value(key, value): + data = dpg.get_item_user_data('main') + data[key] = value + dpg.set_item_user_data('main', data) + +def get_height_map_path(): + return dpg.get_item_user_data('map_window')['height_map_file_path'] + +def get_height_map(): + return dpg.get_item_user_data('map_window')['height_map'] + +def is_drawing(): + return dpg.get_item_user_data('map_window')['drawing'] + +def get_prev_coords(): + return dpg.get_item_user_data('map_window')['prev_coords'] + +def get_current_action(): + return dpg.get_item_user_data('map_window')['current_action'] + +def set_map_window_value(key, value): + data = dpg.get_item_user_data('map_window') + data[key] = value + dpg.set_item_user_data('map_window', data) + +def show_status_text(message): + dpg.set_item_pos('status_text', (dpg.get_item_width('main') - dpg.get_text_size(message)[0] - 2 * SPACE, dpg.get_item_pos('status_text')[1])) + dpg.set_value('status_text', message) + +def path_iter(path, prefix=""): + namespaces = path.split('.') + for i in range(len(namespaces)): + yield prefix + '.' + '.'.join(namespaces[:i+1]) + +def delete_item_and_clear_alias(item): + alias = dpg.get_item_alias(item) + if alias: + dpg.remove_alias(alias) + if dpg.does_item_exist(item): + dpg.delete_item(item) + +def delete_item_children_and_clear_aliases_(item): + if dpg.does_item_exist(item): + for child in dpg.get_item_children(item, 1): + delete_item_children_and_clear_aliases_(child) + delete_item_and_clear_alias(child) + +def delete_item_children_and_clear_aliases(): + if len(dpg.get_item_children('main', 1)) > 1: + delete_item_children_and_clear_aliases_(dpg.get_item_children('main', 1)[1]) + delete_item_and_clear_alias(dpg.get_item_children('main', 1)[1]) + +#################################################################################### +# +# search utils +# +#################################################################################### + +def change_color_temporarily(text_tag, color, duration=1): + color = list(color)[:-1] + [255] + dpg.configure_item(text_tag, color=color) + threading.Timer(duration, lambda: revert_color(text_tag)).start() + +def revert_color(text_tag): + dpg.configure_item(text_tag, color=(255, 255, 255, 255)) + +#################################################################################### +# +# edit map utils +# +#################################################################################### + +def update_texture(height_map): + max_height = np.max(height_map) + normalized_map = height_map / max_height + + texture_data = normalized_map.flatten() + texture_data = np.tile(texture_data, (4, 1)).T.flatten() + + texture_data.resize(800 * 800 * 4) + + width, height = height_map.shape + + dpg.set_value('heatmap_texture', texture_data) + +def check_coords(x, y, w, h): + mx, my = dpg.get_item_pos('map') + mw, mh = dpg.get_item_width('map'), dpg.get_item_height('map') + + return mx <= x <= x + w <= mx + mw and my <= y <= y + h <= my + mh def apply_tint(color, tint=(0, 119, 200)): tint = np.array(tint) / 255 @@ -38,6 +140,42 @@ def load_height_map(file_path): height_map = [list(map(int, line.strip().split())) for line in lines] return np.array(height_map) +def dump_map(height_map, file_path): + with open(file_path, "w", encoding="utf-8") as file: + w, h = height_map.shape + file.write(f"{w} {h}\n") + for row in height_map: + file.write(" ".join(list(map(str, row))) + "\n") + +#################################################################################### +# +# main menu utils +# +#################################################################################### + +def combo_menu(callback): + def func(s, a, u): + for child in dpg.get_item_children(dpg.get_item_parent(s), 1): + if dpg.get_item_alias(child) != dpg.get_item_alias(s): + dpg.set_value(child, False) + else: + dpg.set_value(child, True) + dpg.set_item_user_data(s, dpg.get_item_alias(s)) + callback(s, a, u) + return func + +#################################################################################### +# +# tabs ui utils +# +#################################################################################### + +def does_tab_bar_child_exist(item): + for uuid in dpg.get_item_children(item, 1): + if dpg.get_item_type(uuid) == "mvAppItemType::mvTabBar": + return True, uuid + return False, None + def get_absolute_y(item): y = dpg.get_item_pos(item)[1] parent = dpg.get_item_parent(item) @@ -56,24 +194,14 @@ def get_item_level(item): parent = dpg.get_item_parent(parent) return level -def does_tab_bar_child_exist(item): - for uuid in dpg.get_item_children(item, 1): - if dpg.get_item_type(uuid) == "mvAppItemType::mvTabBar": - return True, uuid - return False, None +def change_height(item): + if not dpg.does_item_exist(item): + return + if dpg.get_item_type(item) == 'mvAppItemType::mvChildWindow': + uuid = dpg.get_item_parent(item) + new_height = dpg.get_viewport_client_height() - get_absolute_y(uuid) - SPACE * get_item_level(item) -def path_iter(path, prefix=""): - namespaces = path.split('.') - for i in range(len(namespaces)): - yield prefix + '.' + '.'.join(namespaces[:i+1]) + dpg.set_item_height(item, max(new_height, dpg.get_item_user_data(item))) -def combo_menu(callback): - def func(s, a, u): - for child in dpg.get_item_children(dpg.get_item_parent(s), 1): - if dpg.get_item_alias(child) != dpg.get_item_alias(s): - dpg.set_value(child, False) - else: - dpg.set_value(child, True) - dpg.set_item_user_data(s, dpg.get_item_alias(s)) - callback(s, a, u) - return func \ No newline at end of file + for child in dpg.get_item_children(item, 1): + change_height(child) \ No newline at end of file