lsurf 1.0.0__py3-none-any.whl

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 (180) hide show
  1. lsurf/__init__.py +471 -0
  2. lsurf/analysis/__init__.py +107 -0
  3. lsurf/analysis/healpix_utils.py +418 -0
  4. lsurf/analysis/sphere_viz.py +1280 -0
  5. lsurf/cli/__init__.py +48 -0
  6. lsurf/cli/build.py +398 -0
  7. lsurf/cli/config_schema.py +318 -0
  8. lsurf/cli/gui_cmd.py +76 -0
  9. lsurf/cli/interactive.py +850 -0
  10. lsurf/cli/main.py +81 -0
  11. lsurf/cli/run.py +806 -0
  12. lsurf/detectors/__init__.py +266 -0
  13. lsurf/detectors/analysis.py +289 -0
  14. lsurf/detectors/base.py +284 -0
  15. lsurf/detectors/constant_size_rings.py +485 -0
  16. lsurf/detectors/directional.py +45 -0
  17. lsurf/detectors/extended/__init__.py +73 -0
  18. lsurf/detectors/extended/local_sphere.py +353 -0
  19. lsurf/detectors/extended/recording_sphere.py +368 -0
  20. lsurf/detectors/planar.py +45 -0
  21. lsurf/detectors/protocol.py +187 -0
  22. lsurf/detectors/recording_spheres.py +63 -0
  23. lsurf/detectors/results.py +1140 -0
  24. lsurf/detectors/small/__init__.py +79 -0
  25. lsurf/detectors/small/directional.py +330 -0
  26. lsurf/detectors/small/planar.py +401 -0
  27. lsurf/detectors/small/spherical.py +450 -0
  28. lsurf/detectors/spherical.py +45 -0
  29. lsurf/geometry/__init__.py +199 -0
  30. lsurf/geometry/builder.py +478 -0
  31. lsurf/geometry/cell.py +228 -0
  32. lsurf/geometry/cell_geometry.py +247 -0
  33. lsurf/geometry/detector_arrays.py +1785 -0
  34. lsurf/geometry/geometry.py +222 -0
  35. lsurf/geometry/surface_analysis.py +375 -0
  36. lsurf/geometry/validation.py +91 -0
  37. lsurf/gui/__init__.py +51 -0
  38. lsurf/gui/app.py +903 -0
  39. lsurf/gui/core/__init__.py +39 -0
  40. lsurf/gui/core/scene.py +343 -0
  41. lsurf/gui/core/simulation.py +264 -0
  42. lsurf/gui/renderers/__init__.py +40 -0
  43. lsurf/gui/renderers/ray_renderer.py +353 -0
  44. lsurf/gui/renderers/source_renderer.py +505 -0
  45. lsurf/gui/renderers/surface_renderer.py +477 -0
  46. lsurf/gui/views/__init__.py +48 -0
  47. lsurf/gui/views/config_editor.py +3199 -0
  48. lsurf/gui/views/properties.py +257 -0
  49. lsurf/gui/views/results.py +291 -0
  50. lsurf/gui/views/scene_tree.py +180 -0
  51. lsurf/gui/views/viewport_3d.py +555 -0
  52. lsurf/gui/views/visualizations.py +712 -0
  53. lsurf/materials/__init__.py +169 -0
  54. lsurf/materials/base/__init__.py +64 -0
  55. lsurf/materials/base/full_inhomogeneous.py +208 -0
  56. lsurf/materials/base/grid_inhomogeneous.py +319 -0
  57. lsurf/materials/base/homogeneous.py +342 -0
  58. lsurf/materials/base/material_field.py +527 -0
  59. lsurf/materials/base/simple_inhomogeneous.py +418 -0
  60. lsurf/materials/base/spectral_inhomogeneous.py +497 -0
  61. lsurf/materials/implementations/__init__.py +120 -0
  62. lsurf/materials/implementations/data/alpha_values_typical_atmosphere_updated.txt +24 -0
  63. lsurf/materials/implementations/duct_atmosphere.py +390 -0
  64. lsurf/materials/implementations/exponential_atmosphere.py +435 -0
  65. lsurf/materials/implementations/gaussian_lens.py +120 -0
  66. lsurf/materials/implementations/interpolated_data.py +123 -0
  67. lsurf/materials/implementations/layered_atmosphere.py +134 -0
  68. lsurf/materials/implementations/linear_gradient.py +109 -0
  69. lsurf/materials/implementations/linsley_atmosphere.py +764 -0
  70. lsurf/materials/implementations/standard_materials.py +126 -0
  71. lsurf/materials/implementations/turbulent_atmosphere.py +135 -0
  72. lsurf/materials/implementations/us_standard_atmosphere.py +149 -0
  73. lsurf/materials/utils/__init__.py +77 -0
  74. lsurf/materials/utils/constants.py +45 -0
  75. lsurf/materials/utils/device_functions.py +117 -0
  76. lsurf/materials/utils/dispersion.py +160 -0
  77. lsurf/materials/utils/factories.py +142 -0
  78. lsurf/propagation/__init__.py +91 -0
  79. lsurf/propagation/detector_gpu.py +67 -0
  80. lsurf/propagation/gpu_device_rays.py +294 -0
  81. lsurf/propagation/kernels/__init__.py +175 -0
  82. lsurf/propagation/kernels/absorption/__init__.py +61 -0
  83. lsurf/propagation/kernels/absorption/grid.py +240 -0
  84. lsurf/propagation/kernels/absorption/simple.py +232 -0
  85. lsurf/propagation/kernels/absorption/spectral.py +410 -0
  86. lsurf/propagation/kernels/detection/__init__.py +64 -0
  87. lsurf/propagation/kernels/detection/protocol.py +102 -0
  88. lsurf/propagation/kernels/detection/spherical.py +255 -0
  89. lsurf/propagation/kernels/device_functions.py +790 -0
  90. lsurf/propagation/kernels/fresnel/__init__.py +64 -0
  91. lsurf/propagation/kernels/fresnel/protocol.py +97 -0
  92. lsurf/propagation/kernels/fresnel/standard.py +258 -0
  93. lsurf/propagation/kernels/intersection/__init__.py +79 -0
  94. lsurf/propagation/kernels/intersection/annular_plane.py +207 -0
  95. lsurf/propagation/kernels/intersection/bounded_plane.py +205 -0
  96. lsurf/propagation/kernels/intersection/plane.py +166 -0
  97. lsurf/propagation/kernels/intersection/protocol.py +95 -0
  98. lsurf/propagation/kernels/intersection/signed_distance.py +742 -0
  99. lsurf/propagation/kernels/intersection/sphere.py +190 -0
  100. lsurf/propagation/kernels/propagation/__init__.py +85 -0
  101. lsurf/propagation/kernels/propagation/grid.py +527 -0
  102. lsurf/propagation/kernels/propagation/protocol.py +105 -0
  103. lsurf/propagation/kernels/propagation/simple.py +460 -0
  104. lsurf/propagation/kernels/propagation/spectral.py +875 -0
  105. lsurf/propagation/kernels/registry.py +331 -0
  106. lsurf/propagation/kernels/surface/__init__.py +72 -0
  107. lsurf/propagation/kernels/surface/bisection.py +232 -0
  108. lsurf/propagation/kernels/surface/detection.py +402 -0
  109. lsurf/propagation/kernels/surface/reduction.py +166 -0
  110. lsurf/propagation/propagator_protocol.py +222 -0
  111. lsurf/propagation/propagators/__init__.py +101 -0
  112. lsurf/propagation/propagators/detector_handler.py +354 -0
  113. lsurf/propagation/propagators/factory.py +200 -0
  114. lsurf/propagation/propagators/fresnel_handler.py +305 -0
  115. lsurf/propagation/propagators/gpu_gradient.py +566 -0
  116. lsurf/propagation/propagators/gpu_surface_propagator.py +707 -0
  117. lsurf/propagation/propagators/gradient.py +429 -0
  118. lsurf/propagation/propagators/intersection_handler.py +327 -0
  119. lsurf/propagation/propagators/material_propagator.py +398 -0
  120. lsurf/propagation/propagators/signed_distance_handler.py +522 -0
  121. lsurf/propagation/propagators/spectral_gpu_gradient.py +553 -0
  122. lsurf/propagation/propagators/surface_interaction.py +616 -0
  123. lsurf/propagation/propagators/surface_propagator.py +719 -0
  124. lsurf/py.typed +1 -0
  125. lsurf/simulation/__init__.py +70 -0
  126. lsurf/simulation/config.py +164 -0
  127. lsurf/simulation/orchestrator.py +462 -0
  128. lsurf/simulation/result.py +299 -0
  129. lsurf/simulation/simulation.py +262 -0
  130. lsurf/sources/__init__.py +128 -0
  131. lsurf/sources/base.py +264 -0
  132. lsurf/sources/collimated.py +252 -0
  133. lsurf/sources/custom.py +409 -0
  134. lsurf/sources/diverging.py +228 -0
  135. lsurf/sources/gaussian.py +272 -0
  136. lsurf/sources/parallel_from_positions.py +197 -0
  137. lsurf/sources/point.py +172 -0
  138. lsurf/sources/uniform_diverging.py +258 -0
  139. lsurf/surfaces/__init__.py +184 -0
  140. lsurf/surfaces/cpu/__init__.py +50 -0
  141. lsurf/surfaces/cpu/curved_wave.py +463 -0
  142. lsurf/surfaces/cpu/gerstner_wave.py +381 -0
  143. lsurf/surfaces/cpu/wave_params.py +118 -0
  144. lsurf/surfaces/gpu/__init__.py +72 -0
  145. lsurf/surfaces/gpu/annular_plane.py +453 -0
  146. lsurf/surfaces/gpu/bounded_plane.py +390 -0
  147. lsurf/surfaces/gpu/curved_wave.py +483 -0
  148. lsurf/surfaces/gpu/gerstner_wave.py +377 -0
  149. lsurf/surfaces/gpu/multi_curved_wave.py +520 -0
  150. lsurf/surfaces/gpu/plane.py +299 -0
  151. lsurf/surfaces/gpu/recording_sphere.py +587 -0
  152. lsurf/surfaces/gpu/sphere.py +311 -0
  153. lsurf/surfaces/protocol.py +336 -0
  154. lsurf/surfaces/registry.py +373 -0
  155. lsurf/utilities/__init__.py +175 -0
  156. lsurf/utilities/detector_analysis.py +814 -0
  157. lsurf/utilities/fresnel.py +628 -0
  158. lsurf/utilities/interactions.py +1215 -0
  159. lsurf/utilities/propagation.py +602 -0
  160. lsurf/utilities/ray_data.py +532 -0
  161. lsurf/utilities/recording_sphere.py +745 -0
  162. lsurf/utilities/time_spread.py +463 -0
  163. lsurf/visualization/__init__.py +329 -0
  164. lsurf/visualization/absorption_plots.py +334 -0
  165. lsurf/visualization/atmospheric_plots.py +754 -0
  166. lsurf/visualization/common.py +348 -0
  167. lsurf/visualization/detector_plots.py +1350 -0
  168. lsurf/visualization/detector_sphere_plots.py +1173 -0
  169. lsurf/visualization/fresnel_plots.py +1061 -0
  170. lsurf/visualization/ocean_simulation_plots.py +999 -0
  171. lsurf/visualization/polarization_plots.py +916 -0
  172. lsurf/visualization/raytracing_plots.py +1521 -0
  173. lsurf/visualization/ring_detector_plots.py +1867 -0
  174. lsurf/visualization/time_spread_plots.py +531 -0
  175. lsurf-1.0.0.dist-info/METADATA +381 -0
  176. lsurf-1.0.0.dist-info/RECORD +180 -0
  177. lsurf-1.0.0.dist-info/WHEEL +5 -0
  178. lsurf-1.0.0.dist-info/entry_points.txt +2 -0
  179. lsurf-1.0.0.dist-info/licenses/LICENSE +32 -0
  180. lsurf-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,318 @@
1
+ # The Clear BSD License
2
+ #
3
+ # Copyright (c) 2026 Tobias Heibges
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted (subject to the limitations in the disclaimer
8
+ # below) provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ #
13
+ # * Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ #
17
+ # * Neither the name of the copyright holder nor the names of its
18
+ # contributors may be used to endorse or promote products derived from this
19
+ # software without specific prior written permission.
20
+ #
21
+ # NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
22
+ # THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
23
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
26
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
30
+ # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+
34
+ """Pydantic models for L-SURF configuration file validation.
35
+
36
+ This module defines the schema for YAML/TOML configuration files used
37
+ to specify ray tracing simulations.
38
+ """
39
+
40
+ from typing import Any, Literal
41
+
42
+ from pydantic import BaseModel, Field, field_validator, model_validator
43
+
44
+ # Import SimulationConfig from the library - used directly in CLI config
45
+ from ..simulation import SimulationConfig
46
+
47
+
48
+ # =============================================================================
49
+ # Media/Material Configuration
50
+ # =============================================================================
51
+
52
+
53
+ class MediaConfig(BaseModel):
54
+ """Configuration for a material/medium."""
55
+
56
+ type: Literal[
57
+ "vacuum",
58
+ "air",
59
+ "water",
60
+ "glass",
61
+ "homogeneous",
62
+ "exponential_atmosphere",
63
+ "duct_atmosphere",
64
+ "sellmeier",
65
+ "cauchy",
66
+ ]
67
+ params: dict[str, Any] = Field(default_factory=dict)
68
+
69
+
70
+ # =============================================================================
71
+ # Source Configuration
72
+ # =============================================================================
73
+
74
+
75
+ class SourceConfig(BaseModel):
76
+ """Configuration for a ray source."""
77
+
78
+ type: Literal[
79
+ "point",
80
+ "collimated_beam",
81
+ "diverging_beam",
82
+ "gaussian_beam",
83
+ "parallel_from_positions",
84
+ "custom",
85
+ ]
86
+ params: dict[str, Any] = Field(default_factory=dict)
87
+
88
+ @field_validator("params")
89
+ @classmethod
90
+ def validate_source_params(cls, v: dict[str, Any], info) -> dict[str, Any]:
91
+ """Validate source parameters based on type."""
92
+ source_type = info.data.get("type")
93
+ required_params: dict[str, list[str]] = {
94
+ "point": ["position", "num_rays", "wavelength"],
95
+ "collimated_beam": [
96
+ "center",
97
+ "direction",
98
+ "beam_radius",
99
+ "num_rays",
100
+ "wavelength",
101
+ ],
102
+ "diverging_beam": [
103
+ "origin",
104
+ "mean_direction",
105
+ "divergence_angle",
106
+ "num_rays",
107
+ "wavelength",
108
+ ],
109
+ "gaussian_beam": [
110
+ "waist_position",
111
+ "direction",
112
+ "waist_radius",
113
+ "num_rays",
114
+ "wavelength",
115
+ ],
116
+ "parallel_from_positions": ["positions", "direction", "wavelength"],
117
+ "custom": ["positions", "directions", "wavelengths", "intensities"],
118
+ }
119
+ if source_type and source_type in required_params:
120
+ missing = [p for p in required_params[source_type] if p not in v]
121
+ if missing:
122
+ raise ValueError(
123
+ f"Missing required parameters for {source_type}: {missing}"
124
+ )
125
+ return v
126
+
127
+
128
+ # =============================================================================
129
+ # Surface Configuration
130
+ # =============================================================================
131
+
132
+
133
+ class SurfaceConfig(BaseModel):
134
+ """Configuration for a surface."""
135
+
136
+ name: str
137
+ type: Literal[
138
+ "plane",
139
+ "bounded_plane",
140
+ "annular_plane",
141
+ "sphere",
142
+ "gerstner_wave",
143
+ "curved_wave",
144
+ "multi_curved_wave",
145
+ ]
146
+ role: Literal["optical", "detector", "absorber"]
147
+ front_medium: str | None = None
148
+ back_medium: str | None = None
149
+ params: dict[str, Any] = Field(default_factory=dict)
150
+
151
+
152
+ # =============================================================================
153
+ # Detector Configuration
154
+ # =============================================================================
155
+
156
+
157
+ class DetectorConfig(BaseModel):
158
+ """Configuration for a detector."""
159
+
160
+ name: str
161
+ type: Literal[
162
+ "spherical",
163
+ "planar",
164
+ "directional",
165
+ "recording_sphere",
166
+ "local_recording_sphere",
167
+ ]
168
+ params: dict[str, Any] = Field(default_factory=dict)
169
+
170
+ @field_validator("params")
171
+ @classmethod
172
+ def validate_detector_params(cls, v: dict[str, Any], info) -> dict[str, Any]:
173
+ """Validate detector parameters based on type."""
174
+ detector_type = info.data.get("type")
175
+
176
+ # recording_sphere has flexible parameters:
177
+ # - Either 'altitude' alone (uses defaults for earth_center/earth_radius)
178
+ # - Or 'center' + 'radius' (radius can be altitude or total sphere radius)
179
+ # - Or 'altitude' + 'earth_center' + optional 'earth_radius'
180
+ if detector_type == "recording_sphere":
181
+ has_altitude = "altitude" in v
182
+ has_center_radius = "center" in v and "radius" in v
183
+ has_radius_only = "radius" in v and "center" not in v
184
+ if not (has_altitude or has_center_radius or has_radius_only):
185
+ raise ValueError(
186
+ "recording_sphere requires either 'altitude', 'radius', "
187
+ "or 'center' + 'radius'"
188
+ )
189
+ return v
190
+
191
+ required_params: dict[str, list[str]] = {
192
+ "spherical": ["center", "radius"],
193
+ "planar": ["center", "normal", "width", "height"],
194
+ "directional": ["position", "direction", "acceptance_angle", "radius"],
195
+ "local_recording_sphere": ["radius"],
196
+ }
197
+ if detector_type and detector_type in required_params:
198
+ missing = [p for p in required_params[detector_type] if p not in v]
199
+ if missing:
200
+ raise ValueError(
201
+ f"Missing required parameters for {detector_type}: {missing}"
202
+ )
203
+ return v
204
+
205
+
206
+ # =============================================================================
207
+ # Output Configuration
208
+ # =============================================================================
209
+
210
+
211
+ class OutputConfig(BaseModel):
212
+ """Output configuration."""
213
+
214
+ directory: str = "./results"
215
+ format: Literal["hdf5", "numpy", "csv"] = "hdf5"
216
+ save_ray_paths: bool = False
217
+ save_statistics: bool = True
218
+ prefix: str = "simulation"
219
+
220
+
221
+ # =============================================================================
222
+ # Root Configuration
223
+ # =============================================================================
224
+
225
+
226
+ class LSURFConfig(BaseModel):
227
+ """Root configuration model for L-SURF simulations.
228
+
229
+ Example YAML:
230
+ version: "1.0"
231
+ media:
232
+ atmosphere:
233
+ type: exponential_atmosphere
234
+ params:
235
+ scale_height: 8500
236
+ ocean:
237
+ type: water
238
+ background: atmosphere
239
+ source:
240
+ type: collimated_beam
241
+ params:
242
+ center: [0, 0, 100000]
243
+ direction: [0, 0, -1]
244
+ beam_radius: 0.005
245
+ num_rays: 100000
246
+ wavelength: 532e-9
247
+ surfaces:
248
+ - name: ocean_surface
249
+ type: curved_wave
250
+ role: optical
251
+ front_medium: atmosphere
252
+ back_medium: ocean
253
+ params:
254
+ center: [0, 0, -6.371e6]
255
+ radius: 6.371e6
256
+ detectors:
257
+ - name: sky_detector
258
+ type: recording_sphere
259
+ params:
260
+ center: [0, 0, -6.371e6]
261
+ radius: 6.404e6
262
+ simulation:
263
+ step_size: 100.0
264
+ max_bounces: 1
265
+ output:
266
+ directory: ./results
267
+ format: hdf5
268
+ """
269
+
270
+ version: str = "1.0"
271
+ media: dict[str, MediaConfig] = Field(default_factory=dict)
272
+ background: str | None = None
273
+ source: SourceConfig
274
+ surfaces: list[SurfaceConfig] = Field(default_factory=list)
275
+ detectors: list[DetectorConfig] = Field(default_factory=list)
276
+ simulation: SimulationConfig = Field(default_factory=SimulationConfig)
277
+ output: OutputConfig = Field(default_factory=OutputConfig)
278
+
279
+ @field_validator("simulation", mode="before")
280
+ @classmethod
281
+ def parse_simulation_config(cls, v):
282
+ """Convert dict to SimulationConfig if needed."""
283
+ if isinstance(v, dict):
284
+ return SimulationConfig(**v)
285
+ return v
286
+
287
+ @model_validator(mode="after")
288
+ def validate_media_references(self) -> "LSURFConfig":
289
+ """Validate that all media references exist."""
290
+ defined_media = set(self.media.keys())
291
+
292
+ # Check background reference
293
+ if self.background and self.background not in defined_media:
294
+ raise ValueError(
295
+ f"Background medium '{self.background}' not defined in media section"
296
+ )
297
+
298
+ # Check surface media references
299
+ for surface in self.surfaces:
300
+ if surface.front_medium and surface.front_medium not in defined_media:
301
+ raise ValueError(
302
+ f"Surface '{surface.name}' references undefined medium "
303
+ f"'{surface.front_medium}'"
304
+ )
305
+ if surface.back_medium and surface.back_medium not in defined_media:
306
+ raise ValueError(
307
+ f"Surface '{surface.name}' references undefined medium "
308
+ f"'{surface.back_medium}'"
309
+ )
310
+
311
+ return self
312
+
313
+ @model_validator(mode="after")
314
+ def validate_has_geometry(self) -> "LSURFConfig":
315
+ """Validate that at least one surface or detector is defined."""
316
+ if not self.surfaces and not self.detectors:
317
+ raise ValueError("At least one surface or detector must be defined")
318
+ return self
lsurf/cli/gui_cmd.py ADDED
@@ -0,0 +1,76 @@
1
+ # The Clear BSD License
2
+ #
3
+ # Copyright (c) 2026 Tobias Heibges
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted (subject to the limitations in the disclaimer
8
+ # below) provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ #
13
+ # * Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ #
17
+ # * Neither the name of the copyright holder nor the names of its
18
+ # contributors may be used to endorse or promote products derived from this
19
+ # software without specific prior written permission.
20
+ #
21
+ # NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
22
+ # THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
23
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
26
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
30
+ # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+
34
+ """GUI command for L-SURF CLI.
35
+
36
+ Launches the interactive 3D visualization GUI.
37
+ """
38
+
39
+ from pathlib import Path
40
+
41
+ import click
42
+
43
+
44
+ @click.command()
45
+ @click.argument("config_file", required=False, type=click.Path(exists=True))
46
+ @click.pass_context
47
+ def gui(ctx: click.Context, config_file: str | None) -> None:
48
+ """Launch the interactive 3D visualization GUI.
49
+
50
+ Optionally load a configuration file (YAML or TOML) on startup.
51
+
52
+ \b
53
+ Examples:
54
+ # Launch empty GUI
55
+ lsurf gui
56
+
57
+ # Launch with a configuration file
58
+ lsurf gui simulation.yaml
59
+ lsurf gui ocean_config.toml
60
+ """
61
+ try:
62
+ from lsurf.gui import launch_gui
63
+ except ImportError as e:
64
+ raise click.ClickException(
65
+ f"GUI dependencies not installed. Install with: pip install 'lsurf[gui]'\n"
66
+ f"Error: {e}"
67
+ ) from e
68
+
69
+ config_path = Path(config_file) if config_file else None
70
+
71
+ if ctx.obj.get("verbose"):
72
+ click.echo("Launching L-SURF GUI...")
73
+ if config_path:
74
+ click.echo(f"Loading configuration: {config_path}")
75
+
76
+ launch_gui(config_path)