Compare commits

...
246 Commits
Author SHA1 Message Date
Pierre-Antoine Rouby a603c3a63f git: Update gitignore. 2023-10-10 09:51:57 +02:00
Pierre-Antoine Rouby d0ea629c10 pamhyr: Fix execution without argument. 2023-10-10 09:36:45 +02:00
Pierre-Antoine Rouby b35be16268 ci: Fix windows exe generator script. 2023-10-10 09:30:40 +02:00
Pierre-Antoine Rouby 7644927de9 pamhyr: Allow Pamhyr2 script run on tty mode. 2023-10-09 17:03:23 +02:00
Pierre-Antoine Rouby 242b1a4e50 pamhyr: Put debug info as logger debug handler. 2023-10-09 16:55:54 +02:00
Pierre-Antoine Rouby 397b5b0cab debug: Display qt5 path in terminal. 2023-10-09 16:22:16 +02:00
Pierre-Antoine Rouby 8da1cb622f ci: Install and update pip packages. 2023-10-09 15:22:28 +02:00
Pierre-Antoine Rouby 7365bf70b6 doc: Add mage documentation open action. 2023-10-09 14:51:19 +02:00
Pierre-Antoine Rouby 2825d02cb2 doc: Fix path for build version. 2023-10-09 14:29:40 +02:00
Pierre-Antoine Rouby 24cb32c96c ci: Fix linux ci. 2023-10-09 13:44:15 +02:00
Pierre-Antoine Rouby 820adf2bb7 ci: Fix linux ci. 2023-10-09 12:08:58 +02:00
Pierre-Antoine Rouby 4211855732 ci: Update ci build instruction. 2023-10-09 12:05:29 +02:00
Pierre-Antoine Rouby 44b057c522 Merge branch 'doc-dev' with refactoring and user/dev documentation. 2023-10-09 11:17:13 +02:00
Pierre-Antoine Rouby f4d580a000 doc: Doc in format html or pdf, back/forward button and firefox button. 2023-10-09 11:11:23 +02:00
Pierre-Antoine Rouby a77c342e69 doc: Fix window display for version PyQt5 > 5.5 2023-10-06 16:21:23 +02:00
Pierre-Antoine Rouby 559d7dc006 doc: Keep doc html and add doc window in Pamhyr2. 2023-10-06 15:54:27 +02:00
Pierre-Antoine Rouby 1a47bbe19d Results: Allow to open last results from mainwindow. 2023-10-06 14:54:17 +02:00
Pierre-Antoine Rouby b1c658d711 refactoring: Delete deprecated unused files. 2023-10-06 14:34:15 +02:00
Pierre-Antoine Rouby 2ff4f63d23 refactoring: refactor Results. 2023-10-06 14:25:44 +02:00
Pierre-Antoine Rouby ba087f12a5 refactoring: Apply table refacto on checklist. 2023-10-06 10:40:18 +02:00
Pierre-Antoine Rouby a64a9867ef refactoring: refacto solver parameters 2023-10-04 17:01:31 +02:00
Pierre-Antoine Rouby 63d6c114c0 refactoring: refacto SL reach and profile window. 2023-10-04 16:23:06 +02:00
Pierre-Antoine Rouby 1d45ccb8b8 refactoring: refacto SL list & edit. 2023-10-04 15:19:27 +02:00
Pierre-Antoine Rouby 4ce597a2a0 refactoring: refacto frictions & stricklers. 2023-10-04 14:34:07 +02:00
Pierre-Antoine Rouby b430cbb62b refactoring: refacto IC. 2023-10-04 13:51:01 +02:00
Pierre-Antoine Rouby 500885ec4d refactoring: refacto LC. 2023-10-04 11:26:43 +02:00
Pierre-Antoine Rouby ea3654f1f8 refactoring: refacto BC. 2023-10-04 10:35:54 +02:00
Pierre-Antoine Rouby 7a5b4779e9 refactoring: Minor fix in geometry. 2023-10-03 17:11:06 +02:00
Pierre-Antoine Rouby c744797c6f refactoring: Minor fix in geometry. 2023-10-03 16:39:31 +02:00
Pierre-Antoine Rouby 11a7486a9c Refactoring: Use ui file in Geometry profile edit window. 2023-10-03 16:26:50 +02:00
Pierre-Antoine Rouby 44ccf99d48 Refactoring: Use ui file for geometry reach window. 2023-10-03 15:10:46 +02:00
Pierre-Antoine Rouby ef672798ef refactoring: Start refactoring plot. 2023-09-29 11:17:08 +02:00
Pierre-Antoine Rouby 245a5057c3 refactoring: Update BC. 2023-09-29 10:14:40 +02:00
Pierre-Antoine Rouby e06610756a model: geometry: Fix 'sl' setter. 2023-09-29 08:33:23 +02:00
Pierre-Antoine Rouby dbc256be1a ui, doc: Update Pamhyr2 debug repl. 2023-09-27 16:49:00 +02:00
Pierre-Antoine Rouby e589fcdd3d doc: dev: Complete dev documentation. 2023-09-27 16:42:42 +02:00
Pierre-Antoine Rouby d20cdfc47f doc: dev: Complete Architecture section. 2023-09-27 15:55:45 +02:00
Pierre-Antoine Rouby ab2e307e3d doc: dev: Update graphs. 2023-09-27 12:10:47 +02:00
Pierre-Antoine Rouby a81564e582 doc: dev: Write Plot section. 2023-09-27 09:20:17 +02:00
Pierre-Antoine Rouby f93f3fe122 doc: dev: Update Table and UndoCommand. 2023-09-27 09:00:18 +02:00
Pierre-Antoine Rouby 2e94348bb8 refactoring: Add PamhyrTranslate class for GUI translation and document it. 2023-09-26 17:16:53 +02:00
Pierre-Antoine Rouby e7fd2077bd doc: dev: Update with PamhyrWindow, and minor change in hash computation. 2023-09-26 14:19:22 +02:00
Pierre-Antoine Rouby 624e9b6f71 refactoring: Apply PamhyrWindow on Debug window. 2023-09-26 12:07:10 +02:00
Pierre-Antoine Rouby ca10f19fe5 refactoring: Continue apply PamhyrWindow. 2023-09-26 12:03:41 +02:00
Pierre-Antoine Rouby 1a36d78ee2 refactoring: Apply PamhyrWindow to SL. 2023-09-26 09:34:26 +02:00
Pierre-Antoine Rouby 809a0dd9cb refarctoring: Apply PamhyrWindow to frictions. 2023-09-25 17:01:59 +02:00
Pierre-Antoine Rouby b2b5c971c3 refactoring: Apply new Pamhyr tools on IC. 2023-09-25 16:38:14 +02:00
Pierre-Antoine Rouby 7c4ba10871 refactoring: Continue refacto using PamhyrWindow. 2023-09-25 15:57:53 +02:00
Pierre-Antoine Rouby cc9f853a34 refactoring: Add PamhyrWidget and continue refacto using PamhyrWindow. 2023-09-25 15:31:41 +02:00
Pierre-Antoine Rouby fa32e42933 refactoring: Add PamhyrWindow and PamhyrDialog (WIP). 2023-09-25 15:10:30 +02:00
Pierre-Antoine Rouby 9c7f1f7ba9 doc: Try with system package. 2023-09-22 11:51:00 +02:00
Pierre-Antoine Rouby 3842e689ac doc: Fix build package missing. 2023-09-22 11:45:12 +02:00
Pierre-Antoine Rouby d461cb8001 doc: Fix htmlize into html build. 2023-09-22 11:29:04 +02:00
Pierre-Antoine Rouby 2bb783b5d4 ci: Add HTML documentation. 2023-09-22 11:09:55 +02:00
Pierre-Antoine Rouby edc5b65234 Scripts: Convert plot_3DST.py to Pamhyr2 script. 2023-09-22 11:03:07 +02:00
Pierre-Antoine Rouby 0237c5a80a MainWindow: Connect menu close model. 2023-09-22 09:25:47 +02:00
Pierre-Antoine Rouby 1201184669 packages: windows: Add Desktop shortcuts and update wine script. 2023-09-22 09:05:35 +02:00
Pierre-Antoine Rouby 36adba84e7 config: Fix arguments in default mage solver. 2023-09-20 17:25:34 +02:00
Pierre-Antoine Rouby afa21c9057 doc: dev: View section (UI, window). 2023-09-20 16:46:37 +02:00
Pierre-Antoine Rouby 42dfdcc3b1 doc: dev: Complete Model section. 2023-09-20 15:32:04 +02:00
Pierre-Antoine Rouby 6c9481296b doc: dev: Add SQL section with examples. 2023-09-20 15:21:46 +02:00
Pierre-Antoine Rouby 89353b656f doc: dev: Some minor change. 2023-09-20 11:52:53 +02:00
Pierre-Antoine Rouby 48851c7d71 ci: Fix documentations dependencies. 2023-09-20 10:10:41 +02:00
Pierre-Antoine Rouby d30028c57d ci: Disable package building at master branch. 2023-09-20 09:34:12 +02:00
Pierre-Antoine Rouby 23e36ca3f7 ci: Fix pipeline release job. 2023-09-20 09:31:54 +02:00
Pierre-Antoine Rouby e92e00f5c8 ci: Disable tests. 2023-09-19 17:37:49 +02:00
Pierre-Antoine Rouby 0d547cc469 Scripts: Fix return value. 2023-09-19 17:25:30 +02:00
Pierre-Antoine Rouby 973e6c8080 ci: Fix dependencies. 2023-09-19 17:08:20 +02:00
Pierre-Antoine Rouby 5f6154c510 ci: tests: Add test-linux and test-windows. 2023-09-19 17:05:15 +02:00
Pierre-Antoine Rouby 2cad6f21ab ci: Add running script for GNU/Linux. 2023-09-19 16:46:02 +02:00
Pierre-Antoine Rouby 41a56c6e77 ci: Fix Linux build. 2023-09-19 16:17:19 +02:00
Pierre-Antoine Rouby 487045dbad ci: Add doc. 2023-09-19 16:07:48 +02:00
Pierre-Antoine Rouby c12c06a604 Merge branch 'doc' into refactoring. 2023-09-19 15:56:53 +02:00
Pierre-Antoine Rouby 6380e3110f doc: Add temporary water mark. 2023-09-19 15:55:37 +02:00
Pierre-Antoine Rouby 3fdc0349da SL: Fix tables translate. 2023-09-19 15:29:42 +02:00
Pierre-Antoine Rouby 0691c64ca9 IC: Fix table translate. 2023-09-19 15:23:04 +02:00
Pierre-Antoine Rouby f4545b0075 LC: Fix table translate. 2023-09-19 15:19:06 +02:00
Pierre-Antoine Rouby ecefb39265 BC: Fix table translate. 2023-09-19 15:13:21 +02:00
Pierre-Antoine Rouby 95288e6eef Friction: Fix talbe translate. 2023-09-19 15:02:33 +02:00
Pierre-Antoine Rouby ffebfbdddc Stricklers: Switch stricklers table to PamhyrTableModel and fix translate. 2023-09-19 14:59:47 +02:00
Pierre-Antoine Rouby 4f471d4d6f Script: Add app in script parameters. 2023-09-19 14:41:05 +02:00
Pierre-Antoine Rouby f5046858c8 trans: Fix Network and Results table translate. 2023-09-19 14:40:04 +02:00
Pierre-Antoine Rouby 47bcf8a748 trans: Update name PAMHYR to Pamhyr2 and minor change. 2023-09-19 14:06:14 +02:00
Pierre-Antoine Rouby 694c84a526 Merge branch 'master' into refactoring. 2023-09-19 13:55:16 +02:00
Pierre-Antoine Rouby c567a0b370 Network: Fix edge selection crash. 2023-09-19 11:56:08 +02:00
Pierre-Antoine Rouby a14dfba6b4 doc: dev: Add solver pipeline graph. 2023-09-18 17:25:21 +02:00
Pierre-Antoine Rouby ed006fe1c3 doc: dev: Add archi and model graph. 2023-09-18 16:49:51 +02:00
Pierre-Antoine Rouby a733e148e2 doc: dev: Complete contribution. 2023-09-18 15:03:30 +02:00
Pierre-Antoine Rouby 77c0dddee3 doc: tools: LaTeX class minor change. 2023-09-18 14:19:18 +02:00
Pierre-Antoine Rouby 8429d04b3a doc: dev: Continue contribution section. 2023-09-18 11:58:03 +02:00
Pierre-Antoine Rouby 4c953d29fe doc: dev: Minor change. 2023-09-18 10:39:03 +02:00
Pierre-Antoine Rouby 129e3f3f88 doc: dev: Add abstract. 2023-09-18 10:36:50 +02:00
Pierre-Antoine Rouby 436f4adaf6 Doc: Add GPL logo and minor change. 2023-09-18 09:55:53 +02:00
Pierre-Antoine Rouby 9f9dd508b1 doc: dev: Add install command for texlive. 2023-09-15 16:24:26 +02:00
Pierre-Antoine Rouby d772a44c67 doc: tools, dev: Add caption and box for verbatim. 2023-09-15 16:19:46 +02:00
Pierre-Antoine Rouby 100f98c56b doc: dev: Finish the documentations files section. 2023-09-15 16:09:10 +02:00
Pierre-Antoine Rouby 445de3b777 doc: dev: Introduction + minor change. 2023-09-15 15:29:03 +02:00
Pierre-Antoine Rouby 7bafaf33d0 pamhyr: Update README. 2023-09-15 14:10:56 +02:00
Pierre-Antoine Rouby d18ee586cc Merge: Branch 'paper-simhydro-2023'. 2023-09-15 14:02:13 +02:00
Pierre-Antoine Rouby fbb8450a01 trans: Update translate. 2023-09-15 12:00:55 +02:00
Pierre-Antoine Rouby d40ca95f68 tests cases: Add Enlargement test case study. 2023-09-15 11:04:58 +02:00
Pierre-Antoine Rouby d62e681be1 SolverParameters: Fix Python syntax warning. 2023-09-15 10:59:44 +02:00
Pierre-Antoine Rouby 58daf689bd pamhyr: Apply some change from paper simhydro 2023 patch. 2023-09-15 10:56:41 +02:00
Pierre-Antoine Rouby 6f5a361338 paper: Minor change (included figure size and position). 2023-09-15 09:52:14 +02:00
Theophile Terraz 2c080dc6af aknowledge PITI 2023-09-15 09:28:03 +02:00
Theophile Terraz 0a656645e8 add guidelines in geometry § 2023-09-13 18:13:37 +02:00
Pierre-Antoine Rouby 1c7ddea417 ci: Fix pipeline. 2023-09-13 15:40:40 +02:00
Pierre-Antoine Rouby ac756ec41e ci: Fix linux package. 2023-09-13 15:36:39 +02:00
Pierre-Antoine Rouby 8c5adb2544 paper: Add a sentence for stricklers list. 2023-09-13 15:32:09 +02:00
Pierre-Antoine Rouby d33d6b166f ci: Some minor fix. 2023-09-13 14:05:13 +02:00
Pierre-Antoine Rouby 1384634aa1 ci: Fix windows exe script. 2023-09-13 13:24:58 +02:00
Pierre-Antoine Rouby 906b7f2d39 ci: Minor fix. 2023-09-13 13:02:50 +02:00
Pierre-Antoine Rouby bb662346fa ci: Minor change. 2023-09-13 12:21:08 +02:00
Pierre-Antoine Rouby efff8fe4dc ci: Minor fix. 2023-09-13 12:00:28 +02:00
Pierre-Antoine Rouby fe6cafba47 ci: Minor change. 2023-09-13 11:51:35 +02:00
Pierre-Antoine Rouby ab3a006e65 ci: Add windows script. 2023-09-13 11:45:29 +02:00
Pierre-Antoine Rouby 5197bce872 ci: Minor change of debug ci. 2023-09-13 11:37:45 +02:00
Pierre-Antoine Rouby 00b4292306 ci: Minor change. 2023-09-13 11:12:04 +02:00
Pierre-Antoine Rouby ace5053c1b ci: Fix circular dependencies. 2023-09-13 10:28:31 +02:00
Pierre-Antoine Rouby 24aebfbcf8 ci: Merge build and dir creation. 2023-09-13 10:26:47 +02:00
Pierre-Antoine Rouby 5d6ae31ee9 ci: Refactoring CI pipeline. 2023-09-13 09:42:47 +02:00
Pierre-Antoine Rouby c59edf4661 Script: Help: Minor change. 2023-09-12 16:16:47 +02:00
Pierre-Antoine Rouby 029e8bb210 Script: Help: Specify the debug mode is for developers. 2023-09-12 16:14:30 +02:00
Pierre-Antoine Rouby 1fc8540bf8 Mainwindow: Display debug in main window title. 2023-09-12 16:11:27 +02:00
Pierre-Antoine Rouby 0c0ac93061 Script: Add gui, help and debug commands. 2023-09-12 16:02:51 +02:00
Pierre-Antoine Rouby 4debe830b6 Script: Add config in script init parameter. 2023-09-12 15:44:42 +02:00
Pierre-Antoine Rouby 4de6784bec Pamhyr: Add 'gui' as dummy script. 2023-09-12 15:39:20 +02:00
Pierre-Antoine Rouby 29cedaa7b2 paper: Fix citation [4] -> [2]. 2023-09-12 15:31:12 +02:00
Pierre-Antoine Rouby 2e404ed005 paper: Update ajustement patch. 2023-09-12 15:09:22 +02:00
Pierre-Antoine Rouby 89f4fea717 paper: Apply Lionel rewrite correction. 2023-09-12 14:59:39 +02:00
Pierre-Antoine Rouby d4568ddbb5 Script: Add pamhyr2 script with exemple hello. 2023-09-12 13:04:27 +02:00
Pierre-Antoine Rouby 75066fa61a Plot: Fix missin import. 2023-09-12 12:57:28 +02:00
Pierre-Antoine Rouby e887df9aac Pamhyr: Add init.py. 2023-09-12 11:24:06 +02:00
Pierre-Antoine Rouby 1662ff1e24 git: Update the README with sections 'features' and 'install'. 2023-09-12 11:00:01 +02:00
Pierre-Antoine Rouby cbc7296132 git: Add editorconfig. 2023-09-12 10:43:08 +02:00
Pierre-Antoine Rouby 8004fbcdcc Solver, View: Add some comment. 2023-09-12 10:42:51 +02:00
Pierre-Antoine Rouby b03ec68044 paper: Update intro with pamhyr history and minor change. 2023-09-12 09:24:35 +02:00
Pierre-Antoine Rouby bb6292ce90 View: Plot: Minor change. 2023-09-11 16:27:29 +02:00
Pierre-Antoine Rouby af72e59830 Plot: Toolbar: Rename file and class. 2023-09-11 16:23:33 +02:00
Pierre-Antoine Rouby 45690690d1 View: BC, LC: Factorise time delegate and widget. 2023-09-11 15:49:10 +02:00
Pierre-Antoine Rouby 602593a6cb View: LC: Use PamhyrTableModel. 2023-09-11 15:20:59 +02:00
Pierre-Antoine Rouby 650df2b56c View: BC: Use PamhyrTableModel. 2023-09-11 14:57:55 +02:00
Pierre-Antoine Rouby c986674306 View: Network: Add missing tranlate file. 2023-09-11 14:43:07 +02:00
Pierre-Antoine Rouby 3eeaea14fa View: Tools: Add PamhyrTableModel and switch Network tables on it. 2023-09-11 14:13:40 +02:00
Pierre-Antoine Rouby 4b9f75b8d1 Tools: SQL: Minor change. 2023-09-11 14:13:19 +02:00
Pierre-Antoine Rouby 2139b42b77 Model: Tools: List, Dict: Minor change. 2023-09-11 14:12:49 +02:00
Pierre-Antoine Rouby 8c9478f403 Model: Delete unsed file. 2023-09-11 11:41:32 +02:00
Pierre-Antoine Rouby 973ea74622 Model: Move Model.DB into Model.Tools.PamhyrDB. 2023-09-11 11:40:26 +02:00
Pierre-Antoine Rouby e63b931dfb Model: Add PamhyrModelDict and use it in IC. 2023-09-11 11:29:18 +02:00
Pierre-Antoine Rouby 0b0c402baf Model: Add PamhyrModelListWithTab. Use it in BC and LC. 2023-09-11 11:20:08 +02:00
Pierre-Antoine Rouby 3aea790829 Model: Switch SedimentLayerList to PamhyrModelList. 2023-09-11 11:06:54 +02:00
Pierre-Antoine Rouby 460683703e Model: Switch SolverParametersList to PamhyrModelList. 2023-09-11 10:58:29 +02:00
Pierre-Antoine Rouby 89b9fd21ff Model: Add a Pamhyr List Abstract implementation and use it for
Stricklers and friction.
2023-09-11 10:53:58 +02:00
Pierre-Antoine Rouby 4b82234e98 paper: Add patch for simhydro paper 2023-09-11 09:37:04 +02:00
Pierre-Antoine Rouby 187135e62b doc: dev: minor change. 2023-09-11 09:31:11 +02:00
Pierre-Antoine Rouby f3ec60500d doc: Update dev doc with some org-mode tips. 2023-09-08 18:43:42 +02:00
Pierre-Antoine Rouby ae857a098a doc: Add user and developers documentation scheme. 2023-09-08 17:41:40 +02:00
Theophile Terraz 9799e225a4 relecture 2023-09-08 16:25:54 +02:00
Theophile Terraz 3d54ac044f merge 2023-09-08 15:29:57 +02:00
Theophile Terraz 0fab381b85 ad txt paper 2023-09-08 15:02:02 +02:00
Theophile Terraz ffbaa8b2a1 modif suite réunion 2023-09-08 14:59:41 +02:00
Theophile Terraz 5abbae6b12 modif §3.1.1 2023-09-08 11:17:43 +02:00
Theophile Terraz 078ea9cd30 modif §2.2 2023-09-08 11:13:25 +02:00
Pierre-Antoine Rouby 909b13e6a2 paper: Minor change (mostly update conclusion). 2023-09-08 10:55:44 +02:00
Pierre-Antoine Rouby d6c4228826 paper: Minor change. 2023-09-08 09:51:26 +02:00
Theophile Terraz 8cee06c875 modif intro papier 2023-09-07 18:02:06 +02:00
Pierre-Antoine Rouby 74dba4290b paper: SimHydro: Update paper. 2023-09-06 16:07:01 +02:00
Pierre-Antoine Rouby de92d9d851 paper: SimHydro: Update image with reverse KP. 2023-09-06 11:51:11 +02:00
Pierre-Antoine Rouby 603963045b paper: SimHydro: Apply some correction from Lionel and Théophile review. 2023-09-06 10:36:19 +02:00
Pierre-Antoine Rouby 571d8f5d69 paper: SimHydro: Update paper. 2023-09-05 16:56:00 +02:00
Pierre-Antoine Rouby e8d0b2c18f paper: Add paper draft for SimHydro 2023. 2023-09-05 11:33:22 +02:00
Pierre-Antoine Rouby 0a968e2184 ci: Update mage version. 2023-08-30 13:57:28 +02:00
Pierre-Antoine Rouby 0ec87a7f24 trans: Update french translate file. 2023-08-30 11:00:09 +02:00
Pierre-Antoine Rouby 0dd727f9d4 packages: Fix linux widget directory. 2023-08-30 10:59:21 +02:00
Pierre-Antoine Rouby 10d6a17a59 Mage: Change Y/N into PAR file to O/N (as french). 2023-08-30 10:43:26 +02:00
Pierre-Antoine Rouby 0c06b4cb2a BC: Add D50 and sigma edition for solid BC. 2023-08-30 10:42:45 +02:00
Pierre-Antoine Rouby fb9c2a4308 Plot: Fix toolbar image and translate. 2023-08-30 09:27:25 +02:00
Pierre-Antoine Rouby e189ab69dc Mage: Add sediment parameter into solver parameter and update DB. 2023-08-29 13:38:51 +02:00
Pierre-Antoine Rouby 79d8bb7dee Mage: Logging BIN and GRA name in reading methode. 2023-08-28 15:34:37 +02:00
Pierre-Antoine Rouby cbaeeaed83 Mage, RunSolver: Minor change. 2023-08-28 11:56:51 +02:00
Pierre-Antoine Rouby d064fc042a Mage: Change file name export from '0.XXX' to '<study_name>.XXX'. 2023-08-28 11:33:19 +02:00
Pierre-Antoine Rouby f9c28034a5 Mage: Add PAR export ligne filter and disable 'command_line_arguments'. 2023-08-28 10:34:00 +02:00
Pierre-Antoine Rouby 3b5b7c09dd Results, River: Minor change. 2023-08-25 11:15:33 +02:00
Pierre-Antoine Rouby ebbe4c4be7 Resutls: Synchronized table and slider. 2023-08-25 10:58:10 +02:00
Pierre-Antoine Rouby 5e5cc8d050 Results: Add status bar timestamp information. 2023-08-25 10:20:18 +02:00
Pierre-Antoine Rouby 09bdb7dd92 Mage: Fix QSO export. 2023-08-23 16:44:14 +02:00
Pierre-Antoine Rouby 2de2c8e077 Pamhyr: Add plot toolbar in most of window. 2023-08-23 16:17:45 +02:00
Pierre-Antoine Rouby 2842bb5339 Results: Add toolbar to each plot. 2023-08-23 15:35:41 +02:00
Pierre-Antoine Rouby 98c5a6b610 Plot: More flexible matplotlib toolbar. 2023-08-23 15:22:11 +02:00
Pierre-Antoine Rouby 0d105f7488 Sediment: Change sediment results display (again)... 2023-08-23 11:40:39 +02:00
Pierre-Antoine Rouby 6b64e5cc99 Results, Sediment: Change sediment display algorithme. 2023-08-22 15:33:32 +02:00
Pierre-Antoine Rouby e8515db8eb Mage: Fix HYD file export (time from second to minute). 2023-08-22 14:04:51 +02:00
Pierre-Antoine Rouby 7429952168 Merge: Merge branch 'sediment' with sediment export and result for mage. 2023-08-22 11:44:04 +02:00
Pierre-Antoine Rouby 798e442fc0 tools: Minor change. 2023-08-22 11:43:03 +02:00
Pierre-Antoine Rouby c1ff99797c Resutls: Read and display sediment results only if available. 2023-08-22 11:39:08 +02:00
Pierre-Antoine Rouby bab1ce9e17 Results: Display custom time in hydrograph. 2023-08-22 11:03:50 +02:00
Pierre-Antoine Rouby d7a8e5de20 Results, Sediment: Add sediment results plots. 2023-08-21 17:21:40 +02:00
Pierre-Antoine Rouby 596cd24cc3 Sediment: Add GRA file reading. 2023-08-21 16:18:28 +02:00
Pierre-Antoine Rouby d58a509c1d Solver: Add custom command line parameters and solver default command line args. 2023-08-21 11:37:38 +02:00
Pierre-Antoine Rouby cd07f3aa8d Sediment: Add SL to geometry into DB and minor change. 2023-08-21 10:56:54 +02:00
Pierre-Antoine Rouby 768a0b9f14 Sediment: Add solid BC, QSO export and profile + point sediment layers export. 2023-08-11 15:46:09 +02:00
Pierre-Antoine Rouby 23fb3e6fb3 Merge branch 'master' into sediment 2023-08-11 10:12:31 +02:00
Pierre-Antoine Rouby bb56c92938 BC, LC: Fix axes scale for none time data. 2023-08-11 09:57:32 +02:00
Pierre-Antoine Rouby 35ee6617ac Results: Add hydrograph. 2023-08-11 09:28:15 +02:00
Pierre-Antoine Rouby add8261ceb Results: Fix minor bug and add tab widget to result window. 2023-08-09 17:47:20 +02:00
Pierre-Antoine Rouby 32fb90521f Results: Plot: XY: Fix fill. 2023-08-09 17:32:19 +02:00
Pierre-Antoine Rouby 6356bab320 Results: Plot: Fill between in KPC and fill water zone in XY. 2023-08-09 17:21:59 +02:00
Pierre-Antoine Rouby 24bc373827 trans: Update (incomplete). 2023-08-09 15:41:48 +02:00
Pierre-Antoine Rouby bf80d870d3 pamhyr: Minor change. 2023-08-09 15:40:57 +02:00
Pierre-Antoine Rouby 2ba3c16a98 Results: Add the 2 last plot into results view. 2023-08-09 15:40:20 +02:00
Pierre-Antoine Rouby 05411c468d Results: Connect sliders. 2023-08-09 11:54:52 +02:00
Pierre-Antoine Rouby b04e325ab5 Results: PlotXY: Add water level. 2023-08-09 11:31:04 +02:00
Pierre-Antoine Rouby 2faab43aed Results: Add XY plot (without water zone) 2023-08-09 10:36:57 +02:00
Pierre-Antoine Rouby d499636cb6 Results: Add tables and open window. 2023-08-09 09:14:53 +02:00
Pierre-Antoine Rouby b31be5ac36 pamhyr: Change PAMHYR ascii art to PAMHYR2. 2023-08-09 08:05:39 +02:00
Pierre-Antoine Rouby 6f95e514b9 Results: Add results view file scheme and ui. 2023-08-08 16:59:53 +02:00
Pierre-Antoine Rouby 1f0d49bd4d Results: Add results model. 2023-08-08 16:36:28 +02:00
Pierre-Antoine Rouby 0c750ec244 Results: Minor change. 2023-08-08 16:17:15 +02:00
Pierre-Antoine Rouby d78310476e Results: Add results model, view scheme and mage results reading. 2023-08-08 13:48:01 +02:00
Pierre-Antoine Rouby e199ac7d5e SL: SL edit disable X axis. 2023-08-02 15:02:43 +02:00
Pierre-Antoine Rouby 47ec0ac9b5 SL: Plots reverse draw order. 2023-08-02 14:06:58 +02:00
Pierre-Antoine Rouby 17d4b15761 SedimentLayer: Add d50, sigma and critical_constraint data. 2023-08-02 13:40:46 +02:00
Pierre-Antoine Rouby d636f5b637 SL: Reach: Profile: Add KP when profile has no name. 2023-07-28 11:48:00 +02:00
Pierre-Antoine Rouby 02ef127942 SL: Reach: Profile: Minor change. 2023-07-26 15:11:44 +02:00
Pierre-Antoine Rouby 003be29eca SL: Fix move down exception. 2023-07-26 13:27:18 +02:00
Pierre-Antoine Rouby a9b905a039 SL: Reach/Profile: Add update. 2023-07-26 12:15:16 +02:00
Pierre-Antoine Rouby bccbc41fe8 SL: Minor fixes. 2023-07-25 16:53:37 +02:00
Pierre-Antoine Rouby b0810f6eed SL: Fix move on edit window and some minor fix. 2023-07-25 15:43:02 +02:00
Pierre-Antoine Rouby ef398c78b1 Debug: Add SqliteBrowser debug action. 2023-07-25 15:07:46 +02:00
Pierre-Antoine Rouby 9e0fb53b60 SL: Fix database update. 2023-07-25 14:48:54 +02:00
Pierre-Antoine Rouby b87977a2d8 SL: Save sl fk in database for point and profile. 2023-07-25 14:22:50 +02:00
Pierre-Antoine Rouby c5402ebb55 SL: Edit: Minor change. 2023-07-25 13:56:16 +02:00
Pierre-Antoine Rouby 181776f33f SL: Edit: Add Plot. 2023-07-25 13:51:49 +02:00
Pierre-Antoine Rouby 043d8d839a SL: Reach: Profile: Add Plot. 2023-07-25 13:28:47 +02:00
Pierre-Antoine Rouby edc99230d7 SL: Add cmd apply for reach. 2023-07-25 12:08:42 +02:00
Pierre-Antoine Rouby ebd05768a4 SL: Add reach sediment layers plot. 2023-07-25 11:35:32 +02:00
Pierre-Antoine Rouby 5fab694c27 Geometry: Plot: Rename KPC to KPZ. 2023-07-25 09:41:05 +02:00
Pierre-Antoine Rouby 550d86683d SL: Propagate profile SL to point and fix SL id cnt. 2023-07-24 17:55:51 +02:00
Pierre-Antoine Rouby d3edf9e5c3 Geometry: Propagate profile information into point. 2023-07-24 17:16:37 +02:00
Pierre-Antoine Rouby 3397754abc SL: Fix SL edition command. 2023-07-24 16:01:53 +02:00
Pierre-Antoine Rouby 1b1bbb6d02 SL: Add profile edition. 2023-07-24 15:38:05 +02:00
Pierre-Antoine Rouby 5a29dca452 SL: Add reach SL edition window. 2023-07-24 11:36:08 +02:00
Pierre-Antoine Rouby e9b00aa148 SL: Add Edit window and fix DB save and load. 2023-07-21 16:11:13 +02:00
Pierre-Antoine Rouby 7898ab86c6 SL: Model add basic command for SL and fix DB save artifact. 2023-07-21 14:53:24 +02:00
Pierre-Antoine Rouby 17c0035a0a SL: Minor change and add missing files... 2023-07-21 14:12:37 +02:00
Pierre-Antoine Rouby 92712dd172 SL: Add sediment layer Model and UI files schemes. 2023-07-21 10:36:01 +02:00
208 changed files with 13783 additions and 4156 deletions

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
# Pamhyr .editorconfig
root = true
[*.py]
charset = utf-8
indent_style = space
indent_size = 4
tab_width = 8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
+334
View File
@@ -1,6 +1,7 @@
### CUSTOM ###
*.exe
*.msi
*.tar.xz
*.zip
*.spec
@@ -9,15 +10,33 @@
tmp
*.tar
*.tar.*
*.tgz
*.zip
*.html
*.pdf
*.odt
doc/dev/images/auto_*
doc/dev/documentation.*
!doc/dev/*.org
doc/dev/images/auto_*
doc/users/documentation.*
!doc/users/*.org
TAGS
pamhyrBuild
wine-pamhyr-build*
_PAMHYR_
__old__
*-venv
### END CUSTOM ###
# Created by https://www.toptal.com/developers/gitignore/api/python
@@ -309,3 +328,318 @@ compile_commands.json
*_qmlcache.qrc
# End of https://www.toptal.com/developers/gitignore/api/qt
# Created by https://www.toptal.com/developers/gitignore/api/latex
# Edit at https://www.toptal.com/developers/gitignore?templates=latex
### LaTeX ###
## Core latex/pdflatex auxiliary files:
*.aux
*.lof
*.log
*.lot
*.fls
*.out
*.toc
*.fmt
*.fot
*.cb
*.cb2
.*.lb
## Intermediate documents:
*.dvi
*.xdv
*-converted-to.*
# these rules might exclude image files for figures etc.
# *.ps
# *.eps
# *.pdf
## Generated if empty string is given at "Please type another file name for output:"
.pdf
## Bibliography auxiliary files (bibtex/biblatex/biber):
*.bbl
*.bcf
*.blg
*-blx.aux
*-blx.bib
*.run.xml
## Build tool auxiliary files:
*.fdb_latexmk
*.synctex
*.synctex(busy)
*.synctex.gz
*.synctex.gz(busy)
*.pdfsync
## Build tool directories for auxiliary files
# latexrun
latex.out/
## Auxiliary and intermediate files from other packages:
# algorithms
*.alg
*.loa
# achemso
acs-*.bib
# amsthm
*.thm
# beamer
*.nav
*.pre
*.snm
*.vrb
# changes
*.soc
# comment
*.cut
# cprotect
*.cpt
# elsarticle (documentclass of Elsevier journals)
*.spl
# endnotes
*.ent
# fixme
*.lox
# feynmf/feynmp
*.mf
*.mp
*.t[1-9]
*.t[1-9][0-9]
*.tfm
#(r)(e)ledmac/(r)(e)ledpar
*.end
*.?end
*.[1-9]
*.[1-9][0-9]
*.[1-9][0-9][0-9]
*.[1-9]R
*.[1-9][0-9]R
*.[1-9][0-9][0-9]R
*.eledsec[1-9]
*.eledsec[1-9]R
*.eledsec[1-9][0-9]
*.eledsec[1-9][0-9]R
*.eledsec[1-9][0-9][0-9]
*.eledsec[1-9][0-9][0-9]R
# glossaries
*.acn
*.acr
*.glg
*.glo
*.gls
*.glsdefs
*.lzo
*.lzs
*.slg
*.slo
*.sls
# uncomment this for glossaries-extra (will ignore makeindex's style files!)
# *.ist
# gnuplot
*.gnuplot
*.table
# gnuplottex
*-gnuplottex-*
# gregoriotex
*.gaux
*.glog
*.gtex
# htlatex
*.4ct
*.4tc
*.idv
*.lg
*.trc
*.xref
# hyperref
*.brf
# knitr
*-concordance.tex
# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
# *.tikz
*-tikzDictionary
# listings
*.lol
# luatexja-ruby
*.ltjruby
# makeidx
*.idx
*.ilg
*.ind
# minitoc
*.maf
*.mlf
*.mlt
*.mtc[0-9]*
*.slf[0-9]*
*.slt[0-9]*
*.stc[0-9]*
# minted
_minted*
*.pyg
# morewrites
*.mw
# newpax
*.newpax
# nomencl
*.nlg
*.nlo
*.nls
# pax
*.pax
# pdfpcnotes
*.pdfpc
# sagetex
*.sagetex.sage
*.sagetex.py
*.sagetex.scmd
# scrwfile
*.wrt
# svg
svg-inkscape/
# sympy
*.sout
*.sympy
sympy-plots-for-*.tex/
# pdfcomment
*.upa
*.upb
# pythontex
*.pytxcode
pythontex-files-*/
# tcolorbox
*.listing
# thmtools
*.loe
# TikZ & PGF
*.dpth
*.md5
*.auxlock
# titletoc
*.ptc
# todonotes
*.tdo
# vhistory
*.hst
*.ver
# easy-todo
*.lod
# xcolor
*.xcp
# xmpincl
*.xmpi
# xindy
*.xdy
# xypic precompiled matrices and outlines
*.xyc
*.xyd
# endfloat
*.ttt
*.fff
# Latexian
TSWLatexianTemp*
## Editors:
# WinEdt
*.bak
*.sav
# Texpad
.texpadtmp
# LyX
*.lyx~
# Kile
*.backup
# gummi
.*.swp
# KBibTeX
*~[0-9]*
# TeXnicCenter
*.tps
# auto folder when using emacs and auctex
./auto/*
*.el
# expex forward references with \gathertags
*-tags.tex
# standalone packages
*.sta
# Makeindex log files
*.lpz
# xwatermark package
*.xwm
# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
# Uncomment the next line to have this generated file ignored.
#*Notes.bib
### LaTeX Patch ###
# LIPIcs / OASIcs
*.vtc
# glossaries
*.glstex
# End of https://www.toptal.com/developers/gitignore/api/latex
+275 -115
View File
@@ -15,96 +15,55 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
stages:
- downloads
- build
- test
- package
- release
#############
# DOWNLOADS #
#############
dl-mage-linux:
stage: downloads
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_linux.tgz
- mkdir -p mage-linux
- cd mage-linux
- tar xvf ../mage.tgz
artifacts:
paths:
- mage-linux/mage
- mage-linux/mage_extraire
- mage-linux/mailleurPF
dl-mage-windows:
stage: downloads
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_windows.tgz
- mkdir -p mage-windows
- cd mage-windows
- tar xvf ../mage.tgz
artifacts:
paths:
- mage-windows/mage.exe
- mage-windows/mage_extraire.exe
- mage-windows/mailleurPF.exe
#########
# BUILD #
#########
build-mage-linux:
stage: build
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test5/downloads/packages/mage_linux.tgz
- mkdir -p mage
- cd mage
- tar xvf ../mage.tgz
artifacts:
paths:
- mage/mage
- mage/mage_extraire
- mage/mailleurPF
# build-mage-linux:
# stage: build
# tags:
# - linux
# rules:
# - if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
# variables:
# GIT_SUBMODULE_STRATEGY: recursive
# GIT_SUBMODULE_DEPTH: 20
# script:
# - cd mage/src/
# - mkdir -p build
# - cd build
# - cmake ..
# - make
# artifacts:
# paths:
# - mage/src/build/mage
# - mage/src/build/mage_extraire
# - mage/src/build/mailleurPF
build-mage-windows:
stage: build
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test5/downloads/packages/mage_windows.tgz
- mkdir -p mage
- cd mage
- tar xvf ../mage.tgz
artifacts:
paths:
- mage/mage.exe
- mage/mage_extraire.exe
- mage/mailleurPF.exe
# build-mage-windows:
# stage: build
# tags:
# - wine
# rules:
# - if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
# variables:
# GIT_SUBMODULE_STRATEGY: recursive
# GIT_SUBMODULE_DEPTH: 20
# script:
# - cd mage/src/
# - sed s/add_compile_definitions(win)/add_compile_definitions(windows)/ CMakeLists.txt > __tmp__
# - echo set(CMAKE_Fortran_COMPILER "C:/Program\ Files/gcc/bin/gfortran.exe") > CMakeLists.txt
# - echo set(CMAKE_MAKE_PROGRAM "C:/Program Files\ \(x86\)/GnuWin32/bin/make.exe") >> CMakeLists.txt
# - type __tmp__ >> CMakeLists.txt
# - mkdir build
# - cd build
# - cmake -G "MinGW Makefiles" ..
# - make
# artifacts:
# paths:
# - mage/src/build/mage.exe
# - mage/src/build/mage_extraire.exe
# - mage/src/build/mailleurPF.exe
build:
set-version:
stage: build
tags:
- linux
@@ -126,60 +85,240 @@ build-lang:
paths:
- src/lang/*.qm
build-users-doc:
stage: build
tags:
- linux
needs:
- job: set-version
artifacts: true
script:
- cd ./doc/users/
- ./build.sh
artifacts:
paths:
- doc/users/documentation.pdf
- doc/users/documentation.html
- doc/users/images/
build-developers-doc:
stage: build
tags:
- linux
needs:
- job: set-version
artifacts: true
script:
- cd ./doc/dev/
- ./build.sh
# Create html documentation
- cd ../../src
- pdoc --html .
# Create archive
- tar cf html.tar ./html
- cd ../doc/dev
- mv ../../src/html.tar ./
artifacts:
paths:
- doc/dev/documentation.pdf
- doc/dev/documentation.html
- doc/dev/html.tar
- doc/dev/images/
build-linux:
stage: build
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
needs:
- job: dl-mage-linux
artifacts: true
- job: set-version
artifacts: true
- job: build-lang
artifacts: true
- job: build-developers-doc
artifacts: true
- job: build-users-doc
artifacts: true
script:
- mkdir -p linux
- cd linux
# Setup virtual env
- python3 -m venv linux-venv
- . linux-venv/bin/activate
- pip3 install -U -r ../requirements.txt
# Run Pyinstaller
- pyinstaller -y --paths linux-venv/lib/python3.8/site-packages ../src/pamhyr.py
# Create directory
- mkdir -p pamhyr
- mkdir -p pamhyr/pamhyr
- mkdir -p pamhyr/pamhyr/_internal
# Copy Pamhyr info
- cp -v ../VERSION pamhyr/pamhyr/_internal/
- cp -v ../AUTHORS pamhyr/pamhyr/_internal/
- cp -v ../LICENSE pamhyr/pamhyr/_internal/
# Copy mage
- mkdir -p pamhyr/mage
- cp -v ../mage-linux/* pamhyr/mage/
# Copy Pamhyr
- cp -r dist/pamhyr pamhyr/
# Copy Pamhyr resources
- mkdir -p pamhyr/pamhyr/_internal/View
- mkdir -p pamhyr/pamhyr/_internal/View/ui
- mkdir -p pamhyr/pamhyr/_internal/View/ui/Widgets
- cp -r ../src/View/ui/*.ui pamhyr/pamhyr/_internal/View/ui/
- cp -r ../src/View/ui/ressources/ pamhyr/pamhyr/_internal/View/ui
- cp -r ../src/View/ui/Widgets/*.ui pamhyr/pamhyr/_internal/View/ui/Widgets
# Copy lang
- mkdir -p pamhyr/pamhyr/_internal/lang
- cp -r ../src/lang/*.qm pamhyr/pamhyr/_internal/lang
# Copy tests cases
- mkdir -p pamhyr/tests_cases/
- mkdir -p pamhyr/tests_cases/Saar
- cp ../tests_cases/Saar/Saar.pamhyr pamhyr/tests_cases/Saar/
# Copy documentations
- mkdir -p pamhyr/doc/
- mkdir -p pamhyr/doc/images/
- mkdir -p pamhyr/doc/html/
- cp ../doc/users/documentation.pdf pamhyr/doc/Pamhyr2-users.pdf
- cp ../doc/users/documentation.html pamhyr/doc/Pamhyr2-users.html
- cp ../doc/dev/documentation.pdf pamhyr/doc/Pamhyr2-dev.pdf
- cp ../doc/dev/documentation.html pamhyr/doc/Pamhyr2-dev.html
#- cp ../doc/users/images/* pamhyr/doc/images
- cp ../doc/dev/images/* pamhyr/doc/images
- cp ../doc/images/* pamhyr/doc/images
# Create running script
- echo "#!/bin/sh" > pamhyr/Pamhyr2
- echo "./pamhyr/pamhyr $@" >> pamhyr/Pamhyr2
- chmod u+x pamhyr/Pamhyr2
artifacts:
paths:
- linux/pamhyr
build-windows:
stage: build
tags:
- wine
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
needs:
- job: dl-mage-windows
artifacts: true
- job: set-version
artifacts: true
- job: build-lang
artifacts: true
- job: build-developers-doc
artifacts: true
- job: build-users-doc
artifacts: true
script:
- mkdir windows
- cd windows
# Setup env
- python -m pip install -U -r ..\requirements.txt
# Run Pyinstaller
- pyinstaller -y ..\src\pamhyr.py
# Create directory
- mkdir pamhyr
- dir
- copy ..\packages\make-windows-dir.bat .\make-windows-dir.bat
- .\make-windows-dir.bat
artifacts:
paths:
- windows/pamhyr
#########
# TESTS #
#########
test:
stage: test
script:
- echo "TODO PAMHYR tests"
# test-windows:
# stage: test
# tags:
# - wine
# needs:
# - job: set-version
# artifacts: true
# - job: build-windows
# artifacts: true
# script:
# - cd windows\pamhyr
# - pamhyr\pamhyr.exe hello
# test-linux:
# stage: test
# tags:
# - linux
# needs:
# - job: set-version
# artifacts: true
# - job: build-linux
# artifacts: true
# script:
# - cd linux/pamhyr
# - ./Pamhyr2 hello
############
# PACKAGES #
############
linux-package:
linux-package-tar:
stage: package
tags:
- release
- linux
needs:
- job: build-lang
artifacts: true
- job: build
artifacts: true
- job: build-mage-linux
# - job: test-linux
- job: build-linux
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_TAG
artifacts:
paths:
- packages/pamhyr-gnulinux-amd64.tar.xz
- linux/pamhyr-gnulinux.tar.xz
script:
- cd packages
- ./linux.sh
- cd linux
- tar --xz -cf pamhyr-gnulinux.tar.xz ./pamhyr --checkpoint=.100
windows-package:
windows-package-zip:
stage: package
tags:
- release
- linux
needs:
# - job: test-windows
- job: build-windows
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_TAG
artifacts:
paths:
- windows/pamhyr-windows.zip
script:
- cd windows
- zip -r pamhyr-windows.zip pamhyr
windows-package-exe:
stage: package
tags:
- release
- wine
needs:
- job: build-lang
artifacts: true
- job: build
artifacts: true
- job: build-mage-windows
# - job: test-windows
- job: build-windows
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_TAG
artifacts:
paths:
- packages/pamhyr-win-amd64.exe
- windows/pamhyr-windows.exe
script:
- cd packages
- ./windows.bat
- cd windows
- copy ..\packages\make-windows-exe.bat make-windows-exe.bat
- copy ..\packages\pamhyr.nsi pamhyr.nsi
- .\make-windows-exe.bat
###########
# RELEASE #
@@ -191,16 +330,25 @@ tag-release:
- release
- linux
needs:
- job: linux-package
- job: linux-package-tar
artifacts: true
- job: windows-package
- job: windows-package-zip
artifacts: true
- job: windows-package-exe
artifacts: true
- job: build-users-doc
artifacts: true
- job: build-developers-doc
artifacts: true
rules:
- if: $CI_COMMIT_TAG
artifacts:
paths:
- packages/pamhyr-gnulinux-amd64.tar.xz
- packages/pamhyr-win-amd64.exe
- linux/pamhyr-gnulinux.tar.xz
- windows/pamhyr-windows.zip
- windows/pamhyr-windows.exe
- doc/dev/documentation.pdf
- doc/users/documentation.pdf
script:
- cd packages
release:
@@ -210,11 +358,23 @@ tag-release:
ref: '$CI_COMMIT_SHA'
assets:
links:
- name: 'GNU/Linux amd64 (tar.xz)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/packages/pamhyr-gnulinux-amd64.tar.xz'
filepath: '/packages/pamhyr-gnulinux-amd64.tar.xz'
- name: 'GNU/Linux (tar.xz)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/linux/pamhyr-gnulinux.tar.xz'
filepath: '/linux/pamhyr-gnulinux.tar.xz'
link_type: 'package'
- name: 'Windows amd64 (exe)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/packages/pamhyr-win-amd64.exe'
filepath: '/packages/pamhyr-win-amd64.exe'
- name: 'Windows (exe)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/windows/pamhyr-windows.exe'
filepath: '/windows/pamhyr-windows.exe'
link_type: 'package'
- name: 'Windows (zip)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/windows/pamhyr-windows.zip'
filepath: '/windows/pamhyr-windows.zip'
link_type: 'package'
- name: 'User documentations (pdf)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/doc/users/documentation.pdf'
filepath: '/doc/users/documentation.pdf'
link_type: 'other'
- name: 'Dev documentations (pdf)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/doc/dev/documentation.pdf'
filepath: '/doc/dev/documentation.pdf'
link_type: 'other'
+50 -1
View File
@@ -1 +1,50 @@
# PamHyr (Programme d’Aide à la Modélisation en Hydraulique des Rivières)
# Pamhyr2
Pamhyr is a free and open source graphical user interface for 1D hydro-sedimentary
modelling of rivers.
## Features
+ Edit a complex river hydraulics network
+ Define the river geometry
+ Define boundary conditions
+ Define lateral contribution
+ Edit river friction
+ Generate initial conditions
+ Edit river sedimentary layers
+ Save the study in a unique file
+ Run Mage 8 on a study and visualize results
## Install
### GNU/Linux
Downloads [last
realease](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/releases/permalink/latest)
GNU/Linux binary archive.
```shell
tar xf pamhyr-gnulinux-amd64.tar.xz
cd pamhyr
./pamhyr
```
### Windows
Downloads [last
realease](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/releases/permalink/latest)
Windows installer.
### From source code
```shell
# Get source code
git clone https://gitlab.irstea.fr/theophile.terraz/pamhyr/
cd pamhyr
# Install dependencies
pip3 install -r requirements.txt
# Run pamhyr
./src/pamhyr.py
```
+18
View File
@@ -0,0 +1,18 @@
#! /bin/sh
echo " PDF documentation.org"
emacs -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./documentation.org \
-f org-latex-export-to-pdf
echo " HTML documentation.org"
emacs -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(require 'ox-html)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./documentation.org \
-f org-html-export-to-html
+1
View File
@@ -0,0 +1 @@
../tools/ref.bib
File diff suppressed because it is too large. Load diff
View File
Whitespace-only changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

@@ -0,0 +1,692 @@
diff --git a/packages/linux.sh b/packages/linux.sh
index 215047e..a19bfb6 100755
--- a/packages/linux.sh
+++ b/packages/linux.sh
@@ -76,11 +76,11 @@ mv ../pamhyr-src.tar.gz ./
echo " *** MAKE BIN PACKAGE"
-OLD_PWD=$PWD
-cd dist/
-tar --xz -cf pamhyr-gnulinux-amd64.tar.xz pamhyr --checkpoint=.100
-cd $OLD_PWD
+# OLD_PWD=$PWD
+# cd dist/
+# tar --xz -cf pamhyr-gnulinux-amd64.tar.xz pamhyr --checkpoint=.100
+# cd $OLD_PWD
-mv dist/pamhyr-gnulinux-amd64.tar.xz ./
+# mv dist/pamhyr-gnulinux-amd64.tar.xz ./
echo " *** DONE"
diff --git a/src/Model/Friction/Friction.py b/src/Model/Friction/Friction.py
index 18167a8..5bf2302 100644
--- a/src/Model/Friction/Friction.py
+++ b/src/Model/Friction/Friction.py
@@ -170,6 +170,7 @@ class Friction(SQLSubModel):
@begin_strickler.setter
def begin_strickler(self, strickler):
self._begin_strickler = strickler
+ self._end_strickler = strickler # HACK: Only one Strickler
self._status.modified()
@property
diff --git a/src/Model/Stricklers/Stricklers.py b/src/Model/Stricklers/Stricklers.py
index baa484f..a5670d2 100644
--- a/src/Model/Stricklers/Stricklers.py
+++ b/src/Model/Stricklers/Stricklers.py
@@ -38,7 +38,7 @@ class Stricklers(SQLSubModel):
else:
self.id = id
- Stricklers._id_cnt = max(Stricklers._id_cnt + 1, self.id)
+ Stricklers._id_cnt = max(Stricklers._id_cnt + 1, id + 1)
self._name = name
self._comment = comment
diff --git a/src/View/BoundaryCondition/translate.py b/src/View/BoundaryCondition/translate.py
index c4ff8f8..172999a 100644
--- a/src/View/BoundaryCondition/translate.py
+++ b/src/View/BoundaryCondition/translate.py
@@ -29,9 +29,9 @@ _translate = QCoreApplication.translate
long_types = {
"ND": _translate("BoundaryCondition", "Not defined"),
"PC": _translate("BoundaryCondition", "Ponctual contribution"),
- "TZ": _translate("BoundaryCondition", "Time over Z"),
- "TD": _translate("BoundaryCondition", "Time over Discharge"),
- "ZD": _translate("BoundaryCondition", "Z over Discharge"),
+ "TZ": _translate("BoundaryCondition", "TimeZ"),
+ "TD": _translate("BoundaryCondition", "TimeDischarge"),
+ "ZD": _translate("BoundaryCondition", "ZDischarge"),
"SL": _translate("BoundaryCondition", "Solid"),
}
diff --git a/src/View/Frictions/PlotStricklers.py b/src/View/Frictions/PlotStricklers.py
index 71ff6e6..b9060bc 100644
--- a/src/View/Frictions/PlotStricklers.py
+++ b/src/View/Frictions/PlotStricklers.py
@@ -106,6 +106,8 @@ class PlotStricklers(APlot):
)
self.draw_frictions(lst, color="b")
+ self.canvas.axes.invert_xaxis()
+
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None:
diff --git a/src/View/Frictions/Window.py b/src/View/Frictions/Window.py
index 4af3b66..f46fae3 100644
--- a/src/View/Frictions/Window.py
+++ b/src/View/Frictions/Window.py
@@ -112,10 +112,10 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow):
)
table.setItemDelegateForColumn(
- 3, self._delegate_stricklers
+ 2, self._delegate_stricklers
)
table.setItemDelegateForColumn(
- 4, self._delegate_stricklers
+ 3, self._delegate_stricklers
)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
diff --git a/src/View/Frictions/translate.py b/src/View/Frictions/translate.py
index 21981ab..82071e9 100644
--- a/src/View/Frictions/translate.py
+++ b/src/View/Frictions/translate.py
@@ -21,10 +21,10 @@ from PyQt5.QtCore import QCoreApplication
_translate = QCoreApplication.translate
table_headers = {
- "name": _translate("Frictions", "Name"),
+ # "name": _translate("Frictions", "Name"),
# "edge": _translate("Frictions", "Reach"),
"begin_kp": _translate("Frictions", "Begin kp (m)"),
"end_kp": _translate("Frictions", "End kp (m)"),
- "begin_strickler": _translate("Frictions", "Begin strickler"),
- "end_strickler": _translate("Frictions", "End strickler"),
+ "begin_strickler": _translate("Frictions", "Strickler"),
+ # "end_strickler": _translate("Frictions", "End strickler"),
}
diff --git a/src/View/Geometry/PlotAC.py b/src/View/Geometry/PlotAC.py
index fe6d933..e43f39e 100644
--- a/src/View/Geometry/PlotAC.py
+++ b/src/View/Geometry/PlotAC.py
@@ -67,18 +67,18 @@ class PlotAC(APlot):
gl = self.data.profile(selected_profile).names()
self.canvas.axes.set_xlabel(
- _translate("MainWindow_reach", "Abscisse en travers (m)"),
+ _translate("MainWindow_reach", "Transverse abscissa (m)"),
color='green', fontsize=12
)
self.canvas.axes.set_ylabel(
- _translate("MainWindow_reach", "Cote (m)"),
+ _translate("MainWindow_reach", "Height (m)"),
color='green', fontsize=12
)
self.canvas.figure.tight_layout()
- label_before_plot_selected = _translate("MainWindow_reach", "Profil précédent")
- label_plot_selected = _translate("MainWindow_reach", "Profil sélectionné")
- label_after_plot_selected = _translate("MainWindow_reach", "Profil suivant")
+ label_before_plot_selected = _translate("MainWindow_reach", "Previous cross-section")
+ label_plot_selected = _translate("MainWindow_reach", "Cross-section")
+ label_after_plot_selected = _translate("MainWindow_reach", "Next cross-section")
color_before_plot_selected = "k" # 'grey'
color_plot_selected = 'b'
color_after_plot_selected = 'm'
diff --git a/src/View/Geometry/PlotKPZ.py b/src/View/Geometry/PlotKPZ.py
index eb7d5e2..64057aa 100644
--- a/src/View/Geometry/PlotKPZ.py
+++ b/src/View/Geometry/PlotKPZ.py
@@ -158,6 +158,8 @@ class PlotKPZ(APlot):
color='lightgrey'
)
+ self.canvas.axes.invert_xaxis()
+
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None:
diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py
index 7776d73..1c8cb5d 100644
--- a/src/View/Geometry/Window.py
+++ b/src/View/Geometry/Window.py
@@ -111,7 +111,7 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow):
self.ui.btn_sort_desc.triggered.connect(self.sort_descending)
self.ui.btn_move_up.triggered.connect(self.move_row_up)
self.ui.btn_move_down.triggered.connect(self.move_row_down)
- self.ui.btn_end_editing.triggered.connect(self.handleSave)
+ # self.ui.btn_end_editing.triggered.connect(self.handleSave)
self.ui.btn_add.triggered.connect(self.insert_row)
self.ui.btn_delete.triggered.connect(self.delete_rows)
self.ui.btn_edit.triggered.connect(self.edit_profile)
diff --git a/src/View/Geometry/mainwindow_ui_reach.py b/src/View/Geometry/mainwindow_ui_reach.py
index 897b698..60bcfa6 100644
--- a/src/View/Geometry/mainwindow_ui_reach.py
+++ b/src/View/Geometry/mainwindow_ui_reach.py
@@ -95,29 +95,29 @@ class Ui_MainWindow(object):
self.btn_edit.setIcon(icon_btn_edit)
self.toolBar.addAction(self.btn_edit)
- self.btn_copy_selected_profile = QtWidgets.QAction(self.widget)
- self.btn_copy_selected_profile.setObjectName("btn_copy_selected_profile")
- icon_btn_copy_selected_profile = QtGui.QIcon()
- icon_btn_copy_selected_profile.addPixmap(
- QtGui.QPixmap(f"{icon_path}gtk-copy.png"))
- self.btn_copy_selected_profile.setIcon(icon_btn_copy_selected_profile)
- self.toolBar.addAction(self.btn_copy_selected_profile)
-
- self.btn_paste_selected_profile = QtWidgets.QAction(self.widget)
- self.btn_paste_selected_profile.setObjectName("btn_paste_selected_profile")
- icon_btn_paste_selected_profile = QtGui.QIcon()
- icon_btn_paste_selected_profile.addPixmap(
- QtGui.QPixmap(f"{icon_path}gtk-paste.png"))
- self.btn_paste_selected_profile.setIcon(icon_btn_paste_selected_profile)
- self.toolBar.addAction(self.btn_paste_selected_profile)
-
- self.btn_duplicate_selected_profile = QtWidgets.QAction(self.widget)
- self.btn_duplicate_selected_profile.setObjectName("btn_duplicate_selected_profile")
- icon_btn_duplicate_selected_profile = QtGui.QIcon()
- icon_btn_duplicate_selected_profile.addPixmap(
- QtGui.QPixmap(f"{icon_path}gtk-dnd-multiple.png"))
- self.btn_duplicate_selected_profile.setIcon(icon_btn_duplicate_selected_profile)
- self.toolBar.addAction(self.btn_duplicate_selected_profile)
+ # self.btn_copy_selected_profile = QtWidgets.QAction(self.widget)
+ # self.btn_copy_selected_profile.setObjectName("btn_copy_selected_profile")
+ # icon_btn_copy_selected_profile = QtGui.QIcon()
+ # icon_btn_copy_selected_profile.addPixmap(
+ # QtGui.QPixmap(f"{icon_path}gtk-copy.png"))
+ # self.btn_copy_selected_profile.setIcon(icon_btn_copy_selected_profile)
+ # self.toolBar.addAction(self.btn_copy_selected_profile)
+
+ # self.btn_paste_selected_profile = QtWidgets.QAction(self.widget)
+ # self.btn_paste_selected_profile.setObjectName("btn_paste_selected_profile")
+ # icon_btn_paste_selected_profile = QtGui.QIcon()
+ # icon_btn_paste_selected_profile.addPixmap(
+ # QtGui.QPixmap(f"{icon_path}gtk-paste.png"))
+ # self.btn_paste_selected_profile.setIcon(icon_btn_paste_selected_profile)
+ # self.toolBar.addAction(self.btn_paste_selected_profile)
+
+ # self.btn_duplicate_selected_profile = QtWidgets.QAction(self.widget)
+ # self.btn_duplicate_selected_profile.setObjectName("btn_duplicate_selected_profile")
+ # icon_btn_duplicate_selected_profile = QtGui.QIcon()
+ # icon_btn_duplicate_selected_profile.addPixmap(
+ # QtGui.QPixmap(f"{icon_path}gtk-dnd-multiple.png"))
+ # self.btn_duplicate_selected_profile.setIcon(icon_btn_duplicate_selected_profile)
+ # self.toolBar.addAction(self.btn_duplicate_selected_profile)
self.btn_sort_asc = QtWidgets.QAction(self.widget)
self.btn_sort_asc.setObjectName("btn_sort_asc")
@@ -151,13 +151,13 @@ class Ui_MainWindow(object):
self.btn_move_down.setIcon(icon_btn_move_down)
self.toolBar.addAction(self.btn_move_down)
- self.btn_end_editing = QtWidgets.QAction(self.widget)
- self.btn_end_editing.setObjectName("btn_end_editing")
- icon_btn_save = QtGui.QIcon()
- icon_btn_save.addPixmap(
- QtGui.QPixmap(f"{icon_path}gtk-apply.png"))
- self.btn_end_editing.setIcon(icon_btn_save)
- self.toolBar.addAction(self.btn_end_editing)
+ # self.btn_end_editing = QtWidgets.QAction(self.widget)
+ # self.btn_end_editing.setObjectName("btn_end_editing")
+ # icon_btn_save = QtGui.QIcon()
+ # icon_btn_save.addPixmap(
+ # QtGui.QPixmap(f"{icon_path}gtk-apply.png"))
+ # self.btn_end_editing.setIcon(icon_btn_save)
+ # self.toolBar.addAction(self.btn_end_editing)
self.verticalLayout.addLayout(self.horizontalLayout)
self.tableView = QtWidgets.QTableView(self.widget)
@@ -314,30 +314,23 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow_reach):
_translate = QtCore.QCoreApplication.translate
self.name_reach = ""
- self.mainwindow_title = _translate("MainWindow_reach", "Jeu de sections du Bief")
+ self.mainwindow_title = _translate("MainWindow_reach", "Geometry reach edit")
MainWindow_reach.setWindowTitle(self.mainwindow_title + f"{self.name_reach}")
- self.text_section_order = _translate("MainWindow_reach", "\nOrdre des sections : "
- "Amont --> Aval")
# self.label_section_order.setText(f"{self.text_section_order}")
self.vertical_slider_value = ""
- self.vertical_slider_label.setText(_translate("MainWindow_reach", "Pk = ") + f"{self.vertical_slider_value}")
- self.btn_open.setToolTip(_translate("MainWindow_reach", " Importer une géométrie"))
- self.btn_add.setToolTip(_translate("MainWindow_reach", " Nouveau profil"))
- self.btn_delete.setToolTip(_translate("MainWindow_reach", " Supprimer le profil sélectionné"))
- self.btn_edit.setToolTip(_translate("MainWindow_reach", " Éditer le profil sélectionné"))
- self.btn_copy_selected_profile.setToolTip(_translate("MainWindow_reach", " Copier le profil sélectionné"))
- self.btn_paste_selected_profile.setToolTip(_translate("MainWindow_reach", "Coller le profil en fin de liste ("
- "penser à modifier le Pk avant de "
- "trier)"))
- self.btn_duplicate_selected_profile.setToolTip(
- _translate("MainWindow_reach", " Dupliquer la section sélectionnée"))
+ self.vertical_slider_label.setText(_translate("MainWindow_reach", "Kp = ") + f"{self.vertical_slider_value}")
+ self.btn_open.setToolTip(_translate("MainWindow_reach", " Import geometry"))
+ self.btn_add.setToolTip(_translate("MainWindow_reach", " New profile"))
+ self.btn_delete.setToolTip(_translate("MainWindow_reach", " Delete selected profile"))
+ self.btn_edit.setToolTip(_translate("MainWindow_reach", " Editer selected profile"))
+
self.btn_sort_asc.setToolTip(_translate("MainWindow_reach", " Trier les profils par ordre croissant des Pk"))
self.btn_sort_desc.setToolTip(_translate("MainWindow_reach", " Trier les profils par ordre décroissant des Pk"))
self.btn_move_up.setToolTip(_translate("MainWindow_reach", " Changer l'ordre des profils (en décalant le "
"profil sélectionné vers le haut)"))
self.btn_move_down.setToolTip(_translate("MainWindow_reach", " Changer l'ordre des profils (en décalant le "
"profil sélectionné vers le bas)"))
- self.btn_end_editing.setToolTip(_translate("MainWindow_reach", " Terminer l'édition"))
+ # self.btn_end_editing.setToolTip(_translate("MainWindow_reach", " Terminer l'édition"))
self.tableView_header = [_translate("MainWindow_reach", "Name"),
_translate("MainWindow_reach", "Kp (m)"),
_translate("MainWindow_reach", "Type")]
diff --git a/src/View/InitialConditions/PlotDKP.py b/src/View/InitialConditions/PlotDKP.py
index 58a8321..445cc08 100644
--- a/src/View/InitialConditions/PlotDKP.py
+++ b/src/View/InitialConditions/PlotDKP.py
@@ -71,6 +71,8 @@ class PlotDKP(APlot):
color='b', marker='+', lw=1.
)
+ self.canvas.axes.invert_xaxis()
+
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None:
diff --git a/src/View/InitialConditions/PlotDischarge.py b/src/View/InitialConditions/PlotDischarge.py
index 1fe05de..0703c59 100644
--- a/src/View/InitialConditions/PlotDischarge.py
+++ b/src/View/InitialConditions/PlotDischarge.py
@@ -65,6 +65,8 @@ class PlotDischarge(APlot):
color='r', lw=1.
)
+ self.canvas.axes.invert_xaxis()
+
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None:
diff --git a/src/View/InitialConditions/translate.py b/src/View/InitialConditions/translate.py
index 370d4a2..3fe45f7 100644
--- a/src/View/InitialConditions/translate.py
+++ b/src/View/InitialConditions/translate.py
@@ -21,11 +21,11 @@ from PyQt5.QtCore import QCoreApplication
_translate = QCoreApplication.translate
table_headers = {
- "name": _translate("LateralContribution", "Name"),
+ # "name": _translate("LateralContribution", "Name"),
"kp": _translate("LateralContribution", "KP (m)"),
# "speed": _translate("LateralContribution", "Speed (m/s)"),
"discharge": _translate("LateralContribution", "Discharge (m³/s)"),
"elevation": _translate("LateralContribution", "Elevation (m)"),
"height": _translate("LateralContribution", "Height (m)"),
- "comment": _translate("LateralContribution", "Comment"),
+ # "comment": _translate("LateralContribution", "Comment"),
}
diff --git a/src/View/SedimentLayers/Edit/translate.py b/src/View/SedimentLayers/Edit/translate.py
index fe9e44a..f9f02cb 100644
--- a/src/View/SedimentLayers/Edit/translate.py
+++ b/src/View/SedimentLayers/Edit/translate.py
@@ -6,7 +6,7 @@ _translate = QCoreApplication.translate
table_headers = {
"name": _translate("SedimentLayers", "Name"),
- "type": _translate("SedimentLayers", "Type"),
+ # "type": _translate("SedimentLayers", "Type"),
"height": _translate("Sedimentlayers", "Height"),
"d50": _translate("Sedimentlayers", "D50"),
"sigma": _translate("Sedimentlayers", "Sigma"),
diff --git a/src/View/SedimentLayers/Reach/Plot.py b/src/View/SedimentLayers/Reach/Plot.py
index a312a8c..5764abf 100644
--- a/src/View/SedimentLayers/Reach/Plot.py
+++ b/src/View/SedimentLayers/Reach/Plot.py
@@ -78,6 +78,8 @@ class Plot(APlot):
color='grey' if i == len(z_sl) - 1 else None
)
+ self.canvas.axes.invert_xaxis()
+
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None:
diff --git a/src/lang/fr.ts b/src/lang/fr.ts
index 8034809..031f349 100644
--- a/src/lang/fr.ts
+++ b/src/lang/fr.ts
@@ -60,21 +60,6 @@
<source>Ponctual contribution</source>
<translation>Contributions ponctuelles</translation>
</message>
- <message>
- <location filename="../View/BoundaryCondition/translate.py" line="32"/>
- <source>Time over Z</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="../View/BoundaryCondition/translate.py" line="33"/>
- <source>Time over Discharge</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="../View/BoundaryCondition/translate.py" line="34"/>
- <source>Z over Discharge</source>
- <translation type="unfinished"></translation>
- </message>
<message>
<location filename="../View/BoundaryCondition/translate.py" line="39"/>
<source>Name</source>
@@ -110,6 +95,21 @@
<source>Solid</source>
<translation type="unfinished">Solide</translation>
</message>
+ <message>
+ <location filename="../View/BoundaryCondition/translate.py" line="32"/>
+ <source>TimeZ</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/BoundaryCondition/translate.py" line="33"/>
+ <source>TimeDischarge</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/BoundaryCondition/translate.py" line="34"/>
+ <source>ZDischarge</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>Checker</name>
@@ -533,7 +533,7 @@
<message>
<location filename="../View/Frictions/translate.py" line="24"/>
<source>Name</source>
- <translation>Nom</translation>
+ <translation type="obsolete">Nom</translation>
</message>
<message>
<location filename="../View/Frictions/translate.py" line="26"/>
@@ -548,12 +548,17 @@
<message>
<location filename="../View/Frictions/translate.py" line="28"/>
<source>Begin strickler</source>
- <translation>strickler de départ</translation>
+ <translation type="obsolete">strickler de départ</translation>
</message>
<message>
<location filename="../View/Frictions/translate.py" line="29"/>
<source>End strickler</source>
- <translation>Strickler de fin</translation>
+ <translation type="obsolete">Strickler de fin</translation>
+ </message>
+ <message>
+ <location filename="../View/Frictions/translate.py" line="28"/>
+ <source>Strickler</source>
+ <translation type="unfinished"></translation>
</message>
</context>
<context>
@@ -587,7 +592,7 @@
<context>
<name>LateralContribution</name>
<message>
- <location filename="../View/InitialConditions/translate.py" line="24"/>
+ <location filename="../View/LateralContribution/translate.py" line="35"/>
<source>Name</source>
<translation>Nom</translation>
</message>
@@ -602,7 +607,7 @@
<translation>Lit moyen</translation>
</message>
<message>
- <location filename="../View/InitialConditions/translate.py" line="30"/>
+ <location filename="../View/Stricklers/translate.py" line="31"/>
<source>Comment</source>
<translation>Commentaire</translation>
</message>
@@ -1715,7 +1720,7 @@ sur le plan défini par les deux points nommés extrêmes </source>
<translation type="unfinished"></translation>
</message>
</context>
-<context>
+<context encoding="UTF-8">
<name>MainWindow_reach</name>
<message>
<location filename="../View/Frictions/PlotStricklers.py" line="76"/>
@@ -1723,7 +1728,7 @@ sur le plan défini par les deux points nommés extrêmes </source>
<translation></translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="342"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="335"/>
<source>Kp (m)</source>
<translation>Pk (m)</translation>
</message>
@@ -1778,68 +1783,47 @@ sur le plan défini par les deux points nommés extrêmes </source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="317"/>
- <source>Jeu de sections du Bief</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="319"/>
- <source>
-Ordre des sections : Amont --&gt; Aval</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="323"/>
- <source>Pk = </source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="325"/>
- <source> Nouveau profil</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="334"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="327"/>
<source> Trier les profils par ordre croissant des Pk</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="341"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="334"/>
<source>Name</source>
<translation>Nom</translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="343"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="336"/>
<source>Type</source>
<translation>Type</translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="345"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="338"/>
<source>Alt+Z</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="348"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="341"/>
<source>Alt+E</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="351"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="344"/>
<source>Alt+R</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="355"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="348"/>
<source>Vue globale automatique (Alt+S)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="359"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="352"/>
<source>Vue globale automatique (Alt+D)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="363"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="356"/>
<source>Vue globale automatique (Alt+F)</source>
<translation type="unfinished"></translation>
</message>
@@ -1889,68 +1873,33 @@ Seuls les 5 premiers seront édités.</source>
<source>Profil sélectionné</source>
<translation type="unfinished"></translation>
</message>
- <message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="324"/>
- <source> Importer une géométrie</source>
- <translation type="unfinished"></translation>
- </message>
- <message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="326"/>
- <source> Supprimer le profil sélectionné</source>
- <translation type="unfinished"></translation>
- </message>
- <message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="327"/>
- <source> Éditer le profil sélectionné</source>
- <translation type="unfinished"></translation>
- </message>
<message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="328"/>
- <source> Copier le profil sélectionné</source>
- <translation type="unfinished"></translation>
- </message>
- <message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="329"/>
- <source>Coller le profil en fin de liste (penser à modifier le Pk avant de trier)</source>
- <translation type="unfinished"></translation>
- </message>
- <message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="332"/>
- <source> Dupliquer la section sélectionnée</source>
- <translation type="unfinished"></translation>
- </message>
- <message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="335"/>
<source> Trier les profils par ordre décroissant des Pk</source>
<translation type="unfinished"></translation>
</message>
<message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="336"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="329"/>
<source> Changer l&apos;ordre des profils (en décalant le profil sélectionné vers le haut)</source>
<translation type="unfinished"></translation>
</message>
<message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="338"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="331"/>
<source> Changer l&apos;ordre des profils (en décalant le profil sélectionné vers le bas)</source>
<translation type="unfinished"></translation>
</message>
<message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="340"/>
- <source> Terminer l&apos;édition</source>
- <translation type="unfinished"></translation>
- </message>
- <message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="346"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="339"/>
<source>Vue isométrique (Alt+Z)</source>
<translation type="unfinished"></translation>
</message>
<message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="349"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="342"/>
<source>Vue isométrique (Alt+E)</source>
<translation type="unfinished"></translation>
</message>
<message encoding="UTF-8">
- <location filename="../View/Geometry/mainwindow_ui_reach.py" line="352"/>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="345"/>
<source>Vue isométrique (Alt+R)</source>
<translation type="unfinished"></translation>
</message>
@@ -1960,7 +1909,7 @@ Seuls les 5 premiers seront édités.</source>
<translation type="unfinished">Altitude (m)</translation>
</message>
<message>
- <location filename="../View/Geometry/PlotKPZ.py" line="66"/>
+ <location filename="../View/Geometry/PlotAC.py" line="73"/>
<source>Height (m)</source>
<translation type="unfinished">Hauteur (m)</translation>
</message>
@@ -1969,6 +1918,56 @@ Seuls les 5 premiers seront édités.</source>
<source>Select destination file</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="317"/>
+ <source>Geometry reach edit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="321"/>
+ <source>Kp = </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="322"/>
+ <source> Import geometry</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="323"/>
+ <source> New profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="324"/>
+ <source> Delete selected profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/Geometry/mainwindow_ui_reach.py" line="325"/>
+ <source> Editer selected profile</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/Geometry/PlotAC.py" line="69"/>
+ <source>Transverse abscissa (m)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/Geometry/PlotAC.py" line="79"/>
+ <source>Previous cross-section</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/Geometry/PlotAC.py" line="80"/>
+ <source>Cross-section</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../View/Geometry/PlotAC.py" line="81"/>
+ <source>Next cross-section</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>Network</name>
@@ -2071,7 +2070,7 @@ Seuls les 5 premiers seront édités.</source>
<message>
<location filename="../View/SedimentLayers/Edit/translate.py" line="9"/>
<source>Type</source>
- <translation type="unfinished">Type</translation>
+ <translation type="obsolete">Type</translation>
</message>
<message>
<location filename="../View/SedimentLayers/Reach/Profile/Window.py" line="68"/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.
+131
View File
@@ -0,0 +1,131 @@
Pamhyr2: a graphical user interface for 1D hydro-sedimentary modelling of rivers
Pierre-Antoine Rouby1, Benoît Camenen, Lionel Pénard, Léa Kieffer, Théophile Terraz1
INRAE, UR RiverLy, centre de Lyon-Grenoble, 5 rue de la Doua, CS20244, 69625, Villeurbanne, France.
pierre-antoine.rouby@inrae.fr, benoit.camenen@inrae.fr, lionel.penard@inrae.fr,
lea.kieffer@inrae.fr, theophile.terraz@inrae.fr
KEY WORDS
1D modelling, sedimentary modelling, user interface
Abstract
In numerical river simulation, one-dimensional models are commonly used to study water level and discharge for large domains or long time series. These models are less time-consuming than two- and three-dimensional numerical models, and require fewer input parameters and allow ensemble runs. To build a one-dimensional hydraulic model, a pre- and post-processing tool is needed for creating reach geometry, specifying initial and boundary conditions, friction coefficients and other numerical parameters. Such a tool needs to ensure the consistency of the model and provide a user-friendly graphical user interface. In this article, we present Pamhyr2, the fully rebuilt version of the PAMHYR modelling platform. It is developed using Python, PyQt and Matplotlib. Pamhyr2 is free and open-source, multilingual, cross-platform (Linux, Windows) and is generic enough to accept various one-dimensional solvers. Pamhyr2 includes and enhances all the features from the previous version: multiple-reach modelling, geometry definition from cross-section data, initial and boundary conditions, friction coefficients, hydraulic structures, lateral inflow, punctual intake and the results visualization window. In addition, this version includes new features such as pollutants modelling, bed-load and bed evolution. We describe several windows: the creation of sedimentary layers in the river bed, the sediment characteristics for each layer, the sediment, and pollutant boundary conditions and the lateral inputs. These functionalities are illustrated with simple examples. We finally show the visualization windows for bed evolution.
1. introduction
Numerous tools are available to simulate river flow. Among them, one-dimensional (1D) numerical hydraulic models have the advantage of being accurate while being inexpensive in terms of computation time, making it possible to simulate long rivers over very long timescales. A lot of 1D numerical solvers for river hydraulics are available, including HEC-RAS2, Mike 113 and Mascaret4 for example. However, in order to investigate specific research issues it is essential to have the ability to edit the source code of numeric solvers and to have an ergonomic Graphical User Interace (GUI). Most of the widespread 1D modeling tools are either written with a proprietary source code (such as HEC-RAS and Mike 11) or are research tools with irregularly maintained GUI. On its side, INRAE has been developing two 1D solvers for over 40 years: Mage5 [5] and RubarBE6. In parallel, a GUI named PAMHyR has been developed to enable modelers to build river models, parameterize calculations, launch Mage and RubarBE solvers and post-process the results [3].
PAMHyR, developed in Java since the end of 90s (originally named X-NT), it is written with the following constraints in mind:
• Multi-platform Windows, Unix-like,
• no dependence on an external graphics library with a paid license for user,
• generate as much code as possible from a UML diagram.
Now, PAMHyR suffers from complexity due to highly specific interface, as well as a significant technological debt [4]: old external library no longer maintained, over dated project architecture, and numerous modifications by multiple developers (and internship) over short periods of time, with no vision of long-term maintainability. What's more, the software is only in French and the code is closed (the sources are not available), which is a hindrance to the notoriety of the software as well as to the vitality of development. In addition, no significant changes have been made in the application since 2016, even if PAMHyR is still often used for river modeling [2].
INRAE needs a new tool to keep up with today’s technologies and with the development of its numerical solvers. In this paper we introduce the current work on the new graphical user interface Pamhyr2.
2. Objective
2.1 History and need
PAMHyR is available for two solvers, with two different types of studies: Mage and RubarBE. These two different kinds of studies have mostly similar interfaces, but have their own specific features. They can both be used to: define reach geometry, boundary conditions, lateral inflows, overflows, friction, and structures. In addition, you can run the solver on the study you are editing, and view the solver's results. Firstly, although both Mage and RubarBE are multiple-reach compatible, only the Mage interface enables multiple-reach study editing. Secondly, many small details change from one interface to the next. In addition, the latest Mage features, for example, are not available for modeling in the interface.
A total overhaul of the Java code would be too much work, given the technological debt accumulated since the beginning of its development. Given the workload involved in modernizing PAMHyR, the decision is made to develop a new version of the software from scratch. This version should be equivalent to the Java version in terms of functionality, and add new features, notably the new Mage functionalities. The aim is also to have an interface that is more modern, more user-friendly, more flexible, available on multiple platforms and in different languages. The interface must also have a certain degree of generality and be able to adapt to different solvers without major changes to the interface. It is also important that the code is open to contributions, to keep the project as lively as possible.
2.2 Technical choices
From a technical point of view, choices were made on the basis of the above-mentioned objectives (modernity, maintainability, multiplatform, multilingual, user-friendliness, flexibility), as well as: the ease of learning the technology to facilitate contribution to the software, the popularity of the technology in the scientific community and the interoperability of this technology.
We chose Python7 as our programming language. It is a popular language in the scientific world, but also in many other fields, with lot of very active software libraries. Moreover, it is a simple and interpreted language, often recommended for beginners, and practiced by beginners and experienced developers. In addition, there is the PyQT8 software library that lets you use the Qt9 GUI library, which is ideal for this project. It is very popular and active, and available on many platforms (GNU/Linux, Windows, MacOS, Unix, BSD, Android, iOS). Qt also includes a number of tools for translating interfaces into different languages. Here we use the community version of Qt, with no paid features. For graphics and visualization, the choice fell on Matplotlib10, which is very popular in the scientific community and can be used to draw a wide variety of graphics. For saving studies, the choice was made to use SQLite11, unlike the previous version which used a text file formatted in XML. The advantages of a SQLite database, in addition to being supplied with Python, include ease of use, interoperability, fast, formal structuring by table and consistency checking between tables. We have chosen to version the database to allow it to evolve with the software, and to make it possible to add columns to tables or make other minor modifications to the database without breaking the compatibility of studies from previous versions. However, PAMHyR and Pamhyr2 study files will not be compatible anymore, and previous studies have to be recreated for this version.
This new version Pamhyr2 remains closely linked to the Mage solver with which it is supplied. But the ambition of flexibility and generalization gives us the opportunity to develop an interface that can be adapted to other solvers. There can be two solutions for using a solver with Pamhyr2: either the solver adapts to Pamhyr2, or Pamhyr2 adapts to the solver. So Pamhyr2 must integrate a generic output containing the study data that can be used by other solvers, which will be launched via an external executable, and then a generic result input that must be supplied by the solver. In addition, Pamhyr2 must be able to handle the execution and reading of results for specific solvers, and the addition of a solver to Pamhyr2 must be reasonably easy for the solver developer. We have opted for the free and open source software (FOSS) [6] license GPLv312, which guarantees freedom to access, study, modify, redistribute and share. In addition, it guarantees that Pamhyr2's code will remain free and is not allowed to be added any closed-source modifications. However, it will still be possible to use closed-source solvers, because solver is not a part of Pamhyr2.
3. DEVELOPMENTS
3.1 New version of PAMHYR
Pamhyr2 currently features the following interfaces: Editing of river network, geometry, boundary conditions, lateral contribution, friction, initial conditions, solver numerical parameters, as well as the interface for launching a solver and displaying hydraulic and sediment results.
3.1.1 River network
The river network corresponds to the topology of the hydraulic network of the river. River network editing (Figure 1) is performed via a window featuring a graphical network area and two tables, one with a list of nodes (zone 3) and the other with a list of reaches (zone 2). The graphic area (zone 1) lets you add, delete and move nodes. It can also be used to link nodes in the network by arrows that represent reaches. The color of the node in the graphic area depends on whether it is a downstream, upstream or internal network node. If there is more than one reach, you can use this interface to select a reach, which will then be selected for editing in the following reach-dependent windows. It is also possible to deactivate reaches, in which case they will not be used for the solver run. Tables can be used to edit additional information on nodes and reaches, such as giving them names.
Figure 1: River network window for a dummy river with 8 reaches, 2 upstream node (yellow), 1 downstream node (green) and 5 internal node (blue).
3.1.2 Geometry
Geometry editing (Figure 2) is linked to a reach; the reach used will be the last reach selected in the river network window. This window contains a table with the list of the reach’s cross-sections (zone 1) and three graphics. A top view based on the XY positions of the cross-sections’ points and guidelines (zone 2). A longitudinal view of all cross-sections with the height of the guidelines’ points for each profile (zone 3). Finally, a cross-sectional view is displayed (zone 4) showing the selected cross-section with the previous and the following one in dotted line. Each cross-section is defined by a Kilometer Point (KP) and an optional name. The KP represents the longitudinal coordinate of the cross-section in the numerical solver. It is possible to add cross-sections and then select them to access an editing window, or to import cross-sections from file. Cross-section editing window takes the form of a table showing the raw data of the points that make up the cross-section, and a graph showing the current cross-section.
Figure 2: Geometry window for Saar river on the unique reach of the Saar river study.
3.1.3 Boundary conditions
Boundary conditions (BC) are associated with a network node. There are three different types: hydrograph, water level chart or rating curve, in three different categories: Liquid, solid and suspension. Boundary conditions can be added, deleted and edited. Editing a BC (Figure 3) takes the form of a two-column table (the type of column varies according to the type of BC) and a graph representing the data. Data can be copied and pasted from a spreadsheet, or edited by hand.
Figure 3: Edition window of the boundary conditions hydrograph for the Saar river upstream node.
3.1.3 Lateral contribution
The user interface for lateral contributions is similar to that for boundary conditions, except that it applies to a part of a reach between two KP, and not to a node.
3.1.4 Friction
Friction editing is linked to a reach, a friction coefficient is defined and then applied to a portion of the reach between two KP (Figure 4). Friction is defined by two Strickler coefficients, one for the river's main channel and one for the flood plain. The list of available Strickler coefficients is defined at the study level.
Figure 4: The reach portions for friction coefficient edition window for the Saar study.
3.1.5 Initial conditions
The initial conditions (Figure 5) are also linked to the reach, corresponding to the water level and flow rate at the initial time of the simulation. These two values can be defined separately for each cross-section of the reach. It is also possible to use predefined functions to generate a water line and flow rate.
Figure 5: Initial conditions edition window for the Saar study and the Saar reach. Initial conditions initialized with minimal height of 3 meter and a constant flow rate of 100 m3/s.
3.1.6 Running a solver
Solver numerical parameters are defined using a key/value table specific to each type of solver known to Pamhyr2. You can select and run the desired solver from the list of configured solvers. Then a window appears with a text area where you can follow the progress of file export and solver output in Figure 6 (zone 3). You can also pause, stop or restart the solver using buttons (zone 1). Once the simulation has finished, you can open a solver trace file, and if the simulation has finished correctly, you can open the results using the “results” button (zone 2).
Figure 6: Solver log window for “Elargissement” tests case with a mage solver named “src”.
3.1.7 Results processing and visualization
The post processing window is made up of tables allowing you to select the reaches, profiles, and timestamps for visualizing the results. The results are presented in different tabs according to the type of data displayed. The Figure 7 shows hydraulic data with water level at a given reach, profile, and time. The graphs shown here are the same as those in the geometry window, with the addition of water level results at a given time.
Figure 7: Hydraulics results window for “Elargissement” tests case with a Mage solver at timestamps 2113200.0 sec for reach R1 and cross-section at KP 427.7778.
3.2 New features
Some new features are already implemented in Pamhyr2. First of all, the software is multilingual with English and French currently available. An other very useful enhancement, is the possibility to use the “Ctrl+Z” or “Ctrl+Shift+Z” shortcuts to undo or redo the user actions.
Another novelty lies in the initial conditions window. Previously the water line could be generated by constant height or altitude. It is now possible to give a minimum height with an increasing function from downstream to upstream, thus avoiding physically unrealistic water holes and getting a little closer to a real and physically coherent water line (Figure 5).
An important new feature in this version is the presence of sediment layers. They can be defined as shown in Figure 8. Sediment layers can be applied separately for each cross-section (Figure 9). As with friction, they must first be defined and then applied to a profile. They can also be applied separately to each point of a cross-section. Layers are defined by a thickness, a median diameter (D50), a grain size sorting coefficient (Sigma) and a critical shear stress as described in Pierre Balayn PhD thesis [1]. The visualization of sedimentary results is currently done using a plot of the river profile, which shows the evolution of the bed bottom and the thickness of layers as a function of time (Figure 10).
Figure 8: Sediment layers definition window for Saar study. This sediment layers has three layers (L1, L2 and F).
Figure 9: Sediment layers defined in Figure 8 applied to the reach Saar.
Figure 10: Sedimentary results window for “Elargissement” tests case at timestamps 2113200.0 sec for reach R1 and cross-section at KP 427.7778. This example has only two sediment layers.
4. Future work
Old PAMHyR functionalities and additional functionalities, allowed the definition of structures, traps, suspended solids or dissolved pollutants in water, have yet to be developed. Additional options for visualizing results also need to be added. One possibility would be a graph generation tool that would allow the user to select x-axis and y-axis values, as well as the time range, to create a customized graph. We also planned an advanced feature with code-editing window to enable the user to define his own Matplotlib drawing function, which would be saved in the study. Presentation work is also planned to harmonize the application's icons and visuals (images, graphics, etc.). The aim is to make the application clearer and more user-friendly.
Supporting new solvers and generic solvers will also be an important part of the remaining work. To use any solver, we need to define a generic output format for Pamhyr2 that is simple enough to be given as solver input without major code modification, or that can be processed by a simple Python script (for example). The same applies to the results file that Pamhyr2 will have to read. The main candidates for these generic formats are :
• SQLite, because it is easy to use and the library is compatible with most programming languages,
• HDF513, a hierarchical file format widely used in scientific circles and compatible with many programming languages.
As the project progresses, it becomes more and more complicated to test all the functionalities by hand. A suite of tests and unit tests is needed to prevent regression bugs and ensure that the code remains maintainable. For the moment, no such test has been defined. Unit tests will have to be developed on the model and each sub-module will have to include tests, but higher-level functionalities will also have to be tested, such as the recording and playback of a complete study. It will be difficult to test the graphical part automatically, so tests will continue to be carried out manually.
Full documentation must also be provided to give users an overview of the features offered by Pamhyr2. This documentation must include descriptions of the configuration options, as well as explanations and examples of how to use each feature. In addition, this documentation must include a section for contributors and developers, with a technical presentation of the application, as well as a presentation of the translation, development and debugging tools available.
For translations into other languages than English and French, we hope that external contributors will take on this work. But no languages other are currently planned by the development team. The application packages are currently available for GNU/Linux and Windows. Under GNU/Linux, a “.tar.xz” archive is available for download. For Windows, this is an installer for application and a compatible version of Mage. An installer for GNU/Linux systems is a work in progress. There are many choices for creating an installer, but their scope depends on the distribution for which they are intended. “.deb”, for example, is intended for Debian distributions and their derivatives (Ubuntu, Linux Mint, and so on). We do not exclude other, more generic options.
5. Conclusion
After a great deal of preparatory work, experimentation, technical selection, proof-of-concept and several months of development, Pamhyr2 is currently in version “v0.0.0”14 and contains almost 20,000 lines of Python code. It allows to create a minimal 1D hydro-sedimentary study, run a Mage simulation and visualize some of the results obtained, all with a more user-friendly interface. But the software is still unstable and under development, and many modifications and improvements are still to be made before it can really be used by everyone. Furthermore, even though many of the features of the original version of PAMHyR are already available (see 3.1 page 3), there are still many to be added to make Pamhyr2 a complete, useful and fully functional piece of software.
ACKNOWLEDGEMENTS
The results presented here partially come from the Pamhyr PITI project funded by the Carnot Eau & Environnement institute and the French National Research Agency. Carnot Eau & Environnement was accredited in March 2020 by the French Ministry of Research and Higher Education on the proposal of the Agence Nationale de la Recherche. The signatory members are INRAE, CNRS, Aix Marseille University, IFTS, INSA Lyon, IRD, Université de Montpellier and Université de Savoie Mont-Blanc.
The authors thank Sylvain Coulibaly for his preparatory work and his technical choices.
REFERENCES and Citations
[1] Balayn, P. (2001). Contribution à la modélisation numérique de l'évolution morphologique des cours d'eau aménagés lors de crues. Université Claude Bernard - Lyon 1
[2] Camenen, B., Faure, J. Décanis, S. Diéval, L. (2021) A 1D numerical tool for real time modelling of a complex river network, SimHydro 2021
[3] Faure, J. (2013). An Integrated Simulation platform - PAMHyR. In John Wiley & Sons, Inc. eBooks (pp. 245–252). https://doi.org/10.1002/9781118557891.ch21
[4] Faure, J. (2017). Développement d’Adis-TS. OSR4 | Action V.1. https://hal.science/hal-03749339
[5] Faure, J-B., (2019). Mage, Résolution des équations de Barré de St Venant 1D en réseaux complexes, Documentation théorique et mode d’emploi. Techreport (in French)
[6] Walt, S. 2007. Free/open source software development. In Proceedings of the the 6th joint meeting of the European software engineering conference and the ACM SIGSOFT symposium on The foundations of software engineering (ESEC-FSE '07). Association for Computing Machinery, New York, NY, USA, 459–468. https://doi.org/10.1145/1287624.1287689
+163
View File
@@ -0,0 +1,163 @@
\ProvidesClass{Pamhyr}[Pamhyr class]
\NeedsTeXFormat{LaTeX2e}
\ProcessOptions\relax
\LoadClass[11pt,a4paper]{article}
\usepackage[hmargin=2cm, vmargin=3cm]{geometry}
\usepackage[british,UKenglish,USenglish,english,american]{babel}
\usepackage[T1]{fontenc}
\usepackage{times}
\usepackage[usenames,dvipsnames]{xcolor}
\usepackage[absolute]{textpos}
\usepackage{titlesec}
\usepackage{amssymb}
\usepackage{tcolorbox}
\usepackage{enumitem}
\setlist[enumerate]{itemsep=-0.5em}
\setlist[itemize]{itemsep=-0.5em}
%%
%% Bibtex
%%
\bibliographystyle{plain}
%%
%% Colors
%%
\definecolor{primary}{HTML}{212121}
\definecolor{lightheadings}{HTML}{5c5c5c}
\definecolor{headings}{HTML}{212121}
\definecolor{links}{HTML}{e12121}
%%
%% Title
%%
\setlength{\columnsep}{20pt}
\setlength{\parskip}{0pt} %% inter paragraph space
\renewcommand{\maketitle}{
{
\centering
{
\fontsize{35}{40}
\selectfont
\scshape
Pamhyr2 \\
\medskip
}
{
%\centering
\scshape
\fontsize{20pt}{24pt}
\selectfont
A graphical user interface for 1D hydro-sedimentary modelling
of rivers \\
\medskip
}
\rule{3cm}{0.4pt}\\
\vspace{0.3cm}
{
%\centering
\scshape
\fontsize{26pt}{24pt}
\selectfont
\@title \\
Document licence: GPLv3 \\
\medskip
\includegraphics[width=3cm]{../images/GPLv3_Logo.png} \\
\medskip
\fontsize{14pt}{16pt}
\selectfont
© \@author \\
\medskip
}
{
%\centering
%\scshape
\fontsize{12pt}{14pt}
\selectfont
\@date
%\medskip
}\\
\vspace{0.3cm}
\rule{3cm}{0.4pt}\\
\vspace{0.3cm}
{
\begin{minipage}[h]{0.24\textwidth}
\includegraphics[width=4cm]{../../src/View/ui/ressources/Logo-INRAE.png}
\end{minipage}
\begin{minipage}[h]{0.24\textwidth}
\includegraphics[width=4cm]{../images/riverly.png}
\end{minipage}
}\\
\vspace{0.3cm}
%%\rule{\textwidth}{0.4pt}
%%\newpage
}
}
%%
%% Texttt
%%
\usepackage[htt]{hyphenat} % Make texttt split at end of line
%%
%% Verbatim
%%
\AtBeginEnvironment{verbatim}{\footnotesize}
%% Add box arrond of verbatim
\BeforeBeginEnvironment{verbatim}{\begin{tcolorbox}[boxsep=0pt, left=0.1cm, right=0.1cm, arc=0pt, boxrule=0.5pt, colback=white]}%
\AfterEndEnvironment{verbatim}{\end{tcolorbox}}%
\makeatletter
\AtBeginEnvironment{verbatim}{\dontdofcolorbox}
\def\dontdofcolorbox{\renewcommand\fcolorbox[4][]{##4}}
\makeatother
%%
%% Source code blocks
%%
%% Use minted
\usepackage{minted}
\usemintedstyle{tango}
\setminted[c]{fontsize=\footnotesize,encoding=utf8,linenos}
\setminted[c++]{breaklines,fontsize=\footnotesize,encoding=utf8,linenos}
\setminted[python]{breaklines,fontsize=\footnotesize,encoding=utf8,linenos}
\setminted[shell]{breaklines,fontsize=\footnotesize,encoding=utf8}
\setminted[scheme]{breaklines,fontsize=\footnotesize,encoding=utf8}
\setminted[commun-lisp]{breaklines,fontsize=\footnotesize,encoding=utf8}
\setminted[text]{breaklines,fontsize=\footnotesize,encoding=utf8}
\setminted[llvm]{breaklines,fontsize=\footnotesize,encoding=utf8}
%% Add box arrond of minted
\BeforeBeginEnvironment{minted}{\begin{tcolorbox}[boxsep=0pt, left=0.1cm, right=0.1cm, arc=0pt, boxrule=0.5pt, colback=white]}%
\AfterEndEnvironment{minted}{\end{tcolorbox}}%
\makeatletter
\AtBeginEnvironment{minted}{\dontdofcolorbox}
\def\dontdofcolorbox{\renewcommand\fcolorbox[4][]{##4}}
\makeatother
%%
%% Water mark
%%
%% FIXME: Delete me for the first realease
\usepackage{draftwatermark}
\SetWatermarkLightness{0.8}
\SetWatermarkAngle{25}
\SetWatermarkScale{3}
\SetWatermarkFontSize{1cm}
\SetWatermarkText{Work in progress}
+19
View File
@@ -0,0 +1,19 @@
# latex.org -- Pamhyr documentation latex config
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
#+LaTeX_CLASS: PamhyrDoc
+51
View File
@@ -0,0 +1,51 @@
# macro.org -- Pamhyr documentation macro
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
# Constant name
#+MACRO: oldPamhyr PAMHyR
#+MACRO: Pamhyr Pamhyr2
#+MACRO: Mage Mage
#+MACRO: Rubarbe RubarBE
#+MACRO: Inrae INRAE
#+MACRO: latex \LaTeX
# Information
#+MACRO: version (eval (pamhyr-version))
#+MACRO: current-file (eval (pamhyr-current-file))
# Special format
#+MACRO: file =$1=
# Biblio
#+MACRO: cite [cite:$1]
#+MACRO: biblio \bibliography{documentation}
# Wrapper
#+NAME: attr_wrap
#+HEADER: :var width="\\textwidth"
#+HEADER: :var caption=""
#+HEADER: :var smallcaption=""
#+HEADER: :var name=""
#+HEADER: :var data=""
#+HEADER: :var float="nil"
#+BEGIN_SRC sh :results output :exports none
echo "#+CAPTION[$smallcaption]: $caption"
echo "#+NAME: $name"
echo "#+ATTR_LATEX: :width $width :float $float"
echo "$data"
#+END_SRC
View File
Whitespace-only changes.
+66
View File
@@ -0,0 +1,66 @@
(require 'org)
(require 'subr-x)
;; LaTeX config
(add-to-list
'org-latex-classes
'("PamhyrDoc"
"\\documentclass{../tools/PamhyrDoc}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
(setq org-confirm-babel-evaluate nil)
(setq org-latex-caption-above nil)
(org-babel-do-load-languages
'org-babel-load-languages
(add-to-list 'org-babel-load-languages '(shell . t)))
(org-babel-do-load-languages
'org-babel-load-languages
(add-to-list 'org-babel-load-languages '(dot . t)))
(org-babel-do-load-languages
'org-babel-load-languages
(add-to-list 'org-babel-load-languages '(python . t)))
(add-to-list 'org-latex-packages-alist '("" "minted"))
(setq org-latex-listings 'minted)
(setq org-src-fontify-natively t)
(setq org-latex-pdf-process
'("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"bibtex documentation"
"pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
;; Citations
(org-add-link-type
"cite" 'ebib
(lambda (path desc format)
(cond
((eq format 'html)
(format "(<cite>%s</cite>)" path))
((eq format 'latex)
(if (or (not desc) (equal 0 (search "cite:" desc)))
(format "\\cite{%s}" path)
(format "\\cite[%s][%s]{%s}"
(cadr (split-string desc ";"))
(car (split-string desc ";")) path))))))
;; Functools
(defun pamhyr-version ()
"Return the contents of the pamhyr version file."
(with-temp-buffer
(insert-file-contents "../../VERSION")
(buffer-string)))
(defun pamhyr-current-file ()
(let ((lst (split-string (buffer-file-name) "/")))
(string-join (nthcdr (- (length lst) 3) lst) "/")))
+18
View File
@@ -0,0 +1,18 @@
#! /bin/sh
echo " PDF documentation.org"
emacs -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./documentation.org \
-f org-latex-export-to-pdf
echo " HTML documentation.org"
emacs -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(require 'ox-html)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./documentation.org \
-f org-html-export-to-html
+1
View File
@@ -0,0 +1 @@
../tools/ref.bib
+20
View File
@@ -0,0 +1,20 @@
#+STARTUP: indent
#+INCLUDE: ../tools/macro.org
#+INCLUDE: ../tools/latex.org
#+TITLE: User documentation
#+SUBTITLE: Version: {{{version}}}
#+AUTHOR: {{{INRAE}}}
#+OPTIONS: toc:t
#+LANGUAGE: UKenglish
* TODO Over view
* TODO Get started
** Installation
*** Linux
*** Windows
* TODO Usage
{{{biblio}}}
View File
Whitespace-only changes.
+28 -12
View File
@@ -43,17 +43,21 @@ cd $OLD_PWD
echo " *** COPY DATA"
mkdir -p dist/pamhyr/View/ui
cp -r ../src/View/ui/ressources/ dist/pamhyr/View/ui/
cp -r ../src/View/ui/Widgets/*.ui dist/pamhyr/View/ui/
cp -r ../src/View/ui/*.ui dist/pamhyr/View/ui/
mkdir -p dist/pamhyr/_internal/View/ui
mkdir -p dist/pamhyr/_internal/View/ui/Widgets
cp -r ../src/View/ui/ressources/ dist/pamhyr/_internal/View/ui/
cp -r ../src/View/ui/Widgets/*.ui dist/pamhyr/_internal/View/ui/Widgets/
cp -r ../src/View/ui/*.ui dist/pamhyr/_internal/View/ui/
mkdir -p dist/pamhyr/lang
cp -r ../src/lang/*.qm dist/pamhyr/lang/
cp ../VERSION dist/pamhyr/
cp ../AUTHORS dist/pamhyr/
cp ../LICENSE dist/pamhyr/
mkdir dist/pamhyr/_internal/
#cp ../VERSION dist/pamhyr/_internal/
VERSION=$(cat ../VERSION)
echo "local-build-$VERSION" > dist/pamhyr/_internal/VERSION
cp ../AUTHORS dist/pamhyr/_internal/
cp ../LICENSE dist/pamhyr/_internal/
mkdir -p dist/pamhyr/mage/
cp ../mage/mage dist/pamhyr/mage/
@@ -64,6 +68,18 @@ mkdir -p dist/pamhyr/tests_cases/
mkdir -p dist/pamhyr/tests_cases/Saar
cp ../tests_cases/Saar/Saar.pamhyr dist/pamhyr/tests_cases/Saar/
mkdir -p dist/pamhyr/doc/
cp ../doc/dev/documentation.pdf dist/pamhyr/doc/Pamhyr2-dev.pdf
cp ../doc/dev/documentation.html dist/pamhyr/doc/Pamhyr2-dev.html
cp ../doc/users/documentation.pdf dist/pamhyr/doc/Pamhyr2-users.pdf
cp ../doc/users/documentation.html dist/pamhyr/doc/Pamhyr2-users.html
mkdir -p dist/pamhyr/doc/images
cp ../doc/users/images/* dist/pamhyr/doc/images/
cp ../doc/dev/images/* dist/pamhyr/doc/images/
cp ../doc/images/* dist/pamhyr/doc/images/
echo " *** MAKE SRC PACKAGE"
OLD_PWD=$PWD
@@ -75,11 +91,11 @@ mv ../pamhyr-src.tar.gz ./
echo " *** MAKE BIN PACKAGE"
OLD_PWD=$PWD
cd dist/
tar --xz -cf pamhyr-gnulinux-amd64.tar.xz pamhyr --checkpoint=.100
cd $OLD_PWD
# OLD_PWD=$PWD
# cd dist/
# tar --xz -cf pamhyr-gnulinux-amd64.tar.xz pamhyr --checkpoint=.100
# cd $OLD_PWD
mv dist/pamhyr-gnulinux-amd64.tar.xz ./
# mv dist/pamhyr-gnulinux-amd64.tar.xz ./
echo " *** DONE"
+60
View File
@@ -0,0 +1,60 @@
rem windows.bat -- Pamhyr Windows batch for windows version building
rem Copyright (C) 2023 INRAE
rem
rem This program is free software: you can redistribute it and/or modify
rem it under the terms of the GNU General Public License as published by
rem the Free Software Foundation, either version 3 of the License, or
rem (at your option) any later version.
rem
rem This program is distributed in the hope that it will be useful,
rem but WITHOUT ANY WARRANTY; without even the implied warranty of
rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
rem GNU General Public License for more details.
rem
rem You should have received a copy of the GNU General Public License
rem along with this program. If not, see <https://www.gnu.org/licenses/>.
@ECHO ON
mkdir pamhyr\pamhyr\_internal
xcopy /s /e /y dist\pamhyr pamhyr\pamhyr
rem Copy data
mkdir pamhyr\pamhyr\_internal\lang
mkdir pamhyr\pamhyr\_internal\View\ui\ressources
mkdir pamhyr\pamhyr\_internal\View\ui\Widgets
rem UI
copy /y ..\src\View\ui\ressources\ pamhyr\pamhyr\_internal\View\ui\ressources
copy /y ..\src\View\ui\Widgets\*.ui pamhyr\pamhyr\_internal\View\ui\Widgets
copy /y ..\src\View\ui\*.ui pamhyr\pamhyr\_internal\View\ui\
rem Lang
copy /y ..\src\lang\*.qm pamhyr\pamhyr\_internal\lang\
rem Information
copy /y ..\VERSION pamhyr\pamhyr\_internal
copy /y ..\AUTHORS pamhyr\pamhyr\_internal
copy /y ..\LICENSE pamhyr\pamhyr\_internal
rem MAGE
mkdir pamhyr\mage
copy /y ..\mage-windows\mage.exe pamhyr\mage\
copy /y ..\mage-windows\mage_extraire.exe pamhyr\mage\
copy /y ..\mage-windows\mailleurPF.exe pamhyr\mage\
rem Copy tests_cases
mkdir pamhyr\tests_cases
mkdir pamhyr\tests_cases\Saar
copy /y ..\tests_cases\Saar\Saar.pamhyr pamhyr\tests_cases\Saar\
rem Documentations
mkdir pamhyr\doc
mkdir pamhyr\doc\images
copy /y ..\doc\users\documentation.pdf pamhyr\doc\Pamhyr2-users.pdf
copy /y ..\doc\dev\documentation.pdf pamhyr\doc\Pamhyr2-dev.pdf
copy /y ..\doc\users\documentation.html pamhyr\doc\Pamhyr2-users.html
copy /y ..\doc\dev\documentation.html pamhyr\doc\Pamhyr2-dev.html
copy /y ..\doc\users\images\* pamhyr\doc\images
copy /y ..\doc\dev\images\* pamhyr\doc\images
copy /y ..\doc\images\* pamhyr\doc\images
+20
View File
@@ -0,0 +1,20 @@
rem windows.bat -- Pamhyr Windows batch for windows version building
rem Copyright (C) 2023 INRAE
rem
rem This program is free software: you can redistribute it and/or modify
rem it under the terms of the GNU General Public License as published by
rem the Free Software Foundation, either version 3 of the License, or
rem (at your option) any later version.
rem
rem This program is distributed in the hope that it will be useful,
rem but WITHOUT ANY WARRANTY; without even the implied warranty of
rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
rem GNU General Public License for more details.
rem
rem You should have received a copy of the GNU General Public License
rem along with this program. If not, see <https://www.gnu.org/licenses/>.
@ECHO ON
rem Make installer
"C:\Program Files (x86)\NSIS\makensis.exe" pamhyr.nsi
+23 -19
View File
@@ -3,17 +3,17 @@
!define LIC_NAME "LICENSE"
!define APP_NAME "PAMHYR"
Name "PAMHYR"
OutFile "pamhyr-win-amd64.exe"
LicenseData "..\LICENSE"
Name "Pamhyr2"
OutFile "pamhyr-windows.exe"
LicenseData "pamhyr\pamhyr\_internal\LICENSE"
LicenseText "I Agree"
RequestExecutionLevel admin
Unicode True
InstallDir $PROGRAMFILES\PAMHYR
InstallDir $PROGRAMFILES\Pamhyr2
InstallDirRegKey HKLM "Software\PAMHYR" "Install_Dir"
InstallDirRegKey HKLM "Software\Pamhyr2" "Install_Dir"
Page license
Page components
@@ -23,35 +23,39 @@ Page instfiles
UninstPage uninstConfirm
UninstPage instfiles
Section "PAMHYR"
Section "Pamhyr2"
SectionIn RO
SetOutPath $INSTDIR
File /r "dist\pamhyr\*"
File /r "pamhyr\*"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "DisplayName" "NSIS PAMHYR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoRepair" 1
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "DisplayName" "NSIS Pamhyr2"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoRepair" 1
WriteUninstaller "$INSTDIR\uninstall.exe"
SectionEnd
Section "Start Menu Shortcuts"
CreateDirectory "$SMPROGRAMS\Pamhyr2"
CreateShortcut "$SMPROGRAMS\Pamhyr2\Uninstall.lnk" "$INSTDIR\uninstall.exe"
CreateShortcut "$SMPROGRAMS\Pamhyr2\Pamhyr2.lnk" "$INSTDIR\pamhyr\pamhyr.exe"
SectionEnd
CreateDirectory "$SMPROGRAMS\PAMHYR"
CreateShortcut "$SMPROGRAMS\PAMHYR\Uninstall.lnk" "$INSTDIR\uninstall.exe"
CreateShortcut "$SMPROGRAMS\PAMHYR\PAMHYR.lnk" "$INSTDIR\pamhyr.exe"
Section "Desktop Shortcuts"
SetShellVarContext all
CreateShortcut "$desktop\Pamhyr2.lnk" "$instdir\pamhyr\pamhyr.exe"
SectionEnd
Section "Uninstall"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR"
DeleteRegKey HKLM SOFTWARE\NSIS_PAMHYR
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2"
DeleteRegKey HKLM SOFTWARE\NSIS_Pamhyr2
Delete "$SMPROGRAMS\PAMHYR\*.lnk"
Delete "$SMPROGRAMS\Pamhyr2\*.lnk"
Delete "$desktop\Pamhyr2.lnk"
RMDir "$SMPROGRAMS\PAMHYR"
RMDir "$SMPROGRAMS\Pamhyr2"
RMDir /R "$INSTDIR"
SectionEnd
+45 -16
View File
@@ -81,13 +81,42 @@ wine pyinstaller ../src/pamhyr.py -y
echo " *** COPY DATA"
mkdir -p dist/pamhyr/View/ui
cp -r ../src/View/ui/ressources/ dist/pamhyr/View/ui/
cp -r ../src/View/ui/Widgets/*.ui dist/pamhyr/View/ui/
cp -r ../src/View/ui/*.ui dist/pamhyr/View/ui/
mkdir -p pamhyr
mkdir -p pamhyr/pamhyr
cp ../VERSION dist/pamhyr/
cp ../AUTHORS dist/pamhyr/
# Copy Pamhyr info
cp -v ../VERSION pamhyr/pamhyr/
cp -v ../AUTHORS pamhyr/pamhyr/
cp -v ../LICENSE pamhyr/pamhyr/
# Copy mage
mkdir -p pamhyr/mage
cp -v ../mage/* pamhyr/mage/
# Copy Pamhyr
cp -r dist/pamhyr pamhyr/
# Copy Pamhyr resources
mkdir -p pamhyr/pamhyr/View
mkdir -p pamhyr/pamhyr/View/ui
mkdir -p pamhyr/pamhyr/View/ui/Widgets
cp -r ../src/View/ui/*.ui pamhyr/pamhyr/View/ui/
cp -r ../src/View/ui/ressources/ pamhyr/pamhyr/View/ui
cp -r ../src/View/ui/Widgets/*.ui pamhyr/pamhyr/View/ui/Widgets
# Copy lang
mkdir -p pamhyr/pamhyr/lang
cp -r ../src/lang/*.qm pamhyr/pamhyr/lang
# Copy tests cases
mkdir -p pamhyr/tests_cases/
mkdir -p pamhyr/tests_cases/Saar
cp ../tests_cases/Saar/Saar.pamhyr pamhyr/tests_cases/Saar/
# Copy documentations
mkdir -p pamhyr/doc/
cp ../doc/users/documentation.pdf pamhyr/doc/Pamhyr2-users.pdf
cp ../doc/dev/documentation.pdf pamhyr/doc/Pamhyr2-dev.pdf
# Update TS and build QM files
OLD_PWD=$PWD
@@ -102,19 +131,19 @@ echo " *** MAKE PACKAGES"
echo " TAR pamhyr-win-amd64.tar.xz"
OLD_PWD=$PWD
cd dist/
tar --xz -cf pamhyr-win-amd64.tar.xz pamhyr --checkpoint=.100
cd $OLD_PWD
mv dist/pamhyr-win-amd64.tar.xz ./
# OLD_PWD=$PWD
# cd dist/
# tar --xz -cf pamhyr-win-amd64.tar.xz pamhyr --checkpoint=.100
# cd $OLD_PWD
# mv dist/pamhyr-win-amd64.tar.xz ./
echo " ZIP pamhyr-win-amd64.zip"
OLD_PWD=$PWD
cd dist/
zip -r pamhyr-win-amd64.zip pamhyr/
cd $OLD_PWD
mv dist/pamhyr-win-amd64.zip ./
# OLD_PWD=$PWD
# cd dist/
# zip -r pamhyr-win-amd64.zip pamhyr/
# cd $OLD_PWD
# mv dist/pamhyr-win-amd64.zip ./
echo " *** MAKE INSTALLER"
+2 -1
View File
@@ -1,6 +1,7 @@
PyQt5==5.15.4
PyQt5-Qt5==5.15.2
PyQt5-sip==12.8.1
PyQt5-sip==12.12.2
PyQtWebEngine==5.15.6
pyqtgraph>=0.12.1
matplotlib>=3.4.1
numpy>=1.24.2
-10
View File
@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
class AbstractModelComponent(object):
def __init__(self):
super(AbstractModelComponent, self).__init__()
def new(self, filename):
def export(self, filename):
@@ -20,7 +20,7 @@ import logging
from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
@@ -83,7 +83,8 @@ class BoundaryCondition(SQLSubModel):
def _get_ctor_from_type(cls, t):
from Model.BoundaryCondition.BoundaryConditionTypes import (
NotDefined, PonctualContribution,
TimeOverZ, TimeOverDischarge, ZOverDischarge
TimeOverZ, TimeOverDischarge, ZOverDischarge,
Solid,
)
res = NotDefined
@@ -95,6 +96,8 @@ class BoundaryCondition(SQLSubModel):
res = TimeOverDischarge
elif t == "ZD":
res = ZOverDischarge
elif t == "SL":
res = Solid
return res
@classmethod
@@ -19,40 +19,23 @@
from copy import copy
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError
from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
from Model.BoundaryCondition.BoundaryConditionTypes import (
NotDefined,
PonctualContribution,
TimeOverZ, TimeOverDischarge, ZOverDischarge
TimeOverZ, TimeOverDischarge, ZOverDischarge,
Solid,
)
class BoundaryConditionList(SQLSubModel):
class BoundaryConditionList(PamhyrModelListWithTab):
_tabs_list = ["liquid", "solid", "suspenssion"]
_sub_classes = [
BoundaryCondition,
]
def __init__(self, status = None):
super(BoundaryConditionList, self).__init__()
self._status = status
self._tabs = {
"liquid" : [],
"solid" : [],
"suspenssion" : []
}
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return True
@classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data['status'])
@@ -81,66 +64,12 @@ class BoundaryConditionList(SQLSubModel):
return True
def len(self, lst):
return len(self._tabs[lst])
def get_tab(self, lst):
return self._tabs[lst].copy()
def get(self, lst, row):
return self._tabs[lst][row]
def set(self, lst, row, new):
self._tabs[lst][row] = new
self._status.modified()
def new(self, lst, index):
n = NotDefined(status=self._status)
self._tabs[lst].insert(index, n)
self._status.modified()
return n
def insert(self, lst, index, new):
self._tabs[lst].insert(index, new)
self._status.modified()
def delete(self, lst, bcs):
for bc in bcs:
self._tabs[lst].remove(bc)
self._status.modified()
def delete_i(self, lst, indexes):
bcs = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._tabs[lst])
)
)
)
self.delete(lst, bcs)
def sort(self, lst, reverse=False, key=None):
self._tabs[lst].sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, lst, index):
if index < len(self._tabs[lst]):
next = index - 1
l = self._tabs[lst]
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, lst, index):
if index >= 0:
prev = index + 1
l = self._tabs[lst]
l[index], l[prev] = l[prev], l[index]
self._status.modified()
def __copy__(self):
new = BoundaryConditionList()
@@ -83,3 +83,19 @@ class ZOverDischarge(BoundaryCondition):
@property
def _default_0(self):
return 0.0
class Solid(BoundaryCondition):
def __init__(self, id:int = -1, name:str = "", status = None):
super(Solid, self).__init__(id=id, name=name, status=status)
self.d50 = 0.002
self.sigma = 1
self._type = "SL"
self._header = ["time", "solid"]
self._types = [TimeOverDischarge.time_convert, float]
@classmethod
def compatibility(cls):
return ["solid"]
+1 -1
View File
@@ -18,7 +18,7 @@
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
class Friction(SQLSubModel):
def __init__(self, name:str = "", status = None):
+8 -67
View File
@@ -21,26 +21,18 @@ import logging
from copy import copy
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Friction.Friction import Friction
logger = logging.getLogger()
class FrictionList(SQLSubModel):
class FrictionList(PamhyrModelList):
_sub_classes = [
Friction
]
def __init__(self, status = None):
super(FrictionList, self).__init__()
self._status = status
self._frictions = []
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update_0_0_1(cls, execute, version):
execute("ALTER TABLE `section` RENAME TO `friction`")
@@ -57,7 +49,7 @@ class FrictionList(SQLSubModel):
def _sql_load(cls, execute, data = None):
new = cls(status = data['status'])
new._frictions = Friction._sql_load(
new._lst = Friction._sql_load(
execute, data
)
@@ -69,70 +61,19 @@ class FrictionList(SQLSubModel):
ok = True
ind = 0
for friction in self._frictions:
for friction in self._lst:
data["ind"] = ind
ok &= friction._sql_save(execute, data = data)
ind += 1
return ok
def __len__(self):
return len(self._frictions)
@property
def frictions(self):
return self._frictions.copy()
def get(self, row):
return self._frictions[row]
def set(self, row, new):
self._frictions[row] = new
self._status.modified()
return self.lst
def new(self, index):
n = Friction(status = self._status)
self._frictions.insert(index, n)
self._lst.insert(index, n)
self._status.modified()
return n
def insert(self, index, new):
self._frictions.insert(index, new)
self._status.modified()
def delete(self, frictions):
for friction in frictions:
self._frictions.remove(friction)
self._status.modified()
def delete_i(self, indexes):
frictions = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._frictions)
)
)
)
self.delete(frictions)
def sort(self, reverse=False, key=None):
self._frictions.sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, index):
if index < len(self._frictions):
next = index - 1
l = self._frictions
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
l = self._frictions
l[index], l[prev] = l[prev], l[index]
self._status.modified()
+18 -1
View File
@@ -19,12 +19,29 @@
from Model.Except import NotImplementedMethodeError
class Point(object):
def __init__(self, name:str = "", status = None):
def __init__(self, name:str = "", profile=None, status = None):
super(Point, self).__init__()
self._status = status
self._name = name
self._profile = profile
self._sl = None
@property
def sl(self):
if self._sl is None:
return self._profile.sl
return self._sl
@sl.setter
def sl(self, sl):
if sl == self._profile.sl:
self._sl = None
else:
self._sl = sl
@property
def name(self):
+36 -9
View File
@@ -19,15 +19,15 @@
from math import dist
import numpy as np
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Geometry.Point import Point
class PointXYZ(Point, SQLSubModel):
_sub_classes = []
def __init__(self, x:float = 0.0, y:float = 0.0, z:float = 0.0,
name:str = "", status = None):
super(PointXYZ, self).__init__(name=name, status=status)
name:str = "", profile = None, status = None):
super(PointXYZ, self).__init__(name=name, profile=profile, status=status)
self._x = float(x)
self._y = float(y)
@@ -44,7 +44,9 @@ class PointXYZ(Point, SQLSubModel):
y INTEGER NOT NULL,
z INTEGER NOT NULL,
profile INTEGER NOT NULL,
FOREIGN KEY(profile) REFERENCES profileXYZ(id)
sl INTEGER,
FOREIGN KEY(profile) REFERENCES profileXYZ(id),
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
)
""")
@@ -53,6 +55,17 @@ class PointXYZ(Point, SQLSubModel):
@classmethod
def _sql_update(cls, execute, version):
cls._update_submodel(execute, version)
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 2:
execute(
"""
ALTER TABLE geometry_pointXYZ
ADD COLUMN sl INTEGER
REFERENCES sedimentary_layer(id)
"""
)
return True
@classmethod
@@ -63,9 +76,9 @@ class PointXYZ(Point, SQLSubModel):
profile = data["profile"]
table = execute(
"SELECT ind, name, x, y, z " +
"SELECT ind, name, x, y, z, sl " +
"FROM geometry_pointXYZ " +
f"WHERE profile = {profile}"
f"WHERE profile = {profile.id}"
)
# Create points list
@@ -79,13 +92,25 @@ class PointXYZ(Point, SQLSubModel):
x = row[2]
y = row[3]
z = row[4]
sl = row[5]
new = cls(
name = name,
x = x, y = y, z = z,
profile = profile,
status = status
)
if sl == -1 or sl == None:
new._sl = None
else:
new._sl = next(
filter(
lambda s: s.id == sl,
data["sediment_layers_list"].sediment_layers
)
)
points[ind] = new
return points
@@ -94,13 +119,15 @@ class PointXYZ(Point, SQLSubModel):
profile = data["profile"]
ind = data["ind"]
sl = self._sl.id if self._sl is not None else -1
sql = (
"INSERT OR REPLACE INTO " +
"geometry_pointXYZ(ind, name, x, y, z, profile) "+
"geometry_pointXYZ(ind, name, x, y, z, profile, sl) "+
"VALUES (" +
f"{ind}, '{self._sql_format(self._name)}', " +
f"{self.x}, {self.y}, {self.z}, " +
f"{profile}" +
f"{profile.id}, {sl}" +
")"
)
execute(sql)
@@ -117,7 +144,7 @@ class PointXYZ(Point, SQLSubModel):
*data
)
else:
valid_header = {'name', 'x', 'y', 'z'}
valid_header = {'name', 'x', 'y', 'z', 'profile'}
d = {}
for i, v in enumerate(data):
h = header[i].strip().lower().split(' ')[0]
+53
View File
@@ -50,11 +50,15 @@ class Profile(object):
self._kp = float(kp)
self._name = str(name)
self._reach = reach
self._sl = None
self._points: List[Point] = []
self._profile_type = _type
def __len__(self):
return len(self._points)
@property
def number_points(self):
return len(self._points)
@@ -63,6 +67,9 @@ class Profile(object):
def points(self):
return self._points.copy()
def point(self, index):
return self._points[index]
@property
def reach(self):
return self._reach
@@ -136,6 +143,19 @@ class Profile(object):
self._name = value.strip()
self._status.modified()
@property
def sl(self):
"""
Returns:
Profile sediment layers.
"""
return self._sl
@sl.setter
def sl(self, value):
self._sl = value
self._status.modified()
@property
def profile_type(self):
"""
@@ -274,6 +294,39 @@ class Profile(object):
)
self._status.modified()
# Sediment Layers
def get_sl(self):
"""Get sediment layer height of points
Get sediment layer of points (without spesific point sl)
Returns:
List of sediment layers height
"""
res = []
psl = [point.sl for point in self.points]
# Compute max number of layers
sl_max = 0
for sl in psl:
n = 0 if sl is None else len(sl)
sl_max = max(n, sl_max)
# Create list of height for each sl and each layer
for i in range(0, sl_max):
cur = []
# Compute new layer line for each sl
for sl in psl:
if sl is not None and i < len(sl):
cur.append(sl.get(i).height)
else:
cur.append(0)
# Add layer line to result
res.append(cur)
return res
# Abstract method, must be implemented for in non abstract class
def get_station(self):
raise NotImplementedMethodeError(self, self.get_station)
+38 -12
View File
@@ -21,7 +21,7 @@ from typing import List
from tools import timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import ClipboardFormatError
from Model.Geometry.Profile import Profile
from Model.Geometry.PointXYZ import PointXYZ
@@ -76,7 +76,9 @@ class ProfileXYZ(Profile, SQLSubModel):
num INTEGER NOT NULL,
code1 INTEGER NOT NULL,
code2 INTEGER NOT NULL,
FOREIGN KEY(reach) REFERENCES river_reach(id)
sl INTEGER,
FOREIGN KEY(reach) REFERENCES river_reach(id),
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
)
""")
@@ -84,6 +86,17 @@ class ProfileXYZ(Profile, SQLSubModel):
@classmethod
def _sql_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 2:
execute(
"""
ALTER TABLE geometry_profileXYZ
ADD COLUMN sl INTEGER
REFERENCES sedimentary_layer(id)
"""
)
return cls._update_submodel(execute, version)
@classmethod
@@ -93,7 +106,7 @@ class ProfileXYZ(Profile, SQLSubModel):
reach = data["reach"]
table = execute(
"SELECT id, ind, name, kp, num, code1, code2 " +
"SELECT id, ind, name, kp, num, code1, code2, sl " +
"FROM geometry_profileXYZ " +
f"WHERE reach = {reach}"
)
@@ -109,6 +122,7 @@ class ProfileXYZ(Profile, SQLSubModel):
num = row[5]
code1 = row[5]
code2 = row[6]
sl = row[7]
new = cls(
id=id, num = num,
@@ -118,7 +132,17 @@ class ProfileXYZ(Profile, SQLSubModel):
status = status
)
data["profile"] = id
if sl == -1 or sl == None:
new._sl = None
else:
new._sl = next(
filter(
lambda s: s.id == sl,
data["sediment_layers_list"].sediment_layers
)
)
data["profile"] = new
new._points = PointXYZ._sql_load(execute, data)
profiles[ind] = new
@@ -129,18 +153,20 @@ class ProfileXYZ(Profile, SQLSubModel):
ok = True
ind = data["ind"]
sl = self._sl.id if self._sl is not None else -1
sql = (
"INSERT OR REPLACE INTO " +
"geometry_profileXYZ(id, ind, name, reach, kp, num, code1, code2) "+
"geometry_profileXYZ(id, ind, name, reach, kp, num, code1, code2, sl) "+
"VALUES (" +
f"{self.id}, {ind}, '{self._sql_format(self._name)}', " +
f"{self.reach.id}, {self.kp}, {self.num}, " +
f"{self.code1}, {self.code1}" +
f"{self.code1}, {self.code1}, {sl}" +
")"
)
execute(sql)
data["profile"] = self.id
data["profile"] = self
execute(f"DELETE FROM geometry_pointXYZ WHERE profile = {self.id}")
ind = 0
@@ -178,11 +204,11 @@ class ProfileXYZ(Profile, SQLSubModel):
try:
if len(header) == 0:
point = PointXYZ(
*data, status=self._status
*data, profile=self, status=self._status
)
else:
valid_header = {'name', 'x', 'y', 'z'}
d = {"status": self._status}
d = {"status": self._status, "profile": self}
for i, v in enumerate(data):
h = header[i].strip().lower().split(' ')[0]
if h in valid_header:
@@ -235,7 +261,7 @@ class ProfileXYZ(Profile, SQLSubModel):
Nothing.
"""
for point in list_points:
pt = PointXYZ(*point, status=self._status)
pt = PointXYZ(*point, profile=self, status=self._status)
self._points.append(pt)
self._status.modified()
@@ -259,7 +285,7 @@ class ProfileXYZ(Profile, SQLSubModel):
Returns:
Nothing.
"""
point_xyz = PointXYZ(0., 0., 0., status=self._status)
point_xyz = PointXYZ(0., 0., 0., profile=self, status=self._status)
self._points.append(point_xyz)
self._status.modified()
@@ -272,7 +298,7 @@ class ProfileXYZ(Profile, SQLSubModel):
Returns:
The new point.
"""
point = PointXYZ(0., 0., 0., status=self._status)
point = PointXYZ(0., 0., 0., profile=self, status=self._status)
self._points.insert(index, point)
self._status.modified()
return point
+38 -2
View File
@@ -27,7 +27,7 @@ from functools import reduce
from tools import flatten, timer, trace
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Geometry.Profile import Profile
from Model.Geometry.ProfileXYZ import ProfileXYZ
@@ -42,7 +42,7 @@ class Reach(SQLSubModel):
]
def __init__(self, status=None, parent=None):
self.id = parent.id
self.id = parent.id if parent is not None else 0
self._status = status
self._parent = parent
self._profiles: List[Profile] = []
@@ -106,6 +106,9 @@ class Reach(SQLSubModel):
return self._parent.name
def __len__(self):
return len(self._profiles)
@property
def profiles(self):
return self._profiles.copy()
@@ -276,6 +279,39 @@ class Reach(SQLSubModel):
return max(self.get_kp())
# Sediment Layers
def get_sl(self):
"""Get sediment layer height of profile
Get sediment layer of profile (without spesific point sl)
Returns:
List of sediment layers height
"""
res = []
psl = [profile.sl for profile in self.profiles]
# Compute max number of layers
sl_max = 0
for sl in psl:
n = 0 if sl is None else len(sl)
sl_max = max(n, sl_max)
# Create list of height for each sl and each layer
for i in range(0, sl_max):
cur = []
# Compute new layer line for each sl
for sl in psl:
if sl is not None and i < len(sl):
cur.append(sl.get(i).height)
else:
cur.append(0)
# Add layer line to result
res.append(cur)
return res
# Guidelines
@timer
@@ -20,7 +20,7 @@ from copy import copy, deepcopy
from tools import trace, timer
from functools import reduce
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
class Data(SQLSubModel):
def __init__(self, name:str = "",
@@ -19,29 +19,14 @@
from copy import copy
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDict import PamhyrModelDict
from Model.InitialConditions.InitialConditions import InitialConditions
class InitialConditionsDict(SQLSubModel):
class InitialConditionsDict(PamhyrModelDict):
_sub_classes = [
InitialConditions,
]
def __init__(self, status = None):
super(InitialConditionsDict, self).__init__()
self._status = status
self._reach = {}
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data["status"])
@@ -55,7 +40,7 @@ class InitialConditionsDict(SQLSubModel):
)
if ic is not None:
new._reach[reach] = ic
new._dict[reach] = ic
return new
@@ -66,30 +51,12 @@ class InitialConditionsDict(SQLSubModel):
execute("DELETE FROM initial_conditions")
for reach in self._reach:
for reach in self._dict:
data["reach"] = reach
ok &= self._reach[reach]._sql_save(execute, data)
ok &= self._dict[reach]._sql_save(execute, data)
return ok
def __len__(self):
return len(self._reach)
def is_defined(self, reach):
return reach in self._reach
def get(self, reach):
if reach in self._reach:
return self._reach[reach]
new = self.new(reach)
self.set(reach, new)
return new
def set(self, reach, new):
self._reach[reach] = new
self._status.modified()
def new(self, reach):
new = InitialConditions(reach = reach, status = self._status)
self.set(reach, new)
@@ -20,7 +20,7 @@ import logging
from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError
from Model.LateralContribution.LateralContribution import LateralContribution
@@ -27,30 +27,12 @@ from Model.LateralContribution.LateralContributionTypes import (
NotDefined, LateralContrib, Rain, Evaporation,
)
class LateralContributionList(SQLSubModel):
class LateralContributionList(PamhyrModelListWithTab):
_tabs_list = ["liquid", "solid", "suspenssion"]
_sub_classes = [
LateralContribution,
]
def __init__(self, status = None):
super(LateralContributionList, self).__init__()
self._status = status
self._tabs = {
"liquid" : [],
"solid" : [],
"suspenssion" : []
}
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return True
@classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data['status'])
@@ -79,66 +61,12 @@ class LateralContributionList(SQLSubModel):
return True
def len(self, lst):
return len(self._tabs[lst])
def get_tab(self, lst):
return self._tabs[lst].copy()
def get(self, lst, row):
return self._tabs[lst][row]
def set(self, lst, row, new):
self._tabs[lst][row] = new
self._status.modified()
def new(self, lst, index):
n = NotDefined(status=self._status)
self._tabs[lst].insert(index, n)
self._status.modified()
return n
def insert(self, lst, index, new):
self._tabs[lst].insert(index, new)
self._status.modified()
def delete(self, lst, bcs):
for bc in bcs:
self._tabs[lst].remove(bc)
self._status.modified()
def delete_i(self, lst, indexes):
bcs = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._tabs[lst])
)
)
)
self.delete(lst, bcs)
def sort(self, lst, reverse=False, key=None):
self._tabs[lst].sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, lst, index):
if index < len(self._tabs[lst]):
next = index - 1
l = self._tabs[lst]
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, lst, index):
if index >= 0:
prev = index + 1
l = self._tabs[lst]
l[index], l[prev] = l[prev], l[index]
self._status.modified()
def __copy__(self):
new = LateralContributionList()
+54
View File
@@ -0,0 +1,54 @@
# Results.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
import numpy as np
from copy import deepcopy
from datetime import datetime
from Model.Results.River.River import River
logger = logging.getLogger()
class Results(object):
def __init__(self, study = None):
self._study = study
self._river = River(self._study)
self._meta_data = {
# Keep results creation date
"creation_date": datetime.now(),
}
@property
def date(self):
date = self._meta_data["creation_date"]
return f"{date.isoformat(sep=' ')}"
@property
def river(self):
return self._river
@property
def study(self):
return self._study
def set(self, key, value):
self._meta_data[key] = value
def get(self, key):
return self._meta_data[key]
+119
View File
@@ -0,0 +1,119 @@
# River.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
from datetime import datetime
logger = logging.getLogger()
class Profile(object):
def __init__(self, profile, study):
self._study = study
self._profile = profile # Source profile in the study
self._data = {} # Dict of dict {<ts>: {<key>: <value>, ...}, ...}
def __len__(self):
return len(self._data)
@property
def name(self):
return self._profile.name
@property
def kp(self):
return self._profile.kp
@property
def geometry(self):
return self._profile
def set(self, timestamp, key, data):
if timestamp not in self._data:
self._data[timestamp] = {}
self._data[timestamp][key] = data
def get_ts(self, timestamp):
return self._data[timestamp]
def get_key(self, key):
return list(
map(lambda ts: self._data[ts][key], self._data)
)
def get_ts_key(self, timestamp, key):
return self._data[timestamp][key]
class Reach(object):
def __init__(self, reach, study):
self._study = study
self._reach = reach # Source reach in the study
self._profiles = list(
map(
lambda p: Profile(p, self._study),
reach.profiles
)
)
def __len__(self):
return len(self._profiles)
@property
def name(self):
return self._reach.name
@property
def geometry(self):
return self._reach
@property
def profiles(self):
return self._profiles.copy()
def profile(self, id):
return self._profiles[id]
def set(self, profile_id, timestamp, key, data):
self._profiles[profile_id].set(timestamp, key, data)
class River(object):
def __init__(self, study):
self._study = study
# Dict with timestamps as key
self._reachs = []
def __len__(self):
return len(self._reachs)
@property
def reachs(self):
return self._reachs.copy()
def reach(self, id):
return self._reachs[id]
def has_reach(self, id):
return 0 <= id < len(self._reachs)
def add(self, reach_id):
reachs = self._study.river.enable_edges()
new = Reach(reachs[reach_id].reach, self._study)
self._reachs.append(new)
return new
+32 -9
View File
@@ -16,7 +16,9 @@
# -*- coding: utf-8 -*-
from Model.DB import SQLSubModel
from tools import flatten
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Network.Node import Node
from Model.Network.Edge import Edge
@@ -31,6 +33,7 @@ from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict
from Model.Stricklers.StricklersList import StricklersList
from Model.Friction.FrictionList import FrictionList
from Model.SolverParameters.SolverParametersList import SolverParametersList
from Model.SedimentLayer.SedimentLayerList import SedimentLayerList
from Solver.Solvers import solver_type_list
@@ -208,6 +211,7 @@ class River(Graph, SQLSubModel):
InitialConditionsDict,
StricklersList,
SolverParametersList,
SedimentLayerList,
]
def __init__(self, status=None):
@@ -223,6 +227,7 @@ class River(Graph, SQLSubModel):
self._initial_conditions = InitialConditionsDict(status=self._status)
self._stricklers = StricklersList(status=self._status)
self._parameters = {}
self._sediment_layers = SedimentLayerList(status=self._status)
@classmethod
def _sql_create(cls, execute):
@@ -246,6 +251,14 @@ class River(Graph, SQLSubModel):
)
data["stricklers"] = new._stricklers
# Initial conditions
new._sediment_layers = SedimentLayerList._sql_load(
execute,
data
)
data["sediment_layers_list"] = new._sediment_layers
# Network
new._nodes = RiverNode._sql_load(
execute,
@@ -289,6 +302,7 @@ class River(Graph, SQLSubModel):
objs.append(self._boundary_condition)
objs.append(self._initial_conditions)
objs.append(self._lateral_contribution)
objs.append(self._sediment_layers)
objs.append(self._stricklers)
for solver in self._parameters:
@@ -297,14 +311,6 @@ class River(Graph, SQLSubModel):
self._save_submodel(execute, objs, data)
return True
@property
def reach(self):
return self._reach
@property
def frictions(self):
return self._frictions
@property
def boundary_condition(self):
return self._boundary_condition
@@ -317,6 +323,10 @@ class River(Graph, SQLSubModel):
def initial_conditions(self):
return self._initial_conditions
@property
def sediment_layers(self):
return self._sediment_layers
@property
def stricklers(self):
return self._stricklers
@@ -366,3 +376,16 @@ class River(Graph, SQLSubModel):
def set_current_reach(self, reach):
self._current_reach = reach
def has_sediment(self):
has = len(self._sediment_layers) != 0
has &= any(
filter(
lambda p: p.sl != None,
flatten(
map(lambda e: e.reach.profiles, self.edges())
)
)
)
return has
+1 -1
View File
@@ -33,5 +33,5 @@ class SavedStatus(object):
self._saved = True
def modified(self):
logger.debug("model status set as modified")
# logger.debug("model status set as modified")
self._saved = False
+339
View File
@@ -0,0 +1,339 @@
# -*- coding: utf-8 -*-
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
class Layer(SQLSubModel):
_sub_classes = []
_id_cnt = 0
def __init__(self,
id:int = -1, name:str = "",
type = "",
height = 0.0, d50 = 0.0, sigma = 0.0,
critical_constraint = 0.0,
sl = None, status = None):
super(Layer, self).__init__()
self._status = status
self._name = name
self._type = type
self._height = height
self._d50 = d50
self._sigma = sigma
self._critical_constraint = critical_constraint
if id == -1:
self.id = Layer._id_cnt
else:
self.id = id
Layer._id_cnt = max(id, Layer._id_cnt+1)
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
@property
def type(self):
return self._type
@type.setter
def type(self, type):
self._type = type
@property
def height(self):
return self._height
@height.setter
def height(self, height):
self._height = float(height)
@property
def d50(self):
return self._d50
@d50.setter
def d50(self, d50):
self._d50 = float(d50)
@property
def sigma(self):
return self._sigma
@sigma.setter
def sigma(self, sigma):
self._sigma = float(sigma)
@property
def critical_constraint(self):
return self._critical_constraint
@critical_constraint.setter
def critical_constraint(self, critical_constraint):
self._critical_constraint = float(critical_constraint)
@classmethod
def _sql_create(cls, execute):
execute("""
CREATE TABLE sedimentary_layer_layer(
id INTEGER NOT NULL PRIMARY KEY,
ind INTEGER NOT NULL,
name TEXT NOT NULL,
type TEXT NOT NULL,
height REAL NOT NULL,
d50 REAL NOT NULL,
sigma REAL NOT NULL,
critical_constraint REAL NOT NULL,
sl INTEGER,
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 2:
cls._sql_create(execute)
return True
@classmethod
def _sql_load(cls, execute, data = None):
new = []
sl = data["sl"]
table = execute(
"SELECT id, ind, name, type, height, d50, sigma, critical_constraint " +
"FROM sedimentary_layer_layer " +
f"WHERE sl = {sl}"
)
for _ in table:
new.append(None)
for row in table:
ind = row[1]
layer = cls(
id = row[0], name = row[2],
type = row[3], height = row[4],
d50 = row[5], sigma = row[6],
critical_constraint = row[7],
sl = sl, status = data['status']
)
new[ind] = layer
return new
def _sql_save(self, execute, data = None):
ind = data["ind"]
sl = data["sl"]
sql = (
"INSERT INTO " +
"sedimentary_layer_layer(id, ind, name, type, height, d50, sigma, critical_constraint, sl) "+
"VALUES (" +
f"{self.id}, {ind}, '{self._sql_format(self._name)}', " +
f"'{self._sql_format(self._type)}', {self._height}, " +
f"{self._d50}, {self._sigma}, {self._critical_constraint}, " +
f"{sl.id}" +
")"
)
execute(sql)
return True
class SedimentLayer(SQLSubModel):
_sub_classes = [Layer]
_id_cnt = 0
def __init__(self, id:int = -1,
name:str = "", comment:str = "",
status = None):
super(SedimentLayer, self).__init__()
self._status = status
self._name = name
self._comment = comment
self._layers = []
if id == -1:
self.id = SedimentLayer._id_cnt
else:
self.id = id
SedimentLayer._id_cnt = max(id, SedimentLayer._id_cnt+1)
def __str__(self):
s = f"{self.name} ({len(self)})"
if self.comment != "":
s += f" - {self.comment}"
return s
def __len__(self):
return len(self._layers)
@property
def layers(self):
return self._layers.copy()
def height(self):
return list(
map(lambda l: l.height, self._layers)
)
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
def names(self):
return list(
map(lambda l: l.name, self._layers)
)
@property
def comment(self):
return self._comment
@comment.setter
def comment(self, comment):
self._comment = comment
@classmethod
def _sql_create(cls, execute):
execute("""
CREATE TABLE sedimentary_layer(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
comment TEXT NOT NULL
)
""")
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 2:
cls._sql_create(execute)
return True
@classmethod
def _sql_load(cls, execute, data = None):
new = []
table = execute(
"SELECT id, name, comment " +
"FROM sedimentary_layer "
)
for row in table:
sl = cls(
id = row[0],
name = row[1],
comment = row[2],
status = data['status']
)
data["sl"] = sl.id
sl._layers = Layer._sql_load(execute, data)
new.append(sl)
return new
def _sql_save(self, execute, data = None):
if data is None:
data = {}
sql = (
"INSERT INTO sedimentary_layer (id, name, comment) " +
f"VALUES ({self.id}, '{self._sql_format(self._name)}', " +
f"'{self._sql_format(self._comment)}')"
)
execute(sql)
data["sl"] = self
ind = 0
for l in self._layers:
data["ind"] = ind
l._sql_save(execute, data)
ind += 1
return True
def get(self, index):
return self._layers[index]
def set(self, index, new):
self._layers[index] = new
self._status.modified()
def insert(self, index, new):
self._layers.insert(index, new)
self._status.modified()
def new(self, index):
n = Layer(sl=self, status = self._status)
self.insert(index, n)
self._status.modified()
return n
def delete(self, els):
for el in els:
self._layers.remove(el)
self._status.modified()
def delete_i(self, indexes):
els = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._layers)
)
)
)
self.delete(els)
def move_up(self, index):
if index >= 0:
next = index - 1
l = self._layers
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, index):
if index + 1 < len(self._layers):
prev = index + 1
l = self._layers
l[index], l[prev] = l[prev], l[index]
self._status.modified()
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
from tools import trace, timer
from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrList import PamhyrModelList
from Model.SedimentLayer.SedimentLayer import SedimentLayer
class SedimentLayerList(PamhyrModelList):
_sub_classes = [SedimentLayer]
@classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data["status"])
new._lst = SedimentLayer._sql_load(execute, data)
return new
def _sql_save(self, execute, data = None):
ok = True
# Delete previous data
execute("DELETE FROM sedimentary_layer")
execute("DELETE FROM sedimentary_layer_layer")
for sl in self._lst:
ok &= sl._sql_save(execute, data)
return ok
@property
def sediment_layers(self):
return self.lst
def new(self, index):
n = SedimentLayer(status = self._status)
self.insert(index, n)
self._status.modified()
return n
@@ -20,7 +20,7 @@ from copy import copy
from tools import trace, timer
from Solver.Solvers import solver_type_list
from Model.DB import SQLSubModel
from Model.Tools.PamhyrList import PamhyrModelList
class Parameter():
def __init__(self,
@@ -63,13 +63,13 @@ class Parameter():
return new
class SolverParametersList(SQLSubModel):
class SolverParametersList(PamhyrModelList):
def __init__(self, solver_type = None, status = None):
super(SolverParametersList, self).__init__()
self._status = status
self._solver = solver_type
self._parameters = list(
self._lst = list(
map(
lambda t: Parameter.from_tuple(t, self._status),
solver_type.default_parameters()
@@ -93,6 +93,66 @@ class SolverParametersList(SQLSubModel):
@classmethod
def _sql_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 3:
execute(f"UPDATE solver_parameter SET name='mage_implicitation' WHERE name='mage_implication'")
execute(f"UPDATE solver_parameter SET name='mage_iteration_type' WHERE name='mage_iter_type'")
if int(release) < 4:
solvers = execute("SELECT DISTINCT solver FROM solver_parameter")
for solver in solvers:
if solver != "mage8":
continue
data = execute("SELECT ind, name, value " +
"FROM solver_parameter "+
f"WHERE solver = '{solver}' " +
"ORDER BY ind ACS")
execute(
"DELETE FROM solver_parameter " +
f"WHERE solver = '{solver}'"
)
insert = lambda name, value, ind: execute(
"INSERT INTO " +
"solver_parameter(ind, name, value, solver) "+
"VALUES (" +
f"{ind}, " +
f"'{cls._sql_format(name)}', " +
f"'{cls._sql_format(value)}', " +
f"'{cls._sql_format(solver)}'" +
")"
)
ind = 0
lst = []
for v in data:
insert(v[0], v[1], ind)
ind += 1
new = [
("mage_sediment_masse_volumique", "2650.0"),
("mage_sediment_angle_repos", "40.0"),
("mage_sediment_porosity", "0.40"),
("mage_distance_han", "0.0"),
("mage_distance_chargement_d50", "100.0"),
("mage_distance_chargement_sigma", "100.0"),
("mage_methode_modification_geometrie", "1"),
("mage_shields_critique", "1"),
("mage_shields_correction", "1"),
("mage_capacite_solide", "1"),
("mage_pas_de_temps_charriage", "1"),
("mage_facteur_multiplicateur", "1.0"),
]
for v in new:
insert(v[0], v[1], ind)
ind += 1
return cls._update_submodel(execute, version)
@classmethod
@@ -134,7 +194,7 @@ class SolverParametersList(SQLSubModel):
)
ind = 0
for param in self._parameters:
for param in self._lst:
sql = (
"INSERT INTO " +
"solver_parameter(ind, name, value, solver) "+
@@ -150,28 +210,36 @@ class SolverParametersList(SQLSubModel):
return True
def __len__(self):
return len(self._parameters)
@property
def parameters(self):
return self._parameters.copy()
return self.lst
def get(self, index):
return self._parameters[index]
return self._lst[index]
def get_by_key(self, key):
try:
return next(
filter(
lambda p: p["name"] == key,
self._lst
)
)["value"]
except:
return None
def set(self, index, new):
self._parameters[index] = new
self._lst[index] = new
self._status.modified()
def set_value(self, key, value):
for p in self._parameters:
for p in self._lst:
if p["name"] == key:
p["value"] = value
self._status.modified()
return
self._parameters.append(
self._lst.append(
Parameter(
name = key,
value = value,
@@ -182,47 +250,6 @@ class SolverParametersList(SQLSubModel):
def new(self, index):
n = Parameter(status = self._status)
self._parameters.insert(index, n)
self._lst.insert(index, n)
self._status.modified()
return n
def insert(self, index, new):
self._parameters.insert(index, new)
self._status.modified()
def delete(self, parameters):
for parameter in parameters:
self._parameters.remove(parameter)
self._status.modified()
def delete_i(self, indexes):
parameters = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._parameters)
)
)
)
self.delete(parameters)
def sort(self, reverse=False, key=None):
self._parameters.sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, index):
if index < len(self._parameters):
next = index - 1
l = self._parameters
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
l = self._parameters
l[index], l[prev] = l[prev], l[index]
self._status.modified()
+2 -2
View File
@@ -18,7 +18,7 @@
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
class Stricklers(SQLSubModel):
_id_cnt = 0
@@ -38,7 +38,7 @@ class Stricklers(SQLSubModel):
else:
self.id = id
Stricklers._id_cnt = max(Stricklers._id_cnt + 1, self.id)
Stricklers._id_cnt = max(Stricklers._id_cnt + 1, id + 1)
self._name = name
self._comment = comment
+8 -49
View File
@@ -18,22 +18,15 @@
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Saved import SavedStatus
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Stricklers.Stricklers import Stricklers
class StricklersList(SQLSubModel):
class StricklersList(PamhyrModelList):
_sub_classes = [
Stricklers,
]
def __init__(self, status = None):
if status is None:
status = SavedStatus()
self._status = status
self._stricks = []
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@@ -46,7 +39,7 @@ class StricklersList(SQLSubModel):
def _sql_load(cls, execute, data = None):
new = cls(status = data["status"])
new._stricks = Stricklers._sql_load(
new._lst = Stricklers._sql_load(
execute,
data = data
)
@@ -56,60 +49,26 @@ class StricklersList(SQLSubModel):
def _sql_save(self, execute, data = None):
execute("DELETE FROM stricklers")
objs = self._stricks
objs = self._lst
return self._save_submodel(execute, objs, data)
def __len__(self):
return len(self._stricks)
@property
def stricklers(self):
return self._stricks.copy()
return self.lst
def get(self, index):
if 0 <= index < len(self._stricks):
return self._stricks[index]
return None
def insert(self, index, strick):
self._stricks.insert(index, strick)
self._status.modified()
def add(self, index):
def new(self, index):
s = Stricklers(status = self._status)
self.insert(index, s)
return s
def delete(self, stricks):
self._stricks = list(
filter(
lambda s: s not in stricks,
self._stricks
)
)
self._status.modified()
def delete_i(self, indexes):
stricks = set(
map(
lambda e: e[1],
filter(
lambda e: e[0] in indexes,
enumerate(self._stricks)
)
)
)
self.delete(stricks)
@timer
def sort(self, reverse:bool = False, key=None):
f = lambda st: st.name
if key is not None:
f = key
self._stricks = sorted(
self._stricks,
self._lst = sorted(
self._lst,
key = f,
reverse = reverse,
)
+2 -2
View File
@@ -23,7 +23,7 @@ from datetime import datetime
from tools import timer, timestamp
from Model.DB import SQLModel
from Model.Tools.PamhyrDB import SQLModel
from Model.Saved import SavedStatus
from Model.Serializable import Serializable
from Model.Except import NotImplementedMethodeError
@@ -40,7 +40,7 @@ class Study(SQLModel):
def __init__(self, filename = None, init_new = True):
# Metadata
self._version = "0.0.1"
self._version = "0.0.4"
self.creation_date = datetime.now()
self.last_modification_date = datetime.now()
self.last_save_date = datetime.now()
@@ -1,4 +1,4 @@
# DB.py -- Pamhyr abstract model database classes
# PamhyrDB.py -- Pamhyr abstract model database classes
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
+74
View File
@@ -0,0 +1,74 @@
# PamhyrDict.py -- Pamhyr abstract dict for model classes
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
class PamhyrModelDict(SQLSubModel):
_sub_classes = []
def __init__(self, status = None):
super(PamhyrModelDict, self).__init__()
self._status = status
self._dict = {}
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _sql_load(cls, execute, data = None):
raise NotImplementedMethodeError(cls, cls._sql_load)
def _sql_save(self, execute, data = None):
raise NotImplementedMethodeError(self, self._sql_save)
def __len__(self):
return len(self._dict)
def is_defined(self, key):
return key in self._dict
def set(self, key, new):
self._dict[key] = new
self._status.modified()
def get(self, key):
if key in self._dict:
return self._dict[key]
new = self.new(key)
self.set(key, new)
return new
def new(self, key):
raise NotImplementedMethodeError(self, self.new)
+229
View File
@@ -0,0 +1,229 @@
# PamhyrList.py -- Pamhyr Abstract List object for the Model
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
class PamhyrModelList(SQLSubModel):
_sub_classes = [
]
def __init__(self, status = None):
super(PamhyrModelList, self).__init__()
self._status = status
self._lst = []
#######
# SQL #
#######
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _sql_load(cls, execute, data = None):
raise NotImplementedMethodeError(cls, cls._sql_load)
def _sql_save(self, execute, data = None):
raise NotImplementedMethodeError(self, self._sql_save)
################
# MODEL METHOD #
################
def __len__(self):
return len(self._lst)
@property
def lst(self):
return self._lst.copy()
def get(self, row):
return self._lst[row]
def set(self, row, new):
self._lst[row] = new
self._status.modified()
def new(self, index):
"""Create new elements and add it to list
Args:
index: The index of new elements
Returns:
The new elements
"""
raise NotImplementedMethodeError(self, self.new)
def insert(self, index, new):
self._lst.insert(index, new)
self._status.modified()
def delete(self, lst):
for el in lst:
self._lst.remove(el)
self._status.modified()
def delete_i(self, indexes):
lst = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._lst)
)
)
)
self.delete(lst)
def sort(self, reverse=False, key=None):
self._lst.sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, index):
if index < len(self._lst):
next = index - 1
l = self._lst
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
l = self._lst
l[index], l[prev] = l[prev], l[index]
self._status.modified()
class PamhyrModelListWithTab(SQLSubModel):
_tabs_list = []
_sub_classes = [
]
def __init__(self, status = None):
super(PamhyrModelListWithTab, self).__init__()
self._status = status
self._tabs = {}
for tab in self._tabs_list:
self._tabs[tab] = []
#######
# SQL #
#######
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _sql_load(cls, execute, data = None):
raise NotImplementedMethodeError(cls, cls._sql_load)
def _sql_save(self, execute, data = None):
raise NotImplementedMethodeError(self, self._sql_save)
################
# MODEL METHOD #
################
def len(self, lst):
return len(self._tabs[lst])
def get_tab(self, lst):
return self._tabs[lst].copy()
def get(self, lst, row):
return self._tabs[lst][row]
def set(self, lst, row, new):
self._tabs[lst][row] = new
self._status.modified()
def new(self, lst, index):
"""Create new elements and add it to list
Args:
lst: The tab name
index: The index of new elements
Returns:
The new elements
"""
raise NotImplementedMethodeError(self, self.new)
def insert(self, lst, index, new):
self._tabs[lst].insert(index, new)
self._status.modified()
def delete(self, lst, els):
for el in els:
self._tabs[lst].remove(el)
self._status.modified()
def delete_i(self, lst, indexes):
els = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._tabs[lst])
)
)
)
self.delete(lst, els)
def sort(self, lst, reverse=False, key=None):
self._tabs[lst].sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, lst, index):
if index < len(self._tabs[lst]):
next = index - 1
l = self._tabs[lst]
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, lst, index):
if index >= 0:
prev = index + 1
l = self._tabs[lst]
l[index], l[prev] = l[prev], l[index]
self._status.modified()
+36
View File
@@ -0,0 +1,36 @@
# AScript.py -- Pamhyr abstract script class
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from Model.Except import NotImplementedMethodeError
class AScript(object):
name = ""
description = ""
def __init__(self, app, conf, args):
super(AScript, self).__init__()
self._app = app
self._conf = conf
self._args = args.copy()
def usage(self):
raise NotImplementedMethodeError(self, self.usage)
def run(self):
raise NotImplementedMethodeError(self, self.run)
+34
View File
@@ -0,0 +1,34 @@
# Hello.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from Scripts.AScript import AScript
logger = logging.getLogger()
class ScriptHello(AScript):
name = "Hello"
description = "Display hello"
def usage(self):
logger.info(f"Usage : {self._args[0]} hello")
def run(self):
logger.info("Hello from Pamhyr2 script !")
return 0
+115
View File
@@ -0,0 +1,115 @@
# P3DST.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import sys
import logging
from matplotlib import pyplot as plt
from numpy import mean
from Scripts.AScript import AScript
from Model.Saved import SavedStatus
from Model.Geometry.Reach import Reach
logger = logging.getLogger()
class Script3DST(AScript):
name = "3DST"
description = "Display a 3D plot of a river reach from ST file"
def usage(self):
logger.info(f"Usage : {self._args[0]} 3DST <INPUT_ST_FILE>")
def set_axes_equal(self, ax):
"""Make axes of 3D plot have equal scale
Make axes of 3D plot have equal scale so that spheres appear
as spheres, cubes as cubes, etc.. This is one possible
solution to Matplotlib's ax.set_aspect('equal') and
ax.axis('equal') not working for 3D.
Args:
ax: a matplotlib axis, e.g., as output from plt.gca()
Returns:
The input axis
"""
x_limits = ax.get_xlim3d()
y_limits = ax.get_ylim3d()
z_limits = ax.get_zlim3d()
x_range = abs(x_limits[1] - x_limits[0])
x_middle = mean(x_limits)
y_range = abs(y_limits[1] - y_limits[0])
y_middle = mean(y_limits)
z_range = abs(z_limits[1] - z_limits[0])
z_middle = mean(z_limits)
# The plot bounding box is a sphere in the sense of the infinity
# norm, hence I call half the max range the plot radius.
plot_radius = 0.5*max([x_range, y_range, z_range])
ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])
return ax
def run(self):
try:
st_file = self._args[2]
logger.info(f"Use ST file: {st_file}")
except Exception as e:
logger.error(f"Arguments parcing: {str(e)}")
return 1
try:
status = SavedStatus()
my_reach = Reach(status = status)
my_reach.import_geometry(st_file)
my_reach.compute_guidelines()
ax = plt.figure().add_subplot(projection="3d")
logger.info(my_reach.get_x())
for x, y, z in zip(
my_reach.get_x(),
my_reach.get_y(),
my_reach.get_z()
):
ax.plot(x, y, z, color='r', lw=1.)
for x, y, z in zip(
my_reach.get_guidelines_x(),
my_reach.get_guidelines_y(),
my_reach.get_guidelines_z()
):
ax.plot(x, y, z, color='b', lw=1.)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.tight_layout()
self.set_axes_equal(ax)
plt.show()
return 0
except Exception as e:
logger.error(str(e))
return 1
-68
View File
@@ -1,68 +0,0 @@
# plot_3DST.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
# a lancer depuis src
import sys
from matplotlib import pyplot as plt
from Model.Geometry.Reach import Reach
from numpy import mean
def set_axes_equal(ax):
'''Make axes of 3D plot have equal scale so that spheres appear as spheres,
cubes as cubes, etc.. This is one possible solution to Matplotlib's
ax.set_aspect('equal') and ax.axis('equal') not working for 3D.
Input
ax: a matplotlib axis, e.g., as output from plt.gca().
'''
x_limits = ax.get_xlim3d()
y_limits = ax.get_ylim3d()
z_limits = ax.get_zlim3d()
x_range = abs(x_limits[1] - x_limits[0])
x_middle = mean(x_limits)
y_range = abs(y_limits[1] - y_limits[0])
y_middle = mean(y_limits)
z_range = abs(z_limits[1] - z_limits[0])
z_middle = mean(z_limits)
# The plot bounding box is a sphere in the sense of the infinity
# norm, hence I call half the max range the plot radius.
plot_radius = 0.5*max([x_range, y_range, z_range])
ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])
st_file = sys.argv[1]
my_reach = Reach(None)
my_reach.import_geometry(st_file)
my_reach.compute_guidelines()
ax = plt.figure().add_subplot(projection="3d")
for x, y, z in zip(my_reach.get_x(), my_reach.get_y(), my_reach.get_z()):
ax.plot(x, y, z, color='r', lw=1.)
for x, y, z in zip(my_reach.get_guidelines_x(), my_reach.get_guidelines_y(), my_reach.get_guidelines_z()):
ax.plot(x, y, z, color='b', lw=1.)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.tight_layout()
set_axes_equal(ax)
plt.show()
+65 -23
View File
@@ -19,7 +19,10 @@
import os
import logging
from tools import timer
try:
# Installation allow Unix-like signal
from signal import SIGTERM, SIGSTOP, SIGCONT
_signal = True
except:
@@ -29,6 +32,9 @@ from enum import Enum
from Model.Except import NotImplementedMethodeError
from Model.Results.Results import Results
from Model.Results.River.River import River, Reach, Profile
logger = logging.getLogger()
class STATUS(Enum):
@@ -62,6 +68,9 @@ class AbstractSolver(object):
self._process = None
self._output = None
# Last study running
self._study = None
def __str__(self):
return f"{self._name} : {self._type} : {self._description}"
@@ -81,6 +90,7 @@ class AbstractSolver(object):
("all_init_time", "000:00:00:00"),
("all_final_time", "999:99:00:00"),
("all_timestep", "300.0"),
("all_command_line_arguments", ""),
]
return lst
@@ -148,6 +158,17 @@ class AbstractSolver(object):
def export(self, study, repertory, qlog = None):
raise NotImplementedMethodeError(self, self.export)
def cmd_args(self, study):
"""Return solver command line arguments list
Returns:
Command line arguments list
"""
params = study.river.get_params(self.type)
args = params.get_by_key("all_command_line_arguments")
return args.split(" ")
def input_param(self):
"""Return input command line parameter(s)
@@ -164,6 +185,15 @@ class AbstractSolver(object):
"""
raise NotImplementedMethodeError(self, self.log_file)
###########
# RESULTS #
###########
@timer
def results(self, study, repertory, qlog = None):
results = Results(study = study)
return results
#######
# Run #
#######
@@ -172,11 +202,11 @@ class AbstractSolver(object):
return os.path.abspath(
os.path.join(
os.path.dirname(__file__),
".."
"..", ".."
)
)
def _format_command(self, cmd, path = ""):
def _format_command(self, study, cmd, path = ""):
"""Format command line
Args:
@@ -187,11 +217,11 @@ class AbstractSolver(object):
The executable and list of arguments
"""
# HACK: Works in most case... Trust me i'm an engineer
cmd = cmd.replace("@install_dir", self._install_dir())
cmd = cmd.replace("@path", path.replace(" ", "\ "))
cmd = cmd.replace("@input", self.input_param())
cmd = cmd.replace("@dir", self._process.workingDirectory())
cmd = cmd.replace("@args", " ".join(self.cmd_args(study)))
logger.debug(f"! {cmd}")
@@ -199,23 +229,33 @@ class AbstractSolver(object):
# Command line executable path is between " char
cmd = cmd.split("\"")
exe = cmd[1].replace("\ ", " ")
args = "\"".join(cmd[2:]).split(" ")[1:]
args = list(
filter(
lambda s: s != "",
"\"".join(cmd[2:]).split(" ")[1:]
)
)
else:
# We suppose the command line executable path as no space char
cmd = cmd.replace("\ ", "&_&").split(" ")
exe = cmd[0].replace("&_&", " ")
args = list(map(lambda s: s.replace("&_&", "\ "), cmd[1:]))
args = list(
filter(
lambda s: s != "",
map(lambda s: s.replace("&_&", "\ "), cmd[1:])
)
)
logger.info(f"! {exe} {args}")
return exe, args
def run_input_data_fomater(self):
def run_input_data_fomater(self, study):
if self._cmd_input == "":
self._run_next()
self._run_next(study)
return True
cmd = self._cmd_input
exe, args = self._format_command(cmd, self._path_input)
exe, args = self._format_command(study, cmd, self._path_input)
if not os.path.exists(exe):
error = f"[ERROR] Path {exe} do not exists"
@@ -228,13 +268,13 @@ class AbstractSolver(object):
return True
def run_solver(self):
def run_solver(self, study):
if self._cmd_solver == "":
self._run_next()
self._run_next(study)
return True
cmd = self._cmd_solver
exe, args = self._format_command(cmd, self._path_solver)
exe, args = self._format_command(study, cmd, self._path_solver)
if not os.path.exists(exe):
error = f"[ERROR] Path {exe} do not exists"
@@ -248,13 +288,13 @@ class AbstractSolver(object):
self._status = STATUS.RUNNING
return True
def run_output_data_fomater(self):
def run_output_data_fomater(self, study):
if self._cmd_output == "":
self._run_next()
self._run_next(study)
return True
cmd = self._cmd_output
exe, args = self._format_command(cmd, self._path_output)
exe, args = self._format_command(study, cmd, self._path_output)
if not os.path.exists(exe):
error = f"[ERROR] Path {exe} do not exists"
@@ -273,29 +313,29 @@ class AbstractSolver(object):
for x in s.split('\n'):
self._output.put(x)
def _run_next(self):
def _run_next(self, study):
self._step += 1
if self._step < len(self._runs):
res = self._runs[self._step]()
res = self._runs[self._step](study)
if res is not True:
self._output.put(res)
else:
self._status = STATUS.STOPED
def _finished(self, exit_code, exit_status):
def _finished(self, study, exit_code, exit_status):
if self._output is not None:
self._output.put(exit_code)
self._run_next()
self._run_next(study)
def run(self, process = None, output_queue = None):
def run(self, study, process = None, output_queue = None):
if process is not None:
self._process = process
if output_queue is not None:
self._output = output_queue
self._process.readyRead.connect(self._data_ready)
self._process.finished.connect(self._finished)
self._process.finished.connect(lambda c, s: self._finished(study, c, s))
self._runs = [
self.run_input_data_fomater,
@@ -304,7 +344,7 @@ class AbstractSolver(object):
]
self._step = 0
# Run first step
res = self._runs[0]()
res = self._runs[0](study)
if res is not True:
self._output.put(res)
@@ -316,14 +356,15 @@ class AbstractSolver(object):
self._status = STATUS.STOPED
return True
def start(self, process = None):
def start(self, study, process = None):
if _signal:
# Solver is PAUSED, so continue execution
if self._status == STATUS.PAUSED:
os.kill(self._process.pid(), SIGCONT)
self._status = STATUS.RUNNING
return True
self.run(process)
self.run(study, process)
return True
def pause(self):
@@ -331,6 +372,7 @@ class AbstractSolver(object):
if self._process is None:
return False
# Send SIGSTOP to PAUSED solver
os.kill(self._process.pid(), SIGSTOP)
self._status = STATUS.PAUSED
return True
+489 -53
View File
@@ -17,12 +17,19 @@
# -*- coding: utf-8 -*-
import os
import logging
import numpy as np
from tools import timer
from Solver.ASolver import AbstractSolver
from Checker.Mage import MageNetworkGraphChecker
from Model.Results.Results import Results
from Model.Results.River.River import River, Reach, Profile
logger = logging.getLogger()
def mage_file_open(filepath, mode):
f = open(filepath, mode)
@@ -54,11 +61,11 @@ class Mage(AbstractSolver):
("mage_timestep_tra", "3600"),
("mage_timestep_bin", "0"),
# ("mage_timestep_melissa", "0"),
("mage_implication", "0.70"),
("mage_implicitation", "0.70"),
("mage_continuity_discretization", "S"),
("mage_qsj_discretization", "B"),
("mage_stop_criterion_iterations", "R"),
("mage_iter_type", "0"),
("mage_iteration_type", "0"),
("mage_smooth_coef", "0"),
("mage_cfl_max", "-1."),
("mage_min_height", "0.1"),
@@ -90,19 +97,31 @@ class Mage(AbstractSolver):
# Export #
##########
def cmd_args(self, study):
l = super(Mage, self).cmd_args(study)
l.append("-r")
return l
def input_param(self):
return "0.REP"
name = self._study.name
return f"{name}.REP"
def log_file(self):
return "0.TRA"
name = self._study.name
return f"{name}.TRA"
@timer
def _export_ST(self, study, repertory, qlog):
def _export_ST(self, study, repertory, qlog, name="0"):
files = []
if qlog is not None:
qlog.put("Export ST file")
os.makedirs(os.path.join(repertory, "net"), exist_ok=True)
gra_file = f"{name}.GRA"
# Write header
edges = study.river.edges()
edges = list(
@@ -117,33 +136,57 @@ class Mage(AbstractSolver):
if edge._name == "":
name = f"Reach_{edge.id}"
with mage_file_open(os.path.join(repertory, f"{name}.ST"), "w+") as f:
files.append(f"{name}.ST")
with mage_file_open(os.path.join(repertory, "net", f"{name}.ST"), "w+") as f:
files.append(str(os.path.join("net", f"{name}.ST")))
cnt_num = 1
for profile in edge.reach.profiles:
num = f"{profile.num:>6}"
num = f"{cnt_num:>6}"
c1 = f"{profile.code1:>6}"
c2 = f"{profile.code2:>6}"
t = f"{len(profile.points):>6}"
kp = f"{profile.kp:>13.4f}"
name = profile.name
pname = profile.name
if profile.name == "":
pname = f"p{profile.id:>3}".replace(" ", "p")
name = f"{pname}"
f.write(f"{num}{c1}{c2}{t}{kp} {name}\n")
sediment = ""
if profile.sl is not None:
if not any(filter(lambda f: ".GRA" in f, files)):
files.append(gra_file)
nl = len(profile.sl)
sediment = f" {nl:>3}"
for l in profile.sl.layers:
sediment += f" {l.height:>10} {l.d50:>10} {l.sigma:>10} {l.critical_constraint:>10}"
f.write(f"{num}{c1}{c2}{t}{kp} {name} {sediment}\n")
cnt_num += 1
for point in profile.points:
x = f"{point.x:>13.4f}"
y = f"{point.y:>13.4f}"
z = f"{point.z:>13.4f}"
n = point.name
n = f"{point.name:<3}"
f.write(f"{x}{y}{z} {n}\n")
sediment = ""
prev = point.z
if point.sl is not None:
nl = len(point.sl)
sediment = f"{nl:>3}"
for l in point.sl.layers:
prev = round(prev - l.height, 5)
sediment += f" {prev:>10} {l.d50:>10} {l.sigma:>10} {l.critical_constraint:>10}"
f.write(f"{x}{y}{z} {n} {sediment}\n")
f.write(f" 999.9990 999.9990 999.9990\n")
return files
@timer
def _export_BC(self, t, bounds, repertory, qlog):
def _export_BC(self, t, bounds, repertory, qlog, name="0"):
files = []
if len(bounds) == 0:
@@ -152,8 +195,8 @@ class Mage(AbstractSolver):
if qlog is not None:
qlog.put(f"Export {t} file")
with mage_file_open(os.path.join(repertory, f"0.{t}"), "w+") as f:
files.append(f"0.{t}")
with mage_file_open(os.path.join(repertory, f"{name}.{t}"), "w+") as f:
files.append(f"{name}.{t}")
for bound in bounds:
name = f"{bound.node.id:3}".replace(" ", "x")
@@ -163,18 +206,25 @@ class Mage(AbstractSolver):
f.write(f"*{header[0]:>9}|{header[1]:>10}\n")
for d in bound.data:
f.write(f"{d[0]:10.3f}{d[1]:10.3f}\n")
v0 = d[0]
v1 = d[1]
if t in ["HYD", "QSO", "LIM"]:
v0 /= 60 # Convert first column to minute
f.write(f"{v0:10}{v1:10}\n")
return files
@timer
def _export_bound_cond(self, study, repertory, qlog):
def _export_bound_cond(self, study, repertory, qlog, name="0"):
files = []
lst = study.river.boundary_condition
AVA = []
HYD = []
LIM = []
QSO = []
for tab in ["liquid", "solid", "suspenssion"]:
for bound in lst.get_tab(tab):
@@ -184,22 +234,25 @@ class Mage(AbstractSolver):
HYD.append(bound)
elif bound.bctype == "TZ":
LIM.append(bound)
elif bound.bctype == "SL":
QSO.append(bound)
files = files + self._export_BC("AVA", AVA, repertory, qlog)
files = files + self._export_BC("HYD", HYD, repertory, qlog)
files = files + self._export_BC("LIM", LIM, repertory, qlog)
files = files + self._export_BC("AVA", AVA, repertory, qlog, name=name)
files = files + self._export_BC("HYD", HYD, repertory, qlog, name=name)
files = files + self._export_BC("LIM", LIM, repertory, qlog, name=name)
files = files + self._export_QSO(QSO, repertory, qlog, name=name)
return files
# @timer
# def _export_LC(self, lateral, repertory, qlog):
# def _export_LC(self, lateral, repertory, qlog, name="0"):
# files = []
# if qlog is not None:
# qlog.put(f"Export LAT file")
# with mage_file_open(os.path.join(repertory, f"0.LAT"), "w+") as f:
# files.append(f"0.LAT")
# with mage_file_open(os.path.join(repertory, f"{name}.LAT"), "w+") as f:
# files.append(f"{name}.LAT")
# name = f"{lateral.node.id:3}".replace(" ", "x")
# f.write(f"* {lateral.node.name} ({name}) {lateral.bctype}\n")
@@ -208,12 +261,12 @@ class Mage(AbstractSolver):
# f.write(f"*{header[0]:>9}|{header[1]:>10}\n")
# for d in lateral.data:
# f.write(f"{d[0]:10.3f}{d[1]:10.3f}\n")
# f.write(f"{d[0]:1{name}.3f}{d[1]:10.3f}\n")
# return files
# @timer
# def _export_lateral_contrib(self, study, repertory, qlog):
# def _export_lateral_contrib(self, study, repertory, qlog, name="0"):
# files = []
# lst = study.river.lateral_contribution
@@ -224,15 +277,15 @@ class Mage(AbstractSolver):
# return files
@timer
def _export_RUG(self, study, repertory, qlog):
def _export_RUG(self, study, repertory, qlog, name="0"):
files = []
if qlog is not None:
qlog.put("Export RUG file")
# Write header
with mage_file_open(os.path.join(repertory, "0.RUG"), "w+") as f:
files.append("0.RUG")
with mage_file_open(os.path.join(repertory, f"{name}.RUG"), "w+") as f:
files.append(f"{name}.RUG")
edges = study.river.edges()
edges = list(
@@ -265,14 +318,14 @@ class Mage(AbstractSolver):
return files
@timer
def _export_INI(self, study, repertory, qlog):
def _export_INI(self, study, repertory, qlog, name="0"):
files = []
if qlog is not None:
qlog.put("Export INI file")
# Write header
with mage_file_open(os.path.join(repertory, "0.INI"), "w+") as f:
with mage_file_open(os.path.join(repertory, f"{name}.INI"), "w+") as f:
has_ini = False
id = 1
reachs = study.river.edges()
@@ -310,33 +363,59 @@ class Mage(AbstractSolver):
id += 1
if has_ini:
files.append("0.INI")
files.append(f"{name}.INI")
return files
@timer
def _export_REP(self, study, repertory, files, qlog):
def _export_REP(self, study, repertory, files, qlog, name="0"):
if qlog is not None:
qlog.put("Export REP file")
# Write header
with mage_file_open(os.path.join(repertory, f"0.REP"), "w+") as f:
with mage_file_open(os.path.join(repertory, f"{name}.REP"), "w+") as f:
f.write("confirmation=non\n")
for file in files:
EXT = file.split('.')[1]
f.write(f"{EXT} {file}\n")
if EXT not in ["ST", "GRA"]:
f.write(f"{EXT} {file}\n")
f.write("* OUTPUT\n")
f.write(f"TRA 0.TRA\n")
f.write(f"BIN 0.BIN\n")
f.write(f"TRA {name}.TRA\n")
f.write(f"BIN {name}.BIN\n")
for file in files:
EXT = file.split('.')[1]
if EXT in ["GRA"]:
f.write(f"{EXT} {file}\n")
@timer
def export(self, study, repertory, qlog = None):
self._export_ST(study, repertory, qlog)
self._study = study
name = study.name.replace(" ", "_")
self._export_ST(study, repertory, qlog, name = name)
return True
###########
# RESULTS #
###########
def read_bin(self, study, repertory, results, qlog = None, name="0"):
return
@timer
def results(self, study, repertory, qlog = None, name="0"):
results = Results(study = study)
self.read_bin(study, repertory, results, qlog, name=name)
return results
##########
# MAGE 7 #
##########
@@ -376,21 +455,43 @@ class Mage8(Mage):
i = names.index("mage_precision_reduction_factor_Q")
lst.insert(i+1, ("mage_precision_reduction_factor_r", "1"))
# Mage parameter for sediment module (added in DB 0.0.4)
lst.append(("mage_sediment_masse_volumique", "2650.0"))
lst.append(("mage_sediment_angle_repos", "40.0"))
lst.append(("mage_sediment_porosity", "0.40"))
lst.append(("mage_distance_Han", "0.0"))
lst.append(("mage_distance_chargement_d50", "100.0"))
lst.append(("mage_distance_chargement_sigma", "100.0"))
lst.append(("mage_methode_modification_geometrie", "1"))
lst.append(("mage_shields_critique", "1"))
lst.append(("mage_shields_correction", "1"))
lst.append(("mage_capacite_solide", "1"))
lst.append(("mage_pas_de_temps_charriage", "1"))
lst.append(("mage_facteur_multiplicateur", "1.0"))
return lst
##########
# Export #
##########
def cmd_args(self, study):
l = super(Mage8, self).cmd_args(study)
if study.river.has_sediment():
l.append("-c=3")
return l
@timer
def _export_PAR(self, study, repertory, qlog = None):
def _export_PAR(self, study, repertory, qlog = None, name = "0"):
files = []
if qlog is not None:
qlog.put("Export PAR file")
with mage_file_open(os.path.join(repertory, "0.PAR"), "w+") as f:
files.append("0.PAR")
with mage_file_open(os.path.join(repertory, f"{name}.PAR"), "w+") as f:
files.append(f"{name}.PAR")
params = study.river.get_params(self.type).parameters
for p in params:
@@ -399,20 +500,25 @@ class Mage8(Mage):
.replace("mage_", "")
value = p.value
f.write(f"{name} {value}\n")
if name in ["command_line_arguments"]:
continue
if name is "mage_compute_reach_volume_balance":
value = "O" if value == "y" else "N"
f.write(f"{name} {value}\n")
return files
@timer
def _export_NET(self, study, repertory, qlog = None):
def _export_NET(self, study, repertory, qlog = None, name="0"):
files = []
if qlog is not None:
qlog.put("Export NET file")
with mage_file_open(os.path.join(repertory, "0.NET"), "w+") as f:
files.append("0.NET")
with mage_file_open(os.path.join(repertory, f"{name}.NET"), "w+") as f:
files.append(f"{name}.NET")
edges = study.river.edges()
edges = list(
@@ -431,22 +537,352 @@ class Mage8(Mage):
n1 = f"{e.node1.id:3}".replace(" ", "x")
n2 = f"{e.node2.id:3}".replace(" ", "x")
file = name + ".ST"
file = os.path.join("net", name + ".ST")
f.write(f"{id} {n1} {n2} {file}\n")
return files
@timer
def export(self, study, repertory, qlog = None):
def _export_QSO(self, bounds, repertory, qlog, name="0"):
files = []
self._export_ST(study, repertory, qlog)
files = files + self._export_PAR(study, repertory, qlog)
files = files + self._export_NET(study, repertory, qlog)
files = files + self._export_bound_cond(study, repertory, qlog)
files = files + self._export_RUG(study, repertory, qlog)
files = files + self._export_INI(study, repertory, qlog)
self._export_REP(study, repertory, files, qlog)
if len(bounds) == 0:
return files
if qlog is not None:
qlog.put(f"Export QSO file")
with mage_file_open(os.path.join(repertory, f"{name}.QSO"), "w+") as f:
files.append(f"{name}.QSO")
for bound in bounds:
# File header
name = f"{bound.node.id:3}".replace(" ", "x")
f.write(f"* {bound.node.name} ({name}) {bound.bctype}\n")
d50 = bound.d50
sigma = bound.sigma
if len(bound.data) == 0:
f.write(f"${name} {d50} {sigma} default\n")
else:
f.write(f"${name} {d50} {sigma}\n")
# Table header
header = bound.header
f.write(f"*{header[0]:>9}|{header[1]:>10}\n")
# Data
for d in bound.data:
f.write(f"{d[0]:10.3f}{d[1]:10.3f}\n")
return files
@timer
def export(self, study, repertory, qlog = None, name="0"):
self._study = study
name = study.name.replace(" ", "_")
files = []
files = self._export_ST(study, repertory, qlog, name=name)
files = files + self._export_PAR(study, repertory, qlog, name=name)
files = files + self._export_NET(study, repertory, qlog, name=name)
files = files + self._export_bound_cond(study, repertory, qlog, name=name)
files = files + self._export_RUG(study, repertory, qlog, name=name)
files = files + self._export_INI(study, repertory, qlog, name=name)
self._export_REP(study, repertory, files, qlog, name=name)
return True
###########
# RESULTS #
###########
@timer
def read_bin(self, study, repertory, results, qlog = None, name="0"):
fname = os.path.join(repertory, f"{name}.BIN")
logger.info(f"read_bin: Start reading '{fname}' ...")
with mage_file_open(fname, "r") as f:
newline = lambda: np.fromfile(f, dtype=np.int32, count=1)
endline = lambda: np.fromfile(f, dtype=np.int32, count=1)
read_int = lambda size: np.fromfile(f, dtype=np.int32, count=size)
read_float = lambda size: np.fromfile(f, dtype=np.float32, count=size)
read_float64 = lambda size: np.fromfile(f, dtype=np.float64, count=size)
# Meta data (1st line)
newline()
data = read_int(3)
nb_reach = data[0]
nb_profile = data[1]
mage_version = data[2]
logger.debug(f"read_bin: nb_reach = {nb_reach}")
logger.debug(f"read_bin: nb_profile = {nb_profile}")
logger.debug(f"read_bin: mage_version = {mage_version}")
if mage_version <= 80:
msg = (
"Read BIN files: " +
f"Possible incompatible mage version '{mage_version}', " +
"please check your solver configuration..."
)
logger.warning(msg)
if qlog is not None:
qlog.put("[WARNING] " + msg)
results.set("solver_version", f"Mage8 ({mage_version})")
results.set("nb_reach", f"{nb_reach}")
results.set("nb_profile", f"{nb_profile}")
endline()
# Reach information (2nd line)
newline()
reachs = []
iprofiles = {}
reach_offset = {}
data = read_int(2*nb_reach)
for i in range(nb_reach):
# Add results reach to reach list
r = results.river.add(i)
reachs.append(r)
# ID of first and last reach profiles
i1 = data[2*i] - 1
i2 = data[2*i+1] - 1
# Add profile id correspondance to reach
key = (i1, i2)
iprofiles[key] = r
# Profile ID offset
reach_offset[r] = i1
logger.debug(f"read_bin: iprofiles = {iprofiles}")
endline()
# X (3rd line)
newline()
_ = read_float(nb_profile)
endline()
# Z and Y (4th line)
newline()
_ = read_float(3*nb_profile)
endline()
# Data
newline()
ip_to_r = lambda i: iprofiles[
next(
filter(
lambda k: k[0] <= i <= k[1],
iprofiles
)
)
]
ip_to_ri = lambda r, i: i - reach_offset[r]
ts = set()
end = False
while not end:
n = read_int(1)[0]
timestamp = read_float64(1)[0]
key = bytearray(np.fromfile(f, dtype=np.byte, count=1)).decode()
data = read_float(n)
logger.debug(f"read_bin: timestamp = {timestamp} sec")
ts.add(timestamp)
if key in ["Z", "Q"]:
for i, d in enumerate(data):
# Get reach corresponding to profile ID
reach = ip_to_r(i)
# Get profile id in reach
ri = ip_to_ri(reach, i)
# Set data for profile RI
reach.set(ri, timestamp, key, d)
endline()
end = newline().size <= 0
logger.debug(reachs[0].profiles[0]._data)
results.set("timestamps", ts)
logger.info(f"read_bin: ... end with {len(ts)} timestamp read")
@timer
def read_gra(self, study, repertory, results, qlog = None, name="0"):
if not study.river.has_sediment():
return
fname = os.path.join(repertory, f"{name}.GRA")
logger.info(f"read_gra: Start reading '{fname}' ...")
with mage_file_open(fname, "r") as f:
newline = lambda: np.fromfile(f, dtype=np.int32, count=1)
endline = lambda: np.fromfile(f, dtype=np.int32, count=1)
read_int = lambda size: np.fromfile(f, dtype=np.int32, count=size)
read_float = lambda size: np.fromfile(f, dtype=np.float32, count=size)
read_float64 = lambda size: np.fromfile(f, dtype=np.float64, count=size)
# Meta data (1st line)
newline()
data = read_int(3)
nb_reach = data[0]
nb_profile = data[1]
mage_version = data[2]
logger.debug(f"read_gra: nb_reach = {nb_reach}")
logger.debug(f"read_gra: nb_profile = {nb_profile}")
logger.debug(f"read_gra: mage_version = {mage_version}")
if mage_version <= 80:
msg = (
"Read GRA files: " +
f"Possible incompatible mage version '{mage_version}', " +
"please check your solver configuration..."
)
logger.warning(msg)
if qlog is not None:
qlog.put("[WARNING] " + msg)
results.set("gra_solver_version", f"Mage8 ({mage_version})")
results.set("gra_nb_reach", f"{nb_reach}")
results.set("gra_nb_profile", f"{nb_profile}")
endline()
# Reach information (2nd line)
newline()
reachs = []
iprofiles = {}
reach_offset = {}
data = read_int(2*nb_reach)
for i in range(nb_reach):
# Get results reach to reach list
r = results.river.reach(i)
reachs.append(r)
# ID of first and last reach profiles
i1 = data[2*i] - 1
i2 = data[2*i+1] - 1
# Add profile id correspondance to reach
key = (i1, i2)
iprofiles[key] = r
# Profile ID offset
reach_offset[r] = i1
logger.debug(f"read_gra: iprofiles = {iprofiles}")
endline()
# X (3rd line)
newline()
_ = read_float(nb_profile)
endline()
# npts (4th line)
newline()
_ = read_int(nb_profile)
endline()
# Data
ip_to_r = lambda i: iprofiles[
next(
filter(
lambda k: k[0] <= i <= k[1],
iprofiles
)
)
]
ip_to_ri = lambda r, i: i - reach_offset[r]
ts = set()
ind = 0
end = False
newline()
while not end:
n = read_int(1)[0]
timestamp = read_float64(1)[0]
logger.debug(f"read_gra: timestamp = {timestamp} sec")
ts.add(timestamp)
endline()
for i in range(n):
newline()
nsl = read_int(1)[0]
endline()
# Get current profile id
reach = ip_to_r(i)
ri = ip_to_ri(reach, i)
if nsl > 1:
logger.warning(
"read_gra: " +
"Multiple sediment layers for one profile " +
"is not implemented yet..."
)
for j in range(nsl):
newline()
nl = read_int(1)[0]
endline()
sl = []
for k in range(nl):
newline()
data = read_float64(3)
endline()
h = data[0]
d50 = data[1]
sigma = data[2]
sl.append((h, d50, sigma))
reach.set(ri, timestamp, "sl", sl)
ind += 1
end = newline().size <= 0
logger.debug(reachs[0].profiles[0]._data)
results.set("timestamps", ts)
logger.info(f"read_gra: ... end with {len(ts)} timestamp read")
@timer
def results(self, study, repertory, qlog = None):
self._study = study
name = study.name.replace(" ", "_")
results = super(Mage8, self).results(study, repertory, qlog, name = name)
self.read_gra(study, repertory, results, qlog, name = name)
return results
+3 -3
View File
@@ -24,9 +24,9 @@ from Solver.Mage import Mage7, Mage8
_translate = QCoreApplication.translate
solver_long_name = {
"generic": _translate("Solvers", "Generic"),
# "mage7": _translate("Solvers", "Mage version 7"),
"mage8": _translate("Solvers", "Mage version 8"),
"generic": "Generic",
# "mage7": "Mage v7",
"mage8": "Mage v8",
}
solver_type_list = {
+13 -6
View File
@@ -19,14 +19,17 @@
import os
import logging
from View.ASubWindow import ASubWindow
from View.Tools.PamhyrWindow import PamhyrDialog
from PyQt5.QtCore import QCoreApplication
_translate = QCoreApplication.translate
logger = logging.getLogger()
class AboutWindow(ASubWindow):
class AboutWindow(PamhyrDialog):
_pamhyr_ui = "about"
_pamhyr_name = "About"
def _path_file(self, filename):
return os.path.abspath(
os.path.join(
@@ -35,10 +38,14 @@ class AboutWindow(ASubWindow):
)
)
def __init__(self, title="About", parent=None):
super(AboutWindow, self).__init__(name=title, ui="about", parent=parent)
self.ui.setWindowTitle(title)
def __init__(self, study = None, config = None, parent=None):
super(AboutWindow, self).__init__(
title = _translate("About", "About"),
study = study,
config = config,
options = [],
parent = parent
)
# Version
with open(self._path_file("VERSION"), "r") as f:
+21 -10
View File
@@ -16,31 +16,42 @@
# -*- coding: utf-8 -*-
import logging
from datetime import datetime
from tools import timer, trace
from View.Plot.APlot import APlot
from View.Tools.PamhyrPlot import PamhyrPlot
from PyQt5.QtCore import (
QCoreApplication
)
from View.BoundaryCondition.Edit.translate import *
from View.BoundaryCondition.Edit.translate import BCETranslate
_translate = QCoreApplication.translate
class Plot(APlot):
def __init__(self, canvas=None, data=None,
mode = "time", toolbar=None):
logger = logging.getLogger()
class Plot(PamhyrPlot):
def __init__(self, mode = "time", data=None,
trad=None, canvas=None, toolbar=None,
parent=None):
super(Plot, self).__init__(
canvas=canvas,
data=data,
toolbar=toolbar
canvas = canvas,
trad = trad,
data = data,
toolbar = toolbar,
parent = parent
)
self._table_headers = self._trad.get_dict("table_headers")
self._mode = mode
def custom_ticks(self):
if self.data.header[0] != "time":
return
t0 = datetime.fromtimestamp(0)
nb = len(self.data.data)
mod = int(nb / 5)
@@ -102,10 +113,10 @@ class Plot(APlot):
# Plot label
header = self.data.header
self.canvas.axes.set_xlabel(
table_headers[header[0]], color='black', fontsize=10
self._table_headers[header[0]], color='black', fontsize=10
)
self.canvas.axes.set_ylabel(
table_headers[header[1]], color='black', fontsize=10
self._table_headers[header[1]], color='black', fontsize=10
)
self.canvas.axes.autoscale_view(True, True, True)
+3 -138
View File
@@ -23,8 +23,7 @@ from datetime import date, time, datetime, timedelta
from tools import trace, timer
from View.ASubWindow import ASubMainWindow, AWidget
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrTable import PamhyrTableModel
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
@@ -47,131 +46,12 @@ from View.BoundaryCondition.Edit.UndoCommand import (
SortCommand, MoveCommand, PasteCommand,
DuplicateCommand,
)
from View.BoundaryCondition.Edit.translate import *
_translate = QCoreApplication.translate
logger = logging.getLogger()
class ExtendedTimeEdit(AWidget):
def __init__(self, parent=None):
super(ExtendedTimeEdit, self).__init__(
ui="extendedTimeEdit",
parent=parent
)
self.parent = parent
self.spinBox_days = self.find(QSpinBox, "spinBox_days")
self.timeEdit = self.find(QTimeEdit, "timeEdit")
def set_time(self, time):
days = 0
stime = time
# if ',' in time, time format is 'DD days, HH:MM:SS',
# otherelse is 'HH:MM:SS'
if "," in time:
s = time.strip().split(" ")
days = int(s[0])
stime = s[-1]
qtime = QTime.fromString(
stime,
"h:mm:ss"
)
self.spinBox_days.setValue(days)
self.timeEdit.setTime(qtime)
def get_time(self):
days = self.spinBox_days.value()
time = self.timeEdit.time().toPyTime()
secs = (
(time.hour * 3600) +
(time.minute * 60) +
time.second
)
return timedelta(days=days, seconds=secs)
class ExtendedDateTimeEdit(AWidget):
def __init__(self, parent=None):
super(ExtendedDateTimeEdit, self).__init__(
ui="extendedDateTimeEdit",
parent=parent
)
self.parent = parent
self.dateTimeEdit = self.find(QDateTimeEdit, "dateTimeEdit")
def set_time(self, time):
qdatetime = QDateTime.fromString(
time,
"yyyy-MM-dd hh:mm:ss"
)
self.dateTimeEdit.setDateTime(qdatetime)
def get_time(self):
time = self.dateTimeEdit.dateTime().toPyDateTime()
return time
class ExTimeDelegate(QItemDelegate):
def __init__(self, data=None, mode="time", parent=None):
super(ExTimeDelegate, self).__init__(parent)
self._data = data
self._mode = mode
def createEditor(self, parent, option, index):
if self._mode == "time":
self.editor = ExtendedTimeEdit(parent=parent)
else:
self.editor = ExtendedDateTimeEdit(parent=parent)
value = index.data(Qt.DisplayRole)
self.editor.set_time(value)
logger.debug(str(value))
return self.editor
def setModelData(self, editor, model, index):
time = editor.get_time()
if self._mode == "time":
model.setData(index, int(time.total_seconds()))
else:
logger.debug(str(time.timestamp()))
model.setData(index, int(time.timestamp()))
editor.close()
editor.deleteLater()
def updateEditorGeometry(self, editor, option, index):
r = QRect(option.rect)
if self.editor.windowFlags() & Qt.Popup and editor.parent() is not None:
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
editor.setGeometry(r)
@pyqtSlot()
def currentItemChanged(self):
self.commitData.emit(self.sender())
class TableModel(QAbstractTableModel):
def __init__(self, data=None, mode="time", undo=None):
super(QAbstractTableModel, self).__init__()
self._headers = data.header
self._data = data
self._mode = mode
self._undo = undo
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
options |= Qt.ItemIsEditable
return options
def rowCount(self, parent):
return len(self._data)
def columnCount(self, parent):
return len(self._headers)
class TableModel(PamhyrTableModel):
def data(self, index, role):
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter | Qt.AlignVCenter
@@ -189,7 +69,7 @@ class TableModel(QAbstractTableModel):
if self._data.get_type_column(column) == float:
value = f"{v:.4f}"
elif self._data.header[column] == "time":
if self._mode == "time":
if self._opt_data == "time":
t0 = datetime.fromtimestamp(0)
t = datetime.fromtimestamp(v)
value = str(t - t0)
@@ -200,12 +80,6 @@ class TableModel(QAbstractTableModel):
return value
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return table_headers[self._headers[section]]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
@@ -315,12 +189,3 @@ class TableModel(QAbstractTableModel):
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
@@ -16,6 +16,8 @@
# -*- coding: utf-8 -*-
import logging
from copy import deepcopy
from tools import trace, timer
@@ -25,6 +27,8 @@ from PyQt5.QtWidgets import (
from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
logger = logging.getLogger()
class SetDataCommand(QUndoCommand):
def __init__(self, data, index, column, new_value):
QUndoCommand.__init__(self)
@@ -42,6 +46,32 @@ class SetDataCommand(QUndoCommand):
def redo(self):
self._data._set_i_c_v(self._index, self._column, self._new)
class SetMetaDataCommand(QUndoCommand):
def __init__(self, data, column, new_value):
QUndoCommand.__init__(self)
self._data = data
self._column = column
if self._column == "d50":
self._old = self._data.d50
elif self._column == "sigma":
self._old = self._data.sigma
self._new = float(new_value)
def undo(self):
if self._column == "d50":
self._data.d50 = self._old
elif self._column == "sigma":
self._data.sigma = self._old
def redo(self):
if self._column == "d50":
self._data.d50 = self._new
elif self._column == "sigma":
self._data.sigma = self._new
class AddCommand(QUndoCommand):
def __init__(self, data, index):
QUndoCommand.__init__(self)
+146 -59
View File
@@ -16,91 +16,152 @@
# -*- coding: utf-8 -*-
import logging
from tools import timer, trace
from View.ASubWindow import ASubMainWindow
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrWindow import PamhyrWindow
from View.Tools.PamhyrWidget import PamhyrWidget
from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
from PyQt5.QtGui import (
QKeySequence,
)
from PyQt5 import QtCore
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, QCoreApplication,
pyqtSlot, pyqtSignal,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QHeaderView,
QHeaderView, QDoubleSpinBox, QVBoxLayout,
)
from View.Plot.MplCanvas import MplCanvas
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.BoundaryCondition.translate import long_types
from View.BoundaryCondition.Edit.Table import TableModel, ExTimeDelegate
from View.BoundaryCondition.Edit.translate import BCETranslate
from View.BoundaryCondition.Edit.UndoCommand import SetMetaDataCommand
from View.BoundaryCondition.Edit.Table import TableModel
from View.BoundaryCondition.Edit.Plot import Plot
_translate = QCoreApplication.translate
class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
def __init__(self, title="Edit boundary condition",
data=None, study=None, parent=None):
self._data = data
self._study = study
self._title = title
logger = logging.getLogger()
self.compute_title()
class WD50Sigma(PamhyrWidget):
_pamhyr_ui = "d50sigma"
super(EditBoundaryConditionWindow, self).__init__(
name=self._title, ui="EditBoundaryConditions", parent=parent
d50Changed = pyqtSignal(float)
sigmaChanged = pyqtSignal(float)
def __init__(self, parent=None):
super(WD50Sigma, self).__init__(
parent=parent
)
self.ui.setWindowTitle(self._title)
self.spinBox_d50 = self.find(QDoubleSpinBox, "doubleSpinBox_d50")
self.spinBox_sigma = self.find(QDoubleSpinBox, "doubleSpinBox_sigma")
self.setup_sc()
self.setup_table()
self.setup_plot()
self.setup_connections()
self.spinBox_d50.valueChanged.connect(self.valueChangedD50)
self.spinBox_sigma.valueChanged.connect(self.valueChangedSigma)
def compute_title(self):
def set_d50(self, d50):
self.spinBox_d50.valueChanged.disconnect(self.valueChangedD50)
self.spinBox_d50.setValue(float(d50))
self.spinBox_d50.valueChanged.connect(self.valueChangedD50)
def get_d50(self):
return float(self.spinBox_d50.value())
def set_sigma(self, sigma):
self.spinBox_sigma.valueChanged.disconnect(self.valueChangedSigma)
self.spinBox_sigma.setValue(float(sigma))
self.spinBox_sigma.valueChanged.connect(self.valueChangedSigma)
def get_sigma(self):
return float(self.spinBox_sigma.value())
@QtCore.pyqtSlot(float)
def valueChangedD50(self, value):
self.d50Changed.emit(value)
@QtCore.pyqtSlot(float)
def valueChangedSigma(self, value):
self.sigmaChanged.emit(value)
class EditBoundaryConditionWindow(PamhyrWindow):
_pamhyr_ui = "EditBoundaryConditions"
_pamhyr_name = "Edit Boundary Conditions"
def __init__(self, data=None, study=None, config=None, parent=None):
self._data = data
trad = BCETranslate()
self._long_types = trad.get_dict("long_types")
name = self._pamhyr_name
if self._data is not None:
node_name = (self._data.node.name if self._data.node is not None
else _translate("BoundaryCondition", "Not associate"))
self._title = (
_translate("Edit boundary condition", self._title) +
f" - {self._study.name} " +
name = (
_translate("Edit boundary condition", self._pamhyr_name) +
f" - {study.name} " +
f" - {self._data.name} ({self._data.id}) " +
f"({long_types[self._data.bctype]} - {node_name})"
f"({self._long_types[self._data.bctype]} - {node_name})"
)
def setup_sc(self):
self._undo_stack = QUndoStack()
self.undo_sc = QShortcut(QKeySequence.Undo, self)
self.redo_sc = QShortcut(QKeySequence.Redo, self)
self.copy_sc = QShortcut(QKeySequence.Copy, self)
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
table = self.find(QTableView, "tableView")
self._table = TableModel(
data = self._data,
undo = self._undo_stack,
mode = self._study.time_system
super(EditBoundaryConditionWindow, self).__init__(
title = name,
study = study,
config = config,
trad = trad,
parent = parent
)
if self._data.header[0] == "time":
self._delegate_time = ExTimeDelegate(
data = self._data,
mode = self._study.time_system,
parent = self
)
self.setup_table()
self.setup_plot()
self.setup_data()
self.setup_connections()
table.setItemDelegateForColumn(
0, self._delegate_time
)
def setup_data(self):
self._is_solid = self._data.bctype == "SL"
if self._is_solid:
layout = self.find(QVBoxLayout, "verticalLayout_table")
self._d50sigma = WD50Sigma(parent = self)
layout.addWidget(self._d50sigma)
self._d50sigma.set_d50(self._data.d50)
self._d50sigma.set_sigma(self._data.sigma)
def setup_table(self):
headers = {}
table_headers = self._trad.get_dict("table_headers")
for h in self._data.header:
headers[h] = table_headers[h]
self._delegate_time = PamhyrExTimeDelegate(
data = self._data,
mode = self._study.time_system,
parent = self
)
table = self.find(QTableView, "tableView")
self._table = TableModel(
table_view = table,
table_headers = headers,
editable_headers = self._data.header,
delegates = {
"time": self._delegate_time,
},
data = self._data,
undo = self._undo_stack,
opt_data = self._study.time_system
)
table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
@@ -110,28 +171,53 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
def setup_plot(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.verticalLayout.addWidget(self.toolbar)
self.verticalLayout.addWidget(self.canvas)
self.plot = Plot(
canvas = self.canvas,
data = self._data,
mode = self._study.time_system,
trad = self._trad,
toolbar = self.toolbar,
)
self.plot.draw()
def setup_connections(self):
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_del").triggered.connect(self.delete)
self.find(QAction, "action_sort").triggered.connect(self.sort)
self.undo_sc.activated.connect(self.undo)
self.redo_sc.activated.connect(self.redo)
self.copy_sc.activated.connect(self.copy)
self.paste_sc.activated.connect(self.paste)
self._table.dataChanged.connect(self.update)
if self._is_solid:
self._d50sigma.d50Changed.connect(self.d50_changed)
self._d50sigma.sigmaChanged.connect(self.sigma_changed)
def d50_changed(self, value):
self._undo_stack.push(
SetMetaDataCommand(
self._data,
"d50", value
)
)
def sigma_changed(self, value):
self._undo_stack.push(
SetMetaDataCommand(
self._data,
"sigma", value
)
)
def widget_update(self):
if self._is_solid:
self._d50sigma.set_d50(self._data.d50)
self._d50sigma.set_sigma(self._data.sigma)
def update(self):
self.plot.update()
@@ -153,7 +239,6 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
)
)
def add(self):
rows = self.index_selected_rows()
if len(self._data) == 0 or len(rows) == 0:
@@ -186,7 +271,7 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
self.plot.update()
def copy(self):
def _copy(self):
rows = self.index_selected_rows()
table = []
@@ -198,7 +283,7 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
self.copyTableIntoClipboard(table)
def paste(self):
def _paste(self):
header, data = self.parseClipboardTable()
if len(data) == 0:
@@ -212,10 +297,12 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
self._table.paste(row, header, data)
self.plot.update()
def undo(self):
def _undo(self):
self._table.undo()
self.plot.update()
self.widget_update()
def redo(self):
def _redo(self):
self._table.redo()
self.plot.update()
self.widget_update()
+17 -8
View File
@@ -18,13 +18,22 @@
from PyQt5.QtCore import QCoreApplication
from View.Tools.PamhyrTranslate import PamhyrTranslate
from View.BoundaryCondition.translate import BCTranslate
_translate = QCoreApplication.translate
table_headers = {
"x": _translate("BoundaryCondition", "X"),
"y": _translate("BoundaryCondition", "Y"),
"time": _translate("BoundaryCondition", "Time"),
"date": _translate("BoundaryCondition", "Date"),
"discharge": _translate("BoundaryCondition", "Discharge (m³/s)"),
"z": _translate("BoundaryCondition", "Z (m)")
}
class BCETranslate(BCTranslate):
def __init__(self):
super(BCETranslate, self).__init__()
self._sub_dict["table_headers"] = {
"x": _translate("BoundaryCondition", "X"),
"y": _translate("BoundaryCondition", "Y"),
"time": _translate("BoundaryCondition", "Time"),
"date": _translate("BoundaryCondition", "Date"),
"discharge": _translate("BoundaryCondition", "Discharge (m³/s)"),
"z": _translate("BoundaryCondition", "Z (m)"),
"solid": _translate("BoundaryCondition", "Solid (kg/s)"),
}
+34 -39
View File
@@ -39,24 +39,31 @@ from Model.BoundaryCondition.BoundaryConditionTypes import (
TimeOverZ, TimeOverDischarge, ZOverDischarge
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.BoundaryCondition.UndoCommand import (
SetNameCommand, SetNodeCommand, SetTypeCommand,
AddCommand, DelCommand, SortCommand,
MoveCommand, PasteCommand, DuplicateCommand,
)
from View.BoundaryCondition.translate import *
from View.BoundaryCondition.translate import BC_types
logger = logging.getLogger()
_translate = QCoreApplication.translate
class ComboBoxDelegate(QItemDelegate):
def __init__(self, data=None, mode="type", tab="", parent=None):
def __init__(self, data=None, mode="type", tab="", trad=None, parent=None):
super(ComboBoxDelegate, self).__init__(parent)
self._data = data
self._mode = mode
self._tab = tab
self._trad = trad
self._long_types = {}
if self._trad is not None:
self._long_types = self._trad.get_dict("long_types")
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
@@ -64,7 +71,7 @@ class ComboBoxDelegate(QItemDelegate):
if self._mode == "type":
lst = list(
map(
lambda k: long_types[k],
lambda k: self._long_types[k],
filter(
lambda k: self._tab in BC_types[k].compatibility(),
BC_types.keys()
@@ -104,26 +111,20 @@ class ComboBoxDelegate(QItemDelegate):
self.commitData.emit(self.sender())
class TableModel(QAbstractTableModel):
def __init__(self, data=None, undo=None, tab=""):
super(QAbstractTableModel, self).__init__()
self._headers = list(table_headers.keys())
self._data = data
self._undo = undo
self._tab = tab
self._bcs = self._data.boundary_condition
class TableModel(PamhyrTableModel):
def __init__(self, **kwargs):
super(TableModel, self).__init__(**kwargs)
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
options |= Qt.ItemIsEditable
self._long_types = {}
if self._trad is not None:
self._long_types = self._trad.get_dict("long_types")
return options
def _setup_lst(self):
self._lst = self._data.boundary_condition
self._tab = self._opt_data
def rowCount(self, parent):
return self._bcs.len(self._tab)
def columnCount(self, parent):
return len(self._headers)
return self._lst.len(self._tab)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
@@ -133,24 +134,18 @@ class TableModel(QAbstractTableModel):
column = index.column()
if self._headers[column] == "name":
return self._bcs.get(self._tab, row).name
return self._lst.get(self._tab, row).name
elif self._headers[column] == "type":
t = self._bcs.get(self._tab, row).bctype
return long_types[t]
t = self._lst.get(self._tab, row).bctype
return self._long_types[t]
elif self._headers[column] == "node":
n = self._bcs.get(self._tab, row).node
n = self._lst.get(self._tab, row).node
if n is None:
return _translate("BoundaryCondition", "Not associate")
return n.name
return QVariant()
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return table_headers[self._headers[section]]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
@@ -162,20 +157,20 @@ class TableModel(QAbstractTableModel):
if self._headers[column] == "name":
self._undo.push(
SetNameCommand(
self._bcs, self._tab,row, value
self._lst, self._tab,row, value
)
)
elif self._headers[column] == "type":
key = next(k for k, v in long_types.items() if v == value)
key = next(k for k, v in self._long_types.items() if v == value)
self._undo.push(
SetTypeCommand(
self._bcs, self._tab,row, BC_types[key]
self._lst, self._tab,row, BC_types[key]
)
)
elif self._headers[column] == "node":
self._undo.push(
SetNodeCommand(
self._bcs, self._tab,row, self._data.node(value)
self._lst, self._tab,row, self._data.node(value)
)
)
except Exception as e:
@@ -190,7 +185,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
AddCommand(
self._bcs, self._tab,row
self._lst, self._tab,row
)
)
@@ -202,7 +197,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
DelCommand(
self._bcs, self._tab,rows
self._lst, self._tab, rows
)
)
@@ -214,7 +209,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
SortCommand(
self._bcs, self._tab,False
self._lst, self._tab, False
)
)
@@ -231,7 +226,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push(
MoveCommand(
self._bcs, self._tab,"up", row
self._lst, self._tab, "up", row
)
)
@@ -239,7 +234,7 @@ class TableModel(QAbstractTableModel):
self.layoutChanged.emit()
def move_down(self, index, parent=QModelIndex()):
if row > len(self._bcs):
if row > len(self._lst):
return
target = row
@@ -248,7 +243,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push(
MoveCommand(
self._bcs, self._tab,"down", row
self._lst, self._tab, "down", row
)
)
+33 -40
View File
@@ -20,8 +20,7 @@ import logging
from tools import trace, timer
from View.ASubWindow import ASubMainWindow
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence,
@@ -56,71 +55,70 @@ from View.BoundaryCondition.Table import (
)
from View.Network.GraphWidget import GraphWidget
from View.BoundaryCondition.translate import *
from View.BoundaryCondition.translate import BCTranslate
from View.BoundaryCondition.Edit.Window import EditBoundaryConditionWindow
_translate = QCoreApplication.translate
logger = logging.getLogger()
class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
def __init__(self, title="Boundary conditions", study=None, parent=None):
self._title = title + " - " + study.name
class BoundaryConditionWindow(PamhyrWindow):
_pamhyr_ui = "BoundaryConditions"
_pamhyr_name = "Boundary conditions"
def __init__(self, study=None, config=None, parent=None):
name = self._pamhyr_name + " - " + study.name
super(BoundaryConditionWindow, self).__init__(
name=title, ui="BoundaryConditions", parent=parent
title = name,
study = study,
config = config,
trad = BCTranslate(),
parent=parent
)
self._study = study
self._bcs = self._study.river.boundary_condition
self.setup_sc()
self.setup_table()
self.setup_graph()
self.setup_connections()
self.ui.setWindowTitle(self._title)
def setup_sc(self):
self._undo_stack = QUndoStack()
self.undo_sc = QShortcut(QKeySequence.Undo, self)
self.redo_sc = QShortcut(QKeySequence.Redo, self)
self.copy_sc = QShortcut(QKeySequence.Copy, self)
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
self._table = {}
for t in ["liquid", "solid", "suspenssion"]:
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
data = self._study.river,
undo = self._undo_stack,
tab = t,
)
table.setModel(self._table[t])
self._delegate_type = ComboBoxDelegate(
trad = self._trad,
data = self._study.river,
mode = "type",
tab = t,
parent=self
)
self._delegate_node = ComboBoxDelegate(
trad = self._trad,
data = self._study.river,
mode = "node",
tab = t,
parent=self
)
table.setItemDelegateForColumn(
1, self._delegate_type
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
table_view = table,
table_headers = self._trad.get_dict("table_headers"),
editable_headers = ["name", "type", "node"],
delegates = {
"type": self._delegate_type,
"node": self._delegate_node,
},
trad = self._trad,
data = self._study.river,
undo = self._undo_stack,
opt_data = t,
)
table.setItemDelegateForColumn(
2, self._delegate_node
)
table.setModel(self._table[t])
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
@@ -141,11 +139,6 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
self.find(QAction, "action_edit").triggered.connect(self.edit)
self.find(QAction, "action_sort").triggered.connect(self.sort)
self.undo_sc.activated.connect(self.undo)
self.redo_sc.activated.connect(self.redo)
self.copy_sc.activated.connect(self.copy)
self.paste_sc.activated.connect(self.paste)
def current_tab(self):
return self.find(QTabWidget, "tabWidget")\
.currentWidget()\
@@ -202,17 +195,17 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
row = self.index_selected_row()
self._table[tab].move_down(row)
def copy(self):
def _copy(self):
logger.info("TODO: copy")
def paste(self):
def _paste(self):
logger.info("TODO: paste")
def undo(self):
def _undo(self):
tab = self.current_tab()
self._table[tab].undo()
def redo(self):
def _redo(self):
tab = self.current_tab()
self._table[tab].redo()
+25 -16
View File
@@ -18,31 +18,40 @@
from PyQt5.QtCore import QCoreApplication
from View.Tools.PamhyrTranslate import PamhyrTranslate
from Model.BoundaryCondition.BoundaryConditionTypes import (
NotDefined, PonctualContribution,
TimeOverZ, TimeOverDischarge, ZOverDischarge
TimeOverZ, TimeOverDischarge, ZOverDischarge,
Solid
)
_translate = QCoreApplication.translate
long_types = {
"ND": _translate("BoundaryCondition", "Not defined"),
"PC": _translate("BoundaryCondition", "Ponctual contribution"),
"TZ": _translate("BoundaryCondition", "Time over Z"),
"TD": _translate("BoundaryCondition", "Time over Discharge"),
"ZD": _translate("BoundaryCondition", "Z over Discharge"),
}
table_headers = {
"name": _translate("BoundaryCondition", "Name"),
"type": _translate("BoundaryCondition", "Type"),
"node": _translate("BoundaryCondition", "Node")
}
BC_types = {
"ND": NotDefined,
"PC": PonctualContribution,
"TZ": TimeOverZ,
"TD": TimeOverDischarge,
"ZD": ZOverDischarge
"ZD": ZOverDischarge,
"SL": Solid,
}
class BCTranslate(PamhyrTranslate):
def __init__(self):
super(BCTranslate, self).__init__()
self._sub_dict["long_types"] = {
"ND": _translate("BoundaryCondition", "Not defined"),
"PC": _translate("BoundaryCondition", "Ponctual contribution"),
"TZ": _translate("BoundaryCondition", "TimeZ"),
"TD": _translate("BoundaryCondition", "TimeDischarge"),
"ZD": _translate("BoundaryCondition", "ZDischarge"),
"SL": _translate("BoundaryCondition", "Solid"),
}
self._sub_dict["table_headers"] = {
"name": _translate("BoundaryCondition", "Name"),
"type": _translate("BoundaryCondition", "Type"),
"node": _translate("BoundaryCondition", "Node")
}
+3 -25
View File
@@ -35,25 +35,12 @@ from PyQt5.QtWidgets import (
QComboBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
_translate = QCoreApplication.translate
class TableModel(QAbstractTableModel):
def __init__(self, data=None):
super(QAbstractTableModel, self).__init__()
self._headers = ["name", "status"]
self._data = data
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
return options
def rowCount(self, parent):
return len(self._data)
def columnCount(self, parent):
return len(self._headers)
class TableModel(PamhyrTableModel):
def data(self, index, role):
row = index.row()
column = index.column()
@@ -81,12 +68,3 @@ class TableModel(QAbstractTableModel):
return self._data[row].summary
return QVariant()
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return self._headers[section]
return QVariant()
def update(self):
self.layoutChanged.emit()
+32
View File
@@ -0,0 +1,32 @@
# Translate.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication
from View.Tools.PamhyrTranslate import PamhyrTranslate
_translate = QCoreApplication.translate
class CheckListTranslate(PamhyrTranslate):
def __init__(self):
super(CheckListTranslate, self).__init__()
self._sub_dict["table_headers"] = {
"name": _translate("CheckList", "Name"),
"status": _translate("CheckList", "Status"),
}
+18 -20
View File
@@ -18,8 +18,7 @@
from tools import trace, timer
from View.ASubWindow import ASubMainWindow
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence,
@@ -41,33 +40,36 @@ from PyQt5.QtWidgets import (
from View.CheckList.Table import TableModel
from View.CheckList.Worker import Worker
from View.CheckList.Translate import CheckListTranslate
_translate = QCoreApplication.translate
class CheckListWindow(ASubMainWindow, ListedSubWindow):
class CheckListWindow(PamhyrWindow):
_pamhyr_ui = "CheckList"
_pamhyr_name = "Check list"
signalStatus = pyqtSignal(str)
def __init__(self, title="Check list",
autorun:bool=True,
def __init__(self, autorun:bool=True,
study=None, config=None,
solver=None, parent=None):
self._title = title + " - " + study.name
self._autorun = autorun
self._study = study
self._config = config
self._solver = solver
self._parent = parent
name = self._pamhyr_name + " - " + study.name
super(CheckListWindow, self).__init__(
name=self._title, ui="CheckList", parent=parent
title = name,
study = study,
config = config,
trad = CheckListTranslate(),
options = [],
parent = parent
)
self.ui.setWindowTitle(self._title)
self._checker_list = (
self._study.checkers() + self._solver.checkers()
self._study.checkers() +
self._solver.checkers()
)
self.setup_table()
@@ -78,16 +80,12 @@ class CheckListWindow(ASubMainWindow, ListedSubWindow):
def setup_table(self):
table = self.find(QTableView, f"tableView")
self._table = TableModel(
table_view = table,
table_headers = self._trad.get_dict("table_headers"),
data = self._checker_list,
)
table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
def setup_progress_bar(self):
self._progress = self.find(QProgressBar, f"progressBar")
self._p = 0 # Progress current step
+2 -1
View File
@@ -37,10 +37,11 @@ class Worker(QObject):
self.signalStatus.emit('end')
def _compute(self):
# The computation loop
for checker in self._checker_list:
self.signalStatus.emit(checker.name)
# time.sleep(1)
# Run checker
checker.run(self._study)
self.signalStatus.emit("progress")
+18 -8
View File
@@ -16,7 +16,7 @@
# -*- coding: utf-8 -*-
from View.ASubWindow import ASubWindow
from View.Tools.PamhyrWindow import PamhyrDialog
from Solver.Solvers import solver_type_list
from Solver.GenericSolver import GenericSolver
@@ -24,12 +24,22 @@ from PyQt5.QtWidgets import (
QPushButton,
)
class ConfigureSolverWindow(ASubWindow):
def __init__(self, data=None, title="Configuration : Add Solver", parent=None):
class ConfigureSolverWindow(PamhyrDialog):
_pamhyr_ui = "ConfigureAddSolverDialog"
_pamhyr_name = "Add/Edit Solver"
def __init__(self, data=None, config=None, parent=None):
if data is not None:
name = f"Edit Solver - {data.name}"
else:
name = "Add a new Solver"
super(ConfigureSolverWindow, self).__init__(
name=title, ui="ConfigureAddSolverDialog", parent=parent
title = name,
config = config,
options = [],
parent=parent
)
self.ui.setWindowTitle(title)
# Combo box item
for solver in solver_type_list:
@@ -37,10 +47,10 @@ class ConfigureSolverWindow(ASubWindow):
# Data to return
self.data = data
if not self.data is None:
if self.data is not None:
self.copy_data()
self.connect()
self.setup_connection()
def copy_data(self):
self.set_combobox_text("comboBox_solver", self.data.type)
@@ -53,7 +63,7 @@ class ConfigureSolverWindow(ASubWindow):
self.set_line_edit_text("lineEdit_output", self.data._path_output)
self.set_line_edit_text("lineEdit_output_cmd", self.data._cmd_output)
def connect(self):
def setup_connection(self):
# File button
buttons = {
"pushButton_input": (lambda: self.file_dialog(
+58 -59
View File
@@ -21,8 +21,7 @@ import logging
from copy import deepcopy
from config import Config
from View.ASubWindow import ASubWindow
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrWindow import PamhyrDialog
from View.Stricklers.Table import TableModel
from View.Stricklers.translate import *
@@ -95,50 +94,42 @@ class SolverTableModel(QAbstractTableModel):
self.layoutChanged.emit()
class ConfigureWindow(ASubWindow, ListedSubWindow):
def __init__(self, conf=None, title="Configure", parent=None):
class ConfigureWindow(PamhyrDialog):
_pamhyr_ui = "ConfigureDialog"
_pamhyr_name = "Configure"
def __init__(self, config=None, parent=None):
if config is None:
config = Config()
super(ConfigureWindow, self).__init__(
name=title, ui="ConfigureDialog", parent=parent
title = self._pamhyr_name,
config = config,
options = [],
parent=parent
)
self.ui.setWindowTitle(title)
if conf is None:
self.conf = Config()
else:
self.conf = conf
self.setup_custom_sc()
self.setup_solver()
self.setup_stricklers()
self.setup_data()
self.setup_connection()
self.setup_sc()
# Solver
def setup_solver(self):
table = self.find(QTableView, "tableView_solver")
self.solver_table_model = SolverTableModel(
headers = ["name", "type", "description"],
rows = conf.solvers
rows = self._config.solvers
)
table.setModel(self.solver_table_model)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
table.resizeColumnsToContents()
# Meshing_Tool
self.set_line_edit_text("lineEdit_meshing_tool", self.conf.meshing_tool)
# Const
self.set_line_edit_text("lineEdit_segment", str(self.conf.segment))
self.set_line_edit_text("lineEdit_max_listing", str(self.conf.max_listing))
# Backup
self.set_check_box("checkBox_backup", self.conf.backup_enable)
self.set_line_edit_text("lineEdit_backup_path", self.conf.backup_path)
self.set_time_edit("timeEdit_backup_frequence", self.conf.backup_frequence)
self.set_spin_box("spinBox_backup_max", self.conf.backup_max)
self.find(QTableView, "tableView_solver").resizeColumnsToContents()
self.connect()
# Stricklers
def setup_stricklers(self):
table = self.find(QTableView, f"tableView_stricklers")
self._stricklers = deepcopy(self.conf.stricklers)
self._stricklers = deepcopy(self._config.stricklers)
self._stricklers_table = TableModel(
data = self._stricklers,
undo = self._undo_stack,
@@ -148,26 +139,35 @@ class ConfigureWindow(ASubWindow, ListedSubWindow):
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
def setup_data(self):
# Meshing_Tool
self.set_line_edit_text("lineEdit_meshing_tool", self._config.meshing_tool)
# Const
self.set_line_edit_text("lineEdit_segment", str(self._config.segment))
self.set_line_edit_text("lineEdit_max_listing", str(self._config.max_listing))
# Backup
self.set_check_box("checkBox_backup", self._config.backup_enable)
self.set_line_edit_text("lineEdit_backup_path", self._config.backup_path)
self.set_time_edit("timeEdit_backup_frequence", self._config.backup_frequence)
self.set_spin_box("spinBox_backup_max", self._config.backup_max)
# Editor
self.set_line_edit_text("lineEdit_editor_cmd", str(self.conf.editor))
self.set_line_edit_text("lineEdit_editor_cmd", str(self._config.editor))
# Language
languages = Config.languages()
for lang in languages:
self.combobox_add_item("comboBox_language", lang)
if self.conf.lang == languages[lang]:
if self._config.lang == languages[lang]:
self.set_combobox_text("comboBox_language", lang)
def setup_sc(self):
self._undo_stack = QUndoStack()
def setup_custom_sc(self):
self._debug_sc = QShortcut(QKeySequence("Ctrl+G"), self)
self._debug_sc.activated.connect(self.set_debug)
self.debug_sc = QShortcut(QKeySequence("Ctrl+G"), self)
self.undo_sc = QShortcut(QKeySequence.Undo, self)
self.redo_sc = QShortcut(QKeySequence.Redo, self)
self.copy_sc = QShortcut(QKeySequence.Copy, self)
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def connect(self):
def setup_connection(self):
buttons = {
# Solvers
"pushButton_solver_add": self.add_solver,
@@ -195,33 +195,31 @@ class ConfigureWindow(ASubWindow, ListedSubWindow):
for button in buttons:
self.find(QPushButton, button).clicked.connect(buttons[button])
self.debug_sc.activated.connect(self.set_debug)
def accept(self):
# Solvers
self.conf.solvers = self.solver_table_model.rows.copy()
self._config.solvers = self.solver_table_model.rows.copy()
# Meshing_Tool
self.conf.meshing_tool = self.get_line_edit_text("lineEdit_meshing_tool")
self._config.meshing_tool = self.get_line_edit_text("lineEdit_meshing_tool")
# Const
self.conf.segment = self.get_line_edit_text("lineEdit_segment")
self.conf.max_listing = self.get_line_edit_text("lineEdit_max_listing")
self._config.segment = self.get_line_edit_text("lineEdit_segment")
self._config.max_listing = self.get_line_edit_text("lineEdit_max_listing")
# Backup
self.conf.backup_enable = self.get_check_box("checkBox_backup")
self.conf.backup_path = self.get_line_edit_text("lineEdit_backup_path")
self.conf.backup_frequence = self.get_time_edit("timeEdit_backup_frequence")
self.conf.backup_max = self.get_spin_box("spinBox_backup_max")
self._config.backup_enable = self.get_check_box("checkBox_backup")
self._config.backup_path = self.get_line_edit_text("lineEdit_backup_path")
self._config.backup_frequence = self.get_time_edit("timeEdit_backup_frequence")
self._config.backup_max = self.get_spin_box("spinBox_backup_max")
# Stricklers
self.conf.stricklers = deepcopy(self._stricklers)
self._config.stricklers = deepcopy(self._stricklers)
# Editor
self.conf.editor = self.get_line_edit_text("lineEdit_editor_cmd")
self._config.editor = self.get_line_edit_text("lineEdit_editor_cmd")
# Language
self.conf.lang = Config.languages()[self.get_combobox_text("comboBox_language")]
self._config.lang = Config.languages()[self.get_combobox_text("comboBox_language")]
self.end()
@@ -230,13 +228,13 @@ class ConfigureWindow(ASubWindow, ListedSubWindow):
self.end()
def end(self):
self.conf.save()
self._config.save()
self.close()
# Debug
def set_debug(self):
self.conf.debug = not self.conf.debug
logger.info(f"Debug mode set : {self.conf.debug}")
self._config.debug = not self._config.debug
logger.info(f"Debug mode set : {self._config.debug}")
self.parent.setup_debug_mode()
# Solvers
@@ -246,6 +244,7 @@ class ConfigureWindow(ASubWindow, ListedSubWindow):
for index in indexes:
self.edit_solver = ConfigureSolverWindow(
data=self.solver_table_model.rows[index.row()],
config=self._config,
parent=self
)
if self.edit_solver.exec_():
+15 -13
View File
@@ -20,8 +20,7 @@ import logging
from tools import trace, timer
from View.ASubWindow import ASubMainWindow
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence,
@@ -45,20 +44,19 @@ logger = logging.getLogger()
_translate = QCoreApplication.translate
class ReplWindow(ASubMainWindow, ListedSubWindow):
def __init__(self, title="Debug REPL",
study=None, config=None,
class ReplWindow(PamhyrWindow):
_pamhyr_ui = "DebugRepl"
_pamhyr_name = "Debug REPL"
def __init__(self, study=None, config=None,
solver=None, parent=None):
self._title = title
self._study = study
self._config = config
self._parent = parent
super(ReplWindow, self).__init__(
name=self._title, ui="DebugRepl", parent=parent
title = self._pamhyr_name,
study = study,
config = config,
options = [],
parent = parent
)
self.ui.setWindowTitle(self._title)
self.__debug_exec_result__ = None
self._history = []
@@ -92,7 +90,9 @@ class ReplWindow(ASubMainWindow, ListedSubWindow):
self.set_plaintext_edit_text("plainTextEdit", "")
self._history_ind = 0
# Code to rich_code
rich_code = code.strip().split("\n")
# Add return variable for results display at last row
rich_code[-1] = "self.__debug_exec_result__ = " + rich_code[-1]
rich_code = "\n".join(rich_code)
logger.debug(f"User debug command : {rich_code}")
@@ -102,6 +102,8 @@ class ReplWindow(ASubMainWindow, ListedSubWindow):
except Exception as e:
value = f"<font color=\"red\">" + str(e) + "</font>"
# Display code
msg = f"<font color=\"grey\"> # " + code + " #</font>"
self.find(QTextEdit, "textEdit").append(msg)
# Display results
self.find(QTextEdit, "textEdit").append(str(value))
+106
View File
@@ -0,0 +1,106 @@
# Window.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import os, sys
import logging
import subprocess
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtCore import QCoreApplication
_translate = QCoreApplication.translate
logger = logging.getLogger()
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QAction
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
class DocWindow(PamhyrWindow):
_pamhyr_ui = "WebView"
_pamhyr_name = "Doc"
@classmethod
def _path_file(cls, filename):
if ".py" in sys.argv[0]:
return os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..", "..", "..", "doc", filename
)
)
return os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..", "..", "..", "..", "doc", filename
)
)
def __init__(self, filename=None,
study=None, config=None,
parent=None):
super(DocWindow, self).__init__(
title = self._pamhyr_name,
study = study,
config = config,
options = [],
parent = parent
)
self._path = self._path_file(filename)
self.setup_web_engine()
self.setup_url(self._path)
self.setup_connection()
def setup_connection(self):
self.findChild(QAction, "action_back").triggered.connect(self.back)
self.findChild(QAction, "action_forward").triggered.connect(self.forward)
self.findChild(QAction, "action_firefox").triggered.connect(self.open_in_firefox)
def setup_web_engine(self):
vl = self.find(QVBoxLayout, "verticalLayout")
self._web_view = QWebEngineView()
settings = self._web_view.settings()
settings.setAttribute(QWebEngineSettings.PluginsEnabled, True)
settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True)
settings.setAttribute(QWebEngineSettings.PdfViewerEnabled, True)
vl.addWidget(self._web_view)
def setup_url(self, filename):
logger.info(f"Open documentation : {filename}")
self._web_view.setUrl(QUrl(f"file://{self._path_file(filename)}"))
def back(self):
self._web_view.back()
def forward(self):
self._web_view.forward()
def open_in_firefox(self):
_ = subprocess.Popen(
f"firefox {self._path}",
shell = True
)
+10 -5
View File
@@ -16,9 +16,14 @@
# -*- coding: utf-8 -*-
from View.ASubWindow import ASubWindow
from View.Tools.PamhyrWindow import PamhyrWindow
class DummyWindow(ASubWindow):
def __init__(self, title="Dummy", parent=None):
super(DummyWindow, self).__init__(name=title, ui="dummy", parent=parent)
self.ui.setWindowTitle(title)
class DummyWindow(PamhyrWindow):
__ui = "Dummy"
__name = "Pamhyr Dummy Window"
def __init__(self, parent=None):
super(DummyWindow, self).__init__(
title = self.__name,
parent = parent,
)
+2 -9
View File
@@ -17,7 +17,7 @@
# -*- coding: utf-8 -*-
from tools import timer, flatten
from View.Plot.APlot import APlot
from View.Tools.PamhyrPlot import PamhyrPlot
from PyQt5.QtCore import (
QCoreApplication
@@ -25,14 +25,7 @@ from PyQt5.QtCore import (
_translate = QCoreApplication.translate
class PlotStricklers(APlot):
def __init__(self, canvas=None, data=None, toolbar=None):
super(PlotStricklers, self).__init__(
canvas=canvas,
data=data,
toolbar=toolbar
)
class PlotStricklers(PamhyrPlot):
def draw_frictions(self, frictions, color="r"):
lst = frictions
lst.sort(key = lambda s: s.begin_kp)
+22 -42
View File
@@ -41,6 +41,8 @@ from View.Frictions.UndoCommand import (
MoveCommand, PasteCommand, DuplicateCommand,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.Frictions.translate import *
logger = logging.getLogger()
@@ -93,26 +95,10 @@ class ComboBoxDelegate(QItemDelegate):
self.commitData.emit(self.sender())
class TableModel(QAbstractTableModel):
def __init__(self, data=None, study=None, undo=None):
super(QAbstractTableModel, self).__init__()
self._headers = list(table_headers.keys())
self._data = data
self._study = study
self._undo = undo
self._frictions = self._data.frictions
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
options |= Qt.ItemIsEditable
return options
def rowCount(self, parent):
return len(self._frictions)
def columnCount(self, parent):
return len(self._headers)
class TableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data.frictions
self._study = self._opt_data
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
@@ -122,30 +108,24 @@ class TableModel(QAbstractTableModel):
column = index.column()
if self._headers[column] == "name":
return self._frictions.get(row).name
return self._lst.get(row).name
elif self._headers[column] == "begin_kp":
return self._frictions.get(row).begin_kp
return self._lst.get(row).begin_kp
elif self._headers[column] == "end_kp":
return self._frictions.get(row).end_kp
return self._lst.get(row).end_kp
elif self._headers[column] == "begin_strickler":
value = self._frictions.get(row).begin_strickler
value = self._lst.get(row).begin_strickler
if value == None:
return _translate("Frictions", "Not defined")
return str(value)
elif self._headers[column] == "end_strickler":
value = self._frictions.get(row).end_strickler
value = self._lst.get(row).end_strickler
if value == None:
return _translate("Frictions", "Not defined")
return str(value)
return QVariant()
def headerData(self, friction, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return table_headers[self._headers[friction]]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
@@ -157,31 +137,31 @@ class TableModel(QAbstractTableModel):
if self._headers[column] == "name":
self._undo.push(
SetNameCommand(
self._frictions, row, value
self._lst, row, value
)
)
elif self._headers[column] == "begin_kp":
self._undo.push(
SetBeginCommand(
self._frictions, row, value
self._lst, row, value
)
)
elif self._headers[column] == "end_kp":
self._undo.push(
SetEndCommand(
self._frictions, row, value
self._lst, row, value
)
)
elif self._headers[column] == "begin_strickler":
self._undo.push(
SetBeginStricklerCommand(
self._frictions, row, self._study.river.strickler(value)
self._lst, row, self._study.river.strickler(value)
)
)
elif self._headers[column] == "end_strickler":
self._undo.push(
SetEndStricklerCommand(
self._frictions, row, self._study.river.strickler(value)
self._lst, row, self._study.river.strickler(value)
)
)
except Exception as e:
@@ -196,7 +176,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
AddCommand(
self._frictions, row, self._data
self._lst, row, self._data
)
)
@@ -208,7 +188,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
DelCommand(
self._frictions, rows
self._lst, rows
)
)
@@ -220,7 +200,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
SortCommand(
self._frictions, False
self._lst, False
)
)
@@ -237,7 +217,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push(
MoveCommand(
self._frictions, "up", row
self._lst, "up", row
)
)
@@ -245,7 +225,7 @@ class TableModel(QAbstractTableModel):
self.layoutChanged.emit()
def move_down(self, index, parent=QModelIndex()):
if row > len(self._frictions):
if row > len(self._lst):
return
target = row
@@ -254,7 +234,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push(
MoveCommand(
self._frictions, "down", row
self._lst, "down", row
)
)
+48 -57
View File
@@ -20,8 +20,7 @@ import logging
from tools import trace, timer
from View.ASubWindow import ASubMainWindow
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence,
@@ -48,62 +47,49 @@ from View.Frictions.Table import (
TableModel, ComboBoxDelegate
)
from View.Plot.MplCanvas import MplCanvas
from View.Geometry.PlotKPC import PlotKPC
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Geometry.PlotKPZ import PlotKPZ
from View.Frictions.PlotStricklers import PlotStricklers
from View.Frictions.translate import *
from View.Frictions.translate import FrictionsTranslate
from View.Stricklers.Window import StricklersWindow
_translate = QCoreApplication.translate
logger = logging.getLogger()
class FrictionsWindow(ASubMainWindow, ListedSubWindow):
def __init__(self, title="Frictions", study=None, parent=None):
self._study = study
self._reach = self._study.river.current_reach()
class FrictionsWindow(PamhyrWindow):
_pamhyr_ui = "Frictions"
_pamhyr_name = "Edit frictions"
def __init__(self, reach=None, study=None, config=None, parent=None):
if reach is not None:
self._reach = reach
else:
self._reach = study.river.current_reach()
self._frictions = self._reach.frictions
self.setup_title(title)
super(FrictionsWindow, self).__init__(
name=title, ui="Frictions", parent=parent
name = (
self._pamhyr_name + " - "
+ study.name + " - "
+ self._reach.name
)
super(FrictionsWindow, self).__init__(
title = name,
study = study,
config = config,
trad = FrictionsTranslate(),
parent = parent
)
self.setup_sc()
self.setup_table()
self.setup_graph()
self.setup_connections()
self.ui.setWindowTitle(self._title)
def setup_title(self, title):
self._title = (
title + " - "
+ self._study.name + " - "
+ self._reach.name
)
def setup_sc(self):
self._undo_stack = QUndoStack()
self.undo_sc = QShortcut(QKeySequence.Undo, self)
self.redo_sc = QShortcut(QKeySequence.Redo, self)
self.copy_sc = QShortcut(QKeySequence.Copy, self)
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
self._table = {}
table = self.find(QTableView, f"tableView")
self._table = TableModel(
data = self._reach,
study = self._study,
undo = self._undo_stack,
)
table.setModel(self._table)
self._delegate_stricklers = ComboBoxDelegate(
data = self._reach,
study = self._study,
@@ -111,13 +97,23 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow):
parent=self
)
table.setItemDelegateForColumn(
3, self._delegate_stricklers
table = self.find(QTableView, f"tableView")
self._table = TableModel(
table_view = table,
table_headers = self._trad.get_dict("table_headers"),
editable_headers = [
"name", "begin_kp", "end_kp",
"begin_strickler", "end_strickler"
],
delegates = {
"begin_strickler": self._delegate_stricklers,
"end_strickler": self._delegate_stricklers,
},
data = self._reach,
undo = self._undo_stack,
opt_data = self._study
)
table.setItemDelegateForColumn(
4, self._delegate_stricklers
)
table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
@@ -128,7 +124,7 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow):
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
self.plot_layout.addWidget(self.canvas)
self.plot = PlotKPC(
self.plot = PlotKPZ(
canvas = self.canvas,
data = self._reach.reach,
toolbar = None,
@@ -155,11 +151,6 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow):
self.find(QAction, "action_sort").triggered.connect(self.sort)
self.find(QAction, "action_edit_stricklers").triggered.connect(self.edit_stricklers)
self.undo_sc.activated.connect(self.undo)
self.redo_sc.activated.connect(self.redo)
self.copy_sc.activated.connect(self.copy)
self.paste_sc.activated.connect(self.paste)
table = self.find(QTableView, f"tableView")
table.selectionModel()\
.selectionChanged\
@@ -193,7 +184,7 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow):
sec = self._frictions.get(rows[0])
highlight = (sec.begin_kp, sec.end_kp)
self.plot = PlotKPC(
self.plot = PlotKPZ(
canvas = self.canvas,
data = reach,
toolbar = None,
@@ -233,16 +224,16 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow):
row = self.index_selected_row()
self._table.move_down(row)
def copy(self):
def _copy(self):
logger.info("TODO: copy")
def paste(self):
def _paste(self):
logger.info("TODO: paste")
def undo(self):
def _undo(self):
self._table.undo()
def redo(self):
def _redo(self):
self._table.redo()
def edit_stricklers(self):
+14 -8
View File
@@ -18,13 +18,19 @@
from PyQt5.QtCore import QCoreApplication
from View.Tools.PamhyrTranslate import PamhyrTranslate
_translate = QCoreApplication.translate
table_headers = {
"name": _translate("Frictions", "Name"),
# "edge": _translate("Frictions", "Reach"),
"begin_kp": _translate("Frictions", "Begin kp (m)"),
"end_kp": _translate("Frictions", "End kp (m)"),
"begin_strickler": _translate("Frictions", "Begin strickler"),
"end_strickler": _translate("Frictions", "End strickler"),
}
class FrictionsTranslate(PamhyrTranslate):
def __init__(self):
super(FrictionsTranslate, self).__init__()
self._sub_dict["table_headers"] = {
"name": _translate("Frictions", "Name"),
# "edge": _translate("Frictions", "Reach"),
"begin_kp": _translate("Frictions", "Begin kp (m)"),
"end_kp": _translate("Frictions", "End kp (m)"),
"begin_strickler": _translate("Frictions", "Begin strickler"),
"end_strickler": _translate("Frictions", "End strickler"),
}
Loaded 100 of 208 files, more files were not shown because too many files have changed in this diff. Show more