diff --git a/app.py b/app.py index d5a523d211b7433367b51d173b1d49bf6677f735..a76068abaa898a781f10dd1a7059e02ed521b5c3 100644 --- a/app.py +++ b/app.py @@ -3,68 +3,92 @@ import ctypes import sys import dearpygui.dearpygui as dpg +import numpy as np from styles import GuiStyle, INDENT, SPACE, FONT_SIZE, ICON_PATH from config import load_config, dump_config +from generator import LCZ +from utils import * ctypes.windll.shcore.SetProcessDpiAwareness(1) -def get_absolute_y(item): - y = dpg.get_item_pos(item)[1] - parent = dpg.get_item_parent(item) - while parent: - if dpg.get_item_type(parent) == 'mvAppItemType::mvChildWindow': - y += dpg.get_item_pos(parent)[1] - parent = dpg.get_item_parent(parent) - return y - -def get_item_level(item): - level = 0 - parent = dpg.get_item_parent(item) - while parent: - if dpg.get_item_type(parent) == 'mvAppItemType::mvTabBar': - level += 1 - parent = dpg.get_item_parent(parent) - return level - -def config_iter(config, prefix=""): - for path in config: - namespaces = path.split('.') - for i in range(len(namespaces)): - yield prefix + '.' + '.'.join(namespaces[:i+1]) +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): - dpg.create_context() - self.styles = GuiStyle() self.config = dict() self.config_file_path = str() - def delete_config_window_and_clear_aliases(self, file_path): - if dpg.does_item_exist(file_path): + 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() - if dpg.get_item_type(file_path) == "mvAppItemType::mvChildWindow": - uuid = dpg.get_item_parent(dpg.get_item_parent(file_path)) + 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(file_path) + dpg.delete_item(self.config_file_path) - if dpg.does_alias_exist(file_path): - dpg.remove_alias(file_path) - for path in config_iter(self.config, prefix=self.config_file_path): - if dpg.does_alias_exist(path): - dpg.remove_alias(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(self): + 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 check_tabview(self, root): - for uuid in dpg.get_item_children(root, 1): - if dpg.get_item_type(uuid) == "mvAppItemType::mvTabBar": - return True, uuid - return False, None + 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'] @@ -72,81 +96,198 @@ class ConfigEditor: if not file_path: return - if user_data == "open": - self.delete_config_window_and_clear_aliases(self.config_file_path) + 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() - elif user_data == "save_as" or user_data == "save": - self.update_config() + 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") + 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) + 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) + 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 - def construct_config_ui(self): - self.update_config() - self.delete_config_window_and_clear_aliases(self.config_file_path) - if dpg.get_value("use_tabs_ui"): + 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(self): + def construct_main_window_ui(self): dpg.bind_font(self.styles.montserrat_font) - dpg.bind_theme(self.styles.main_theme) + 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.window(label="config file editor", pos=(10, 10), width=dpg.get_viewport_client_width(), height=dpg.get_viewport_client_height(), tag="main", menubar=True): - with dpg.menu_bar(tag="main_menu_bar"): - with dpg.menu(label="file"): - dpg.add_menu_item(label="open", callback=self.open_file_dialog, tag="open_btn", user_data="open") - 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='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="view"): - dpg.add_menu_item(label="use default theme", check=True, callback=lambda _, v: dpg.bind_theme(None if v else self.styles.main_theme), default_value=False) - dpg.add_menu_item(label="use default font", check=True, callback=lambda _, v: dpg.bind_font(None if v else self.styles.montserrat_font), default_value=False) - dpg.add_menu_item(label="use tabs", check=True, callback=lambda: self.construct_config_ui(), default_value=False, tag="use_tabs_ui") - dpg.add_menu_item(label="show theme editor", callback=dpg.show_style_editor) + 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) + 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) + 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=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.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.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") as handler: + 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.bind_item_handler_registry('main', 'main_handler') dpg.set_viewport_resize_callback(lambda: self.change_height(self.config_file_path)) - uuid = dpg.add_handler_registry() + 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)) - dpg.add_mouse_move_handler(parent=uuid, callback=clb) + 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) + 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('.') @@ -162,9 +303,9 @@ class ConfigEditor: self.create_widget(full_path) def construct_config_ui_tabs(self): - tab_bar_exists, tab_bar_uuid = self.check_tabview("main") + 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_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) @@ -175,7 +316,7 @@ class ConfigEditor: for level in range(len(namespaces) - 1): path = self.config_file_path + '.' + '.'.join(namespaces[:level+1]) - tab_bar_exists, tab_bar_uuid = self.check_tabview(parent) + 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)) @@ -193,7 +334,7 @@ class ConfigEditor: def change_height(self, item): if not dpg.does_item_exist(item): return - if dpg.get_item_type(item) == "mvAppItemType::mvChildWindow": + 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) @@ -211,49 +352,55 @@ class ConfigEditor: 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: + 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"): + if dpg.get_value('use_tabs_ui'): indent = 0 - with dpg.group(horizontal=True, parent=parent, indent=indent): - text_uuid = 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") - elif value_type == 'INT': - dpg.add_input_int(default_value=value, tag=tag) - elif "dir" in key.lower(): - dpg.add_input_text(default_value=value, tag=tag) - dpg.add_button(label="...", callback=self.open_folder_dialog, user_data=tag) + 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_input_text(default_value=value, tag=tag) - if comment: - dpg.add_text(comment, parent=dpg.add_tooltip(parent=text_uuid)) + 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.create_viewport( - title='config file editor', - small_icon=ICON_PATH, - large_icon=ICON_PATH - ) - dpg.setup_dearpygui() - dpg.show_viewport() - self.construct_main_window() - if self.config_file_path: - self.construct_config_ui() - dpg.set_primary_window("main", True) + dpg.set_primary_window('main', True) dpg.start_dearpygui() dpg.destroy_context() -if __name__ == "__main__": +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() + app.run() \ No newline at end of file diff --git a/configs/compact_high_rise.json b/configs/compact_high_rise.json new file mode 100644 index 0000000000000000000000000000000000000000..53774152ab5c90afd4c457a692ff8e72ef1f983a --- /dev/null +++ b/configs/compact_high_rise.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 600, + "aspect_ratio": 5, + "building_surface_fraction": 0.5, + "mean_height_of_roughness_elements": 50, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 75, + "min_building_height": 25, + "sky_view_factor": 0.2, + "impervious_surface_fraction": 0.6, + "pervious_surface_fraction": 0.09, + "terrain_roughness_class": 8 +} \ No newline at end of file diff --git a/configs/compact_low_rise.json b/configs/compact_low_rise.json new file mode 100644 index 0000000000000000000000000000000000000000..85f5da15afe7e6a4bc1089b3e3b0acf27fc22ef1 --- /dev/null +++ b/configs/compact_low_rise.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 600, + "aspect_ratio": 1, + "building_surface_fraction": 0.5, + "mean_height_of_roughness_elements": 6, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 10, + "min_building_height": 3, + "sky_view_factor": 0.3, + "impervious_surface_fraction": 0.3, + "pervious_surface_fraction": 0.2, + "terrain_roughness_class": 6 +} \ No newline at end of file diff --git a/configs/compact_mid_rise.json b/configs/compact_mid_rise.json new file mode 100644 index 0000000000000000000000000000000000000000..8c13e0695c1752cb933a2973b9c5964dc6a6fab0 --- /dev/null +++ b/configs/compact_mid_rise.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 600, + "aspect_ratio": 1.2, + "building_surface_fraction": 0.5, + "mean_height_of_roughness_elements": 18, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 25, + "min_building_height": 10, + "sky_view_factor": 0.3, + "impervious_surface_fraction": 0.6, + "pervious_surface_fraction": 0.1, + "terrain_roughness_class": 6 +} \ No newline at end of file diff --git a/configs/heavy_industry.json b/configs/heavy_industry.json new file mode 100644 index 0000000000000000000000000000000000000000..53ae904adb1ecd4c10ee7566733037baf8c12cf5 --- /dev/null +++ b/configs/heavy_industry.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 600, + "aspect_ratio": 0.3, + "building_surface_fraction": 0.25, + "mean_height_of_roughness_elements": 10, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 15, + "min_building_height": 5, + "sky_view_factor": 0.7, + "impervious_surface_fraction": 0.3, + "pervious_surface_fraction": 0.45, + "terrain_roughness_class": 5 +} \ No newline at end of file diff --git a/configs/large_low_rise.json b/configs/large_low_rise.json new file mode 100644 index 0000000000000000000000000000000000000000..5cc56dd3ad5b2a2d734141bfff423ac6f2d81db5 --- /dev/null +++ b/configs/large_low_rise.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 600, + "aspect_ratio": 0.2, + "building_surface_fraction": 0.4, + "mean_height_of_roughness_elements": 5, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 9, + "min_building_height": 3, + "sky_view_factor": 0.8, + "impervious_surface_fraction": 0.4, + "pervious_surface_fraction": 0.1, + "terrain_roughness_class": 5 +} \ No newline at end of file diff --git a/configs/lightweight_low_rise.json b/configs/lightweight_low_rise.json new file mode 100644 index 0000000000000000000000000000000000000000..40a845224aafdefd87c0088d56d9e950a9800b7a --- /dev/null +++ b/configs/lightweight_low_rise.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 600, + "aspect_ratio": 1.2, + "building_surface_fraction": 0.8, + "mean_height_of_roughness_elements": 4, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 6, + "min_building_height": 2, + "sky_view_factor": 0.3, + "impervious_surface_fraction": 0.1, + "pervious_surface_fraction": 0.2, + "terrain_roughness_class": 4 +} \ No newline at end of file diff --git a/configs/open_high_rise.json b/configs/open_high_rise.json new file mode 100644 index 0000000000000000000000000000000000000000..2329717adefe43b755b58cb97f83cab22a05c685 --- /dev/null +++ b/configs/open_high_rise.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 600, + "aspect_ratio": 0.9, + "building_surface_fraction": 0.3, + "mean_height_of_roughness_elements": 30, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 40, + "min_building_height": 20, + "sky_view_factor": 0.6, + "impervious_surface_fraction": 0.3, + "pervious_surface_fraction": 0.3, + "terrain_roughness_class": 7 +} \ No newline at end of file diff --git a/configs/open_low_rise.json b/configs/open_low_rise.json new file mode 100644 index 0000000000000000000000000000000000000000..fae24b54d97fe48784e3780a75c049c09131d92e --- /dev/null +++ b/configs/open_low_rise.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 600, + "aspect_ratio": 0.6, + "building_surface_fraction": 0.3, + "mean_height_of_roughness_elements": 6, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 10, + "min_building_height": 3, + "sky_view_factor": 0.7, + "impervious_surface_fraction": 0.3, + "pervious_surface_fraction": 0.4, + "terrain_roughness_class": 5 +} \ No newline at end of file diff --git a/configs/open_mid_rise.json b/configs/open_mid_rise.json new file mode 100644 index 0000000000000000000000000000000000000000..21bb900a8e91c4a164d6a4ba4d0db0a4831cfc10 --- /dev/null +++ b/configs/open_mid_rise.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 800, + "aspect_ratio": 0.5, + "building_surface_fraction": 0.3, + "mean_height_of_roughness_elements": 18, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 25, + "min_building_height": 10, + "sky_view_factor": 0.6, + "impervious_surface_fraction": 0.4, + "pervious_surface_fraction": 0.3, + "terrain_roughness_class": 5 +} \ No newline at end of file diff --git a/configs/sparsley_build.json b/configs/sparsley_build.json new file mode 100644 index 0000000000000000000000000000000000000000..e3bdd6677772a61aae94c8ea1b6987b1ead88055 --- /dev/null +++ b/configs/sparsley_build.json @@ -0,0 +1,14 @@ +{ + "width": 800, + "height": 600, + "aspect_ratio": 0.12, + "building_surface_fraction": 0.1, + "mean_height_of_roughness_elements": 6, + "standard_deviation_of_roughness_elements": 15.1, + "max_building_height": 10, + "min_building_height": 3, + "sky_view_factor": 0.9, + "impervious_surface_fraction": 0.1, + "pervious_surface_fraction": 0.7, + "terrain_roughness_class": 6 +} \ No newline at end of file diff --git a/generator.py b/generator.py new file mode 100644 index 0000000000000000000000000000000000000000..7cac3db8e20c108cfabcafe393648789f2285279 --- /dev/null +++ b/generator.py @@ -0,0 +1,316 @@ +import json +import numpy as np +from random import shuffle +#from scipy.stats import truncnorm, beta, uniform +import os +import sys + +SURFACE = 0 +ROAD = -1 +TREE = -2 + +ROAD_COLOR = (64, 61, 57) +SURFACE_COLOR = (96, 108, 56) +TREE_COLOR = (58, 90, 64) + +def uniform_(low, upper): + return uniform(low, upper - low) + +class Splitter: + def __init__(self, min_distance, min_block_width, rng): + self.min_distance = min_distance + self.min_block_width = min_block_width + self.rng = rng + + self.split_distance = self.min_block_width * 2 + self.min_distance + 1 + + def gen(self, x1, y1, x2, y2): + self.blocks = [] + self.gen_blocks(x1, y1, x2, y2) + return self.blocks + + def gen_blocks(self, top_left_x, top_left_y, bottom_right_x, bottom_right_y): + width = bottom_right_x - top_left_x + height = bottom_right_y - top_left_y + + if width > self.split_distance and height > self.split_distance: + split_x = self.rng.integers(top_left_x + self.min_block_width + 1, bottom_right_x - self.min_block_width - self.min_distance) + split_y = self.rng.integers(top_left_y + self.min_block_width + 1, bottom_right_y - self.min_block_width - self.min_distance) + + self.gen_blocks(top_left_x, top_left_y, split_x, split_y) + self.gen_blocks(split_x + self.min_distance, top_left_y, bottom_right_x, split_y) + self.gen_blocks(top_left_x, split_y + self.min_distance, split_x, bottom_right_y) + self.gen_blocks(split_x + self.min_distance, split_y + self.min_distance, bottom_right_x, bottom_right_y) + + elif width > self.split_distance and height >= self.min_block_width: + split_x = self.rng.integers(top_left_x + self.min_block_width + 1, bottom_right_x - self.min_block_width - self.min_distance) + + self.gen_blocks(top_left_x, top_left_y, split_x, bottom_right_y) + self.gen_blocks(split_x + self.min_distance, top_left_y, bottom_right_x, bottom_right_y) + + elif height > self.split_distance and width >= self.min_block_width: + split_y = self.rng.integers(top_left_y + self.min_block_width + 1, bottom_right_y - self.min_block_width - self.min_distance) + + self.gen_blocks(top_left_x, top_left_y, bottom_right_x, split_y) + self.gen_blocks(top_left_x, split_y + self.min_distance, bottom_right_x, bottom_right_y) + + else: # width <= self.split_width and height <= self.split_width: + self.blocks.append((top_left_x, top_left_y, bottom_right_x, bottom_right_y)) + +class LCZ: + def __init__(self, config_path='config.json', output_folder='.', seed=None): + self.rng = np.random.default_rng(seed) + self.config = self.load_config(config_path) + self.output_folder = output_folder + + if not os.path.exists(output_folder): + os.mkdir(output_folder) + + self.width = self.config['width'] + self.height = self.config['height'] + + self.total_area = self.config['width'] * self.config['height'] + self.config_building_area = self.total_area * self.config['building_surface_fraction'] + self.mean_street_width = self.config['mean_height_of_roughness_elements'] / self.config['aspect_ratio'] # == ширина улицы, так как все улицы имеют одинаковую ширину + + self.config_tree_area = self.total_area * self.config['pervious_surface_fraction'] + + self.min_building_height = self.config['min_building_height'] + self.max_building_height = self.config['max_building_height'] + + self.possible_buildings = [] + + self.n_buildings = 0 + self.real_building_area = 0 + self.buildings = [] + + self.n_trees = 0 + self.real_tree_area = 0 + self.trees = [] + + self.building_heights = np.array([]) + self.tree_heights = np.array([]) + + self.min_building_distance = max(int(self.mean_street_width), 1) + max_n_street = (self.total_area - self.config_building_area) / (self.mean_street_width * min(self.width, self.height)) - 1 + + self.min_building_width = 2 * (int(self.config_building_area / (min(self.width, self.height) * max_n_street))) # int((max(self.width, self.height) - max_n_street * self.mean_street_width) / max_n_street) + self.split_width = self.min_building_width * 2 + self.min_building_distance + 1 + + self.gen_buildings_v2() + + def check_params(self): + real_mean_height = np.mean(self.building_heights) + real_std_height = np.std(self.building_heights) + real_max_building_height = np.max(self.building_heights) + real_min_building_height = np.min(self.building_heights) + + real = f""" + generated LCZ params: + std of height of roughness elements: {real_std_height} + mean height of roughness elements: {real_mean_height} + max height of roughness elements: {real_max_building_height} + min height of roughness elements: {real_min_building_height} + aspect ratio: {real_mean_height / self.mean_street_width} + building surface fraction: {self.real_building_area / self.total_area} + """ + + desired = f""" + desired LCZ params: + std of height of roughness elements: {self.config['standard_deviation_of_roughness_elements']} + mean height of roughness elements: {self.config['mean_height_of_roughness_elements']} + max height of roughness elements: {self.config['max_building_height']} + min height of roughness elements: {self.config['min_building_height']} + aspect ratio: {self.config['aspect_ratio']} + building surface fraction: {self.config['building_surface_fraction']} + """ + + print(real) + print(desired) + + def gen_building_heights(self): + self.building_heights = self.rng.uniform(self.min_building_height, self.max_building_height - self.min_building_height, self.n_buildings) + + def gen_buildings(self): + splitter = Splitter(self.min_building_distance, self.min_building_width, self.rng) + possible_buildings = splitter.gen(self.min_building_distance, self.min_building_distance, self.width - self.min_building_distance, self.height - self.min_building_distance) + + shuffle(possible_buildings) + + while self.real_building_area < self.config_building_area: + building = possible_buildings.pop() + self.buildings.append(building) + + self.real_building_area += self.area(building) + self.n_buildings += 1 + + if not possible_buildings: + print("end of area for buildings") + break + + self.gen_building_heights() + + def gen_buildings_v2(self): + self.main_road = int((3/2) * self.min_building_distance) + self.road = 2 * self.min_building_distance - self.main_road + + splitter_big = Splitter(self.main_road, self.width // 5, self.rng) + + blocks = splitter_big.gen(self.min_building_distance, self.min_building_distance, self.width - self.min_building_distance, self.height - self.min_building_distance) + + splitter_small = Splitter(self.road, self.min_building_width, self.rng) + + possible_buildings = [] + + for block in blocks: + possible_buildings += splitter_small.gen(*block) + + shuffle(possible_buildings) + + self.possible_buildings = possible_buildings[:] + + while self.real_building_area < self.config_building_area: + building = possible_buildings.pop() + self.buildings.append(building) + + self.real_building_area += self.area(building) + self.n_buildings += 1 + + if not possible_buildings: + print("end of area for buildings") + break + + # while self.real_tree_area < self.config_tree_area: + # tree = possible_buildings.pop() + # self.trees.append(tree) + + # self.real_tree_area += self.area(tree) + # self.n_trees += 1 + + # if not possible_buildings: + # print("end of area for trees") + # print(self.trees) + # break + + self.gen_building_heights() + + def area(self, bbox): + return (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) + + def add_building(self, lcz, bbox, height=2): + lcz[bbox[1]:bbox[3], bbox[0]:bbox[2]] = height + + def add_tree(self, lcz, bbox, height=2): + dx, dy = (bbox[2] - bbox[0]) / 2, (bbox[3] - bbox[1]) / 2 + r = min([dx, dy]) + cx = bbox[0] + dx + cy = bbox[1] + dy + yy, xx = np.mgrid[:self.height, :self.width] + circle = (xx - cx) ** 2 + (yy - cy) ** 2 <= r ** 2 + lcz[circle] = height + + def load_config(self, config_path): + return json.load(open(config_path, 'r', encoding='utf-8')) + + def put_buildings(self, lcz): + for i in range(self.n_buildings): + self.add_building(lcz, self.buildings[i], self.building_heights[i]) + + def put_trees(self, lcz): + for i in range(self.n_trees): + self.add_tree(lcz, self.trees[i], height=16) + + def to_model_input_building(self, filename='lcz.txt'): + lcz = self.to_height_map() + with open(os.path.join(self.output_folder, filename), "w", encoding='utf-8') as file: + file.write(f"{self.height} {self.width}\n") + file.write("\n".join([" ".join(list(map(str, row))) for row in lcz])) + + def to_model_input_tree(self, filename='lcz.txt'): + lcz = self.to_height_map(False, True) + with open(os.path.join(self.output_folder, filename), "w", encoding='utf-8') as file: + file.write(f"{self.height} {self.width}\n") + file.write("\n".join([" ".join(list(map(str, row))) for row in lcz])) + + def to_model_input_road(self, filename='lcz.txt'): + lcz = np.ones((self.height, self.width), dtype=int) + for bbox in self.possible_buildings: + self.add_building(lcz, bbox, 0) + with open(os.path.join(self.output_folder, filename), "w", encoding='utf-8') as file: + 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) + if building: + self.put_buildings(lcz) + if tree: + self.put_trees(lcz) + return lcz + + def model_input_to_height_map(self, filename='map.txt'): + height_map = np.array([list(map(int, row.split())) for row in open(filename).read().split("\n")]) + return height_map + +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" + + out_folder = "." + + for i, ax in enumerate(axs.flat): + lcz = LCZ(config_path=f'configs/{lcz_type}.json', output_folder=out_folder) + + lcz.to_model_input_building(f'lcz_{i + 1}_building.txt') + lcz.to_model_input_tree(f'lcz_{i + 1}_tree.txt') + lcz.to_model_input_road(f'lcz_{i + 1}_road.txt') + lcz.check_params() + + height_map = lcz.to_height_map() + im = ax.imshow(height_map, cmap='viridis', origin='lower') + ax.set_title(f"{lcz_type} {i}") + fig.colorbar(im, ax=ax, shrink=0.3) + + plt.tight_layout() + 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/prerun.py b/prerun.py index ad253af620e7981a9c14737483ba14132382754e..80a164057b41f47a5fd1d9c2cb89e4bb4085c752 100644 --- a/prerun.py +++ b/prerun.py @@ -7,4 +7,4 @@ subprocess.run(['g++', '-std=c++11', '-Wall', '-DEXCLUDE_GPU_BRANCH', '-c', 'con subprocess.run(['g++', '-std=c++11', '-Wall', '-DEXCLUDE_GPU_BRANCH', '-c', 'cfg-var.cpp', '-o', 'cfg-var.o']) subprocess.run(['g++', '-std=c++11', '-Wall', '-DEXCLUDE_GPU_BRANCH', '-c', 'str-com.cpp', '-o', 'str-com.o']) subprocess.run(['g++', '-std=c++11', '-Wall', '-DEXCLUDE_GPU_BRANCH', '-o', 'parser-cli', 'parser-cli.o', 'config-parser.o', 'cfg-var.o', 'str-com.o']) -os.chdir('..') \ No newline at end of file +os.chdir('..') diff --git a/styles.py b/styles.py index eb4d17cab1bd4001f44c422fab006cc405e4336e..fcb78614a5ec75f64b873bbb00f46d2ffc4cf1dc 100644 --- a/styles.py +++ b/styles.py @@ -1,66 +1,106 @@ +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 -ICON_PATH = "icons\\icon.ico" 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) +} + +def apply_alpha(color, divisor=2): + r, g, b, a = color + return (r, g, b, a / divisor) + +def add_colors_to_theme(colors, theme): + with dpg.theme_component(dpg.mvAll, parent=theme): + for color_type in COLOR_TYPE_TO_DPG_ITEM: + for item in COLOR_TYPE_TO_DPG_ITEM[color_type]: + color = colors[color_type] + if item in APPLY_ALPHA: + color = apply_alpha(color) + dpg.add_theme_color(item, color, category=dpg.mvThemeCat_Core) + +def add_styles_to_theme(theme): + rounding = ( + dpg.mvStyleVar_ChildRounding, + dpg.mvStyleVar_WindowRounding, + dpg.mvStyleVar_FrameRounding, + dpg.mvStyleVar_TabRounding, + dpg.mvStyleVar_GrabRounding, + dpg.mvStyleVar_ScrollbarRounding, + dpg.mvStyleVar_PopupRounding + ) + + spacing = ( + dpg.mvStyleVar_ItemSpacing, + dpg.mvStyleVar_ItemInnerSpacing, + dpg.mvStyleVar_WindowPadding, + dpg.mvStyleVar_FramePadding, + ) + + with dpg.theme_component(dpg.mvAll, parent=theme): + for item in rounding: + dpg.add_theme_style(item, ROUNDING, category=dpg.mvThemeCat_Core) + + for item in spacing: + dpg.add_theme_style(item, SPACE, SPACE, category=dpg.mvThemeCat_Core) + + dpg.add_theme_style(dpg.mvStyleVar_ChildBorderSize, BORDER, category=dpg.mvThemeCat_Core) + dpg.add_theme_style(dpg.mvStyleVar_WindowBorderSize, 0, category=dpg.mvThemeCat_Core) + 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.main_theme = dpg.add_theme() - theme_all = dpg.add_theme_component(dpg.mvAll, parent=self.main_theme, tag="theme_all") - - accents = ( - dpg.mvThemeCol_Border, - dpg.mvThemeCol_CheckMark, - dpg.mvThemeCol_SliderGrab, - dpg.mvThemeCol_ScrollbarGrab, - dpg.mvThemeCol_TabActive, - dpg.mvThemeCol_ButtonActive, - dpg.mvThemeCol_Separator, - dpg.mvThemeCol_ResizeGripActive - ) - - rounding = ( - dpg.mvStyleVar_ChildRounding, - dpg.mvStyleVar_WindowRounding, - dpg.mvStyleVar_FrameRounding, - dpg.mvStyleVar_TabRounding, - dpg.mvStyleVar_GrabRounding, - dpg.mvStyleVar_ScrollbarRounding, - dpg.mvStyleVar_PopupRounding - ) - - spacing = ( - dpg.mvStyleVar_ItemSpacing, - dpg.mvStyleVar_ItemInnerSpacing, - dpg.mvStyleVar_WindowPadding, - dpg.mvStyleVar_FramePadding, - ) - - for item in accents: - dpg.add_theme_color(item, ACCENT_COLOR, category=dpg.mvThemeCat_Core, parent=theme_all) - - # dpg.add_theme_color(dpg.mvThemeCol_Header, (255, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_all) - # dpg.add_theme_color(dpg.mvThemeCol_HeaderHovered, (255, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_all) - # dpg.add_theme_color(dpg.mvThemeCol_HeaderActive, (255, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_all) + self.fonts = { + 'montserrat': self.montserrat_font + } + self.themes = { + 'blue': BLUE, + 'orange': ORANGE + } - for item in rounding: - dpg.add_theme_style(item, ROUNDING, category=dpg.mvThemeCat_Core, parent=theme_all) - - for item in spacing: - dpg.add_theme_style(item, SPACE, SPACE, category=dpg.mvThemeCat_Core, parent=theme_all) + self.dpg_themes = dict() + + 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_font(self, font): + dpg.bind_font(self.fonts[font]) - dpg.add_theme_style(dpg.mvStyleVar_IndentSpacing, 30, category=dpg.mvThemeCat_Core, parent=theme_all) - dpg.add_theme_style(dpg.mvStyleVar_ChildBorderSize, BORDER, category=dpg.mvThemeCat_Core, parent=theme_all) - dpg.add_theme_style(dpg.mvStyleVar_WindowBorderSize, 0, category=dpg.mvThemeCat_Core, parent=theme_all) - dpg.add_theme_style(dpg.mvStyleVar_FrameBorderSize, 0, category=dpg.mvThemeCat_Core, parent=theme_all) - dpg.add_theme_style(dpg.mvStyleVar_WindowMinSize, 200, 200, category=dpg.mvThemeCat_Core, parent=theme_all) \ No newline at end of file + def apply_theme(self, theme): + print('appling', theme) + dpg.bind_theme(self.dpg_themes[theme]) \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..78dc7629d32c409416ed4852beaad3527114c936 --- /dev/null +++ b/utils.py @@ -0,0 +1,79 @@ +import dearpygui.dearpygui as dpg +import numpy as np + +from styles import SPACE + +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 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 get_absolute_y(item): + y = dpg.get_item_pos(item)[1] + parent = dpg.get_item_parent(item) + while parent: + if dpg.get_item_type(parent) == 'mvAppItemType::mvChildWindow': + y += dpg.get_item_pos(parent)[1] + parent = dpg.get_item_parent(parent) + return y + +def get_item_level(item): + level = 0 + parent = dpg.get_item_parent(item) + while parent: + if dpg.get_item_type(parent) == 'mvAppItemType::mvTabBar': + level += 1 + 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 path_iter(path, prefix=""): + namespaces = path.split('.') + for i in range(len(namespaces)): + yield prefix + '.' + '.'.join(namespaces[:i+1]) + +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