RSSeismicScripting 3.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 (94) hide show
  1. rsseismic/Application.py +196 -0
  2. rsseismic/ApplicationManager.py +111 -0
  3. rsseismic/BedrockLayer.py +29 -0
  4. rsseismic/MeanProfile.py +169 -0
  5. rsseismic/MeanProfileSoilLayer.py +119 -0
  6. rsseismic/Model.py +204 -0
  7. rsseismic/Motions.py +140 -0
  8. rsseismic/ProfileGenerationSettings.py +29 -0
  9. rsseismic/Profiles.py +186 -0
  10. rsseismic/ProjectSettings.py +46 -0
  11. rsseismic/ReferenceCurve.py +73 -0
  12. rsseismic/Results.py +419 -0
  13. rsseismic/SoilLayer.py +34 -0
  14. rsseismic/SoilLayerCurve.py +124 -0
  15. rsseismic/SoilLayerOperations.py +120 -0
  16. rsseismic/__init__.py +79 -0
  17. rsseismic/_application_paths.py +74 -0
  18. rsseismic/_batch_compute.py +420 -0
  19. rsseismic/_client.py +81 -0
  20. rsseismic/_proto_converters.py +345 -0
  21. rsseismic/_proxyObject.py +11 -0
  22. rsseismic/_result_bulk_dataframes.py +202 -0
  23. rsseismic/_result_dataframes.py +77 -0
  24. rsseismic/_rpc_helpers.py +55 -0
  25. rsseismic/enums/__init__.py +23 -0
  26. rsseismic/enums/_generated.py +120 -0
  27. rsseismic/enums/fitting_procedure.py +15 -0
  28. rsseismic/exceptions.py +11 -0
  29. rsseismic/generatedFiles/ApplicationService_pb2.py +46 -0
  30. rsseismic/generatedFiles/ApplicationService_pb2.pyi +67 -0
  31. rsseismic/generatedFiles/ApplicationService_pb2_grpc.py +274 -0
  32. rsseismic/generatedFiles/BedrockLayerDataService_pb2.py +27 -0
  33. rsseismic/generatedFiles/BedrockLayerDataService_pb2.pyi +5 -0
  34. rsseismic/generatedFiles/BedrockLayerDataService_pb2_grpc.py +231 -0
  35. rsseismic/generatedFiles/CommonMessages_pb2.py +50 -0
  36. rsseismic/generatedFiles/CommonMessages_pb2.pyi +113 -0
  37. rsseismic/generatedFiles/CommonMessages_pb2_grpc.py +29 -0
  38. rsseismic/generatedFiles/MeanProfileService_pb2.py +62 -0
  39. rsseismic/generatedFiles/MeanProfileService_pb2.pyi +176 -0
  40. rsseismic/generatedFiles/MeanProfileService_pb2_grpc.py +458 -0
  41. rsseismic/generatedFiles/MeanProfileSoilLayerDataService_pb2.py +45 -0
  42. rsseismic/generatedFiles/MeanProfileSoilLayerDataService_pb2.pyi +87 -0
  43. rsseismic/generatedFiles/MeanProfileSoilLayerDataService_pb2_grpc.py +360 -0
  44. rsseismic/generatedFiles/ModelService_pb2.py +54 -0
  45. rsseismic/generatedFiles/ModelService_pb2.pyi +122 -0
  46. rsseismic/generatedFiles/ModelService_pb2_grpc.py +360 -0
  47. rsseismic/generatedFiles/MotionService_pb2.py +78 -0
  48. rsseismic/generatedFiles/MotionService_pb2.pyi +224 -0
  49. rsseismic/generatedFiles/MotionService_pb2_grpc.py +575 -0
  50. rsseismic/generatedFiles/ProfileGenerationSettingsService_pb2.py +27 -0
  51. rsseismic/generatedFiles/ProfileGenerationSettingsService_pb2.pyi +5 -0
  52. rsseismic/generatedFiles/ProfileGenerationSettingsService_pb2_grpc.py +145 -0
  53. rsseismic/generatedFiles/ProfileService_pb2.py +76 -0
  54. rsseismic/generatedFiles/ProfileService_pb2.pyi +246 -0
  55. rsseismic/generatedFiles/ProfileService_pb2_grpc.py +532 -0
  56. rsseismic/generatedFiles/ProjectSettingsDataService_pb2.py +35 -0
  57. rsseismic/generatedFiles/ProjectSettingsDataService_pb2.pyi +40 -0
  58. rsseismic/generatedFiles/ProjectSettingsDataService_pb2_grpc.py +232 -0
  59. rsseismic/generatedFiles/ReferenceCurveDataService_pb2.py +41 -0
  60. rsseismic/generatedFiles/ReferenceCurveDataService_pb2.pyi +73 -0
  61. rsseismic/generatedFiles/ReferenceCurveDataService_pb2_grpc.py +281 -0
  62. rsseismic/generatedFiles/ResultsService_pb2.py +82 -0
  63. rsseismic/generatedFiles/ResultsService_pb2.pyi +373 -0
  64. rsseismic/generatedFiles/ResultsService_pb2_grpc.py +489 -0
  65. rsseismic/generatedFiles/SoilLayerCurveDataService_pb2.py +52 -0
  66. rsseismic/generatedFiles/SoilLayerCurveDataService_pb2.pyi +150 -0
  67. rsseismic/generatedFiles/SoilLayerCurveDataService_pb2_grpc.py +280 -0
  68. rsseismic/generatedFiles/SoilLayerDataService_pb2.py +27 -0
  69. rsseismic/generatedFiles/SoilLayerDataService_pb2.pyi +5 -0
  70. rsseismic/generatedFiles/SoilLayerDataService_pb2_grpc.py +231 -0
  71. rsseismic/generatedFiles/SoilLayerService_pb2.py +46 -0
  72. rsseismic/generatedFiles/SoilLayerService_pb2.pyi +122 -0
  73. rsseismic/generatedFiles/SoilLayerService_pb2_grpc.py +317 -0
  74. rsseismic/generatedFiles/__init__.py +4 -0
  75. rsseismic/properties/_PropertyAccessor.py +151 -0
  76. rsseismic/properties/__init__.py +6 -0
  77. rsseismic/properties/bedrockLayer/__init__.py +1 -0
  78. rsseismic/properties/bedrockLayer/_generated.py +127 -0
  79. rsseismic/properties/meanProfileSoilLayer/__init__.py +1 -0
  80. rsseismic/properties/meanProfileSoilLayer/_generated.py +87 -0
  81. rsseismic/properties/profileGenerationSettings/__init__.py +1 -0
  82. rsseismic/properties/profileGenerationSettings/_generated.py +195 -0
  83. rsseismic/properties/projectSettings/__init__.py +1 -0
  84. rsseismic/properties/projectSettings/_generated.py +270 -0
  85. rsseismic/properties/referenceCurve/__init__.py +1 -0
  86. rsseismic/properties/referenceCurve/_generated.py +264 -0
  87. rsseismic/properties/soilLayer/__init__.py +1 -0
  88. rsseismic/properties/soilLayer/_generated.py +523 -0
  89. rsseismic/types.py +311 -0
  90. rsseismicscripting-3.0.0.dist-info/METADATA +88 -0
  91. rsseismicscripting-3.0.0.dist-info/RECORD +94 -0
  92. rsseismicscripting-3.0.0.dist-info/WHEEL +5 -0
  93. rsseismicscripting-3.0.0.dist-info/licenses/License.txt +20 -0
  94. rsseismicscripting-3.0.0.dist-info/top_level.txt +1 -0
rsseismic/Results.py ADDED
@@ -0,0 +1,419 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+
5
+ import pandas as pd
6
+
7
+ from rsseismic._client import Client
8
+ from rsseismic._proto_converters import from_result_database_entry
9
+ from rsseismic._proto_converters import (
10
+ from_result_database_entry,
11
+ from_result_layer_entry,
12
+ from_result_motion_entry,
13
+ )
14
+ from rsseismic._proxyObject import _ProxyObject
15
+ from rsseismic._result_bulk_dataframes import (
16
+ fetch_bulk_dataframes,
17
+ fetch_bulk_layer_series_dataframes,
18
+ fetch_bulk_profile_series_dataframes,
19
+ infer_el_convergence_motion_type_filter,
20
+ normalize_result_motion_type_filters,
21
+ resolve_layer_ids,
22
+ select_convergence_tab_motion_names,
23
+ select_displacement_animation_motion_names,
24
+ select_profile_motion_names,
25
+ )
26
+ from rsseismic._result_dataframes import data_table_to_dataframe, resolve_result_series_kind
27
+ from rsseismic._rpc_helpers import require_success
28
+ from rsseismic.generatedFiles import ResultsService_pb2 as ResultsService_pb2
29
+ from rsseismic.generatedFiles import ResultsService_pb2_grpc as ResultsService_pb2_grpc
30
+ from rsseismic.types import (
31
+ ResultDatabaseInfo,
32
+ ResultLayerSummary,
33
+ ResultMotionSummary,
34
+ ResultMotionType,
35
+ ResultSeriesKind,
36
+ )
37
+
38
+
39
+ class Results(_ProxyObject):
40
+ """
41
+ Query computed analysis results for an open project.
42
+
43
+ Access via ``model.Results``. After ``model.runCompute()``, use ``listResultDatabases``
44
+ or ``listResultMotions()`` to discover valid profile and motion names, then fetch
45
+ tabular data as pandas ``DataFrame`` objects.
46
+
47
+ Use ``getResultDatabasePath`` to see which result files exist for a profile/motion
48
+ pair before calling specialized queries (strain convergence, displacement animation, etc.).
49
+ """
50
+
51
+ def __init__(self, client: Client, modelId: str):
52
+ super().__init__(client, modelId)
53
+ self._results_service = ResultsService_pb2_grpc.ResultsServiceStub(self._client.channel)
54
+
55
+ def getResultsRoot(self) -> str:
56
+ """Return the folder where RSSeismic stores computed results for this project."""
57
+ request = ResultsService_pb2.GetResultsRootRequest(modelID=self._objectId)
58
+ response = self._client.callFunction(self._results_service.GetResultsRoot, request)
59
+ require_success(response, "GetResultsRoot")
60
+ return response.resultsRoot
61
+
62
+ def listResultDatabases(
63
+ self,
64
+ profileName: str = "",
65
+ onlyExisting: bool = False,
66
+ ) -> list[ResultDatabaseInfo]:
67
+ """
68
+ List profile/motion pairs and their result file paths.
69
+
70
+ When ``profileName`` is empty, entries for all profiles are returned.
71
+ When ``onlyExisting`` is True, only pairs whose main result database file
72
+ exists on disk are included.
73
+
74
+ Use the ``motionName`` values from these entries (or from ``listResultMotions``)
75
+ when calling result DataFrame methods — they may differ from names shown in
76
+ the Motion view, especially for complementary analyses.
77
+ """
78
+ request = ResultsService_pb2.ListResultDatabasesRequest(
79
+ modelID=self._objectId,
80
+ profileName=profileName,
81
+ onlyExisting=onlyExisting,
82
+ )
83
+ response = self._client.callFunction(self._results_service.ListResultDatabases, request)
84
+ require_success(response, "ListResultDatabases")
85
+ return [from_result_database_entry(entry) for entry in response.entries]
86
+
87
+ def getResultDatabasePath(
88
+ self,
89
+ profileName: str,
90
+ motionName: str,
91
+ ) -> ResultDatabaseInfo:
92
+ """
93
+ Return result file paths and availability flags for one profile and motion.
94
+
95
+ ``motionName`` must be a compute-aligned name from ``listResultDatabases`` or
96
+ ``listResultMotions`` after ``runCompute()``. Check fields such as
97
+ ``resultDatabaseExists``, ``strainConvergenceExists``, and
98
+ ``deconvolutionBedrockMotionExists`` before calling related DataFrame methods.
99
+ """
100
+ request = ResultsService_pb2.GetResultDatabasePathRequest(
101
+ modelID=self._objectId,
102
+ profileName=profileName,
103
+ motionName=motionName,
104
+ )
105
+ response = self._client.callFunction(self._results_service.GetResultDatabasePath, request)
106
+ require_success(response, "GetResultDatabasePath")
107
+ return from_result_database_entry(response.entry)
108
+
109
+ def listResultMotions(self) -> list[ResultMotionSummary]:
110
+ """
111
+ List motion names available for result queries after compute.
112
+
113
+ Includes complementary motions when project settings add them (for example
114
+ ``ChiChi-EL`` derived from selected motion ``ChiChi``). Prefer these names over
115
+ Motion-view labels when fetching results.
116
+ """
117
+ request = ResultsService_pb2.ListResultMotionsRequest(modelID=self._objectId)
118
+ response = self._client.callFunction(self._results_service.ListResultMotions, request)
119
+ require_success(response, "ListResultMotions")
120
+ return [from_result_motion_entry(motion) for motion in response.motions]
121
+
122
+ def listResultLayers(self, profileName: str) -> list[ResultLayerSummary]:
123
+ """
124
+ List layers that can be used with layer-scoped result queries for a profile.
125
+
126
+ Includes soil layers with result output enabled and the top-of-rock layer when
127
+ bedrock output is configured.
128
+ """
129
+ if not profileName:
130
+ raise ValueError("Profile name is required.")
131
+ request = ResultsService_pb2.ListResultLayersRequest(
132
+ modelID=self._objectId,
133
+ profileName=profileName,
134
+ )
135
+ response = self._client.callFunction(self._results_service.ListResultLayers, request)
136
+ require_success(response, "ListResultLayers")
137
+ return [from_result_layer_entry(layer) for layer in response.layers]
138
+
139
+ def getTopOfRockLayerId(self, profileName: str) -> str:
140
+ """
141
+ Return the layer ID to use for top-of-rock result queries on a profile.
142
+
143
+ Raises ValueError when top of rock output is not available in
144
+ ``listResultLayers(profileName)``.
145
+ """
146
+ for layer in self.listResultLayers(profileName):
147
+ if layer.isTopOfRock:
148
+ return layer.layerID
149
+ raise ValueError(
150
+ f"Top of rock is not available for profile '{profileName}'. "
151
+ "Enable bedrockOutputDeconvolution and ensure the profile is configured for top of rock output."
152
+ )
153
+
154
+ def getLayerTimeHistoryDataFrame(
155
+ self,
156
+ profileName: str,
157
+ motionName: str,
158
+ layerID: str,
159
+ ) -> pd.DataFrame:
160
+ """
161
+ Return layer time-history results as a pandas DataFrame.
162
+
163
+ Columns typically include time, acceleration, velocity, and related series for
164
+ the given layer. For finite-element analysis, effective vertical stress may also
165
+ be included.
166
+
167
+ ``motionName`` must be compute-aligned. ``layerID`` comes from
168
+ ``listResultLayers(profileName)`` or ``listSoilLayers()`` on the same profile.
169
+ Column units are in ``dataframe.attrs["column_units"]`` when available.
170
+ """
171
+ request = ResultsService_pb2.GetLayerTimeHistoryTableRequest(
172
+ modelID=self._objectId,
173
+ profileName=profileName,
174
+ motionName=motionName,
175
+ layerID=layerID,
176
+ )
177
+ response = self._client.callFunction(
178
+ self._results_service.GetLayerTimeHistoryTable,
179
+ request,
180
+ )
181
+ require_success(response, "GetLayerTimeHistoryTable")
182
+ return data_table_to_dataframe(response.table)
183
+
184
+ def getProfileResultsDataFrame(
185
+ self,
186
+ profileName: str,
187
+ motionName: str,
188
+ ) -> pd.DataFrame:
189
+ """
190
+ Return profile-level summary results as a pandas DataFrame.
191
+
192
+ Provides depth-based summary columns for the profile (for example maximum strain
193
+ by layer). Not available for deconvolution profiles — use layer-scoped methods instead.
194
+
195
+ ``motionName`` must be compute-aligned after ``runCompute()``.
196
+ """
197
+ request = ResultsService_pb2.GetProfileResultsTableRequest(
198
+ modelID=self._objectId,
199
+ profileName=profileName,
200
+ motionName=motionName,
201
+ )
202
+ response = self._client.callFunction(
203
+ self._results_service.GetProfileResultsTable,
204
+ request,
205
+ )
206
+ require_success(response, "GetProfileResultsTable")
207
+ return data_table_to_dataframe(response.table)
208
+
209
+ def getResultSeriesDataFrame(
210
+ self,
211
+ profileName: str,
212
+ motionName: str,
213
+ kind: ResultSeriesKind,
214
+ layerID: str = "",
215
+ ) -> pd.DataFrame:
216
+ """
217
+ Return one result chart series as a pandas DataFrame.
218
+
219
+ Pass a ``ResultSeriesKind`` value for ``kind`` (for example ``MAX_STRAIN``,
220
+ ``PGA``, ``ACCELERATION``). When ``kind.requires_layer`` is True, ``layerID``
221
+ is required — use ``listResultLayers(profileName)`` to obtain valid IDs.
222
+
223
+ ``motionName`` must be compute-aligned after ``runCompute()``.
224
+ """
225
+ request = ResultsService_pb2.GetResultSeriesRequest(
226
+ modelID=self._objectId,
227
+ profileName=profileName,
228
+ motionName=motionName,
229
+ kind=resolve_result_series_kind(kind),
230
+ layerID=layerID,
231
+ )
232
+ response = self._client.callFunction(
233
+ self._results_service.GetResultSeries,
234
+ request,
235
+ )
236
+ require_success(response, "GetResultSeries")
237
+ return data_table_to_dataframe(response.table)
238
+
239
+ def getStrainConvergenceDataFrame(
240
+ self,
241
+ profileName: str,
242
+ motionName: str,
243
+ ) -> pd.DataFrame:
244
+ """
245
+ Return equivalent-linear strain convergence data as a pandas DataFrame.
246
+
247
+ Table columns show iteration and per-layer strain values. Requires equivalent-linear
248
+ analysis and a computed strain-convergence file for the motion. Check
249
+ ``getResultDatabasePath(...).strainConvergenceExists`` before calling.
250
+
251
+ For complementary equivalent-linear runs, pass the complementary motion name
252
+ (for example ``ChiChi-EL``).
253
+ """
254
+ request = ResultsService_pb2.GetStrainConvergenceTableRequest(
255
+ modelID=self._objectId,
256
+ profileName=profileName,
257
+ motionName=motionName,
258
+ )
259
+ response = self._client.callFunction(
260
+ self._results_service.GetStrainConvergenceTable,
261
+ request,
262
+ )
263
+ require_success(response, "GetStrainConvergenceTable")
264
+ return data_table_to_dataframe(response.table)
265
+
266
+ def getRealTimeDisplacementDataFrame(
267
+ self,
268
+ profileName: str,
269
+ motionName: str,
270
+ ) -> pd.DataFrame:
271
+ """
272
+ Return real-time relative displacement at each layer as a pandas DataFrame.
273
+
274
+ Requires the displacement animation option enabled in project settings and a
275
+ standard result database for the motion.
276
+ """
277
+ request = ResultsService_pb2.GetRealTimeDisplacementTableRequest(
278
+ modelID=self._objectId,
279
+ profileName=profileName,
280
+ motionName=motionName,
281
+ )
282
+ response = self._client.callFunction(
283
+ self._results_service.GetRealTimeDisplacementTable,
284
+ request,
285
+ )
286
+ require_success(response, "GetRealTimeDisplacementTable")
287
+ return data_table_to_dataframe(response.table)
288
+
289
+ def getAllStrainConvergenceDataFrames(
290
+ self,
291
+ profileName: str,
292
+ *,
293
+ skipErrors: bool = True,
294
+ ) -> dict[str, pd.DataFrame]:
295
+ """
296
+ Fetch strain convergence DataFrames for all motions on the Check convergence tab.
297
+
298
+ Uses the same motion list as that tab in the RSSeismic UI: primary equivalent-linear
299
+ motions, plus complementary EL motions when that option is enabled. Each entry is
300
+ loaded via ``getStrainConvergenceDataFrame``.
301
+
302
+ When ``skipErrors`` is True (default), motions without convergence data are skipped
303
+ instead of raising an error.
304
+ """
305
+ self._require_profile_name(profileName)
306
+ motion_names = select_convergence_tab_motion_names(
307
+ self.listResultMotions(),
308
+ self._resolve_el_convergence_motion_type_filter(),
309
+ )
310
+ return fetch_bulk_dataframes(
311
+ motion_names,
312
+ lambda motion_name: self.getStrainConvergenceDataFrame(profileName, motion_name),
313
+ skip_errors=skipErrors,
314
+ )
315
+
316
+ def getAllRealTimeDisplacementDataFrames(
317
+ self,
318
+ profileName: str,
319
+ *,
320
+ skipErrors: bool = True,
321
+ ) -> dict[str, pd.DataFrame]:
322
+ """
323
+ Fetch real-time displacement DataFrames for all result motions on a profile.
324
+
325
+ Uses the same motion list as the displacement animation tab. Requires the
326
+ displacement animation project setting. When ``skipErrors`` is True (default),
327
+ motions without data are skipped instead of raising an error.
328
+ """
329
+ self._require_profile_name(profileName)
330
+ motion_names = select_displacement_animation_motion_names(self.listResultMotions())
331
+ return fetch_bulk_dataframes(
332
+ motion_names,
333
+ lambda motion_name: self.getRealTimeDisplacementDataFrame(profileName, motion_name),
334
+ skip_errors=skipErrors,
335
+ )
336
+
337
+ def getAllResultSeriesDataFrames(
338
+ self,
339
+ profileName: str,
340
+ kind: ResultSeriesKind,
341
+ motionTypeFilters: Sequence[ResultMotionType] | None = None,
342
+ layerIds: Sequence[str] | None = None,
343
+ *,
344
+ skipErrors: bool = True,
345
+ ) -> dict[str, pd.DataFrame] | dict[tuple[str, str], pd.DataFrame]:
346
+ """
347
+ Fetch one result series kind for many motions (and layers when required).
348
+
349
+ Returns a dict keyed by motion name for profile-level series, or by
350
+ ``(motion_name, layer_id)`` when ``kind.requires_layer`` is True.
351
+
352
+ ``motionTypeFilters`` limits which motion variants are included (standard,
353
+ complementary equivalent-linear, complementary nonlinear total stress). Pass
354
+ ``None`` or an empty sequence to include all types.
355
+
356
+ ``layerIds`` limits layer-scoped series. When omitted or empty and ``kind``
357
+ requires a layer, all layers from ``listResultLayers(profileName)`` are used.
358
+
359
+ When ``skipErrors`` is True (default), missing combinations are skipped
360
+ instead of raising an error.
361
+ """
362
+ self._require_profile_name(profileName)
363
+ resolved_kind = ResultSeriesKind(resolve_result_series_kind(kind))
364
+ motion_type_filters = normalize_result_motion_type_filters(motionTypeFilters)
365
+ motion_names = select_profile_motion_names(
366
+ self._list_result_databases(profileName, onlyExisting=True),
367
+ profileName,
368
+ motion_type_filters,
369
+ require_result_database=True,
370
+ )
371
+
372
+ if resolved_kind.requires_layer:
373
+ layer_ids = resolve_layer_ids(
374
+ layerIds,
375
+ lambda: self.listResultLayers(profileName),
376
+ )
377
+ return fetch_bulk_layer_series_dataframes(
378
+ motion_names,
379
+ layer_ids,
380
+ lambda motion_name, layer_id: self.getResultSeriesDataFrame(
381
+ profileName,
382
+ motion_name,
383
+ resolved_kind,
384
+ layer_id,
385
+ ),
386
+ skip_errors=skipErrors,
387
+ )
388
+
389
+ return fetch_bulk_profile_series_dataframes(
390
+ motion_names,
391
+ lambda motion_name: self.getResultSeriesDataFrame(
392
+ profileName,
393
+ motion_name,
394
+ resolved_kind,
395
+ ),
396
+ skip_errors=skipErrors,
397
+ )
398
+
399
+ def _require_profile_name(self, profileName: str) -> None:
400
+ if not profileName:
401
+ raise ValueError("Profile name is required.")
402
+
403
+ def _list_result_databases(
404
+ self,
405
+ profileName: str,
406
+ *,
407
+ onlyExisting: bool,
408
+ ) -> list[ResultDatabaseInfo]:
409
+ return self.listResultDatabases(profileName=profileName, onlyExisting=onlyExisting)
410
+
411
+ def _resolve_el_convergence_motion_type_filter(self) -> frozenset[ResultMotionType] | None:
412
+ from rsseismic.ProjectSettings import ProjectSettings
413
+
414
+ data = ProjectSettings(self._client, self._objectId).Data
415
+ return infer_el_convergence_motion_type_filter(
416
+ data.getEnumValue("solutionType", str),
417
+ data.getEnumValue("analysisMode", str),
418
+ data.getBoolValue("equivalentLinear"),
419
+ )
rsseismic/SoilLayer.py ADDED
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from rsseismic._client import Client
4
+ from rsseismic._proxyObject import _ProxyObject
5
+ from rsseismic.generatedFiles import SoilLayerDataService_pb2_grpc as SoilLayerDataService_pb2_grpc
6
+ from rsseismic.properties._PropertyAccessor import ProjectScopedPropertyAccessor
7
+ from rsseismic.properties.soilLayer._generated import SoilLayerPropertiesMixin
8
+ from rsseismic.ReferenceCurve import ReferenceCurve
9
+ from rsseismic.SoilLayerCurve import SoilLayerCurve
10
+
11
+
12
+ class SoilLayer(_ProxyObject, SoilLayerPropertiesMixin):
13
+ """
14
+ Read and edit properties for one soil layer on a profile.
15
+
16
+ Obtain an instance from ``model.Profiles.getSoilLayer(layerId)``. Layer IDs
17
+ come from ``model.Profiles.listSoilLayers()`` or ``listActiveSoilLayers()``.
18
+
19
+ Use the generated ``get*`` / ``set*`` helpers for scalar layer properties such as
20
+ thickness, unit weight, and soil model. Reference curves are edited through
21
+ ``layer.ReferenceCurve``; fitted dynamics and curve fitting use ``layer.Curve``.
22
+ """
23
+
24
+ def __init__(self, client: Client, modelId: str, layerId: str):
25
+ super().__init__(client, layerId)
26
+ self.modelId = modelId
27
+ stub = SoilLayerDataService_pb2_grpc.SoilLayerDataServiceStub(self._client.channel)
28
+ self.Data = ProjectScopedPropertyAccessor(client, modelId, layerId, stub)
29
+ self.ReferenceCurve = ReferenceCurve(client, modelId, layerId)
30
+ self.Curve = SoilLayerCurve(client, modelId, layerId)
31
+
32
+ @property
33
+ def layerId(self) -> str:
34
+ return self._objectId
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from rsseismic._client import Client
6
+ from rsseismic._proto_converters import (
7
+ from_discrete_points,
8
+ from_dynamics_points,
9
+ to_discrete_points,
10
+ to_dynamics_points,
11
+ to_gq_h_fitting_limits_proto,
12
+ )
13
+ from rsseismic._proxyObject import _ProxyObject
14
+ from rsseismic._rpc_helpers import require_success
15
+ from rsseismic.enums import FittingProcedureModel
16
+ from rsseismic.generatedFiles import SoilLayerCurveDataService_pb2 as SoilLayerCurveDataService_pb2
17
+ from rsseismic.generatedFiles import SoilLayerCurveDataService_pb2_grpc as SoilLayerCurveDataService_pb2_grpc
18
+ from rsseismic.types import DiscretePoint, DynamicsPoint, GqHFittingLimits
19
+
20
+
21
+ def _resolve_fitting_procedure(
22
+ fitting_procedure: FittingProcedureModel | str,
23
+ ) -> str:
24
+ if isinstance(fitting_procedure, FittingProcedureModel):
25
+ return fitting_procedure.value
26
+ return fitting_procedure
27
+
28
+
29
+ class SoilLayerCurve(_ProxyObject):
30
+ """
31
+ Fitted dynamics and discrete-point curve data for a profile soil layer.
32
+
33
+ Access via ``layer.Curve`` on a ``SoilLayer`` from
34
+ ``model.Profiles.getSoilLayer(layerId)``. This is separate from
35
+ ``layer.ReferenceCurve``, which holds the reference curve used as input
36
+ for curve fitting.
37
+
38
+ Use ``getDynamics`` / ``setDynamics`` for the fitted layer curve, or
39
+ ``getDiscretePoints`` / ``setDiscretePoints`` when the layer uses a
40
+ discrete-points soil model. Call ``runCurveFit`` to fit dynamics from
41
+ the layer's reference curve.
42
+ """
43
+
44
+ def __init__(self, client: Client, modelId: str, layerId: str):
45
+ super().__init__(client, layerId)
46
+ self.modelId = modelId
47
+ self._service = SoilLayerCurveDataService_pb2_grpc.SoilLayerCurveDataServiceStub(
48
+ self._client.channel
49
+ )
50
+
51
+ @property
52
+ def layerId(self) -> str:
53
+ return self._objectId
54
+
55
+ def getDynamics(self) -> list[DynamicsPoint]:
56
+ """Return fitted dynamics points for the layer."""
57
+ request = SoilLayerCurveDataService_pb2.GetSoilLayerDynamicsRequest(
58
+ projectId=self.modelId,
59
+ layerID=self._objectId,
60
+ )
61
+ response = self._client.callFunction(self._service.GetDynamics, request)
62
+ require_success(response, "GetDynamics")
63
+ return from_dynamics_points(list(response.points))
64
+
65
+ def setDynamics(self, points: list[DynamicsPoint]) -> None:
66
+ """Set layer dynamics points."""
67
+ if not points:
68
+ raise ValueError("At least one dynamics point is required.")
69
+ request = SoilLayerCurveDataService_pb2.SetSoilLayerDynamicsRequest(
70
+ projectId=self.modelId,
71
+ layerID=self._objectId,
72
+ points=to_dynamics_points(points),
73
+ )
74
+ response = self._client.callFunction(self._service.SetDynamics, request)
75
+ require_success(response, "SetDynamics")
76
+
77
+ def getDiscretePoints(self) -> list[DiscretePoint]:
78
+ """Return discrete curve points (equivalent-linear analysis, Points soil model)."""
79
+ request = SoilLayerCurveDataService_pb2.GetDiscretePointsRequest(
80
+ projectId=self.modelId,
81
+ layerID=self._objectId,
82
+ )
83
+ response = self._client.callFunction(self._service.GetDiscretePoints, request)
84
+ require_success(response, "GetDiscretePoints")
85
+ return from_discrete_points(list(response.points))
86
+
87
+ def setDiscretePoints(self, points: list[DiscretePoint]) -> None:
88
+ """Set discrete curve points."""
89
+ if not points:
90
+ raise ValueError("At least one discrete point is required.")
91
+ request = SoilLayerCurveDataService_pb2.SetDiscretePointsRequest(
92
+ projectId=self.modelId,
93
+ layerID=self._objectId,
94
+ points=to_discrete_points(points),
95
+ )
96
+ response = self._client.callFunction(self._service.SetDiscretePoints, request)
97
+ require_success(response, "SetDiscretePoints")
98
+
99
+ def runCurveFit(
100
+ self,
101
+ fittingProcedure: FittingProcedureModel | str,
102
+ gqHLimits: Optional[GqHFittingLimits] = None,
103
+ ) -> int:
104
+ """
105
+ Fit layer dynamics from the layer's reference curve.
106
+
107
+ Args:
108
+ fittingProcedure: Fitting procedure, for example
109
+ ``FittingProcedureModel.MRDF`` or ``FittingProcedureModel.MRDF_UIUC``.
110
+ gqHLimits: Optional G–H fitting limits for procedures that support them.
111
+
112
+ Returns:
113
+ The number of fitted points.
114
+ """
115
+ request = SoilLayerCurveDataService_pb2.RunCurveFitRequest(
116
+ projectId=self.modelId,
117
+ layerID=self._objectId,
118
+ fittingProcedure=_resolve_fitting_procedure(fittingProcedure),
119
+ )
120
+ if gqHLimits is not None:
121
+ request.gq_h_limits.CopyFrom(to_gq_h_fitting_limits_proto(gqHLimits))
122
+ response = self._client.callFunction(self._service.RunCurveFit, request)
123
+ require_success(response, "RunCurveFit")
124
+ return response.pointCount
@@ -0,0 +1,120 @@
1
+ from __future__ import annotations
2
+
3
+ from rsseismic._client import Client
4
+ from rsseismic._proto_converters import (
5
+ from_append_layers_response,
6
+ from_delete_layer_response,
7
+ from_insert_layer_response,
8
+ from_move_layer_response,
9
+ )
10
+ from rsseismic._proxyObject import _ProxyObject
11
+ from rsseismic._rpc_helpers import require_success
12
+ from rsseismic.generatedFiles import SoilLayerService_pb2 as SoilLayerService_pb2
13
+ from rsseismic.generatedFiles import SoilLayerService_pb2_grpc as SoilLayerService_pb2_grpc
14
+ from rsseismic.types import AppendLayersResult, DeleteLayerResult, InsertLayerResult, MoveLayerResult
15
+
16
+
17
+ class SoilLayerOperations(_ProxyObject):
18
+ """Structural soil layer operations (insert, delete, duplicate, move) for an open model."""
19
+
20
+ def __init__(self, client: Client, modelId: str):
21
+ super().__init__(client, modelId)
22
+ self._soil_layer_service = SoilLayerService_pb2_grpc.SoilLayerServiceStub(self._client.channel)
23
+
24
+ def insertLayerBefore(
25
+ self,
26
+ referenceLayerID: str,
27
+ profileName: str = "",
28
+ ) -> InsertLayerResult:
29
+ """
30
+ Insert a soil layer before referenceLayerID.
31
+
32
+ referenceLayerID may be a soil layer GUID or the bedrock layer GUID.
33
+ """
34
+ request = SoilLayerService_pb2.InsertLayerBeforeRequest(
35
+ modelID=self._objectId,
36
+ profileName=profileName,
37
+ referenceLayerID=referenceLayerID,
38
+ )
39
+ response = self._client.callFunction(self._soil_layer_service.InsertLayerBefore, request)
40
+ require_success(response, "InsertLayerBefore")
41
+ return from_insert_layer_response(response)
42
+
43
+ def insertLayerAfter(
44
+ self,
45
+ referenceLayerID: str,
46
+ profileName: str = "",
47
+ ) -> InsertLayerResult:
48
+ """Insert a soil layer after a soil layer GUID."""
49
+ request = SoilLayerService_pb2.InsertLayerAfterRequest(
50
+ modelID=self._objectId,
51
+ profileName=profileName,
52
+ referenceLayerID=referenceLayerID,
53
+ )
54
+ response = self._client.callFunction(self._soil_layer_service.InsertLayerAfter, request)
55
+ require_success(response, "InsertLayerAfter")
56
+ return from_insert_layer_response(response)
57
+
58
+ def appendLayers(
59
+ self,
60
+ count: int = 1,
61
+ profileName: str = "",
62
+ ) -> AppendLayersResult:
63
+ """Append one or more soil layers before bedrock."""
64
+ request = SoilLayerService_pb2.AppendLayersRequest(
65
+ modelID=self._objectId,
66
+ profileName=profileName,
67
+ count=count,
68
+ )
69
+ response = self._client.callFunction(self._soil_layer_service.AppendLayers, request)
70
+ require_success(response, "AppendLayers")
71
+ return from_append_layers_response(response)
72
+
73
+ def deleteLayer(
74
+ self,
75
+ layerID: str,
76
+ profileName: str = "",
77
+ ) -> DeleteLayerResult:
78
+ """Delete a soil layer. At least one soil layer must remain."""
79
+ request = SoilLayerService_pb2.DeleteLayerRequest(
80
+ modelID=self._objectId,
81
+ profileName=profileName,
82
+ layerID=layerID,
83
+ )
84
+ response = self._client.callFunction(self._soil_layer_service.DeleteLayer, request)
85
+ require_success(response, "DeleteLayer")
86
+ return from_delete_layer_response(response)
87
+
88
+ def duplicateLayer(
89
+ self,
90
+ sourceLayerID: str,
91
+ insertAfter: bool = True,
92
+ profileName: str = "",
93
+ ) -> InsertLayerResult:
94
+ """Duplicate a soil layer before or after the source layer."""
95
+ request = SoilLayerService_pb2.DuplicateLayerRequest(
96
+ modelID=self._objectId,
97
+ profileName=profileName,
98
+ sourceLayerID=sourceLayerID,
99
+ insertAfter=insertAfter,
100
+ )
101
+ response = self._client.callFunction(self._soil_layer_service.DuplicateLayer, request)
102
+ require_success(response, "DuplicateLayer")
103
+ return from_insert_layer_response(response)
104
+
105
+ def moveLayer(
106
+ self,
107
+ layerID: str,
108
+ newIndex1Based: int,
109
+ profileName: str = "",
110
+ ) -> MoveLayerResult:
111
+ """Move a soil layer to a new 1-based index."""
112
+ request = SoilLayerService_pb2.MoveLayerRequest(
113
+ modelID=self._objectId,
114
+ profileName=profileName,
115
+ layerID=layerID,
116
+ newIndex1Based=newIndex1Based,
117
+ )
118
+ response = self._client.callFunction(self._soil_layer_service.MoveLayer, request)
119
+ require_success(response, "MoveLayer")
120
+ return from_move_layer_response(response)