From 29a9a7fa7f48300f45da759f62335c7e3c096fa5 Mon Sep 17 00:00:00 2001 From: L-Nafaryus Date: Fri, 31 Dec 2021 01:18:39 +0500 Subject: [PATCH] New: gui Mod: global env --- anisotropy/__init__.py | 43 ++- anisotropy/core/cli.py | 431 +++++------------------------ anisotropy/core/config.py | 3 + anisotropy/core/utils.py | 39 ++- anisotropy/gui/__init__.py | 5 + anisotropy/gui/about.py | 23 ++ anisotropy/gui/app.py | 9 + anisotropy/gui/assets/gh-light.png | Bin 0 -> 2330 bytes anisotropy/gui/assets/simple.png | Bin 0 -> 58841 bytes anisotropy/gui/main.py | 70 +++++ anisotropy/gui/runner.py | 168 +++++++++++ anisotropy/gui/settings.py | 107 +++++++ anisotropy/gui/styles.py | 56 ++++ anisotropy/gui/utils.py | 7 + 14 files changed, 556 insertions(+), 405 deletions(-) create mode 100644 anisotropy/gui/__init__.py create mode 100644 anisotropy/gui/about.py create mode 100644 anisotropy/gui/app.py create mode 100644 anisotropy/gui/assets/gh-light.png create mode 100644 anisotropy/gui/assets/simple.png create mode 100644 anisotropy/gui/main.py create mode 100644 anisotropy/gui/runner.py create mode 100644 anisotropy/gui/settings.py create mode 100644 anisotropy/gui/styles.py create mode 100644 anisotropy/gui/utils.py diff --git a/anisotropy/__init__.py b/anisotropy/__init__.py index fab208c..082189b 100644 --- a/anisotropy/__init__.py +++ b/anisotropy/__init__.py @@ -6,38 +6,37 @@ *anisotropy* is a ``Python`` package that is the result of science-research work on the anisotropy of permeability in the periodic porous media. -A project uses own wrappers around external applications -for constructing a shapes and meshes (``Salome``) and computing a flow (``OpenFOAM``). """ __license__ = "GPL3" __version__ = "1.2.0" __author__ = __maintainer__ = "George Kusayko" __email__ = "gkusayko@gmail.com" +__repository__ = "https://github.com/L-Nafaryus/anisotropy" ### # Environment ## import os +from os import path -env = dict( - ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -) -env.update( - CLI = os.path.join(env["ROOT"], "anisotropy/core/cli.py") -) -env.update( - BUILD = os.path.join(env["ROOT"], "build"), - LOG = os.path.join(env["ROOT"], "logs"), - CONFIG = os.path.join(env["ROOT"], "anisotropy/config/default.toml"), - DOCS = os.path.join(env["ROOT"], "docs") -) -env.update( - logger_name = "anisotropy", - db_name = "anisotropy", - db_path = env["BUILD"], - salome_timeout = 15 * 60, - openfoam_template = os.path.join(env["ROOT"], "anisotropy/openfoam/template") -) +PROJECT = path.abspath(path.dirname(__file__)) +TMP = "/tmp/anisotropy" -del os +env = { + "PROJECT": path.abspath(path.dirname(__file__)), + "TMP": TMP, + "CWD": TMP, + "BUILD_DIR": "build", + "CONF_FILE": "anisotropy.toml", + "LOG_FILE": "anisotropy.log", + "DB_FILE": "anisotropy.db" +} + +def loadEnv(): + prefix = "ANISOTROPY_" + + for k, v in env.items(): + os.environ[f"{ prefix }{ k }"] = v + +del os, path, PROJECT, TMP diff --git a/anisotropy/core/cli.py b/anisotropy/core/cli.py index 1c9a1cb..1901c7f 100644 --- a/anisotropy/core/cli.py +++ b/anisotropy/core/cli.py @@ -77,6 +77,7 @@ def verboseLevel(level: int): @click.group() +@click.version_option() def anisotropy(): pass @@ -119,19 +120,19 @@ def init(path, verbose): @click.option( "-N", "--nprocs", "nprocs", type = click.INT, - default = 1, + #default = 1, help = "Count of parallel processes" ) @click.option( "-s", "--stage", "stage", type = click.Choice(["all", "shape", "mesh", "flow", "postProcess"]), - default = "all", + #default = "all", help = "Current computation stage" ) @click.option( "-f", "--force", "overwrite", is_flag = True, - default = False, + #default = False, help = "Overwrite existing entries" ) @click.option( @@ -149,12 +150,24 @@ def init(path, verbose): @click.option( "--exec-id", "execution" ) -def compute(path, configFile, nprocs, stage, overwrite, params, verbose, execution): +@click.option( + "--pid", "pid", + help = "Specify pid file path" +) +@click.option( + "--logfile", "logfile", + help = "Specify log file path" +) +def compute(path, configFile, nprocs, stage, overwrite, params, verbose, execution, pid, logfile): from anisotropy.core.runner import UltimateRunner from anisotropy.core.config import DefaultConfig from anisotropy.core.utils import setupLogger - setupLogger(verboseLevel(verbose)) + if path: + os.makedirs(os.path.abspath(path), exist_ok = True) + os.chdir(os.path.abspath(path)) + + setupLogger(verboseLevel(verbose), logfile) logger = logging.getLogger(__name__) config = DefaultConfig() @@ -162,34 +175,69 @@ def compute(path, configFile, nprocs, stage, overwrite, params, verbose, executi if configFile: filepath = os.path.abspath(configFile) logger.info(f"Loading file from { filepath }") - config.load(configFile) + + try: + config.load(configFile) + + except FileNotFoundError: + config.dump(configFile) else: logger.info("Using default configuration") - config.update( - nprocs = nprocs, - stage = stage, - overwrite = overwrite - ) + args = { + "nprocs": nprocs, + "stage": stage, + "overwrite": overwrite + } + + for k, v in args.items(): + if v is not None: + config.update(**{ k: v }) + if pid: + pidpath = os.path.abspath(pid) + + with open(pidpath, "w") as io: + io.write(str(os.getpid())) + runner = UltimateRunner(config = config, exec_id = execution) runner.fill() runner.start() + os.remove(pidpath) + +@anisotropy.command() +@click.option( + "-P", "--path", "path", + default = os.getcwd(), + help = "Specify directory to use (instead of cwd)" +) +@click.option( + "-v", "--verbose", "verbose", + count = True, + help = "Increase verbose level" +) +def gui(verbose): + import anisotropy + from anisotropy.core.utils import setupLogger + from anisotropy.gui import app + + anisotropy.loadEnv() + + os.makedirs(os.path.abspath(path), exist_ok = True) + os.chdir(os.path.abspath(path)) + os.environ["ANISOTROPY_CWD"] = path + + setupLogger(verboseLevel(verbose)) + logger = logging.getLogger(__name__) + + app.run_server(debug = True) + + ############## """ -def version(): - msg = "Missed package anisotropy" - try: - from anisotropy.core.main import Anisotropy - msg = Anisotropy.version() - - except ImportError: - pass - - return msg @click.group() @click.version_option(version = "", message = version()) @@ -197,339 +245,6 @@ def anisotropy(): pass -@anisotropy.command( - help = "Initialize new anisotropy project." -) -@click.option( - "-P", "--path", "path", - default = os.getcwd(), - help = "Specify directory to use (instead of cwd)" -) -def init(path): - from anisotropy import env - from anisotropy.core.main import Database - - if not os.path.exists(path) or not os.path.isdir(path): - click.echo(f"Cannot find directory { path }") - - return - - wds = [ "build", "logs" ] - - for wd in wds: - os.makedirs(os.path.join(path, wd), exist_ok = True) - - shutil.copy(env["CONFIG"], os.path.join(path, "anisotropy.toml"), follow_symlinks = True) - - db = Database(env["db_name"], path) - db.setup() - - click.echo(f"Initialized anisotropy project in { path }") - - -@anisotropy.command( - help = "Load parameters from configuration file and update database." -) -@click.option( - "-f", "--force", "force", - is_flag = True, - default = False, - help = "Overwrite existing entries" -) -@click.option( - "-p", "--params", "params", - metavar = "key=value", - multiple = True, - cls = KeyValueOption, - help = "Specify control parameters to update (type, direction, theta)" -) -@click.option( - "-P", "--path", "path", - default = os.getcwd(), - help = "Specify directory to use (instead of cwd)" -) -def update(force, params, path): - from anisotropy import env - from anisotropy.core.main import Anisotropy, Database - - env.update( - LOG = os.path.join(path, "logs"), - BUILD = os.path.join(path, "build"), - CONFIG = os.path.join(path, "anisotropy.toml"), - db_path = path - ) - - args = dict() - - for param in params: - args.update(param) - - - model = Anisotropy() - database = Database(env["db_name"], env["db_path"]) - - click.echo("Configuring database ...") - database.setup() - - if database.isempty() or update: - paramsAll = model.loadFromScratch(env["CONFIG"]) - - if args.get("type"): - paramsAll = [ entry for entry in paramsAll if args["type"] == entry["structure"]["type"] ] - - if args.get("direction"): - paramsAll = [ entry for entry in paramsAll if args["direction"] == entry["structure"]["direction"] ] - - if args.get("theta"): - paramsAll = [ entry for entry in paramsAll if args["theta"] == entry["structure"]["theta"] ] - - from anisotropy.core.models import Structure, Mesh - #from numpy - for entry in paramsAll: - database.update(entry) - - - click.echo("{} entries was updated.".format(len(paramsAll))) - - else: - click.echo("Database was not modified.") - - -@anisotropy.command( -# help = """#Compute cases by chain (mesh -> flow) -# -# Control parameters: type, direction, theta (each parameter affects on a queue) -""" -) -@click.option( - "-s", "--stage", "stage", - type = click.Choice(["all", "mesh", "flow", "postProcessing"]), - default = "all", - help = "Current computation stage" -) -@click.option( - "-N", "--nprocs", "nprocs", - type = click.INT, - default = 1, - help = "Count of parallel processes" -) -@click.option( - "-f", "--force", "force", - is_flag = True, - default = False, - help = "Overwrite existing entries" -) -@click.option( - "-p", "--params", "params", - metavar = "key=value", - multiple = True, - cls = KeyValueOption, - help = "Overwrite existing parameter (except control variables)" -) -@click.option( - "-P", "--path", "path", - default = os.getcwd(), - help = "Specify directory to use (instead of cwd)" -) -def compute(stage, nprocs, force, params, path): - from anisotropy import env - from anisotropy.core.main import Anisotropy, Database, logger - from anisotropy.core.utils import setupLogger, parallel - - env.update( - LOG = os.path.join(path, "logs"), - BUILD = os.path.join(path, "build"), - CONFIG = os.path.join(path, "anisotropy.toml"), - db_path = path - ) - - setupLogger(logger, logging.INFO, env["LOG"]) - args = dict() - - for param in params: - args.update(param) - - ### - logger.info("Writing pid ...") - pidpath = os.path.join(path, "anisotropy.pid") - - with open(pidpath, "w") as io: - io.write(str(os.getpid())) - - ### - # Preparations - ## - database = Database(env["db_name"], env["db_path"]) - - logger.info("Loading database ...") - database.setup() - - params = database.loadGeneral( - args.get("type"), - args.get("direction"), - args.get("theta") - ) - queueargs = [] - - for p in params: - s = p["structure"] - - queueargs.append((s["type"], s["direction"], s["theta"])) - - ### - # Wrap function - ## - def computeCase(type, direction, theta): - case = Anisotropy() - case.db = database - case.load(type, direction, theta) - case.evalParams() - case.update() - - logger.info(f"Case: type = { type }, direction = { direction }, theta = { theta }") - logger.info(f"Stage mode: { stage }") - - if stage in ["mesh", "all"]: - case.load(type, direction, theta) - - if not case.params["meshresult"]["meshStatus"] == "Done" or force: - logger.info("Current stage: mesh") - out, err, returncode = case.computeMesh(path) - - if out: logger.info(out) - if err: logger.error(err) - if returncode: - logger.error("Mesh computation failed. Skipping flow computation ...") - - return - - else: - logger.info("Mesh exists. Skipping ...") - - if stage in ["flow", "all"]: - case.load(type, direction, theta) - - if not case.params["flowresult"]["flowStatus"] == "Done" or force: - logger.info("Current stage: flow") - out, err, returncode = case.computeFlow(path) - - if out: logger.info(out) - if err: logger.error(err) - if returncode: - logger.error("Flow computation failed.") - - return - - else: - logger.info("Flow exists. Skipping ...") - - if stage in ["postProcessing", "all"]: - case.load(type, direction, theta) - - if case.params["meshresult"]["meshStatus"] == "Done": - logger.info("Current stage: mesh postProcessing") - case.porosity() - - else: - logger.warning("Cannot compute mesh post processing values.") - - if case.params["flowresult"]["flowStatus"] == "Done": - logger.info("Current stage: flow postProcessing") - case.flowRate() - - else: - logger.warning("Cannot compute flow post processing values.") - - - ### - # Run - ## - if nprocs == 1: - for pos, qarg in enumerate(queueargs): - computeCase(*qarg) - - else: - parallel(nprocs, queueargs, computeCase) - - if os.path.exists(pidpath): - logger.info("Removing pid ...") - os.remove(pidpath) - - logger.info("Computation done.") - - -@anisotropy.command( - help = "Kill process by pid file" -) -@click.option( - "-P", "--path", "path", - default = os.getcwd(), - help = "Specify directory to use (instead of cwd)" -) -@click.argument("pidfile") -def kill(path, pidfile): - from anisotropy.salomepl.utils import SalomeManager - - try: - with open(os.path.join(path, pidfile), "r") as io: - pid = io.read() - - os.kill(int(pid), 9) - - except FileNotFoundError: - click.echo(f"Unknown file { pidfile }") - - except ProcessLookupError: - click.echo(f"Cannot find process with pid { pid }") - - # TODO: killall method kills all salome instances. Not a good way - SalomeManager().killall() - -@anisotropy.command( - help = "! Internal command" -) -@click.argument("root") -@click.argument("type") -@click.argument("direction") -@click.argument("theta") -@click.argument("path") -def computemesh(root, type, direction, theta, path): - # ISSUE: can't hide command from help, 'hidden = True' doesn't work - # [Salome Environment] - - ### - # Args - ## - direction = [ float(num) for num in direction[1:-1].split(" ") if num ] - theta = float(theta) - - ### - # Modules - ## - import os, sys - - pyversion = "{}.{}".format(3, 9) #(*sys.version_info[:2]) - sys.path.extend([ - root, - os.path.join(root, "env/lib/python{}/site-packages".format(pyversion)), - os.path.abspath(".local/lib/python{}/site-packages".format(pyversion)) - ]) - - from anisotropy import env - from anisotropy.core.main import Anisotropy, Database - import salome - - ### - model = Anisotropy() - model.db = Database(env["db_name"], path) - model.load(type, direction, theta) - - salome.salome_init() - model.genmesh(path) - salome.salome_close() - - - @anisotropy.command() @click.option( "-p", "--params", "params", @@ -683,12 +398,4 @@ if __name__ == "__main__": # click.echo("Interrupted!") #finally: - # from anisotropy.salomepl.utils import SalomeManager - # click.echo("Exiting ...") - - # if os.path.exists("anisotropy.pid"): - # os.remove("anisotropy.pid") - - # SalomeManager().killall() - # sys.exit(0) diff --git a/anisotropy/core/config.py b/anisotropy/core/config.py index 25a47e6..3535789 100644 --- a/anisotropy/core/config.py +++ b/anisotropy/core/config.py @@ -42,6 +42,7 @@ class Config(object): self.content = toml.load(path) self.options = deepcopy(self.content["options"]) + self.content.pop("options") def dump(self, filename: str): path = os.path.abspath(filename) @@ -52,6 +53,8 @@ class Config(object): os.makedirs(os.path.split(path)[0], exist_ok = True) + self.content.update(options = self.options) + with open(path, "w") as io: toml.dump(self.content, io, encoder = toml.TomlNumpyEncoder()) diff --git a/anisotropy/core/utils.py b/anisotropy/core/utils.py index 53de9ac..6a45590 100644 --- a/anisotropy/core/utils.py +++ b/anisotropy/core/utils.py @@ -13,16 +13,16 @@ from types import FunctionType import os class CustomFormatter(logging.Formatter): - def _getFormat(self, level: int): + def __init__(self, colors: bool = True): + self.colors = colors + + def applyColors(self, level: int, info: str, msg: str): grey = "\x1b[38;21m" yellow = "\x1b[33;21m" red = "\x1b[31;21m" bold_red = "\x1b[31;1m" reset = "\x1b[0m" - info = "[%(levelname)s %(processName)s %(asctime)s %(funcName)s]" # [ %(processName)s ] - msg = " %(message)s" - formats = { logging.DEBUG: grey + info + reset + msg, logging.INFO: grey + info + reset + msg, @@ -34,7 +34,15 @@ class CustomFormatter(logging.Formatter): return formats.get(level) def format(self, record): - log_fmt = self._getFormat(record.levelno) + info = "[%(levelname)s %(processName)s %(asctime)s %(funcName)s]" # [ %(processName)s ] + msg = " %(message)s" + + if self.colors: + log_fmt = self.applyColors(record.levelno, info, msg) + + else: + log_fmt = info + msg + time_fmt = "%d-%m-%y %H:%M:%S" formatter = logging.Formatter(log_fmt, time_fmt) @@ -42,19 +50,14 @@ class CustomFormatter(logging.Formatter): def setupLogger(level: int, filepath: str = None): - """Applies settings to logger + """Applies settings to root logger - :param logger: - Instance of :class:`logging.Logger` - :param level: Logging level (logging.INFO, logging.WARNING, ..) :param filepath: Path to directory """ - #logger.handlers = [] - #logger.setLevel(level) logging.addLevelName(logging.INFO, "II") logging.addLevelName(logging.WARNING, "WW") @@ -64,20 +67,14 @@ def setupLogger(level: int, filepath: str = None): streamhandler = logging.StreamHandler() streamhandler.setLevel(level) streamhandler.setFormatter(CustomFormatter()) - #logger.addHandler(streamhandler) - - logging.root.setLevel(level) logging.root.addHandler(streamhandler) + logging.root.setLevel(level) + if filepath: - if not os.path.exists(filepath): - os.makedirs(filepath, exist_ok = True) - - filehandler = logging.FileHandler( - os.path.join(filepath, "{}.log".format("anisotropy")) - ) + filehandler = logging.FileHandler(filepath) filehandler.setLevel(logging.INFO) - filehandler.setFormatter(CustomFormatter()) + filehandler.setFormatter(CustomFormatter(colors = False)) logging.root.addHandler(filehandler) diff --git a/anisotropy/gui/__init__.py b/anisotropy/gui/__init__.py new file mode 100644 index 0000000..cd9c312 --- /dev/null +++ b/anisotropy/gui/__init__.py @@ -0,0 +1,5 @@ + +from .main import app + +if __name__ == "__main__": + app.run_server(debug = True) \ No newline at end of file diff --git a/anisotropy/gui/about.py b/anisotropy/gui/about.py new file mode 100644 index 0000000..86c0a32 --- /dev/null +++ b/anisotropy/gui/about.py @@ -0,0 +1,23 @@ + +from dash import html +from dash import dcc +import dash_bootstrap_components as dbc + +from .styles import * +import anisotropy + + +### +# Layout +## +layout = html.Div([ + html.H1(anisotropy.__name__), + html.Hr(), + html.P([ + "Author: {}".format(anisotropy.__author__), + html.Br(), + "License: {}".format(anisotropy.__license__), + html.Br(), + "Version: {}".format(anisotropy.__version__) + ]) +]) diff --git a/anisotropy/gui/app.py b/anisotropy/gui/app.py new file mode 100644 index 0000000..8aba166 --- /dev/null +++ b/anisotropy/gui/app.py @@ -0,0 +1,9 @@ + +import dash +import dash_bootstrap_components as dbc + +app = dash.Dash(__name__, external_stylesheets = [ dbc.themes.LUX ]) +app.title = "anisotropy" +app.config.update( + update_title = None +) \ No newline at end of file diff --git a/anisotropy/gui/assets/gh-light.png b/anisotropy/gui/assets/gh-light.png new file mode 100644 index 0000000000000000000000000000000000000000..73db1f61f3aa55fcaecbca896dbea067706bb7bd GIT binary patch literal 2330 zcmaJ@dpy&7A0KVQT+TU3iOLKmvh6n;HrL%O6)ohFIM()K!!EXAIjO}-B}W%^)GMS^ zbRj&Vi`ep%r%r`PE0rauRMJE)p`NWn=lP@Od|$8M_wsqY-=EL>bNS=9n;FC~MOYyq z5Qu5ON?Ne?^wWKYaP5AUm;6k7ER@nCq#?pMX&OrmLdYE9CJ-6GXT^iTAd9m(`4;E} zfy}Yzg+@pt0@sk(LOz-_L`>RbTB#+~b3MT|& z14UGj%UhW)21C+=LfPqwY$6Bc>x1-?k+cr@pp=D_@sk7+lFS?R)h|hV*O@UW6`Ai_H`049j)JozV)4d8r`BUo7M!zgCCD`;G!S0dV&q zp8LL%~Ba#6Eg$iKtZe$N47EeRXa5>+}e1_})f4LZ~7>us9|I_MOlXiY|*KdQX zeS8~zP@tW6v39VXfr*USwp~rS`=B$j`)nk?@}kq(ClF4d>YOz8hkr;=W~kkm zCW7a<(pcz5X#A0R<9Z{E*BfJ?`u2(tHC?l!+(h-PRUM^q=-1CbjbNI^81U7VyU;3( zT2f^?#ma%q8;EN&iz|Pk@=)pdaMq3d25mbDNrIP#bi|hqP`3*fT}kuvR+%ng#;F!o z!3#SBB2F;iNzfJ(L`*fNFT`NdGQiAH|Ej|Hc2f5bF>p2ufwkUZTFe}(dJ$`MX;C(0 z-V{r-E2m(c5BI}drL(~k6SEswne#fEUdvi&3FiZh%N)80&$C_2kh}Zs$!WSMT5)2L z->t!4_PuW(@0iMUf!tM3EXsxR=8aqQJS!tI77vxXD$Q$NZ1vCFp)TCJPPKgJ6$dq6 z6AvwIT>T_kTfhs9Ro&hblgcP`#)4Y!f6RfcKKz5}fFeLswhx__Ru z`f>qsFw=AMC$D2)!n`KC&FVyxLH=oq<|UuIL`hLk2jvOa!Qq_R3CqNXSp|NlZhBlh z@V0BxvHNAmtK__)^rv^SNw9j3M7}#zg*ToWtbKT~2{x#g2)k9TC|R>Dzhq!T-pA~R z>ZL3u2sdInHHePlU?n!;#-16SNWGJd=j+gQ4vI}N_YbJpa3kc$Q-$nCw8DusXXMl4 z*nJS|qzj=rNJpZI; z$vRbg>YS|lmxjj--{P0zyGBQ?yoS4%MX$EkOMqE$O+Pm%_1o-3WM-q=b4f2dQfTt> zOFR@zTq!4JBAQzKEZEFs#YOE+WUR@=e`!;~PCh-+ddeAuP%>I*Cd8Cb%!1YNZFKDIJTra-- zG#+}%TS;ZSZaqDK8^0CL=AU{B|LU>Hu0mU~}a9D5U= zlszxG=SokFC;8liaQHBlh z2BUO}iYG`REj!%$Xj-syeaVfwq3hF5*L>e!Jm^2?Jo?@}>G*`TrXZHmap&CnfJ4u^ zk68!bvsByruv+!zzl{#!R8}8y9^Ra-sENMbl68A9w_o}?z5eO*E&If08t#Rq+K#s{ zb}mq^_${jVjmeHhTXNWUoFMIe)lJ26`vXz zWvk~HR@whs?OF8cKV8YyEs1et|A`i*;GS`_+RH=ns17TKJg|lJv;Y(H<+2}hIXsat z5ANw;3-!HT?J@e?t>VkCV$sSaXTfoVh-!;h=kN@TPi^X~Dlo@yIP4fl2 z#pt2|1Jj>Ov$71^ei~h1H$uM|R6wY=ZG3Q99U>X|mV`BoS9`4vYw~J@(FazM_8hI9o-&aIo@yE| z6xi92RAt7!kM;6HVQV)mjXu!6Snn}L!YlYRm7CF)XL~-WsBd9Uk6GUDFm8S4@oR}s z!f_@&&0G2FHTy?nQd8F&@&}*SrC)xRgU!h6eepUXk}}=;?q@wX1aZ#AJc}avS@*XR MKo6o-Qerdz3wwRZj{pDw literal 0 HcmV?d00001 diff --git a/anisotropy/gui/assets/simple.png b/anisotropy/gui/assets/simple.png new file mode 100644 index 0000000000000000000000000000000000000000..bf4a02bfdd1822f77ecb6dd8ada0b7b606890ea1 GIT binary patch literal 58841 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 ZKLzL*kk;q~PiLSEa8}FDWt~$(69C{QgE0UA literal 0 HcmV?d00001 diff --git a/anisotropy/gui/main.py b/anisotropy/gui/main.py new file mode 100644 index 0000000..8111b39 --- /dev/null +++ b/anisotropy/gui/main.py @@ -0,0 +1,70 @@ + +from dash import html +from dash import dcc +from dash.dependencies import Input, Output, State +import dash_bootstrap_components as dbc + +from . import ( + runner, + settings, + about +) +from .app import app +from .styles import * +import anisotropy + +### +# Layout +## +app.layout = html.Div([ + # Location + dcc.Location(id = "url", refresh = False), + + # Sidebar + html.Div([ + # Sidebar + html.H2([html.Img(src = "/assets/simple.png", height = "150px")], style = logo), + html.Hr(style = { "color": "#ffffff" }), + dbc.Nav([ + dbc.NavLink("Runner", href = "/", active = "exact", style = white), + dbc.NavLink("Settings", href = "/settings", active = "exact", style = white), + dbc.NavLink("About", href = "/about", active = "exact", style = white), + ], vertical = True, pills = True), + + # Misc + html.Hr(style = white), + dbc.Container([ + dbc.Row([ + dbc.Col("v1.2.0"), + dbc.Col( + html.A( + html.Img(src = "/assets/gh-light.png", height = "20px"), + href = anisotropy.__repository__ + ) + ) + ]) + ], style = misc) + ], style = sidebar), + + # Content + html.Div(id = "page-content", style = page), +], style = content) + + +### +# Callbacks +## +@app.callback( + Output("page-content", "children"), + [ Input("url", "pathname") ] +) +def displayPage(pathname): + if pathname == "/settings": + return settings.layout + + elif pathname == "/about": + return about.layout + + else: + return runner.layout + diff --git a/anisotropy/gui/runner.py b/anisotropy/gui/runner.py new file mode 100644 index 0000000..5deb6dc --- /dev/null +++ b/anisotropy/gui/runner.py @@ -0,0 +1,168 @@ + +from dash.dash_table import DataTable +from dash import html +from dash import dcc +import dash_bootstrap_components as dbc +from dash.dependencies import Input, Output, State + +import os + +from .app import app +from .styles import * +from .utils import getSize + + +### +# Layout +## +layout = html.Div([ + # Messages and timer + dbc.Alert( + id = "status", + duration = 10000, + dismissable = True, + is_open = False, + style = message + ), + dcc.Interval(id = "interval", interval = 1000, n_intervals = 0), + + # Runner + html.H2("Runner"), + html.Hr(), + dbc.Button("Start", id = "start", color = "success", style = minWidth), + dbc.Button("Stop", id = "stop", color = "danger", disabled = True, style = minWidth), + + # Monitor + html.H2("Monitor"), + html.Hr(), + html.P(id = "runner-status"), + DataTable(id = "monitor", columns = [], data = [], style_table = table), + + # Log + html.H2("Log"), + html.Hr(), + dbc.Button("Delete", id = "delete", style = minWidth), + dcc.Textarea(id = "logger", disabled = True, style = bigText) + +]) + + +### +# Callbacks +## +@app.callback( + Output("start", "active"), + [ Input("start", "n_clicks") ], + prevent_initial_call = True +) +def runnerStart(clicks): + import subprocess + + command = [ + "anisotropy", + "compute", + "-v", + "--path", "/tmp/anisotropy", + "--conf", "anisotropy.toml", + "--pid", "anisotropy.pid", + "--logfile", "anisotropy.log" + ] + + subprocess.run( + command, + start_new_session = True, + ) + + return True + +@app.callback( + Output("stop", "active"), + [ Input("stop", "n_clicks") ], + prevent_initial_call = True +) +def runnerStop(clicks): + import psutil + import signal + + pidpath = "/tmp/anisotropy/anisotropy.pid" + + try: + pid = int(open(pidpath, "r").read()) + master = psutil.Process(pid) + + except (FileNotFoundError, psutil.NoSuchProcess): + return True + + else: + os.killpg(master.pid, signal.SIGTERM) + + return True + + + +@app.callback( + Output("monitor", "columns"), + Output("monitor", "data"), + Output("runner-status", "children"), + Output("start", "disabled"), + Output("stop", "disabled"), + Output("delete", "disabled"), + [ Input("interval", "n_intervals") ], +) +def monitorUpdate(intervals): + import psutil + + pidpath = "/tmp/anisotropy/anisotropy.pid" + processes = [] + + try: + pid = int(open(pidpath, "r").read()) + master = psutil.Process(pid) + + except (FileNotFoundError, psutil.NoSuchProcess) as e: + return [], [], "Status: not running", False, True, False + + else: + for process in [ master, *master.children() ]: + created = psutil.time.localtime(process.create_time()) + processes.append({ + "name": process.name(), + "pid": process.pid, + "status": process.status(), + "memory": getSize(process.memory_full_info().uss), + "threads": process.num_threads(), + "created": "{}:{}:{}".format(created.tm_hour, created.tm_min, created.tm_sec) + }) + + columns = [ { "name": col, "id": col } for col in processes[0].keys() ] + + return columns, processes, "Status: running", True, False, True + +@app.callback( + Output("logger", "value"), + [ Input("interval", "n_intervals") ] +) +def logUpdate(intervals): + logpath = "/tmp/anisotropy/anisotropy.log" + + if os.path.exists(logpath): + with open(logpath, "r") as io: + log = io.read() + + return log + + else: + return "Not found" + + +@app.callback( + Output("delete", "active"), + [ Input("delete", "n_clicks") ] +) +def logDelete(clicks): + logpath = "/tmp/anisotropy/anisotropy.log" + + if os.path.exists(logpath): + os.remove(logpath) + + return True \ No newline at end of file diff --git a/anisotropy/gui/settings.py b/anisotropy/gui/settings.py new file mode 100644 index 0000000..c462cec --- /dev/null +++ b/anisotropy/gui/settings.py @@ -0,0 +1,107 @@ + +from dash import html +from dash import dcc +import dash_bootstrap_components as dbc +from dash.dependencies import Input, Output, State + +import os +from .app import app +from .styles import * + + +### +# Layout +## +layout = html.Div([ + # Messages + dbc.Alert( + id = "status", + duration = 10000, + dismissable = True, + is_open = False, + style = message + ), + + # General + html.H2("General"), + html.Hr(), + html.P("Path: {}".format(os.environ.get("ANISOTROPY_CWD", ""))), + dbc.Button("Save", id = "submit", style = minWidth), + + # Options + html.H2("Options"), + html.Hr(), + html.P("Nprocs"), + dcc.Input(id = "nprocs", type = "number", style = minWidth), + html.P("Stage"), + dcc.Dropdown( + id = "stage", + options = [ { "label": k, "value": k } for k in ["all", "shape", "mesh", "flow", "postProcess"] ], + style = minWidth + ), + + # Cases + html.H2("Cases"), + html.Hr(), + dcc.Textarea(id = "cases", style = bigText), +]) + + +### +# Callbacks +## +@app.callback( + Output("nprocs", "value"), + Output("stage", "value"), + Output("cases", "value"), + [ Input("url", "pathname") ] +) +def settingsLoad(pathname): + from anisotropy.core.config import DefaultConfig + import toml + + filepath = os.path.join(os.environ["ANISOTROPY_CWD"], os.environ["ANISOTROPY_CONF_FILE"]) + config = DefaultConfig() + + if os.path.exists(filepath): + config.load(filepath) + + return config["nprocs"], config["stage"], toml.dumps(config.content) + + +@app.callback( + Output("status", "children"), + Output("status", "is_open"), + Output("status", "color"), + [ Input("submit", "n_clicks") ], + [ + State("nprocs", "value"), + State("stage", "value"), + State("cases", "value") + ], + prevent_initial_call = True +) +def settingsSave(nclick, nprocs, stage, cases): + from anisotropy.core.config import DefaultConfig + import toml + + filepath = os.path.join(os.environ["ANISOTROPY_CWD"], os.environ["ANISOTROPY_CONF_FILE"]) + config = DefaultConfig() + + if os.path.exists(filepath): + config.load(filepath) + + config.update( + nprocs = nprocs, + stage = stage + ) + + try: + config.content = toml.loads(cases) + config.dump(filepath) + + except Exception as e: + return str(e), True, "danger" + + else: + return f"Saved to { filepath }", True, "success" \ No newline at end of file diff --git a/anisotropy/gui/styles.py b/anisotropy/gui/styles.py new file mode 100644 index 0000000..7aaa6a9 --- /dev/null +++ b/anisotropy/gui/styles.py @@ -0,0 +1,56 @@ + +minWidth = { + "min-width": "200px", + "max-width": "200px", + "margin-bottom": "15px" +} + +bigText = { + "min-width": "100%", + "min-height": "500px" +} + +message = { + "position": "fixed", + "top": "30px", + "right": "30px", +} + +page = { + "padding": "30px 50px 0px 50px" +} + +table = { + "margin-bottom": "15px", + "min-width": "100%" +} + +sidebar = { + "position": "fixed", + "top": 0, + "left": 0, + "bottom": 0, + "width": "16rem", + "padding": "2rem 1rem", + "background-color": "#363636" +} + +content = { + "margin-left": "18rem", + "margin-right": "2rem", + "padding": "2rem 1rem", +} + +misc = { + "color": "#757575", + "text-align": "center" +} + +logo = { + "color": "#ffffff", + "text-align": "center" +} + +white = { + "color": "#ffffff" +} \ No newline at end of file diff --git a/anisotropy/gui/utils.py b/anisotropy/gui/utils.py new file mode 100644 index 0000000..3b25cb1 --- /dev/null +++ b/anisotropy/gui/utils.py @@ -0,0 +1,7 @@ + +def getSize(bytes): + for unit in ["", "K", "M", "G", "T", "P"]: + if bytes < 1024: + return f"{bytes:.2f} {unit}iB" + + bytes /= 1024 \ No newline at end of file