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
@@ -0,0 +1,345 @@
1
+ """Convert protobuf messages to public types (internal)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from rsseismic.generatedFiles import MeanProfileService_pb2 as MeanProfileService_pb2
6
+ from rsseismic.generatedFiles import MotionService_pb2 as MotionService_pb2
7
+ from rsseismic.generatedFiles import ProfileService_pb2 as ProfileService_pb2
8
+ from rsseismic.generatedFiles import ReferenceCurveDataService_pb2 as ReferenceCurveDataService_pb2
9
+ from rsseismic.generatedFiles import ResultsService_pb2 as ResultsService_pb2
10
+ from rsseismic.generatedFiles import SoilLayerCurveDataService_pb2 as SoilLayerCurveDataService_pb2
11
+ from rsseismic.generatedFiles import SoilLayerService_pb2 as SoilLayerService_pb2
12
+ from rsseismic.types import (
13
+ ActiveProfile,
14
+ AppendLayersResult,
15
+ BedrockLayerSummary,
16
+ DeconvolutionLayerResultFiles,
17
+ DeleteLayerResult,
18
+ DiscretePoint,
19
+ DynamicsPoint,
20
+ GqHFittingLimits,
21
+ InsertLayerResult,
22
+ MeanProfileAppendLayersResult,
23
+ MeanProfileDeleteLayerResult,
24
+ MeanProfileInsertLayerResult,
25
+ MeanProfileMoveLayerResult,
26
+ MeanProfileSummary,
27
+ MotionInfo,
28
+ MotionPairInfo,
29
+ MoveLayerResult,
30
+ ProfileSummary,
31
+ ResultDatabaseInfo,
32
+ ResultLayerSummary,
33
+ ResultMotionSummary,
34
+ SoilLayerSummary,
35
+ )
36
+
37
+
38
+ def from_mean_profile_response(
39
+ response: MeanProfileService_pb2.GetMeanProfileResponse,
40
+ ) -> MeanProfileSummary:
41
+ return MeanProfileSummary(
42
+ profileName=response.profileName,
43
+ layerCount=response.layerCount,
44
+ totalThickness=response.totalThickness,
45
+ )
46
+
47
+
48
+ def from_mean_profile_soil_layer_entry(
49
+ entry: MeanProfileService_pb2.SoilLayerEntry,
50
+ ) -> SoilLayerSummary:
51
+ return SoilLayerSummary(
52
+ layerID=entry.layerID,
53
+ index1Based=entry.index1Based,
54
+ name=entry.name,
55
+ thickness=entry.thickness,
56
+ )
57
+
58
+
59
+ def from_mean_profile_bedrock_layer_entry(
60
+ entry: MeanProfileService_pb2.BedrockLayerEntry,
61
+ ) -> BedrockLayerSummary:
62
+ return BedrockLayerSummary(
63
+ layerID=entry.layerID,
64
+ name=entry.name,
65
+ thickness=entry.thickness,
66
+ )
67
+
68
+
69
+ def from_mean_profile_insert_layer_response(
70
+ response: MeanProfileService_pb2.InsertLayerResponse,
71
+ ) -> MeanProfileInsertLayerResult:
72
+ return MeanProfileInsertLayerResult(
73
+ layerID=response.layerID,
74
+ index1Based=response.index1Based,
75
+ )
76
+
77
+
78
+ def from_mean_profile_append_layers_response(
79
+ response: MeanProfileService_pb2.AppendLayersResponse,
80
+ ) -> MeanProfileAppendLayersResult:
81
+ return MeanProfileAppendLayersResult(layerIDs=tuple(response.layerIDs))
82
+
83
+
84
+ def from_mean_profile_delete_layer_response(
85
+ response: MeanProfileService_pb2.DeleteLayerResponse,
86
+ ) -> MeanProfileDeleteLayerResult:
87
+ return MeanProfileDeleteLayerResult(deletedLayerID=response.deletedLayerID)
88
+
89
+
90
+ def from_mean_profile_move_layer_response(
91
+ response: MeanProfileService_pb2.MoveLayerResponse,
92
+ ) -> MeanProfileMoveLayerResult:
93
+ return MeanProfileMoveLayerResult(
94
+ layerID=response.layerID,
95
+ index1Based=response.index1Based,
96
+ )
97
+
98
+
99
+ def from_profile_entry(entry: ProfileService_pb2.ProfileEntry) -> ProfileSummary:
100
+ return ProfileSummary(
101
+ name=entry.name,
102
+ isActive=entry.isActive,
103
+ layerCount=entry.layerCount,
104
+ totalThickness=entry.totalThickness,
105
+ )
106
+
107
+
108
+ def from_active_profile_response(
109
+ response: ProfileService_pb2.GetActiveProfileResponse,
110
+ ) -> ActiveProfile:
111
+ return ActiveProfile(
112
+ profileName=response.profileName,
113
+ layerCount=response.layerCount,
114
+ totalThickness=response.totalThickness,
115
+ )
116
+
117
+
118
+ def from_soil_layer_entry(entry: ProfileService_pb2.SoilLayerEntry) -> SoilLayerSummary:
119
+ return SoilLayerSummary(
120
+ layerID=entry.layerID,
121
+ index1Based=entry.index1Based,
122
+ name=entry.name,
123
+ thickness=entry.thickness,
124
+ )
125
+
126
+
127
+ def from_bedrock_layer_entry(entry: ProfileService_pb2.BedrockLayerEntry) -> BedrockLayerSummary:
128
+ return BedrockLayerSummary(
129
+ layerID=entry.layerID,
130
+ name=entry.name,
131
+ thickness=entry.thickness,
132
+ )
133
+
134
+
135
+ def from_motion_entry(entry: MotionService_pb2.MotionEntry) -> MotionInfo:
136
+ return MotionInfo(
137
+ name=entry.name,
138
+ filePath=entry.filePath,
139
+ isSelected=entry.isSelected,
140
+ )
141
+
142
+
143
+ def from_motion_pair_entry(entry: MotionService_pb2.MotionPairEntry) -> MotionPairInfo:
144
+ return MotionPairInfo(
145
+ pairId=entry.pairId,
146
+ motionH1Name=entry.motionH1Name,
147
+ motionH2Name=entry.motionH2Name,
148
+ motionH1FilePath=entry.motionH1FilePath,
149
+ motionH2FilePath=entry.motionH2FilePath,
150
+ isSelected=entry.isSelected,
151
+ )
152
+
153
+
154
+ def _motion_type_name(motion_type: int) -> str:
155
+ return ResultsService_pb2.MotionType.Name(motion_type)
156
+
157
+
158
+ def from_deconvolution_layer_files(
159
+ entry: ResultsService_pb2.DeconvolutionLayerResultFiles,
160
+ ) -> DeconvolutionLayerResultFiles:
161
+ return DeconvolutionLayerResultFiles(
162
+ layerIndex=entry.layerIndex,
163
+ layerMotionPath=entry.layerMotionPath,
164
+ layerMotionExists=entry.layerMotionExists,
165
+ layerFasTransferFunctionPath=entry.layerFasTransferFunctionPath,
166
+ layerFasTransferFunctionExists=entry.layerFasTransferFunctionExists,
167
+ )
168
+
169
+
170
+ def from_result_layer_entry(entry: ResultsService_pb2.ResultLayerEntry) -> ResultLayerSummary:
171
+ return ResultLayerSummary(
172
+ layerID=entry.layerID,
173
+ index1Based=entry.index1Based,
174
+ name=entry.name,
175
+ isTopOfRock=entry.isTopOfRock,
176
+ )
177
+
178
+
179
+ def from_result_motion_entry(entry: ResultsService_pb2.ResultMotionEntry) -> ResultMotionSummary:
180
+ return ResultMotionSummary(
181
+ motionName=entry.motionName,
182
+ motionType=_motion_type_name(entry.motionType),
183
+ sourceMotionName=entry.sourceMotionName,
184
+ )
185
+
186
+
187
+ def from_result_database_entry(entry: ResultsService_pb2.ResultDatabaseEntry) -> ResultDatabaseInfo:
188
+ return ResultDatabaseInfo(
189
+ profileName=entry.profileName,
190
+ motionName=entry.motionName,
191
+ motionType=_motion_type_name(entry.motionType),
192
+ resultsFolderPath=entry.resultsFolderPath,
193
+ resultDatabasePath=entry.resultDatabasePath,
194
+ resultDatabaseExists=entry.resultDatabaseExists,
195
+ complementaryResultDatabasePath=entry.complementaryResultDatabasePath,
196
+ complementaryResultDatabaseExists=entry.complementaryResultDatabaseExists,
197
+ inputFilePath=entry.inputFilePath,
198
+ inputFileExists=entry.inputFileExists,
199
+ strainConvergencePath=entry.strainConvergencePath,
200
+ strainConvergenceExists=entry.strainConvergenceExists,
201
+ strainNonConvergenceIdPath=entry.strainNonConvergenceIdPath,
202
+ strainNonConvergenceExists=entry.strainNonConvergenceExists,
203
+ deconvolutionBedrockMotionPath=entry.deconvolutionBedrockMotionPath,
204
+ deconvolutionBedrockMotionExists=entry.deconvolutionBedrockMotionExists,
205
+ deconvolutionBedrockFasTransferFunctionPath=entry.deconvolutionBedrockFasTransferFunctionPath,
206
+ deconvolutionBedrockFasTransferFunctionExists=entry.deconvolutionBedrockFasTransferFunctionExists,
207
+ deconvolutionLayerFiles=tuple(
208
+ from_deconvolution_layer_files(layer_file)
209
+ for layer_file in entry.deconvolutionLayerFiles
210
+ ),
211
+ topOfRockOutputEnabled=entry.topOfRockOutputEnabled,
212
+ topOfRockLayerID=entry.topOfRockLayerID,
213
+ topOfRockLayerIndex1Based=entry.topOfRockLayerIndex1Based,
214
+ )
215
+
216
+
217
+ def from_insert_layer_response(response: SoilLayerService_pb2.InsertLayerResponse) -> InsertLayerResult:
218
+ return InsertLayerResult(
219
+ profileName=response.profileName,
220
+ layerID=response.layerID,
221
+ index1Based=response.index1Based,
222
+ )
223
+
224
+
225
+ def from_append_layers_response(response: SoilLayerService_pb2.AppendLayersResponse) -> AppendLayersResult:
226
+ return AppendLayersResult(
227
+ profileName=response.profileName,
228
+ layerIDs=tuple(response.layerIDs),
229
+ )
230
+
231
+
232
+ def from_delete_layer_response(response: SoilLayerService_pb2.DeleteLayerResponse) -> DeleteLayerResult:
233
+ return DeleteLayerResult(
234
+ profileName=response.profileName,
235
+ deletedLayerID=response.deletedLayerID,
236
+ )
237
+
238
+
239
+ def from_move_layer_response(response: SoilLayerService_pb2.MoveLayerResponse) -> MoveLayerResult:
240
+ return MoveLayerResult(
241
+ profileName=response.profileName,
242
+ layerID=response.layerID,
243
+ index1Based=response.index1Based,
244
+ )
245
+
246
+
247
+ def to_gq_h_fitting_limits_proto(
248
+ limits: GqHFittingLimits,
249
+ ) -> SoilLayerCurveDataService_pb2.GqHFittingLimits:
250
+ return SoilLayerCurveDataService_pb2.GqHFittingLimits(
251
+ enable_modulus_min=limits.enableModulusMin,
252
+ modulus_min=limits.modulusMin,
253
+ enable_modulus_max=limits.enableModulusMax,
254
+ modulus_max=limits.modulusMax,
255
+ enable_strength_min=limits.enableStrengthMin,
256
+ strength_min=limits.strengthMin,
257
+ enable_damp_min=limits.enableDampMin,
258
+ damp_min=limits.dampMin,
259
+ enable_damp_max=limits.enableDampMax,
260
+ damp_max=limits.dampMax,
261
+ enable_fix_theta3=limits.enableFixTheta3,
262
+ fix_theta3=limits.fixTheta3,
263
+ )
264
+
265
+
266
+ def to_reference_curve_points(
267
+ points: list[DynamicsPoint],
268
+ ) -> list[ReferenceCurveDataService_pb2.ReferenceCurvePoint]:
269
+ return [
270
+ ReferenceCurveDataService_pb2.ReferenceCurvePoint(
271
+ strain=point.strain,
272
+ ggmax=point.ggmax,
273
+ damping=point.damping,
274
+ strength=point.strength,
275
+ )
276
+ for point in points
277
+ ]
278
+
279
+
280
+ def from_reference_curve_points(
281
+ points: list[ReferenceCurveDataService_pb2.ReferenceCurvePoint],
282
+ ) -> list[DynamicsPoint]:
283
+ return [
284
+ DynamicsPoint(
285
+ strain=point.strain,
286
+ ggmax=point.ggmax,
287
+ damping=point.damping,
288
+ strength=point.strength,
289
+ )
290
+ for point in points
291
+ ]
292
+
293
+
294
+ def to_dynamics_points(
295
+ points: list[DynamicsPoint],
296
+ ) -> list[SoilLayerCurveDataService_pb2.SoilLayerDynamicsPoint]:
297
+ return [
298
+ SoilLayerCurveDataService_pb2.SoilLayerDynamicsPoint(
299
+ strain=point.strain,
300
+ ggmax=point.ggmax,
301
+ damping=point.damping,
302
+ strength=point.strength,
303
+ )
304
+ for point in points
305
+ ]
306
+
307
+
308
+ def from_dynamics_points(
309
+ points: list[SoilLayerCurveDataService_pb2.SoilLayerDynamicsPoint],
310
+ ) -> list[DynamicsPoint]:
311
+ return [
312
+ DynamicsPoint(
313
+ strain=point.strain,
314
+ ggmax=point.ggmax,
315
+ damping=point.damping,
316
+ strength=point.strength,
317
+ )
318
+ for point in points
319
+ ]
320
+
321
+
322
+ def to_discrete_points(
323
+ points: list[DiscretePoint],
324
+ ) -> list[SoilLayerCurveDataService_pb2.DiscreteCurvePoint]:
325
+ return [
326
+ SoilLayerCurveDataService_pb2.DiscreteCurvePoint(
327
+ strain=point.strain,
328
+ ggmax=point.ggmax,
329
+ damping=point.damping,
330
+ )
331
+ for point in points
332
+ ]
333
+
334
+
335
+ def from_discrete_points(
336
+ points: list[SoilLayerCurveDataService_pb2.DiscreteCurvePoint],
337
+ ) -> list[DiscretePoint]:
338
+ return [
339
+ DiscretePoint(
340
+ strain=point.strain,
341
+ ggmax=point.ggmax,
342
+ damping=point.damping,
343
+ )
344
+ for point in points
345
+ ]
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from rsseismic._client import Client
4
+
5
+
6
+ class _ProxyObject:
7
+ """Base type for handles to objects owned by RSSeismic."""
8
+
9
+ def __init__(self, client: Client, objectId: str):
10
+ self._client = client
11
+ self._objectId = objectId
@@ -0,0 +1,202 @@
1
+ """Helpers for bulk result DataFrame queries (compose single-motion RPCs)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Iterable, Sequence
6
+
7
+ import pandas as pd
8
+
9
+ from rsseismic.exceptions import ScriptingError
10
+ from rsseismic.generatedFiles import ResultsService_pb2 as ResultsService_pb2
11
+ from rsseismic.types import ResultDatabaseInfo, ResultMotionSummary, ResultMotionType
12
+
13
+
14
+ def infer_el_convergence_motion_type_filter(
15
+ solution_type: str,
16
+ analysis_mode: str,
17
+ equivalent_linear: bool,
18
+ ) -> frozenset[ResultMotionType] | None:
19
+ """Mirror ResultsViewVM.GetELMotions motion-type rules for the convergence tab."""
20
+ if solution_type == "FrequencyDomain" and analysis_mode == "EquivalentLinear":
21
+ return None
22
+
23
+ if solution_type == "TimeDomain" and analysis_mode == "Nonlinear" and equivalent_linear:
24
+ return frozenset({ResultMotionType.COMPLEMENTARY_EQUIVALENT_LINEAR})
25
+
26
+ return frozenset()
27
+
28
+
29
+ def select_motion_names_from_result_motions(
30
+ motions: Iterable[ResultMotionSummary],
31
+ motion_type_filters: frozenset[ResultMotionType] | None,
32
+ ) -> list[str]:
33
+ """Distinct motion names from listResultMotions(), optionally filtered by type."""
34
+ if motion_type_filters is not None and len(motion_type_filters) == 0:
35
+ return []
36
+
37
+ motion_names: list[str] = []
38
+ seen: set[str] = set()
39
+
40
+ for motion in motions:
41
+ if not motion_type_matches_filters(motion.motionType, motion_type_filters):
42
+ continue
43
+ if motion.motionName in seen:
44
+ continue
45
+ seen.add(motion.motionName)
46
+ motion_names.append(motion.motionName)
47
+
48
+ return motion_names
49
+
50
+
51
+ def select_convergence_tab_motion_names(
52
+ motions: Iterable[ResultMotionSummary],
53
+ motion_type_filters: frozenset[ResultMotionType] | None,
54
+ ) -> list[str]:
55
+ """Motions shown on the Check convergence tab (GetELMotions)."""
56
+ return select_motion_names_from_result_motions(motions, motion_type_filters)
57
+
58
+
59
+ def select_displacement_animation_motion_names(
60
+ motions: Iterable[ResultMotionSummary],
61
+ ) -> list[str]:
62
+ """All compute-aligned result motions (displacement animation tab motion list)."""
63
+ return select_motion_names_from_result_motions(motions, motion_type_filters=None)
64
+
65
+
66
+ def normalize_result_motion_type_filters(
67
+ motion_type_filters: Sequence[ResultMotionType] | None,
68
+ ) -> frozenset[ResultMotionType] | None:
69
+ """Return None when filters are empty (meaning all motion types)."""
70
+ if motion_type_filters is None:
71
+ return None
72
+
73
+ filters = tuple(motion_type_filters)
74
+ if len(filters) == 0:
75
+ return None
76
+
77
+ for item in filters:
78
+ if not isinstance(item, ResultMotionType):
79
+ raise TypeError(
80
+ f"motionTypeFilters must contain ResultMotionType values; got {type(item)!r}."
81
+ )
82
+
83
+ return frozenset(filters)
84
+
85
+
86
+ def result_motion_type_to_proto_name(motion_type: ResultMotionType) -> str:
87
+ if motion_type is ResultMotionType.COMPLEMENTARY_NONLINEAR_TOTAL_STRESS_LUMPED_MASS:
88
+ return ResultsService_pb2.MotionType.Name(
89
+ ResultsService_pb2.MOTION_TYPE_COMPLEMENTARY_NONLINEAR_TOTAL_STRESS
90
+ )
91
+ return ResultsService_pb2.MotionType.Name(int(motion_type))
92
+
93
+
94
+ def _result_motion_type_from_proto_name(proto_name: str) -> ResultMotionType:
95
+ if proto_name == "MOTION_TYPE_COMPLEMENTARY_NONLINEAR_TOTAL_STRESS":
96
+ return ResultMotionType.COMPLEMENTARY_NONLINEAR_TOTAL_STRESS_LUMPED_MASS
97
+
98
+ try:
99
+ proto_value = ResultsService_pb2.MotionType.Value(proto_name)
100
+ except ValueError as exc:
101
+ raise ValueError(f"Unknown result motion type: {proto_name!r}") from exc
102
+
103
+ return ResultMotionType(proto_value)
104
+
105
+
106
+ def motion_type_matches_filters(
107
+ motion_type_name: str,
108
+ motion_type_filters: frozenset[ResultMotionType] | None,
109
+ ) -> bool:
110
+ if motion_type_filters is None:
111
+ return True
112
+
113
+ motion_type = _result_motion_type_from_proto_name(motion_type_name)
114
+ return motion_type in motion_type_filters
115
+
116
+
117
+ def select_profile_motion_names(
118
+ databases: Iterable[ResultDatabaseInfo],
119
+ profile_name: str,
120
+ motion_type_filters: frozenset[ResultMotionType] | None,
121
+ *,
122
+ require_strain_convergence: bool = False,
123
+ require_result_database: bool = False,
124
+ ) -> list[str]:
125
+ """Return distinct motion names for a profile from listResultDatabases entries."""
126
+ motion_names: list[str] = []
127
+ seen: set[str] = set()
128
+
129
+ for entry in databases:
130
+ if entry.profileName != profile_name:
131
+ continue
132
+ if require_strain_convergence and not entry.strainConvergenceExists:
133
+ continue
134
+ if require_result_database and not entry.resultDatabaseExists:
135
+ continue
136
+ if not motion_type_matches_filters(entry.motionType, motion_type_filters):
137
+ continue
138
+ if entry.motionName in seen:
139
+ continue
140
+
141
+ seen.add(entry.motionName)
142
+ motion_names.append(entry.motionName)
143
+
144
+ return motion_names
145
+
146
+
147
+ def resolve_layer_ids(
148
+ layer_ids: Sequence[str] | None,
149
+ list_layers: Callable[[], Sequence[object]],
150
+ ) -> list[str]:
151
+ if layer_ids is None or len(layer_ids) == 0:
152
+ return [layer.layerID for layer in list_layers()]
153
+
154
+ return list(layer_ids)
155
+
156
+
157
+ def fetch_bulk_dataframes(
158
+ motion_names: Sequence[str],
159
+ fetch_one: Callable[[str], pd.DataFrame],
160
+ *,
161
+ skip_errors: bool,
162
+ ) -> dict[str, pd.DataFrame]:
163
+ results: dict[str, pd.DataFrame] = {}
164
+
165
+ for motion_name in motion_names:
166
+ try:
167
+ results[motion_name] = fetch_one(motion_name)
168
+ except ScriptingError:
169
+ if not skip_errors:
170
+ raise
171
+
172
+ return results
173
+
174
+
175
+ def fetch_bulk_layer_series_dataframes(
176
+ motion_names: Sequence[str],
177
+ layer_ids: Sequence[str],
178
+ fetch_one: Callable[[str, str], pd.DataFrame],
179
+ *,
180
+ skip_errors: bool,
181
+ ) -> dict[tuple[str, str], pd.DataFrame]:
182
+ results: dict[tuple[str, str], pd.DataFrame] = {}
183
+
184
+ for motion_name in motion_names:
185
+ for layer_id in layer_ids:
186
+ key = (motion_name, layer_id)
187
+ try:
188
+ results[key] = fetch_one(motion_name, layer_id)
189
+ except ScriptingError:
190
+ if not skip_errors:
191
+ raise
192
+
193
+ return results
194
+
195
+
196
+ def fetch_bulk_profile_series_dataframes(
197
+ motion_names: Sequence[str],
198
+ fetch_one: Callable[[str], pd.DataFrame],
199
+ *,
200
+ skip_errors: bool,
201
+ ) -> dict[str, pd.DataFrame]:
202
+ return fetch_bulk_dataframes(motion_names, fetch_one, skip_errors=skip_errors)
@@ -0,0 +1,77 @@
1
+ """Convert ResultsService DataTable messages to pandas DataFrames (internal)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pandas as pd
6
+
7
+ from rsseismic.generatedFiles import ResultsService_pb2 as ResultsService_pb2
8
+ from rsseismic.types import ResultSeriesKind
9
+
10
+
11
+ def data_table_to_dataframe(table: ResultsService_pb2.DataTable) -> pd.DataFrame:
12
+ """Build a DataFrame from a ResultsService DataTable proto message."""
13
+ column_count = len(table.columns)
14
+ data_count = len(table.data)
15
+ if column_count != data_count:
16
+ raise ValueError(
17
+ f"DataTable column count ({column_count}) does not match "
18
+ f"data column count ({data_count})."
19
+ )
20
+
21
+ frame_data = {
22
+ column_name: list(column.values)
23
+ for column_name, column in zip(table.columns, table.data)
24
+ }
25
+ dataframe = pd.DataFrame(frame_data)
26
+
27
+ if table.rowCount and len(dataframe) != table.rowCount:
28
+ raise ValueError(
29
+ f"DataTable rowCount ({table.rowCount}) does not match "
30
+ f"built DataFrame length ({len(dataframe)})."
31
+ )
32
+
33
+ if table.columnUnits:
34
+ dataframe.attrs["column_units"] = dict(zip(table.columns, table.columnUnits))
35
+
36
+ return dataframe
37
+
38
+
39
+ def resolve_result_series_kind(kind: ResultSeriesKind | int | str) -> int:
40
+ """Resolve a public ResultSeriesKind (or legacy int/str) to a proto enum value."""
41
+ if isinstance(kind, ResultSeriesKind):
42
+ return int(kind)
43
+
44
+ if isinstance(kind, int):
45
+ try:
46
+ return int(ResultSeriesKind(kind))
47
+ except ValueError as exc:
48
+ raise ValueError(f"Unknown result series kind value: {kind}") from exc
49
+
50
+ if isinstance(kind, str):
51
+ name = kind.strip()
52
+ if not name:
53
+ raise ValueError("Result series kind is required.")
54
+
55
+ if name.startswith("RESULT_SERIES_"):
56
+ member_name = name.removeprefix("RESULT_SERIES_")
57
+ try:
58
+ return int(ResultSeriesKind[member_name])
59
+ except KeyError:
60
+ pass
61
+
62
+ normalized = name.upper().replace("-", "_")
63
+ try:
64
+ return int(ResultSeriesKind[normalized])
65
+ except KeyError:
66
+ pass
67
+
68
+ upper_name = normalized
69
+ if not upper_name.startswith("RESULT_SERIES_"):
70
+ upper_name = f"RESULT_SERIES_{upper_name}"
71
+
72
+ if upper_name in ResultsService_pb2.ResultSeriesKind.keys():
73
+ return ResultsService_pb2.ResultSeriesKind.Value(upper_name)
74
+
75
+ raise ValueError(f"Unknown result series kind: {kind!r}")
76
+
77
+ raise TypeError(f"Unsupported result series kind type: {type(kind)!r}")
@@ -0,0 +1,55 @@
1
+ """Shared helpers for RSSeismic gRPC wrappers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+ from typing import Any
7
+
8
+ from rsseismic.exceptions import ScriptingError
9
+ from rsseismic.generatedFiles import CommonMessages_pb2 as CommonMessages_pb2
10
+
11
+
12
+ def require_success(response: Any, operation: str) -> None:
13
+ """Raise ScriptingError when a response message has success=false."""
14
+ if getattr(response, "success", False):
15
+ return
16
+ message = getattr(response, "errorMessage", "") or f"{operation} failed."
17
+ raise ScriptingError(message)
18
+
19
+
20
+ def log_success_advisory(response: Any) -> None:
21
+ """When success is true, log a non-empty errorMessage as a warning (server advisory)."""
22
+ if not getattr(response, "success", False):
23
+ return
24
+ advisory_message = getattr(response, "errorMessage", "") or ""
25
+ if advisory_message:
26
+ from rsseismic import logger
27
+
28
+ logger.warning(advisory_message)
29
+
30
+
31
+ def property_value_from_python(value: Any) -> CommonMessages_pb2.PropertyValue:
32
+ """Build a CommonMessages PropertyValue from a Python scalar."""
33
+ if isinstance(value, CommonMessages_pb2.PropertyValue):
34
+ return value
35
+ if isinstance(value, bool):
36
+ return CommonMessages_pb2.PropertyValue(boolValue=value)
37
+ if isinstance(value, int):
38
+ return CommonMessages_pb2.PropertyValue(longValue=value)
39
+ if isinstance(value, float):
40
+ return CommonMessages_pb2.PropertyValue(doubleValue=value)
41
+ if isinstance(value, bytes):
42
+ return CommonMessages_pb2.PropertyValue(bytesValue=value)
43
+ if isinstance(value, Enum):
44
+ return CommonMessages_pb2.PropertyValue(enumValue=value.value)
45
+ if isinstance(value, str):
46
+ return CommonMessages_pb2.PropertyValue(stringValue=value)
47
+ raise TypeError(f"Unsupported property value type: {type(value)!r}")
48
+
49
+
50
+ def property_value_to_python(value: CommonMessages_pb2.PropertyValue) -> Any:
51
+ """Extract the Python scalar stored in a PropertyValue oneof."""
52
+ field = value.WhichOneof("value")
53
+ if field is None:
54
+ return None
55
+ return getattr(value, field)