diff --git a/notebooks/Plaxis3D_input_controller.ipynb b/notebooks/Plaxis3D_input_controller.ipynb index 4b33f7a..4dced56 100644 --- a/notebooks/Plaxis3D_input_controller.ipynb +++ b/notebooks/Plaxis3D_input_controller.ipynb @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -33,8 +33,9 @@ "metadata": {}, "outputs": [], "source": [ + "from plxscripting.easy import new_server\n", "from plxcontroller.plaxis_3d_input_controller import Plaxis3DInputController\n", - "from plxscripting.easy import new_server" + "from plxcontroller.geometry_3d.polygon_3d import Polygon3D" ] }, { @@ -54,7 +55,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -66,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -76,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -93,23 +94,53 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "# Uncomment below line if you would like to create a new Plaxis model (otherwise continue with the already opened model)\n", - "# co.s_i.new()" + "# Comment line below if you would like to continue with existing model\n", + "co.s_i.new() # creates new model" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "# Create cuboid (example)\n", "co.g_i.gotostructures()\n", - "co.g_i.cuboid(10, (0, 0, 0))" + "co.g_i.cuboid(\n", + " 10, (0, 0, -5)\n", + ") # cube with side length 10, centered at (x,y) = (0,0) and with z_min = -5.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check that filter function works\n", + "co.g_i.gotostages()\n", + "\n", + "filtered_volumes = co.filter_volumes_above_polygons(\n", + " polygons=[\n", + " Polygon3D([(-10, -10, -5), (10, -10, -5), (10, 10, -20), (-10, 10, -20)]),\n", + " ],\n", + " plaxis_volumes=None,\n", + ")\n", + "filtered_volumes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Activate filtered volumes\n", + "co.g_i.activate(filtered_volumes, co.g_i.InitialPhase)" ] } ], diff --git a/src/plxcontroller/plaxis_3d_input_controller.py b/src/plxcontroller/plaxis_3d_input_controller.py index b4f6369..dce91db 100644 --- a/src/plxcontroller/plaxis_3d_input_controller.py +++ b/src/plxcontroller/plaxis_3d_input_controller.py @@ -1,9 +1,17 @@ from __future__ import annotations -from typing import Any +from typing import Dict, List +from plxscripting.plxproxy import PlxProxyGlobalObject, PlxProxyObject from plxscripting.server import Server +from plxcontroller.geometry_3d.bounding_box_3d import BoundingBox3D +from plxcontroller.geometry_3d.operations_3d import ( + project_vertically_point_onto_polygon_3d, +) +from plxcontroller.geometry_3d.point_3d import Point3D +from plxcontroller.geometry_3d.polygon_3d import Polygon3D + class Plaxis3DInputController: def __init__(self, server: Server): @@ -13,6 +21,7 @@ def __init__(self, server: Server): server (Server): the server connection with the Plaxis program. """ self.server = server + self._plaxis_volumes_bounding_boxes: Dict[PlxProxyObject, BoundingBox3D] = {} @property def s_i(self) -> Server: @@ -20,6 +29,103 @@ def s_i(self) -> Server: return self.server @property - def g_i(self) -> Any: + def g_i(self) -> PlxProxyGlobalObject: """Returns the global project object. This is a typical alias for the global project object.""" return self.server.plx_global + + @property + def plaxis_volumes_bounding_boxes(self) -> Dict[PlxProxyObject, BoundingBox3D]: + """Returns the mapping between the plaxis volumes and their corresponding bounding boxes.""" + return self._plaxis_volumes_bounding_boxes + + def filter_volumes_above_polygons( + self, + polygons: List[Polygon3D], + plaxis_volumes: List[PlxProxyObject] | None = None, + ) -> List[PlxProxyObject]: + """Filters the given plaxis volumes if its centroid is located above any polygon + in the given list of polygons. + + Note that if the centroid of the plaxis volume falls outside the projection + of a polygon is not considered to be above the polygon. + + Parameters + ---------- + polygons : List[Polygon3D] + the list of polygons. + plaxis_volumes : List[PlxProxyObject] | None, optional + the list of plaxis volumes to filter from. + If None is given then all the plaxis volumes in the model are used. + Defaults to None. + + Returns + ------- + List[PlxProxyObject] + the filtered plaxis volumes. + + Raises + ------ + TypeError + if parameters are not of the expected type. + ValueError + if any item of plaxis_volumes is not present in the volumes of the plaxis model. + """ + + # Validate input + if not isinstance(polygons, list): + raise TypeError( + f"Unexpected type for polygons. Expected list, but got {type(polygons)}." + ) + for i, polygon in enumerate(polygons): + if not isinstance(polygon, Polygon3D): + raise TypeError( + f"Unexpected type for item {i} of polygons. Expected Polygon3D, but got {type(polygon)}." + ) + + if plaxis_volumes is not None: + if not isinstance(plaxis_volumes, list): + raise TypeError( + f"Unexpected type for plaxis_volumes. Expected list, but got {type(plaxis_volumes)}." + ) + for i, plaxis_volume in enumerate(plaxis_volumes): + if not isinstance(plaxis_volume, PlxProxyObject): + raise TypeError( + f"Unexpected type for item {i} of plaxis_volumes. Expected PlxProxyObject, but got {type(plaxis_volume)}." + ) + if plaxis_volume not in self.g_i.Volumes: + raise ValueError( + f"Plaxis object {plaxis_volume} is not present in the volumes of the plaxis model." + ) + + # Initialize plaxis_volume list as all the volumes in the Plaxis model. + if plaxis_volumes is None: + plaxis_volumes = self.g_i.Volumes + + # Map plaxis volumes to bounding boxes + for plaxis_volume in plaxis_volumes: + if plaxis_volume not in self.plaxis_volumes_bounding_boxes.keys(): + self._plaxis_volumes_bounding_boxes[plaxis_volume] = BoundingBox3D( + x_min=plaxis_volume.BoundingBox.xMin.value, + y_min=plaxis_volume.BoundingBox.yMin.value, + z_min=plaxis_volume.BoundingBox.zMin.value, + x_max=plaxis_volume.BoundingBox.xMax.value, + y_max=plaxis_volume.BoundingBox.yMax.value, + z_max=plaxis_volume.BoundingBox.zMax.value, + ) + + # Filter the volumes if it is above any of the polygons + filtered_plaxis_volumes = [] + for plaxis_volume in plaxis_volumes: + bbox = self.plaxis_volumes_bounding_boxes[plaxis_volume] + for polygon in polygons: + projected_point = project_vertically_point_onto_polygon_3d( + point=bbox.centroid, polygon=polygon + ) + if ( + isinstance(projected_point, Point3D) + and bbox.centroid.z >= projected_point.z + ): + filtered_plaxis_volumes.append(plaxis_volume) + break + + return filtered_plaxis_volumes