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,196 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+
5
+ from rsseismic._application_paths import getInstalledExecutablePath
6
+ from rsseismic._client import Client, DEFAULT_SCRIPTING_PORT
7
+ from rsseismic.ApplicationManager import ApplicationManager
8
+ from rsseismic._rpc_helpers import require_success
9
+ from rsseismic.exceptions import ScriptingError
10
+ from rsseismic.generatedFiles import ApplicationService_pb2 as ApplicationService_pb2
11
+ from rsseismic.generatedFiles import ApplicationService_pb2_grpc as ApplicationService_pb2_grpc
12
+ from rsseismic._batch_compute import (
13
+ BatchComputeJobResult,
14
+ BatchComputeResult,
15
+ run_batch_compute,
16
+ )
17
+ from rsseismic.Model import Model
18
+
19
+
20
+ class RSSeismicApplication:
21
+ """
22
+ Entry point for connecting to a running RSSeismic scripting server.
23
+
24
+ Mirrors RS3's RS3Modeler: connect on a port, open projects, return Model proxies.
25
+ Use startApplication() to launch RSSeismic with -startScriptingServer when it is
26
+ not already running.
27
+ """
28
+
29
+ def __init__(self, port: int = DEFAULT_SCRIPTING_PORT, host: str = "localhost"):
30
+ self._client = Client(port=port, host=host)
31
+ self._port = port
32
+ self._host = host
33
+
34
+ @property
35
+ def port(self) -> int:
36
+ return self._port
37
+
38
+ @property
39
+ def client(self) -> Client:
40
+ return self._client
41
+
42
+ @classmethod
43
+ def startApplication(
44
+ cls,
45
+ port: int = DEFAULT_SCRIPTING_PORT,
46
+ overridePathToExecutable: str | None = None,
47
+ timeout: float = ApplicationManager.defaultTimeout,
48
+ ) -> None:
49
+ """
50
+ Launch RSSeismic and wait until the scripting server on port accepts ping.
51
+
52
+ Args:
53
+ port: Scripting port (49152–65535). Same value as DEFAULT_SCRIPTING_PORT / 60058.
54
+ overridePathToExecutable: Full path to RSSeismic.exe. When omitted, the latest
55
+ RSSeismic install is resolved from the Windows registry.
56
+ timeout: Seconds to wait for the server to become reachable.
57
+
58
+ Raises:
59
+ ValueError: port out of range
60
+ RuntimeError: port occupied, executable not found, or registry lookup failed
61
+ TimeoutError: server not ready within timeout
62
+ """
63
+ executable_path = (
64
+ overridePathToExecutable
65
+ if overridePathToExecutable
66
+ else getInstalledExecutablePath()
67
+ )
68
+ app_manager = ApplicationManager()
69
+ app_logger = logging.getLogger("Rocscience.RSSeismic")
70
+ app_manager.startApplication(
71
+ executable_path,
72
+ port,
73
+ cls._verifyServerRunning,
74
+ app_logger,
75
+ timeout,
76
+ )
77
+
78
+ @staticmethod
79
+ def _verifyServerRunning(port: int) -> None:
80
+ client = Client(port=port)
81
+ try:
82
+ client.ping()
83
+ finally:
84
+ client.closeConnection()
85
+
86
+ def ping(self) -> None:
87
+ """Verify the scripting server is reachable."""
88
+ self._client.ping()
89
+
90
+ def getMaxConcurrentAnalyses(self) -> int:
91
+ """
92
+ Return the effective maxConcurrentAnalyses from RSSeismic preferences.
93
+
94
+ This is the within-project cap on simultaneous engine runs used during
95
+ RunCompute. Batch parallel_degree capping uses this value when available.
96
+ """
97
+ stub = ApplicationService_pb2_grpc.ApplicationServiceStub(self._client.channel)
98
+ request = ApplicationService_pb2.GetMaxConcurrentAnalysesRequest()
99
+ response = self._client.callFunction(stub.GetMaxConcurrentAnalyses, request)
100
+ return int(response.maxConcurrentAnalyses)
101
+
102
+ def setMaxConcurrentAnalyses(self, maxConcurrentAnalyses: int) -> int:
103
+ """
104
+ Set maxConcurrentAnalyses in RSSeismic preferences (within-project engine cap).
105
+
106
+ Values are clamped to 1..256, matching the Preferences UI. Returns the effective
107
+ value after save (same semantics as getMaxConcurrentAnalyses).
108
+ """
109
+ stub = ApplicationService_pb2_grpc.ApplicationServiceStub(self._client.channel)
110
+ request = ApplicationService_pb2.SetMaxConcurrentAnalysesRequest(
111
+ maxConcurrentAnalyses=maxConcurrentAnalyses,
112
+ )
113
+ response = self._client.callFunction(stub.SetMaxConcurrentAnalyses, request)
114
+
115
+ if not response.success:
116
+ raise ScriptingError(
117
+ response.errorMessage or "SetMaxConcurrentAnalyses failed."
118
+ )
119
+
120
+ return int(response.maxConcurrentAnalyses)
121
+
122
+ def openFile(self, path: str) -> Model:
123
+ """Open a DeepSoil project file and return a Model handle."""
124
+ stub = ApplicationService_pb2_grpc.ApplicationServiceStub(self._client.channel)
125
+ request = ApplicationService_pb2.OpenFileRequest(fileName=path)
126
+ response = self._client.callFunction(stub.OpenFile, request)
127
+ require_success(response, "OpenFile")
128
+
129
+ if not response.modelID:
130
+ raise ScriptingError("OpenFile succeeded but returned an empty modelID.")
131
+
132
+ return Model(self._client, response.modelID)
133
+
134
+ def newProject(self) -> Model:
135
+ """Create a blank project with default settings and return a Model handle."""
136
+ stub = ApplicationService_pb2_grpc.ApplicationServiceStub(self._client.channel)
137
+ request = ApplicationService_pb2.NewProjectRequest()
138
+ response = self._client.callFunction(stub.NewProject, request)
139
+
140
+ if not response.success:
141
+ raise ScriptingError("NewProject failed.")
142
+
143
+ if not response.modelID:
144
+ raise ScriptingError("NewProject succeeded but returned an empty modelID.")
145
+
146
+ return Model(self._client, response.modelID)
147
+
148
+ def runBatchCompute(
149
+ self,
150
+ file_paths: list[str],
151
+ motion_names: list[str] | None = None,
152
+ *,
153
+ parallel_degree: int = 1,
154
+ max_concurrent_analyses: int | None = None,
155
+ timeout: float | None = None,
156
+ close_save_project: bool = False,
157
+ continue_on_error: bool = True,
158
+ ) -> BatchComputeResult:
159
+ """
160
+ Run compute for many project files (open → motion selection → compute → close).
161
+
162
+ Motion selection applies to the open session only; it is not stored in the
163
+ project file. Each job calls setMotionSelection before runCompute.
164
+
165
+ Args:
166
+ file_paths: Project files to process (.rsseismicfile). Duplicate paths are omitted.
167
+ motion_names: Motions to select for every project. When None, each project keeps
168
+ its current selection or uses the first available motion.
169
+ parallel_degree: How many projects may run at once (cross-project only). Capped
170
+ using maxConcurrentAnalyses from RSSeismic preferences when available.
171
+ max_concurrent_analyses: When set, applies this within-project engine cap for the
172
+ batch via setMaxConcurrentAnalyses and restores the previous preference after
173
+ the batch. When None, uses getMaxConcurrentAnalyses().
174
+ timeout: Optional RunCompute deadline in seconds for each project.
175
+ close_save_project: When True, save each project on close.
176
+ continue_on_error: When False, stop starting new jobs after the first failure
177
+ (in-flight jobs may still finish).
178
+
179
+ Returns:
180
+ BatchComputeResult with per-file outcomes in input order.
181
+ """
182
+ return run_batch_compute(
183
+ self,
184
+ file_paths,
185
+ motion_names,
186
+ parallel_degree=parallel_degree,
187
+ max_concurrent_analyses=max_concurrent_analyses,
188
+ timeout=timeout,
189
+ close_save_project=close_save_project,
190
+ continue_on_error=continue_on_error,
191
+ logger=logging.getLogger("Rocscience.RSSeismic"),
192
+ )
193
+
194
+ def close(self) -> None:
195
+ """Close the gRPC connection."""
196
+ self._client.closeConnection()
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ import time
8
+ from logging import Logger
9
+ from typing import Callable
10
+
11
+ from multiprocessing.connection import Listener
12
+
13
+
14
+ class ApplicationManager:
15
+ minimumPort = 49152
16
+ maximumPort = 65535
17
+ defaultTimeout = 120.0
18
+
19
+ def startApplication(
20
+ self,
21
+ pathToExecutable: str,
22
+ port: int,
23
+ isServerRunning: Callable[[int], None],
24
+ logger: Logger,
25
+ timeout: float = defaultTimeout,
26
+ ) -> None:
27
+ """
28
+ Starts the application specified by pathToExecutable with ``-startScriptingServer`` on the given port.
29
+ Returns when the scripting server is ready to accept requests.
30
+
31
+ Args:
32
+ pathToExecutable: Full path to the RSSeismic executable to start.
33
+ port: Port number for the scripting server (49152–65535).
34
+ isServerRunning: Callable invoked to verify the server on ``port`` is reachable.
35
+ logger: Logger used during startup; logging is suppressed while polling the server.
36
+ timeout: Seconds to wait for the server to become ready.
37
+
38
+ Raises:
39
+ ValueError: port outside 49152–65535
40
+ RuntimeError: port occupied or executable missing
41
+ TimeoutError: server not ready within timeout
42
+ """
43
+ if port < self.minimumPort or port > self.maximumPort:
44
+ msg = f"port must be in the range {self.minimumPort} to {self.maximumPort}"
45
+ logger.error(msg)
46
+ raise ValueError(msg)
47
+
48
+ if not os.path.isfile(pathToExecutable):
49
+ msg = f"executable not found: {pathToExecutable}"
50
+ logger.error(msg)
51
+ raise RuntimeError(msg)
52
+
53
+ if not self.isPortAvailable(port):
54
+ msg = f"port number {port} is occupied. Please choose another port."
55
+ logger.error(msg)
56
+ raise RuntimeError(msg)
57
+
58
+ logger.info(
59
+ "Starting RSSeismic at %s with scripting server on port %s...",
60
+ pathToExecutable,
61
+ port,
62
+ )
63
+ popen_kwargs: dict = {
64
+ "args": [pathToExecutable, "-startScriptingServer", str(port)],
65
+ "start_new_session": True,
66
+ }
67
+ if sys.platform == "win32":
68
+ popen_kwargs["creationflags"] = subprocess.DETACHED_PROCESS
69
+
70
+ subprocess.Popen(**popen_kwargs)
71
+ self._tryToConnectToServer(port, isServerRunning, timeout, logger)
72
+
73
+ @staticmethod
74
+ def isPortAvailable(port: int) -> bool:
75
+ listener = None
76
+ try:
77
+ listener = Listener(("localhost", port), "AF_INET")
78
+ return True
79
+ except Exception:
80
+ return False
81
+ finally:
82
+ if listener is not None:
83
+ listener.close()
84
+
85
+ def _tryToConnectToServer(
86
+ self,
87
+ port: int,
88
+ isServerRunning: Callable[[int], None],
89
+ timeout: float,
90
+ logger: Logger,
91
+ ) -> None:
92
+ start_time = time.time()
93
+
94
+ while True:
95
+ if timeout and (time.time() - start_time) > timeout:
96
+ msg = "RSSeismic did not start the scripting server within the given timeout."
97
+ logger.error(msg)
98
+ raise TimeoutError(msg)
99
+
100
+ try:
101
+ logger.debug("Trying to connect to the scripting server...")
102
+ previous_disabled = logger.disabled
103
+ logger.disabled = True
104
+ try:
105
+ isServerRunning(port)
106
+ finally:
107
+ logger.disabled = previous_disabled
108
+ logger.debug("Connected to scripting server.")
109
+ return
110
+ except Exception:
111
+ time.sleep(0.5)
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from rsseismic._client import Client
4
+ from rsseismic._proxyObject import _ProxyObject
5
+ from rsseismic.generatedFiles import BedrockLayerDataService_pb2_grpc as BedrockLayerDataService_pb2_grpc
6
+ from rsseismic.properties._PropertyAccessor import ProjectScopedPropertyAccessor
7
+ from rsseismic.properties.bedrockLayer._generated import BedrockLayerPropertiesMixin
8
+
9
+
10
+ class BedrockLayer(_ProxyObject, BedrockLayerPropertiesMixin):
11
+ """
12
+ Read and edit bedrock layer properties for a profile.
13
+
14
+ Obtain an instance from ``model.Profiles.getBedrockLayerProxy(profileName)``.
15
+
16
+ Bedrock thickness cannot be changed through this object; it is calculated from
17
+ the soil layers above bedrock. To read thickness, use
18
+ ``model.Profiles.getBedrockLayer(profileName).thickness``.
19
+ """
20
+
21
+ def __init__(self, client: Client, modelId: str, layerId: str):
22
+ super().__init__(client, layerId)
23
+ self.modelId = modelId
24
+ stub = BedrockLayerDataService_pb2_grpc.BedrockLayerDataServiceStub(self._client.channel)
25
+ self.Data = ProjectScopedPropertyAccessor(client, modelId, layerId, stub)
26
+
27
+ @property
28
+ def layerId(self) -> str:
29
+ return self._objectId
@@ -0,0 +1,169 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from rsseismic._client import Client
6
+ from rsseismic._proto_converters import (
7
+ from_mean_profile_append_layers_response,
8
+ from_mean_profile_bedrock_layer_entry,
9
+ from_mean_profile_delete_layer_response,
10
+ from_mean_profile_insert_layer_response,
11
+ from_mean_profile_move_layer_response,
12
+ from_mean_profile_response,
13
+ from_mean_profile_soil_layer_entry,
14
+ )
15
+ from rsseismic._proxyObject import _ProxyObject
16
+ from rsseismic._rpc_helpers import require_success
17
+ from rsseismic.generatedFiles import MeanProfileService_pb2 as MeanProfileService_pb2
18
+ from rsseismic.generatedFiles import MeanProfileService_pb2_grpc as MeanProfileService_pb2_grpc
19
+ from rsseismic.types import (
20
+ BedrockLayerSummary,
21
+ MeanProfileAppendLayersResult,
22
+ MeanProfileDeleteLayerResult,
23
+ MeanProfileInsertLayerResult,
24
+ MeanProfileMoveLayerResult,
25
+ MeanProfileSummary,
26
+ SoilLayerSummary,
27
+ )
28
+
29
+ if TYPE_CHECKING:
30
+ from rsseismic.MeanProfileSoilLayer import MeanProfileSoilLayer
31
+
32
+
33
+ class MeanProfile(_ProxyObject):
34
+ """
35
+ Template mean profile for automatic profile generation.
36
+
37
+ When automatic profile generation is enabled, configure this profile before
38
+ calling ``Model.generateProfiles()``. The individual profiles created by that
39
+ step are based on this template, not on the profiles under ``model.Profiles``.
40
+
41
+ Use ``listSoilLayers``, ``getSoilLayer``, ``insertLayerBefore``, and related
42
+ methods to edit the template. To change layer properties, use the
43
+ ``MeanProfileSoilLayer`` returned by ``getSoilLayer`` (including reference
44
+ curves via ``layer.ReferenceCurve``).
45
+ """
46
+
47
+ def __init__(self, client: Client, modelId: str):
48
+ super().__init__(client, modelId)
49
+ self.modelId = modelId
50
+ self._mean_profile_service = MeanProfileService_pb2_grpc.MeanProfileServiceStub(
51
+ self._client.channel
52
+ )
53
+
54
+ def getMeanProfile(self) -> MeanProfileSummary:
55
+ """Return summary information for the mean profile."""
56
+ request = MeanProfileService_pb2.GetMeanProfileRequest(modelID=self._objectId)
57
+ response = self._client.callFunction(self._mean_profile_service.GetMeanProfile, request)
58
+ require_success(response, "GetMeanProfile")
59
+ return from_mean_profile_response(response)
60
+
61
+ def listSoilLayers(self) -> list[SoilLayerSummary]:
62
+ """List soil layers on the mean profile."""
63
+ request = MeanProfileService_pb2.ListSoilLayersRequest(modelID=self._objectId)
64
+ response = self._client.callFunction(self._mean_profile_service.ListSoilLayers, request)
65
+ require_success(response, "ListSoilLayers")
66
+ return [from_mean_profile_soil_layer_entry(layer) for layer in response.layers]
67
+
68
+ def getBedrockLayer(self) -> BedrockLayerSummary:
69
+ """Return the bedrock layer on the mean profile."""
70
+ request = MeanProfileService_pb2.GetBedrockLayerRequest(modelID=self._objectId)
71
+ response = self._client.callFunction(self._mean_profile_service.GetBedrockLayer, request)
72
+ require_success(response, "GetBedrockLayer")
73
+ return from_mean_profile_bedrock_layer_entry(response.bedrock)
74
+
75
+ def getSoilLayer(self, layerId: str) -> MeanProfileSoilLayer:
76
+ """Return a property proxy for a mean-profile soil layer GUID."""
77
+ from rsseismic.MeanProfileSoilLayer import MeanProfileSoilLayer
78
+
79
+ return MeanProfileSoilLayer(self._client, self._objectId, layerId)
80
+
81
+ def insertLayerBefore(
82
+ self,
83
+ referenceLayerID: str,
84
+ ) -> MeanProfileInsertLayerResult:
85
+ """
86
+ Insert a soil layer before referenceLayerID.
87
+
88
+ referenceLayerID may be a soil layer GUID or the bedrock layer GUID
89
+ (insert before bedrock appends a layer).
90
+ """
91
+ request = MeanProfileService_pb2.InsertLayerBeforeRequest(
92
+ modelID=self._objectId,
93
+ referenceLayerID=referenceLayerID,
94
+ )
95
+ response = self._client.callFunction(
96
+ self._mean_profile_service.InsertLayerBefore,
97
+ request,
98
+ )
99
+ require_success(response, "InsertLayerBefore")
100
+ return from_mean_profile_insert_layer_response(response)
101
+
102
+ def insertLayerAfter(
103
+ self,
104
+ referenceLayerID: str,
105
+ ) -> MeanProfileInsertLayerResult:
106
+ """Insert a soil layer after a soil layer GUID."""
107
+ request = MeanProfileService_pb2.InsertLayerAfterRequest(
108
+ modelID=self._objectId,
109
+ referenceLayerID=referenceLayerID,
110
+ )
111
+ response = self._client.callFunction(
112
+ self._mean_profile_service.InsertLayerAfter,
113
+ request,
114
+ )
115
+ require_success(response, "InsertLayerAfter")
116
+ return from_mean_profile_insert_layer_response(response)
117
+
118
+ def appendLayers(self, count: int = 1) -> MeanProfileAppendLayersResult:
119
+ """Append one or more soil layers before bedrock."""
120
+ request = MeanProfileService_pb2.AppendLayersRequest(
121
+ modelID=self._objectId,
122
+ count=count,
123
+ )
124
+ response = self._client.callFunction(self._mean_profile_service.AppendLayers, request)
125
+ require_success(response, "AppendLayers")
126
+ return from_mean_profile_append_layers_response(response)
127
+
128
+ def deleteLayer(self, layerID: str) -> MeanProfileDeleteLayerResult:
129
+ """Delete a mean-profile soil layer. At least one soil layer must remain."""
130
+ request = MeanProfileService_pb2.DeleteLayerRequest(
131
+ modelID=self._objectId,
132
+ layerID=layerID,
133
+ )
134
+ response = self._client.callFunction(self._mean_profile_service.DeleteLayer, request)
135
+ require_success(response, "DeleteLayer")
136
+ return from_mean_profile_delete_layer_response(response)
137
+
138
+ def duplicateLayer(
139
+ self,
140
+ sourceLayerID: str,
141
+ insertAfter: bool = True,
142
+ ) -> MeanProfileInsertLayerResult:
143
+ """Duplicate a mean-profile soil layer before or after the source layer."""
144
+ request = MeanProfileService_pb2.DuplicateLayerRequest(
145
+ modelID=self._objectId,
146
+ sourceLayerID=sourceLayerID,
147
+ insertAfter=insertAfter,
148
+ )
149
+ response = self._client.callFunction(
150
+ self._mean_profile_service.DuplicateLayer,
151
+ request,
152
+ )
153
+ require_success(response, "DuplicateLayer")
154
+ return from_mean_profile_insert_layer_response(response)
155
+
156
+ def moveLayer(
157
+ self,
158
+ layerID: str,
159
+ newIndex1Based: int,
160
+ ) -> MeanProfileMoveLayerResult:
161
+ """Move a mean-profile soil layer to a new 1-based index."""
162
+ request = MeanProfileService_pb2.MoveLayerRequest(
163
+ modelID=self._objectId,
164
+ layerID=layerID,
165
+ newIndex1Based=newIndex1Based,
166
+ )
167
+ response = self._client.callFunction(self._mean_profile_service.MoveLayer, request)
168
+ require_success(response, "MoveLayer")
169
+ return from_mean_profile_move_layer_response(response)
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ from rsseismic._client import Client
4
+ from rsseismic._proxyObject import _ProxyObject
5
+ from rsseismic._rpc_helpers import require_success
6
+ from rsseismic.generatedFiles import MeanProfileSoilLayerDataService_pb2 as MeanProfileSoilLayerDataService_pb2
7
+ from rsseismic.generatedFiles import MeanProfileSoilLayerDataService_pb2_grpc as MeanProfileSoilLayerDataService_pb2_grpc
8
+ from rsseismic.properties._PropertyAccessor import ProjectScopedPropertyAccessor
9
+ from rsseismic.properties.meanProfileSoilLayer._generated import MeanProfileSoilLayerPropertiesMixin
10
+ from rsseismic.ReferenceCurve import ReferenceCurve
11
+
12
+
13
+ def _to_vary_with_depth_points(
14
+ points: list[tuple[float, float]],
15
+ ) -> list[MeanProfileSoilLayerDataService_pb2.VaryWithDepthPoint]:
16
+ return [
17
+ MeanProfileSoilLayerDataService_pb2.VaryWithDepthPoint(depth=depth, value=value)
18
+ for depth, value in points
19
+ ]
20
+
21
+
22
+ def _from_vary_with_depth_points(
23
+ points: list[MeanProfileSoilLayerDataService_pb2.VaryWithDepthPoint],
24
+ ) -> list[tuple[float, float]]:
25
+ return [(point.depth, point.value) for point in points]
26
+
27
+
28
+ class MeanProfileSoilLayer(_ProxyObject, MeanProfileSoilLayerPropertiesMixin):
29
+ """
30
+ Read and edit properties for one soil layer on the mean profile template.
31
+
32
+ Obtain an instance from ``model.MeanProfile.getSoilLayer(layerId)``. Layer IDs
33
+ come from ``model.MeanProfile.listSoilLayers()``.
34
+
35
+ Use the generated ``get*`` / ``set*`` helpers for scalar layer properties. When
36
+ shear wave velocity or shear strength varies with depth, set the variation mode
37
+ first, then use ``getShearWaveValues`` / ``setShearWaveValues`` or
38
+ ``getShearStrengthValues`` / ``setShearStrengthValues`` for the depth table.
39
+
40
+ Reference curves are edited through ``layer.ReferenceCurve``. Unlike regular
41
+ profile layers, ``ReferenceCurve.generateReferenceCurve()`` is not available here;
42
+ set curve properties and points on ``ReferenceCurve`` directly.
43
+ """
44
+
45
+ def __init__(self, client: Client, modelId: str, layerId: str):
46
+ super().__init__(client, layerId)
47
+ self.modelId = modelId
48
+ stub = MeanProfileSoilLayerDataService_pb2_grpc.MeanProfileSoilLayerDataServiceStub(
49
+ self._client.channel
50
+ )
51
+ self.Data = ProjectScopedPropertyAccessor(client, modelId, layerId, stub)
52
+ self.ReferenceCurve = ReferenceCurve(client, modelId, layerId)
53
+ self._service = stub
54
+
55
+ @property
56
+ def layerId(self) -> str:
57
+ return self._objectId
58
+
59
+ def getShearWaveValues(self) -> list[tuple[float, float]]:
60
+ """
61
+ Return vary-with-depth shear wave velocity points as (depth, value) pairs.
62
+
63
+ Requires shearWaveVelocityVariation = Vary_With_Depth.
64
+ """
65
+ request = MeanProfileSoilLayerDataService_pb2.GetShearWaveValuesRequest(
66
+ projectId=self.modelId,
67
+ layerID=self._objectId,
68
+ )
69
+ response = self._client.callFunction(self._service.GetShearWaveValues, request)
70
+ require_success(response, "GetShearWaveValues")
71
+ return _from_vary_with_depth_points(list(response.points))
72
+
73
+ def setShearWaveValues(self, points: list[tuple[float, float]]) -> None:
74
+ """
75
+ Set vary-with-depth shear wave velocity points from (depth, value) pairs.
76
+
77
+ Requires shearWaveVelocityVariation = Vary_With_Depth. The server requires
78
+ at least two points.
79
+ """
80
+ if not points:
81
+ raise ValueError("At least one vary-with-depth point is required.")
82
+ request = MeanProfileSoilLayerDataService_pb2.SetShearWaveValuesRequest(
83
+ projectId=self.modelId,
84
+ layerID=self._objectId,
85
+ points=_to_vary_with_depth_points(points),
86
+ )
87
+ response = self._client.callFunction(self._service.SetShearWaveValues, request)
88
+ require_success(response, "SetShearWaveValues")
89
+
90
+ def getShearStrengthValues(self) -> list[tuple[float, float]]:
91
+ """
92
+ Return vary-with-depth shear strength points as (depth, value) pairs.
93
+
94
+ Requires shearStrengthVariation = Vary_With_Depth.
95
+ """
96
+ request = MeanProfileSoilLayerDataService_pb2.GetShearStrengthValuesRequest(
97
+ projectId=self.modelId,
98
+ layerID=self._objectId,
99
+ )
100
+ response = self._client.callFunction(self._service.GetShearStrengthValues, request)
101
+ require_success(response, "GetShearStrengthValues")
102
+ return _from_vary_with_depth_points(list(response.points))
103
+
104
+ def setShearStrengthValues(self, points: list[tuple[float, float]]) -> None:
105
+ """
106
+ Set vary-with-depth shear strength points from (depth, value) pairs.
107
+
108
+ Requires shearStrengthVariation = Vary_With_Depth. The server requires
109
+ at least two points.
110
+ """
111
+ if not points:
112
+ raise ValueError("At least one vary-with-depth point is required.")
113
+ request = MeanProfileSoilLayerDataService_pb2.SetShearStrengthValuesRequest(
114
+ projectId=self.modelId,
115
+ layerID=self._objectId,
116
+ points=_to_vary_with_depth_points(points),
117
+ )
118
+ response = self._client.callFunction(self._service.SetShearStrengthValues, request)
119
+ require_success(response, "SetShearStrengthValues")