fennil 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. fennil-1.3.0/.gitignore +6 -0
  2. fennil-1.3.0/LICENSE +21 -0
  3. fennil-1.3.0/PKG-INFO +114 -0
  4. fennil-1.3.0/README.md +79 -0
  5. fennil-1.3.0/pyproject.toml +113 -0
  6. fennil-1.3.0/src/fennil/__init__.py +1 -0
  7. fennil-1.3.0/src/fennil/app/__init__.py +5 -0
  8. fennil-1.3.0/src/fennil/app/components/__init__.py +4 -0
  9. fennil-1.3.0/src/fennil/app/components/file_browser.py +202 -0
  10. fennil-1.3.0/src/fennil/app/components/scale.py +65 -0
  11. fennil-1.3.0/src/fennil/app/core.py +330 -0
  12. fennil-1.3.0/src/fennil/app/deck/__init__.py +7 -0
  13. fennil-1.3.0/src/fennil/app/deck/builder.py +18 -0
  14. fennil-1.3.0/src/fennil/app/deck/faults.py +117 -0
  15. fennil-1.3.0/src/fennil/app/deck/mapbox.py +32 -0
  16. fennil-1.3.0/src/fennil/app/deck/primitives.py +130 -0
  17. fennil-1.3.0/src/fennil/app/deck/stations.py +25 -0
  18. fennil-1.3.0/src/fennil/app/deck/styles.py +43 -0
  19. fennil-1.3.0/src/fennil/app/deck/tde.py +38 -0
  20. fennil-1.3.0/src/fennil/app/deck/tooltips.py +21 -0
  21. fennil-1.3.0/src/fennil/app/deck/vectors.py +45 -0
  22. fennil-1.3.0/src/fennil/app/geo_projs.py +128 -0
  23. fennil-1.3.0/src/fennil/app/io.py +309 -0
  24. fennil-1.3.0/src/fennil/app/main.py +10 -0
  25. fennil-1.3.0/src/fennil/app/registry.py +91 -0
  26. fennil-1.3.0/src/fennil/app/state.py +53 -0
  27. fennil-1.3.0/src/fennil/app/viz/__init__.py +29 -0
  28. fennil-1.3.0/src/fennil/app/viz/fault_proj.py +49 -0
  29. fennil-1.3.0/src/fennil/app/viz/locs.py +38 -0
  30. fennil-1.3.0/src/fennil/app/viz/mod.py +45 -0
  31. fennil-1.3.0/src/fennil/app/viz/mog.py +38 -0
  32. fennil-1.3.0/src/fennil/app/viz/obs.py +45 -0
  33. fennil-1.3.0/src/fennil/app/viz/res.py +45 -0
  34. fennil-1.3.0/src/fennil/app/viz/res_compare.py +27 -0
  35. fennil-1.3.0/src/fennil/app/viz/rot.py +45 -0
  36. fennil-1.3.0/src/fennil/app/viz/seg.py +45 -0
  37. fennil-1.3.0/src/fennil/app/viz/slip.py +62 -0
  38. fennil-1.3.0/src/fennil/app/viz/str.py +38 -0
  39. fennil-1.3.0/src/fennil/app/viz/tde.py +41 -0
  40. fennil-1.3.0/src/fennil/app/viz/tri.py +45 -0
@@ -0,0 +1,6 @@
1
+ __pycache__/*
2
+ *.pyc
3
+
4
+ uv.lock
5
+
6
+ .env
fennil-1.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Brendan Meade
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fennil-1.3.0/PKG-INFO ADDED
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: fennil
3
+ Version: 1.3.0
4
+ Summary: Viewer for kinematic earthquake simulations
5
+ Author: Kitware, Inc.
6
+ Author-email: Brendan Meade <brendanjmeade@gmail.com>
7
+ License: MIT License
8
+ License-File: LICENSE
9
+ Keywords: Application,Framework,Interactive,Python,Web
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Web Environment
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: numpy
20
+ Requires-Dist: pandas<3
21
+ Requires-Dist: pydeck
22
+ Requires-Dist: trame-dataclass
23
+ Requires-Dist: trame-deckgl>=2.0.4
24
+ Requires-Dist: trame-vuetify
25
+ Requires-Dist: trame>=3.12
26
+ Provides-Extra: app
27
+ Requires-Dist: pywebview; extra == 'app'
28
+ Provides-Extra: dev
29
+ Requires-Dist: nox; extra == 'dev'
30
+ Requires-Dist: pre-commit; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=3; extra == 'dev'
32
+ Requires-Dist: pytest>=6; extra == 'dev'
33
+ Requires-Dist: ruff; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ ## fennil
37
+
38
+ Attempt at a much faster rebuild of
39
+ [`result_manager`](https://github.com/brendanjmeade/result_manager) for larger
40
+ [`celeri`](https://github.com/brendanjmeade/celeri) models.
41
+
42
+ Viewer for kinematic earthquake simulations
43
+
44
+ ## License
45
+
46
+ This library is OpenSource and follows the MIT License
47
+
48
+ ## Installation
49
+
50
+ Install the application/library
51
+
52
+ ```console
53
+ pip install fennil
54
+ ```
55
+
56
+ Run the application
57
+
58
+ ```console
59
+ fennil
60
+ ```
61
+
62
+ ## Mapbox token
63
+
64
+ Set a Mapbox access token so base maps render with Mapbox styles. Either export
65
+ it in your shell or place it in a local `.env` (already gitignored):
66
+
67
+ ```console
68
+ export FENNIL_MAP_BOX_TOKEN="YOUR_TOKEN_HERE"
69
+ ```
70
+
71
+ Or create a `.env` file in the project root containing:
72
+
73
+ ```
74
+ FENNIL_MAP_BOX_TOKEN=YOUR_TOKEN_HERE
75
+ ```
76
+
77
+ ## Development setup
78
+
79
+ We recommend using uv for setting up and managing a virtual environment for your
80
+ development.
81
+
82
+ ```console
83
+ # Create venv and install all dependencies
84
+ uv sync --all-extras --dev
85
+
86
+ # Activate environment
87
+ source .venv/bin/activate
88
+
89
+ # Install commit analysis
90
+ pre-commit install
91
+ pre-commit install --hook-type commit-msg
92
+
93
+ # Allow live code edit
94
+ uv pip install -e .
95
+ ```
96
+
97
+ For running tests and checks, you can run `nox`.
98
+
99
+ ```console
100
+ # run all
101
+ nox
102
+
103
+ # lint
104
+ nox -s lint
105
+
106
+ # tests
107
+ nox -s tests
108
+ ```
109
+
110
+ ## Commit message convention
111
+
112
+ Semantic release rely on
113
+ [conventional commits](https://www.conventionalcommits.org/) to generate new
114
+ releases and changelog.
fennil-1.3.0/README.md ADDED
@@ -0,0 +1,79 @@
1
+ ## fennil
2
+
3
+ Attempt at a much faster rebuild of
4
+ [`result_manager`](https://github.com/brendanjmeade/result_manager) for larger
5
+ [`celeri`](https://github.com/brendanjmeade/celeri) models.
6
+
7
+ Viewer for kinematic earthquake simulations
8
+
9
+ ## License
10
+
11
+ This library is OpenSource and follows the MIT License
12
+
13
+ ## Installation
14
+
15
+ Install the application/library
16
+
17
+ ```console
18
+ pip install fennil
19
+ ```
20
+
21
+ Run the application
22
+
23
+ ```console
24
+ fennil
25
+ ```
26
+
27
+ ## Mapbox token
28
+
29
+ Set a Mapbox access token so base maps render with Mapbox styles. Either export
30
+ it in your shell or place it in a local `.env` (already gitignored):
31
+
32
+ ```console
33
+ export FENNIL_MAP_BOX_TOKEN="YOUR_TOKEN_HERE"
34
+ ```
35
+
36
+ Or create a `.env` file in the project root containing:
37
+
38
+ ```
39
+ FENNIL_MAP_BOX_TOKEN=YOUR_TOKEN_HERE
40
+ ```
41
+
42
+ ## Development setup
43
+
44
+ We recommend using uv for setting up and managing a virtual environment for your
45
+ development.
46
+
47
+ ```console
48
+ # Create venv and install all dependencies
49
+ uv sync --all-extras --dev
50
+
51
+ # Activate environment
52
+ source .venv/bin/activate
53
+
54
+ # Install commit analysis
55
+ pre-commit install
56
+ pre-commit install --hook-type commit-msg
57
+
58
+ # Allow live code edit
59
+ uv pip install -e .
60
+ ```
61
+
62
+ For running tests and checks, you can run `nox`.
63
+
64
+ ```console
65
+ # run all
66
+ nox
67
+
68
+ # lint
69
+ nox -s lint
70
+
71
+ # tests
72
+ nox -s tests
73
+ ```
74
+
75
+ ## Commit message convention
76
+
77
+ Semantic release rely on
78
+ [conventional commits](https://www.conventionalcommits.org/) to generate new
79
+ releases and changelog.
@@ -0,0 +1,113 @@
1
+ [project]
2
+ name = "fennil"
3
+ version = "1.3.0"
4
+ description = "Viewer for kinematic earthquake simulations"
5
+ authors = [
6
+ {name = "Brendan Meade", email = "brendanjmeade@gmail.com"},
7
+ {name = "Kitware, Inc."},
8
+ ]
9
+ dependencies = [
10
+ "trame>=3.12",
11
+ "trame-vuetify",
12
+ "trame-deckgl>=2.0.4",
13
+ "trame-dataclass",
14
+ "pydeck",
15
+ "numpy",
16
+ "pandas<3",
17
+ ]
18
+ requires-python = ">=3.10"
19
+ readme = "README.md"
20
+ license = {text = "MIT License"}
21
+ keywords = ["Python", "Interactive", "Web", "Application", "Framework"]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Environment :: Web Environment",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Natural Language :: English",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3 :: Only",
29
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
30
+ "Topic :: Software Development :: Libraries :: Python Modules",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ app = [
35
+ "pywebview",
36
+ ]
37
+ dev = [
38
+ "pre-commit",
39
+ "ruff",
40
+ "pytest >=6",
41
+ "pytest-cov >=3",
42
+ "nox",
43
+ ]
44
+
45
+ [project.scripts]
46
+ fennil = "fennil.app:main"
47
+
48
+ [build-system]
49
+ requires = ["hatchling"]
50
+ build-backend = "hatchling.build"
51
+
52
+
53
+ [tool.hatch.build]
54
+ include = [
55
+ "/src/fennil/**/*.py",
56
+ "/src/fennil/**/*.js",
57
+ "/src/fennil/**/*.css",
58
+ ]
59
+
60
+ [tool.hatch.build.targets.wheel]
61
+ packages = [
62
+ "src/fennil",
63
+ ]
64
+
65
+ [tool.ruff]
66
+
67
+ [tool.ruff.lint]
68
+ extend-select = [
69
+ "ARG", # flake8-unused-arguments
70
+ "B", # flake8-bugbear
71
+ "C4", # flake8-comprehensions
72
+ "EM", # flake8-errmsg
73
+ "EXE", # flake8-executable
74
+ "G", # flake8-logging-format
75
+ "I", # isort
76
+ "ICN", # flake8-import-conventions
77
+ "NPY", # NumPy specific rules
78
+ "PD", # pandas-vet
79
+ "PGH", # pygrep-hooks
80
+ "PIE", # flake8-pie
81
+ "PL", # pylint
82
+ "PT", # flake8-pytest-style
83
+ "PTH", # flake8-use-pathlib
84
+ "RET", # flake8-return
85
+ "RUF", # Ruff-specific
86
+ "SIM", # flake8-simplify
87
+ "T20", # flake8-print
88
+ "UP", # pyupgrade
89
+ "YTT", # flake8-2020
90
+ ]
91
+ ignore = [
92
+ "PLR09", # Too many <...>
93
+ "PLR2004", # Magic value used in comparison
94
+ "ISC001", # Conflicts with formatter
95
+ ]
96
+ isort.required-imports = []
97
+
98
+ [tool.ruff.lint.per-file-ignores]
99
+ "tests/**" = ["T20"]
100
+ "noxfile.py" = ["T20"]
101
+ "src/**" = ["SIM117"]
102
+
103
+ [tool.semantic_release]
104
+ version_toml = [
105
+ "pyproject.toml:project.version",
106
+ ]
107
+ version_variables = [
108
+ "src/fennil/__init__.py:__version__",
109
+ ]
110
+
111
+ [tool.semantic_release.publish]
112
+ dist_glob_patterns = ["dist/*"]
113
+ upload_to_vcs_release = true
@@ -0,0 +1 @@
1
+ __version__ = "1.3.0"
@@ -0,0 +1,5 @@
1
+ from .main import main
2
+
3
+ __all__ = [
4
+ "main",
5
+ ]
@@ -0,0 +1,4 @@
1
+ from .file_browser import FileBrowser
2
+ from .scale import Scale
3
+
4
+ __all__ = ["FileBrowser", "Scale"]
@@ -0,0 +1,202 @@
1
+ from pathlib import Path
2
+
3
+ from trame.widgets import dataclass, html
4
+ from trame.widgets import vuetify3 as v3
5
+ from trame_dataclass.core import StateDataModel
6
+
7
+ from fennil.app.io import is_valid_data_folder
8
+
9
+ FILE_BROWSER_HEADERS = [
10
+ {"title": "Name", "align": "start", "key": "name", "sortable": False},
11
+ {"title": "Type", "align": "start", "key": "type", "sortable": False},
12
+ ]
13
+
14
+
15
+ class FileBrowserState(StateDataModel):
16
+ show: bool = False
17
+ current: str = "/"
18
+ listing: list
19
+ active: int = -1
20
+ error: str | None
21
+ headers: list = FILE_BROWSER_HEADERS
22
+
23
+
24
+ class FileBrowser(dataclass.Provider):
25
+ def __init__(self, current_directory=None, on_open=None, **kwargs):
26
+ if current_directory is None:
27
+ current_directory = Path.cwd()
28
+ self._on_open = on_open
29
+ self._state = None
30
+ super().__init__(name="browser", **kwargs)
31
+ self._state = FileBrowserState(self.server, current=str(current_directory))
32
+ self.instance = self._state._id
33
+ self.update_listing()
34
+
35
+ with (
36
+ self,
37
+ v3.VDialog(
38
+ v_model="browser.show",
39
+ max_width="900",
40
+ persistent=True,
41
+ ),
42
+ ):
43
+ with v3.VCard(title="Select data folder", rounded="lg"):
44
+ with v3.VCardText():
45
+ with v3.VRow(dense=True, classes="pb-1 align-center"):
46
+ v3.VBtn(
47
+ icon="mdi-home",
48
+ variant="text",
49
+ size="small",
50
+ click=self.go_home,
51
+ )
52
+ v3.VBtn(
53
+ icon="mdi-folder-upload-outline",
54
+ variant="text",
55
+ size="small",
56
+ click=self.go_parent,
57
+ )
58
+ v3.VTextField(
59
+ v_model="browser.current",
60
+ hide_details=True,
61
+ density="compact",
62
+ variant="outlined",
63
+ readonly=True,
64
+ classes="ml-2 flex-grow-1",
65
+ )
66
+ with v3.VDataTable(
67
+ density="compact",
68
+ fixed_header=True,
69
+ headers=["browser.header"],
70
+ items=["browser.listing"],
71
+ height="50vh",
72
+ style="user-select: none; cursor: pointer;",
73
+ items_per_page=-1,
74
+ ):
75
+ v3.Template(v_slot_bottom=True)
76
+ with v3.Template(v_slot_item="{ item }"):
77
+ with v3.VDataTableRow(
78
+ item=["item"],
79
+ click=(self.select_entry, "[item]"),
80
+ dblclick=(self.open_entry, "[item]"),
81
+ classes=[
82
+ "{ 'bg-grey-lighten-3': item.index === browser.active }"
83
+ ],
84
+ ):
85
+ with v3.Template(raw_attrs=["v-slot:item.name"]):
86
+ with html.Div(classes="d-flex align-center"):
87
+ v3.VIcon(
88
+ "{{ item.icon }}",
89
+ size="small",
90
+ classes="mr-2",
91
+ )
92
+ html.Div("{{ item.name }}")
93
+ with v3.Template(raw_attrs=["v-slot:item.type"]):
94
+ html.Div("{{ item.type }}")
95
+
96
+ with v3.VCardActions(classes="pa-3"):
97
+ html.Div(
98
+ "{{ browser.error }}",
99
+ v_if="browser.error",
100
+ classes="text-error text-caption",
101
+ )
102
+ v3.VSpacer()
103
+ v3.VBtn(
104
+ text="Cancel",
105
+ variant="flat",
106
+ click="browser.show = false",
107
+ )
108
+ v3.VBtn(
109
+ text="Select folder",
110
+ color="primary",
111
+ variant="flat",
112
+ click=self.select_folder,
113
+ )
114
+
115
+ def update_listing(self):
116
+ current = Path(self._state.current)
117
+ if not current.exists():
118
+ current = Path.home()
119
+ self._state.current = str(current.resolve())
120
+
121
+ entries = []
122
+ for entry in current.iterdir():
123
+ name = entry.name
124
+ if name.startswith("."):
125
+ continue
126
+ if entry.is_dir():
127
+ entries.append(
128
+ {
129
+ "name": name,
130
+ "type": "directory",
131
+ "icon": "mdi-folder",
132
+ }
133
+ )
134
+ elif entry.is_file():
135
+ entries.append(
136
+ {
137
+ "name": name,
138
+ "type": "file",
139
+ "icon": "mdi-file-document-outline",
140
+ }
141
+ )
142
+ entries.sort(key=lambda item: (item["type"] != "directory", item["name"]))
143
+ listing = [{**item, "index": idx} for idx, item in enumerate(entries)]
144
+ self._state.listing = listing
145
+ self._state.active = -1
146
+
147
+ def select_entry(self, entry):
148
+ self._state.active = entry.get("index", -1) if entry else -1
149
+
150
+ def open_entry(self, entry):
151
+ if not entry or entry.get("type") != "directory":
152
+ return
153
+ current = Path(self._state.current)
154
+ next_path = (current / entry.get("name")).resolve()
155
+
156
+ if is_valid_data_folder(next_path):
157
+ self._state.error = None
158
+ self._state.show = False
159
+ if self._on_open:
160
+ self._on_open(next_path)
161
+ return
162
+
163
+ self._state.current = str(next_path)
164
+ self.update_listing()
165
+
166
+ def go_home(self):
167
+ self._state.current = str(Path.home().resolve())
168
+ self.update_listing()
169
+
170
+ def go_parent(self):
171
+ current = Path(self._state.current)
172
+ parent = current.parent if current.parent != current else current
173
+ self._state.current = str(parent.resolve())
174
+ self.update_listing()
175
+
176
+ def open(self, existing_path=None):
177
+ if existing_path:
178
+ self._state.current = str(Path(existing_path).resolve())
179
+
180
+ self._state.show = True
181
+ self._state.error = None
182
+ self.update_listing()
183
+
184
+ def select_folder(self):
185
+ current = Path(self._state.current)
186
+ folder_path = current
187
+ active_idx = self._state.active
188
+ listing = self._state.listing
189
+ if active_idx is not None and active_idx >= 0:
190
+ if active_idx >= len(listing):
191
+ active_idx = -1
192
+ else:
193
+ entry = listing[active_idx]
194
+ if entry.get("type") == "directory":
195
+ folder_path = (current / entry.get("name")).resolve()
196
+ if not is_valid_data_folder(folder_path):
197
+ self._state.error = "Selected folder is missing required model_*.csv files."
198
+ return
199
+ self._state.error = None
200
+ self._state.show = False
201
+ if self._on_open:
202
+ self._on_open(folder_path)
@@ -0,0 +1,65 @@
1
+ from trame.widgets import vuetify3 as v3
2
+
3
+ v3.enable_lab()
4
+
5
+
6
+ class Scale(v3.VCol):
7
+ def __init__(self, name="scale", **kwargs):
8
+ self._name = name
9
+ super().__init__(**kwargs)
10
+
11
+ with self:
12
+ with v3.VRow(dense=True, no_gutter=True):
13
+ with v3.VCol():
14
+ v3.VBtn(
15
+ "50%",
16
+ click=(self.scale, "[0.5]"),
17
+ size="small",
18
+ variant="outlined",
19
+ block=True,
20
+ )
21
+ with v3.VCol():
22
+ v3.VBtn(
23
+ "90%",
24
+ click=(self.scale, "[0.9]"),
25
+ size="small",
26
+ variant="outlined",
27
+ block=True,
28
+ )
29
+ with v3.VCol():
30
+ v3.VBtn(
31
+ "110%",
32
+ click=(self.scale, "[1.1]"),
33
+ size="small",
34
+ variant="outlined",
35
+ block=True,
36
+ )
37
+ with v3.VCol():
38
+ v3.VBtn(
39
+ "200%",
40
+ click=(self.scale, "[2]"),
41
+ size="small",
42
+ variant="outlined",
43
+ block=True,
44
+ )
45
+ with v3.VRow():
46
+ with v3.VCol(classes="pt-0"):
47
+ v3.VNumberInput(
48
+ click_prepend=self.reset,
49
+ prepend_icon="mdi-vector-line",
50
+ v_model=(name, 1),
51
+ precision=6,
52
+ min=[0.0001],
53
+ max=[10000],
54
+ step=[0.01],
55
+ density="compact",
56
+ hide_details=True,
57
+ variant="outlined",
58
+ control_variant="split",
59
+ )
60
+
61
+ def scale(self, value):
62
+ self.state[self._name] *= value
63
+
64
+ def reset(self):
65
+ self.state[self._name] = 1