From 7b83d0d2619bf31f9eeeec1a45755af482b4e8a1 Mon Sep 17 00:00:00 2001 From: Maryshca <kuzmichova.maria@ya.ru> Date: Fri, 11 Oct 2024 17:25:06 +0300 Subject: [PATCH] major update --- README.md | 2 +- app.py | 2 +- callbacks.py | 268 +++++++++++++++++++++++++++++++++++++------------- config.py | 2 +- constants.py | 27 ++++- dynamic_ui.py | 119 ++++++++++++++++++++++ generator.py | 4 +- handlers.py | 53 +--------- map_edit.py | 175 ++++++++++++++++++++++++++++++++ remote_run.py | 143 +++++++++++++++++++-------- static_ui.py | 101 ++++++++++++++++--- utils.py | 136 ++++++++++++------------- 12 files changed, 784 insertions(+), 248 deletions(-) create mode 100644 map_edit.py diff --git a/README.md b/README.md index 08342a5..9df03c3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Как запустить? -`pip install dearpygui numpy paramiko scp python-dotenv` +`pip install dearpygui numpy paramiko scp python-dotenv tifffile` `git clone http://tesla.parallel.ru/kuzmichovamary/gui.git` diff --git a/app.py b/app.py index 5c4fc7c..6efa891 100644 --- a/app.py +++ b/app.py @@ -28,4 +28,4 @@ if __name__ == '__main__': dpg.set_primary_window('main', True) dpg.start_dearpygui() - dpg.destroy_context() \ No newline at end of file + dpg.destroy_context() diff --git a/callbacks.py b/callbacks.py index 34417fc..ad52205 100644 --- a/callbacks.py +++ b/callbacks.py @@ -1,58 +1,93 @@ import dearpygui.dearpygui as dpg +import tifffile from utils import * -from remote_run import start_remote_execution +from constants import SPACE, TREE_COLOR, ROAD_COLOR, BUILDING_COLOR +from remote_run import start_remote_execution, start_model_build, get_server_output_structure +from map_edit import dump_height_map, load_height_map, update_texture 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 +from dynamic_ui import construct_config_ui, construct_model_output_structure_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}) + delete_item_and_clear_alias(f'map_buildings_texture') + delete_item_and_clear_alias(f'map_trees_texture') + delete_item_and_clear_alias(f'map_roads_texture') + delete_item_children_and_clear_aliases(f'map_drawlist') + user_data = { + 'buildings_map': None, + 'trees_map': None, + 'roads_map': None, + 'height_map_file_path': None, + 'drawing': False, + 'prev_coords': None, + 'current_action': None, + 'max_height': 0, + 'output_dirs': None, + } + dpg.set_item_user_data('map_window', user_data) + +def set_file_path_clb(s, a, u): + file_path = a['file_path_name'] + dpg.set_value(u, file_path) def construct_config_ui_clb(s, a, u): if not get_config_file_path(): return - delete_item_children_and_clear_aliases() + delete_config_items_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() +################################################################################ +# +# Config +# +################################################################################ - 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 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 map_save_clb(s, a, u): - dump_map(get_height_map(), get_height_map_file_path()) +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 map_save_as_clb(s, a, u): - dump_map(get_height_map(), a['file_path_name']) +def config_open_clb(s, a, u): + delete_config_items_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 open_map_save_as_dialog_clb(s, a, u): - dpg.show_item('map_save_as') +def open_config_open_dialog_clb(s, a, u): + dpg.show_item('config_open') -def open_map_open_dialog_clb(s, a, u): - dpg.show_item('map_open') +def show_config_search_window_clb(s, a, u): + dpg.show_item('search_popup') -def open_config_open_dialog_clb(s, a, u): +def open_config_save_as_dialog_clb(s, a, u): dpg.show_item('config_open') +################################################################################ +# +# Config Search +# +################################################################################ + + def search_clb(s, data, u): if not get_config_file_path(): return @@ -70,59 +105,160 @@ def search_clb(s, data, u): 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') +################################################################################ +# +# Map edit +# +################################################################################ - dpg.show_item('login_modal') +def construct_map_layers(w, h): + dpg.set_item_width('map_child_window', w) + dpg.set_item_height('map_child_window', h) -def apply_theme_clb(s, a, u): - apply_theme(s) + # dpg.add_texture_registry(tag='textures') + dpg.add_dynamic_texture(w, h, np.zeros(w * h * 4), tag=f'map_buildings_texture', parent='textures') + dpg.add_dynamic_texture(w, h, np.zeros(w * h * 4), tag=f'map_trees_texture', parent='textures') + dpg.add_dynamic_texture(w, h, np.zeros(w * h * 4), tag=f'map_roads_texture', parent='textures') -def set_file_path_clb(s, a, u): - file_path = a['file_path_name'] - dpg.set_value(u, file_path) + dpg.bind_item_theme('map_child_window', 'map_image_theme') + dpg.add_drawlist(w, h, tag=f'map_drawlist', parent='map_child_window', pos=[0, 0]) -def show_config_search_window_clb(s, a, u): - dpg.show_item('search_popup') + dpg.draw_image(f'map_buildings_texture', color=BUILDING_COLOR, tag=f'map_buildings_image', pmin=(0, 0), pmax=(w, h), parent=f'map_drawlist') + dpg.draw_image(f'map_trees_texture', color=TREE_COLOR, tag=f'map_trees_image', pmin=(0, 0), pmax=(w, h), parent=f'map_drawlist') + dpg.draw_image(f'map_roads_texture', color=ROAD_COLOR, tag=f'map_roads_image', pmin=(0, 0), pmax=(w, h), parent=f'map_drawlist') -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 map_open_clb(s, a, u): + height_map_file_path = a['file_path_name'] -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) + if height_map_file_path.endswith('txt'): + height_map = load_height_map(height_map_file_path) + max_height = np.max(height_map) + height_map /= max_height + else: + height_map = tifffile.imread(height_map_file_path) + max_height = 1 -def config_save_as_clb(s, a, u): - update_config_values() - dump_config(get_config(), a['file_path_name']) - dpg.show_item('message_popup') + h, w = height_map.shape -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) + print(dpg.get_item_user_data('map_window')) -def config_save_clb(s, a, u): - update_config_values() - dump_config(get_config(), get_config_file_path()) + set_map_window_value('buildings_map', height_map) + set_map_window_value('roads_map', np.zeros((h, w))) + set_map_window_value('trees_map', np.zeros((h, w))) + set_map_window_value('height_map_file_path', height_map_file_path) + set_map_window_value('max_height', max_height) + + construct_map_layers(w, h) + + update_texture() + + dpg.show_item('map_window') + +def generate_map_clb(s, a, u): + height_map = LCZ(config_path=f'configs/{s}.json').to_height_map(dtype=np.float64) + + max_height = np.max(height_map) + height_map /= max_height + + h, w = height_map.shape + + set_map_window_value('buildings_map', height_map) + set_map_window_value('roads_map', np.zeros((h, w))) + set_map_window_value('trees_map', np.zeros((h, w))) + set_map_window_value('height_map_file_path', f'{dpg.generate_uuid()}.txt') + set_map_window_value('max_height', max_height) + + construct_map_layers(w, h) + + update_texture() + + dpg.show_item('map_window') + +def save_map(file_path): + if file_path.endswith('.tif') or file_path.endswith('.tiff'): + with TiffWriter(file_path) as tif: + tif.write(get_map_window_value('buildings_map')) + tif.write(get_map_window_value('roads_map')) + tif.write(get_map_window_value('trees_map')) + else: + dump_height_map(get_map_window_value('buildings_map'), file_path) + +def map_save_clb(s, a, u): + file_path = get_height_map_file_path() + save_map(file_path) + dpg.show_item('message_popup') + +def map_save_as_clb(s, a, u): + file_path = a['file_path_name'] + save_map(file_path) 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 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 + +################################################################################ +# +# Styles +# +################################################################################ + + +def apply_theme_clb(s, a, u): + apply_theme(s) + + +################################################################################ +# +# Remote execution +# +################################################################################ + + +def start_remote_execution_clb(s, a, u): + dpg.configure_item('login_modal', show=False) + u = dpg.get_item_user_data('login_modal') + if u == 'build_model': + start_model_build(dpg.get_value('server_username'), dpg.get_value('server_password'), dpg.get_value('gitlab_username'), dpg.get_value('gitlab_password')) + elif u == 'run_on_lab_server': + start_remote_execution(dpg.get_value('server_username'), dpg.get_value('server_password')) + elif u == 'download_output': + s = get_server_output_structure(dpg.get_value('server_username'), dpg.get_value('server_password')) + folders, filepaths = parse_dirs(s) + delete_item_children_and_clear_aliases('output') + construct_model_output_structure_ui(filepaths, folders) + dpg.show_item('model_output_window') + +def build_model_on_lab_server_clb(s, a, u): + pass + +def show_get_credentials_modal_clb(s, a, u): + dpg.set_item_user_data('login_modal', u) + + if u == 'run_on_lab_server': + 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.hide_item('gitlab_password') + dpg.hide_item('gitlab_username') + + elif u == 'download_output': + dpg.hide_item('gitlab_password') + dpg.hide_item('gitlab_username') + + dpg.show_item('login_modal') \ No newline at end of file diff --git a/config.py b/config.py index e910835..0096a2b 100644 --- a/config.py +++ b/config.py @@ -12,7 +12,7 @@ class CfgVar: self.comment = comment def __repr__(self): - return f"CfgVar(key={self.key}, value={self.value}, comment={self.comment})" + return f"CfgVar(key='{self.key}', value={self.value}, value_type='{self.value_type}', comment='{self.comment}')" TokenType = Enum('TokenType', ['BRACE_OPEN', 'BRACE_CLOSE', 'VARIABLE', 'NAMESPACE']) diff --git a/constants.py b/constants.py index dbeadab..ba44caf 100644 --- a/constants.py +++ b/constants.py @@ -1,11 +1,32 @@ import os +from config import CfgVar + +ROAD_COLOR = (221, 161, 94, 255) +TREE_COLOR = (96, 108, 56, 255) +BUILDING_COLOR = (188, 108, 37, 255) +EMISSION_POINT_COLOR = (255, 0, 0, 255) + 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'] +MAX_MAP_WIDTH = 2000 +MAX_MAP_HEIGHT = 2000 + +EMISSION_POINT_CONFIG_VARIABLES = { + 'value': CfgVar(key='value', value=28481176.531511, value_type='DOUBLE', comment=''), + 'begin': CfgVar(key='begin', value=25200.0, value_type='DOUBLE', comment='[s]'), + 'xpos': CfgVar(key='xpos', value=200.0, value_type='DOUBLE', comment='[m]'), + 'ypos': CfgVar(key='ypos', value=200.0, value_type='DOUBLE', comment='[m]'), + 'zpos': CfgVar(key='zpos', value=60.0, value_type='DOUBLE', comment='[m]'), + 'sx': CfgVar(key='sx', value=20.0, value_type='DOUBLE', comment='[m]'), + 'sy': CfgVar(key='sy', value=20.0, value_type='DOUBLE', comment='[m]'), + 'sz': CfgVar(key='sz', value=10.0, value_type='DOUBLE', comment='[m]') +} + UIS = [TABS_UI, COLLAPSING_HEADERS_UI] FONT_PATH = os.path.join('fonts', 'Montserrat-Medium.ttf') @@ -22,7 +43,11 @@ ACCENT = (5, 18, 19, 20, 15, 16, 35, 37, 34, 23, 22, 25, 26, 27, 28, 29, 10, 11, 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_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 = { diff --git a/dynamic_ui.py b/dynamic_ui.py index 3b6ab2e..1b1df18 100644 --- a/dynamic_ui.py +++ b/dynamic_ui.py @@ -3,6 +3,22 @@ import dearpygui.dearpygui as dpg from utils import * from constants import * +def check_every_child_checkbox(s, a): + parent = dpg.get_item_parent(s) + + for child in dpg.get_item_children(parent, 1): + print(s, dpg.get_item_alias(child), child, dpg.get_item_type(child)) + t = dpg.get_item_type(child) + if t == 'mvAppItemType::mvGroup': + dpg.set_value(dpg.get_item_children(child, 1)[1], a) + if t == 'mvAppItemType::mvCollapsingHeader': + checkbox = dpg.get_item_children(child, 1)[0] + dpg.set_value(checkbox, a) + check_every_child_checkbox(checkbox, a) + +def folder_checkbox_clb(s, a, u): + check_every_child_checkbox(s, a) + def open_folder_dialog_clb(s, a, u): dpg.show_item('set_folder_dialog') dpg.set_item_user_data('set_folder_dialog', u) @@ -16,6 +32,109 @@ def construct_config_ui(config_file_path, config): else: construct_config_ui_collapsing_headers(config_file_path, config) +def add_item_to_config_ui(full_path): + config_file_path = get_config_file_path() + config = get_config() + if get_current_ui_type() == TABS_UI: + 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 + else: + 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 + + + if not dpg.does_item_exist(config_file_path + '.' + full_path): + create_widget(config_file_path, config, full_path) + +def add_emission_point_and_update_ui(x, y): + emission_point = { + 'value': CfgVar(key='value', value=28481176.531511, value_type='DOUBLE', comment=''), + 'begin': CfgVar(key='begin', value=25200.0, value_type='DOUBLE', comment='[s]'), + 'xpos': CfgVar(key='xpos', value=x, value_type='DOUBLE', comment='[m]'), + 'ypos': CfgVar(key='ypos', value=y, value_type='DOUBLE', comment='[m]'), + 'zpos': CfgVar(key='zpos', value=60.0, value_type='DOUBLE', comment='[m]'), + 'sx': CfgVar(key='sx', value=20.0, value_type='DOUBLE', comment='[m]'), + 'sy': CfgVar(key='sy', value=20.0, value_type='DOUBLE', comment='[m]'), + 'sz': CfgVar(key='sz', value=10.0, value_type='DOUBLE', comment='[m]') + } + prefix = f'passive_tracers.tracer_{get_max_n_emission_point() + 1}.point_emission' + + config = get_config() + + for key in emission_point: + full_path = '.'.join([prefix, key]) + config[full_path] = emission_point[key] + add_item_to_config_ui(full_path) + + set_main_window_value('config', config) + +def delete_emission_point_and_update_ui(x, y): + emission_point = { + 'value': CfgVar(key='value', value=28481176.531511, value_type='DOUBLE', comment=''), + 'begin': CfgVar(key='begin', value=25200.0, value_type='DOUBLE', comment='[s]'), + 'xpos': CfgVar(key='xpos', value=x, value_type='DOUBLE', comment='[m]'), + 'ypos': CfgVar(key='ypos', value=y, value_type='DOUBLE', comment='[m]'), + 'zpos': CfgVar(key='zpos', value=60.0, value_type='DOUBLE', comment='[m]'), + 'sx': CfgVar(key='sx', value=20.0, value_type='DOUBLE', comment='[m]'), + 'sy': CfgVar(key='sy', value=20.0, value_type='DOUBLE', comment='[m]'), + 'sz': CfgVar(key='sz', value=10.0, value_type='DOUBLE', comment='[m]') + } + prefix = f'passive_tracers.tracer_{get_max_n_emission_point() + 1}.point_emission' + + config = get_config() + + for key in emission_point: + full_path = '.'.join([prefix, key]) + config[full_path] = emission_point[key] + add_item_to_config_ui(full_path) + + set_main_window_value('config', config) + +def construct_model_output_structure_ui(filepaths, folders): + dpg.add_collapsing_header(label='output', parent='model_output_window', tag='output_header', indent=0, default_open=True) + dpg.add_checkbox(label='select all', default_value=False, parent='output_header', tag='output', callback=folder_checkbox_clb) + + for path in folders: + folders = path.split('/') + parent = 'output_header' + for folder in folders: + if not dpg.does_item_exist(f'{folder}_header'): + with dpg.collapsing_header(label=folder, parent=parent, tag=f'{folder}_header', indent=INDENT): + dpg.add_checkbox(label='select all', default_value=False, tag=folder, callback=folder_checkbox_clb) + + parent = f'{folder}_header' + + for filepath in filepaths: + if '/' not in filepath: + parent = 'output_header' + else: + parent = filepath.split('/')[-2] + '_header' + with dpg.group(horizontal=True, parent=parent, indent=INDENT, xoffset=XOFFSET): + dpg.add_text(filepath.split('/')[-1], tag=f'{filepath}.text') + dpg.add_checkbox(default_value=False, tag=filepath) + 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) diff --git a/generator.py b/generator.py index df871be..bc10c5b 100644 --- a/generator.py +++ b/generator.py @@ -241,8 +241,8 @@ class LCZ: file.write(f"{self.height} {self.width}\n") file.write("\n".join([" ".join(list(map(str, row))) for row in lcz])) - def to_height_map(self, building=True, tree=False): - lcz = np.zeros((self.height, self.width), dtype=int) + def to_height_map(self, dtype=np.int64, building=True, tree=False): + lcz = np.zeros((self.height, self.width), dtype=dtype) if building: self.put_buildings(lcz) if tree: diff --git a/handlers.py b/handlers.py index f77165e..77be6bd 100644 --- a/handlers.py +++ b/handlers.py @@ -2,50 +2,7 @@ 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) +from map_edit import mouse_down_callback, mouse_release_callback, mouse_click_callback def on_ctrl_o(sender, app_data): if dpg.is_key_down(dpg.mvKey_Control) and dpg.is_key_down(dpg.mvKey_O): @@ -79,11 +36,7 @@ def setup_handlers(): 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 + dpg.add_mouse_release_handler(callback=mouse_release_callback) + dpg.add_mouse_click_handler(button=1, callback=mouse_click_callback) \ No newline at end of file diff --git a/map_edit.py b/map_edit.py new file mode 100644 index 0000000..4b2a861 --- /dev/null +++ b/map_edit.py @@ -0,0 +1,175 @@ +import dearpygui.dearpygui as dpg + +from utils import * +from callbacks import * +from constants import MAX_MAP_WIDTH, MAX_MAP_HEIGHT, EMISSION_POINT_COLOR +from dynamic_ui import construct_config_ui, add_emission_point_and_update_ui + +def load_height_map(file_path): + with open(file_path, "r") as file: + lines = file.readlines()[1:] + height_map = [list(map(int, line.strip().split())) for line in lines] + return np.array(height_map, dtype=np.float64) + +def dump_height_map(height_map, file_path): + with open(file_path, "w", encoding="utf-8") as file: + w, h = height_map.shape + height_map *= get_map_window_value('max_height') + file.write(f"{w} {h}\n") + for row in height_map: + file.write(" ".join(list(map(str, map(int, row)))) + "\n") + +def update_texture(): + if get_config_file_path(): + emission_points = get_emission_points() + for n in emission_points: + x = int(emission_points[n]['xpos']) + y = int(emission_points[n]['ypos']) + coords = get_triangle_coords(x, y) + dpg.draw_polygon(coords, color=EMISSION_POINT_COLOR, fill=(255, 0, 0, 255), parent=f'{get_height_map_file_path()}map_drawlist', thickness=0) + + texture_map = np.repeat(get_map_window_value(f'{dpg.get_value('layer')}_map'), 4) + + print(texture_map.shape, np.all(texture_map == 0), f'map_{dpg.get_value('layer')}_texture', np.all(get_map_window_value(f'{dpg.get_value('layer')}_map') == 0)) + + texture_map[3::4] = (texture_map[::4] != 0) + + dpg.set_value(f'map_{dpg.get_value('layer')}_texture', texture_map) + +def mouse_pos_to_height_map_coords(coords): + return map(int, coords) + +def check_coords(x, y, w, h): + return 0 <= x <= x + w <= dpg.get_item_width('map_child_window') and 0 <= y <= y + h <= dpg.get_item_height('map_child_window') + +def get_rect(cur_pos, end_pos): + x1, y1 = mouse_pos_to_height_map_coords(cur_pos) + x2, y2 = mouse_pos_to_height_map_coords(end_pos) + + if y2 < y1: + y1, y2 = y2, y1 + + if x2 < x1: + x1, x2 = x2, x1 + + return y1, y2, x1, x2 + +def mouse_down_callback(): + if not get_height_map_file_path(): + return + if not dpg.is_item_focused('map_child_window'): + return + if not is_drawing(): + set_map_window_value('prev_coords', dpg.get_drawing_mouse_pos()) + set_map_window_value('drawing', True) + else: + if get_current_action() == 'erase' or get_current_action() == 'draw_rect': + x, y = dpg.get_drawing_mouse_pos() + px, py = get_prev_coords() + 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_file_path(): + return + if not dpg.is_item_focused('map_child_window'): + return + + dpg.hide_item('emission_values') + + coords = dpg.get_drawing_mouse_pos() + + if get_current_action() == 'erase': + draw_rectangle(coords, 0) + elif get_current_action() == 'draw_rect': + layer = dpg.get_value('layer') + if layer == 'roads': + height = 1. + else: + height = dpg.get_value('height_input') + draw_rectangle(coords, height) + + update_texture() + + set_map_window_value('drawing', False) + + dpg.hide_item('drawing_frame') + +def mouse_click_callback(): + if not get_height_map_file_path(): + return + if not get_config_file_path(): + return + + emission_points = get_emission_points() + + x, y = dpg.get_drawing_mouse_pos() + + is_pos_emission_point, n = is_mouse_pos_emission_point(x, y, emission_points) + + if is_pos_emission_point: + dpg.set_item_width('emission_values', 300) + dpg.set_item_height('emission_values', 300) + dpg.set_item_pos('emission_values', dpg.get_drawing_mouse_pos()) + dpg.show_item('emission_values') + dpg.set_value('emission_settings', f'value: {emission_points[n]['value']}\nbegin: {emission_points[n]['begin']}') + else: + if 0 <= x < dpg.get_item_width('map_child_window') and 0 <= y < dpg.get_item_height('map_child_window'): + add_emission_point_and_update_ui(x, y) + update_texture() + +def draw_rectangle(coords, height): + y1, y2, x1, x2 = get_rect(get_prev_coords(), coords) + map_type = f'{dpg.get_value('layer')}_map' + height_map = get_map_window_value(map_type) + height_map[y1:y2, x1:x2] = height + set_map_window_value(map_type, height_map) + +def is_point_in_triangle(x1, y1, x2, y2, x3, y3, x, y): + denominator = ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3)) + a = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / denominator + b = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / denominator + c = 1 - a - b + return 0 <= a <= 1 and 0 <= b <= 1 and 0 <= c <= 1 + +def is_mouse_pos_emission_point(posx, posy, emission_points, side=30): + h = (3 ** 0.5 / 2) * side + for n in emission_points: + x = emission_points[n]['xpos'] + y = emission_points[n]['ypos'] + x1, y1 = x, y - (2 / 3) * h + x2, y2 = x + side / 2, y + (1 / 3) * h + x3, y3 = x - side / 2, y + (1 / 3) * h + + if is_point_in_triangle(x1, y1, x2, y2, x3, y3, posx, posy): + return True, n + return False, 0 + +def draw_triangle(array, x, y, side=30, color=1.): + + h = (3 ** 0.5 / 2) * side + + x1, y1 = x, y - (2 / 3) * h + x2, y2 = x + side / 2, y + (1 / 3) * h + x3, y3 = x - side / 2, y + (1 / 3) * h + + for i in range(y - side, y + side): + for j in range(x - side, x + side): + if is_point_in_triangle(x1, y1, x2, y2, x3, y3, j, i): + array[i, j] = color + + return array + +def get_triangle_coords(x, y, side=30): + h = (3 ** 0.5 / 2) * side + + x1, y1 = x, y - (2 / 3) * h + x2, y2 = x + side / 2, y + (1 / 3) * h + x3, y3 = x - side / 2, y + (1 / 3) * h + + return (x1, y1), (x2, y2), (x3, y3) \ No newline at end of file diff --git a/remote_run.py b/remote_run.py index 721b75e..6e49ed3 100644 --- a/remote_run.py +++ b/remote_run.py @@ -10,39 +10,85 @@ 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" +HOST = 'geophyslab.srcc.msu.ru' +URBAN_LES_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): +def build_model(server_username, server_password, gitlab_username, gitlab_password, repos=URBAN_LES_REPOS): 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") + ssh.connect(HOST, username=server_username, password=server_password) commands = [ - "mkdir run", - "cd ./run" + 'mkdir run', + 'cd ./run' ] - # for repo in REPOS: - # commands.append(repo.format(username=gitlab_username, password=gitlab_password)) + 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" + 'cd ./nse-gabls1-urban-les/nse-gabls1-urban-les/', + 'chmod +x cpall.sh', + './cpall.sh ../../code', + 'cd ../../code', + 'make -B MACHINE=local COMPILER=gnu -j 23', + ] + + commands = '; '.join(commands) + + 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)) + + stdin, stdout, stderr = ssh.exec_command(commands) + + with open('log.txt', 'w') as file: + file.write(stdout.read().decode()) + + ssh.close() + + show_status_text('') + dpg.hide_item('loading') + + show_status_text('model builded:) log written to log.txt.') + + except Exception as e: + show_status_text(f'error: {str(e)}') + +def make_run_command(): + command = f'mpirun -np {int(dpg.get_value('-np'))} ./nsenx' + if dpg.get_value('-arch') != 'cpu': + command += f' -arch {dpg.get_value('-arch')}' + if dpg.get_value('use-udump'): + command += f' -udump {dpg.get_value('-udump')}' + if dpg.get_value('use-model-output'): + command += f' -model-output {dpg.get_value('-model-output')}' + return command + +def run_on_lab_server(username, password): + try: + show_status_text('model running...') + dpg.show_item('progress_bar') + 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', 'run/code/config.txt') + + command = make_run_command() + + commands = [ + 'cd ./run', + 'cd ./code', + command ] commands = '; '.join(commands) @@ -56,38 +102,57 @@ def run_on_remote_server(username, password, gitlab_username, gitlab_password): output = stdout.channel.recv(512).decode() update_progress(output) log += '\n\n' + output - time.sleep(1) + # time.sleep(1) output = stdout.read().decode() - dpg.set_value("progress_bar", 1.) - dpg.hide_item('loading') + dpg.set_value('progress_bar', 1.) log += '\n\n' + output - with open("log.txt", "w") as res_file: - res_file.write(output) + with open('log.txt', 'w') as file: + file.write(log) scp.close() ssh.close() - show_status_text("success:) log written to log.txt.") + dpg.hide_item('progress_bar') + show_status_text('success:) log written to log.txt.') except Exception as e: - show_status_text(f"error: {str(e)}") + show_status_text(f'error: {str(e)}') + +def update_progress_building_model(): + 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)) 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) + dpg.set_value('progress_bar', progress) + +def start_remote_execution(server_username, server_password): + threading.Thread(target=run_on_lab_server, args=(server_username, server_password)).start() -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() +def start_model_build(server_username, server_password, gitlab_username, gitlab_password): + threading.Thread(target=build_model, args=(server_username, server_password, gitlab_username, gitlab_password)).start() + +def get_server_output_structure(username, password): + try: + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(HOST, username=username, password=password) + + stdin, stdout, stderr = ssh.exec_command('cd ./run/code/output; find . -type f') + + output = stdout.read().decode() + + ssh.close() + + return output + + except Exception as e: + show_status_text(f'error: {str(e)}') \ No newline at end of file diff --git a/static_ui.py b/static_ui.py index 051a130..8704c2f 100644 --- a/static_ui.py +++ b/static_ui.py @@ -8,6 +8,10 @@ from callbacks import * def construct_main_window_ui(): + with dpg.theme() as progress_bar_theme: + with dpg.theme_component(dpg.mvAll): + dpg.add_theme_color(dpg.mvThemeCol_FrameBg, (255, 255, 255, 100), category=dpg.mvThemeCat_Core) + 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'): @@ -23,7 +27,10 @@ def construct_main_window_ui(): 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') + dpg.add_menu_item(label='edit remote run default values', callback=lambda: dpg.show_item('remote_run_settings_window')) + dpg.add_menu_item(label='build model on lab server', callback=show_get_credentials_modal_clb, user_data='build_model') + dpg.add_menu_item(label='run on lab server', callback=show_get_credentials_modal_clb, user_data='run_on_lab_server') + dpg.add_menu_item(label='download output', callback=show_get_credentials_modal_clb, user_data='download_output') with dpg.menu(label='view'): with dpg.menu(label='ui', tag='ui'): @@ -36,12 +43,40 @@ def construct_main_window_ui(): 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_progress_bar(label='progress', show=False, 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) + dpg.bind_item_theme('progress_bar', progress_bar_theme) + +def construct_remote_run_settings_window(): + xoffset = 400 + width = 300 + with dpg.window(label='remote run settings', show=False, tag='remote_run_settings_window', autosize=True, pos=[50, 50]): + with dpg.group(horizontal=True, xoffset=xoffset): + dpg.add_text('number of processes for mpi run') + dpg.add_input_int(default_value=8, width=width, min_value=1, max_value=200, min_clamped=True, max_clamped=True, tag='-np') + with dpg.group(horizontal=True, xoffset=xoffset): + dpg.add_text('arch') + dpg.add_radio_button(items=('gpu', 'cpu', 'mix'), label='arch', default_value='cpu', tag='-arch', horizontal=True) + with dpg.group(horizontal=True, xoffset=xoffset): + dpg.add_text('use dump') + dpg.add_checkbox(default_value=False, tag='use-udump', callback=lambda s, a, u: dpg.show_item('-udump-group') if a else dpg.hide_item('-udump-group')) + with dpg.group(horizontal=True, xoffset=xoffset, tag='-udump-group', show=False): + dpg.add_text('dump from this control points') + dpg.add_input_int(default_value=1, width=width, min_value=1, max_value=200, min_clamped=True, max_clamped=True, tag='-udump', enabled=False) + with dpg.group(horizontal=True, xoffset=xoffset): + dpg.add_text('write model stdout to file') + dpg.add_checkbox(default_value=False, tag='use-model-stdout', callback=lambda s, a, u: dpg.show_item('-model-stdout-group') if a else dpg.hide_item('-model-stdout-group')) + with dpg.group(horizontal=True, xoffset=xoffset, tag='-model-stdout-group', show=False): + dpg.add_text('filename') + dpg.add_input_text(default_value='log.txt', tag='-model-stdout', width=width, enabled=False) + +def construct_model_output_window(): + with dpg.window(label='select model output to download', show=False, tag='model_output_window', width=600, height=600, pos=[50, 50]): + dpg.add_button(label='download selected files') def construct_crendentials_window(): - with dpg.window(label='credentials', show=False, tag='login_modal', width=550, height=350): + with dpg.window(label='credentials', show=False, tag='login_modal', autosize=True, pos=[50, 50]): 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) @@ -61,11 +96,15 @@ def construct_config_save_as_dialog(): 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('.tif', color=(0, 255, 0, 255)) + dpg.add_file_extension('.tiff', 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('.tif', color=(0, 255, 0, 255)) + dpg.add_file_extension('.tiff', color=(0, 255, 0, 255)) dpg.add_file_extension('.*', color=(255, 255, 255, 255)) def construct_set_file_path_dialog(): @@ -81,18 +120,47 @@ def construct_search_window(): 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') + user_data = { + 'buildings_map': None, + 'trees_map': None, + 'roads_map': None, + 'height_map_file_path': None, + 'drawing': False, + 'prev_coords': None, + 'current_action': None, + 'max_height': 0, + 'output_dirs': None, + } + + dpg.add_texture_registry(tag='textures') 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.theme(tag='map_image_theme'): + with dpg.theme_component(dpg.mvAll): + dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 0, 0, category=dpg.mvThemeCat_Core) + dpg.add_theme_style(dpg.mvStyleVar_ItemSpacing, 0, 0, category=dpg.mvThemeCat_Core) + dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 0, 0, category=dpg.mvThemeCat_Core) + dpg.add_theme_style(dpg.mvStyleVar_ItemInnerSpacing, 0, 0, category=dpg.mvThemeCat_Core) - 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): + dpg.add_colormap_registry(label='colormap registry', tag='colormap_registry') + dpg.add_colormap(list(map(apply_tint_func(BUILDING_COLOR), [[0, 0, 0], [100, 100, 100], [255, 255, 255]])), False, tag='buildings_colormap', parent='colormap_registry') + dpg.add_colormap(list(map(apply_tint_func(TREE_COLOR), [[0, 0, 0], [100, 100, 100], [255, 255, 255]])), False, tag='trees_colormap', parent='colormap_registry') + dpg.add_colormap(list(map(apply_tint_func(ROAD_COLOR), [[255, 255, 255], [255, 255, 255], [255, 255, 255]])), False, tag='roads_colormap', parent='colormap_registry') + + with dpg.window( + label='edit map', + tag='map_window', + user_data=user_data, + 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') @@ -100,15 +168,21 @@ def construct_map_edit_window(): with dpg.group(horizontal=True): with dpg.group(width=200, tag='tools'): + dpg.add_radio_button(items=('buildings', 'trees', 'roads'), tag='layer', default_value='buildings', callback=lambda s, a, u: dpg.bind_colormap('height_input', f'{a}_colormap')) 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_button(label='add 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.bind_colormap(dpg.last_item(), 'buildings_colormap') + + with dpg.child_window(no_scrollbar=True, no_scroll_with_mouse=True, menubar=False, border=True, tag='map_child_window'): + dpg.add_child_window(tag='drawing_frame', show=False, width=0, height=0) - dpg.add_child_window(tag='drawing_frame', show=False, width=0, height=0) + with dpg.child_window(tag='emission_values', show=False, width=100, height=200): + dpg.add_button(label='delete', tag='delete_emission_point')#, callback=delete_emission_point_clb) + dpg.add_text(tag='emission_settings') dpg.bind_item_theme('drawing_frame', item_theme) + dpg.bind_item_theme('map_child_window', 'map_image_theme') def construct_ui(): @@ -119,7 +193,10 @@ def construct_ui(): construct_map_open_dialog() construct_map_save_as_dialog() construct_map_edit_window() + construct_crendentials_window() + construct_remote_run_settings_window() + construct_model_output_window() construct_config_save_as_dialog() construct_config_open_dialog() diff --git a/utils.py b/utils.py index bb18a0a..baf43fa 100644 --- a/utils.py +++ b/utils.py @@ -1,10 +1,22 @@ -import dearpygui.dearpygui as dpg +import re +import threading import numpy as np -import threading +import dearpygui.dearpygui as dpg from constants import * +def parse_dirs(s): + filepaths = set(map(lambda x: x[2:], s.strip('\n').split('\n')[1:])) + dirs = set() + for path in filepaths: + lst = path.split('/') + dir_ = '/'.join(lst[:-1]) + if dir_: + dirs.add(dir_) + + return dirs, filepaths + def get_current_ui_type(): for child in dpg.get_item_children('ui', 1): if dpg.get_value(child): @@ -26,11 +38,11 @@ def set_main_window_value(key, value): 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_main_window_value(key): + return dpg.get_item_user_data('main').get(key, None) -def get_height_map(): - return dpg.get_item_user_data('map_window')['height_map'] +def get_height_map_file_path(): + return dpg.get_item_user_data('map_window')['height_map_file_path'] def is_drawing(): return dpg.get_item_user_data('map_window')['drawing'] @@ -46,32 +58,66 @@ def set_map_window_value(key, value): data[key] = value dpg.set_item_user_data('map_window', data) +def get_map_window_value(key): + return dpg.get_item_user_data('map_window').get(key, None) + 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 get_emission_points(): + points = dict() + config = get_config() + for key in config: + if 'point_emission' in key: + point_n = re.findall(r'tracer_([0-9]+)', key)[-1] + if point_n not in points: + points[point_n] = dict() + points[point_n][config[key].key] = config[key].value + return points + +def get_max_n_emission_point(): + config = get_config() + n = 0 + for key in config: + if 'point_emission' in key: + n = max(int(re.findall(r'tracer_([0-9]+)', key)[-1]), n) + return n + +def apply_tint_func(tint): + tint = np.array(tint[:-1]) / 255 + def func(color): + color = np.array(color) / 255 + color *= tint + return list(map(int, (color * 255))) + return func + +def apply_tint(color, tint=(0, 119, 200)): + tint = np.array(tint) / 255 + color = np.array(color) / 255 + color *= tint + return list(map(int, (color * 255))) + 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): +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) + delete_item_children_and_clear_aliases(child) + for child in dpg.get_item_children(item, 2): + delete_item_children_and_clear_aliases(child) + delete_item_and_clear_alias(item) -def delete_item_children_and_clear_aliases(): +def delete_config_items_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]) + delete_item_children_and_clear_aliases(dpg.get_item_children('main', 1)[1]) #################################################################################### # @@ -87,66 +133,6 @@ def change_color_temporarily(text_tag, color, duration=1): 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 - color = np.array(color) / 255 - color *= tint - return list(map(int, (color * 255))) - -def adjust_coords(coords): - x, y = coords - return int(x - dpg.get_item_pos('map')[0] + dpg.get_x_scroll('map_window')), int(y - dpg.get_item_pos('map')[1] + dpg.get_text_size('edit map')[1] + 2 * SPACE + dpg.get_y_scroll('map_window')) - -def get_rect(cur_pos, end_pos): - x1, y1 = adjust_coords(cur_pos) - x2, y2 = adjust_coords(end_pos) - - if y2 < y1: - y1, y2 = y2, y1 - - if x2 < x1: - x1, x2 = x2, x1 - - return y1, y2, x1, x2 - -def load_height_map(file_path): - with open(file_path, "r") as file: - lines = file.readlines()[1:] - 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 -- GitLab