Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/api/tree_objects.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ ACP objects
ModelingPly
ProductionPly
AnalysisPly
Sensor
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/ansys/acp/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
ParallelSelectionRule,
ProductionPly,
Rosette,
Sensor,
SphericalSelectionRule,
Stackup,
SubLaminate,
Expand Down
2 changes: 2 additions & 0 deletions src/ansys/acp/core/_tree_objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .parallel_selection_rule import ParallelSelectionRule
from .production_ply import ProductionPly
from .rosette import Rosette
from .sensor import Sensor
from .spherical_selection_rule import SphericalSelectionRule
from .stackup import FabricWithAngle, Stackup
from .sublaminate import Lamina, SubLaminate
Expand Down Expand Up @@ -52,6 +53,7 @@
"ModelingPly",
"ProductionPly",
"AnalysisPly",
"Sensor",
"UnitSystemType",
"EdgeSetType",
"ElementalDataType",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,30 @@ def inner(self: Readable) -> CreatableFromResourcePath | None:
return inner


def grpc_data_getter(name: str, from_protobuf: _FROM_PROTOBUF_T) -> Callable[[Readable], Any]:
def grpc_data_getter(
name: str, from_protobuf: _FROM_PROTOBUF_T, check_optional: bool = False
) -> Callable[[Readable], Any]:
"""
Creates a getter method which obtains the server object via the gRPC
Get endpoint.

Parameters
----------
from_protobuf :
Function to convert the protobuf object to the type exposed by the
property.
check_optional :
If ``True``, the getter will return ``None`` if the property is not
set on the protobuf object. Otherwise, the default protobuf value
will be used.
"""

def inner(self: Readable) -> Any:
self._get_if_stored()
return from_protobuf(_get_data_attribute(self._pb_object, name))
pb_attribute = _get_data_attribute(self._pb_object, name, check_optional=check_optional)
if check_optional and pb_attribute is None:
return None
return from_protobuf(pb_attribute)

return inner

Expand All @@ -94,8 +109,12 @@ def inner(self: Editable, value: Any) -> None:
return inner


def _get_data_attribute(pb_obj: ObjectInfo, name: str) -> Any:
def _get_data_attribute(pb_obj: ObjectInfo, name: str, check_optional: bool = False) -> Any:
name_parts = name.split(".")
if check_optional:
parent_obj = reduce(getattr, name_parts[:-1], pb_obj)
if hasattr(parent_obj, "HasField") and not parent_obj.HasField(name_parts[-1]):
return None
return reduce(getattr, name_parts, pb_obj)


Expand All @@ -118,15 +137,39 @@ def _set_data_attribute(pb_obj: ObjectInfo, name: str, value: Any) -> None:
target_object.add().CopyFrom(item)


def _wrap_doc(obj: Any, doc: str | None) -> Any:
if doc is not None:
obj.__doc__ = doc
return obj


def grpc_data_property(
name: str,
to_protobuf: _TO_PROTOBUF_T = lambda x: x,
from_protobuf: _FROM_PROTOBUF_T = lambda x: x,
check_optional: bool = False,
doc: str | None = None,
) -> Any:
"""
Helper for defining properties accessed via gRPC. The property getter
and setter make calls to the gRPC Get and Put endpoints to synchronize
the local object with the remote backend.

Parameters
----------
name :
Name of the property.
to_protobuf :
Function to convert the property value to the protobuf type.
from_protobuf :
Function to convert the protobuf object to the type exposed by the
property.
check_optional :
If ``True``, the getter will return ``None`` if the property is not
set on the protobuf object. Otherwise, the default protobuf value
will be used.
doc :
Docstring for the property.
"""
# Note jvonrick August 2023: We don't ensure with typechecks that the property returned here is
# compatible with the class on which this property is created. For example:
Expand All @@ -135,21 +178,45 @@ def grpc_data_property(
# Protocol
# See the discussion here on why it is hard to have typed properties:
# https://github.com/python/typing/issues/985
return _exposed_grpc_property(grpc_data_getter(name, from_protobuf=from_protobuf)).setter(
grpc_data_setter(name, to_protobuf=to_protobuf)
return _wrap_doc(
_exposed_grpc_property(
grpc_data_getter(name, from_protobuf=from_protobuf, check_optional=check_optional)
).setter(grpc_data_setter(name, to_protobuf=to_protobuf)),
doc=doc,
)


def grpc_data_property_read_only(
name: str,
from_protobuf: _FROM_PROTOBUF_T = lambda x: x,
check_optional: bool = False,
doc: str | None = None,
) -> Any:
"""
Helper for defining properties accessed via gRPC. The property getter
makes call to the gRPC Get endpoints to synchronize
the local object with the remote backend.
"""
return _exposed_grpc_property(grpc_data_getter(name, from_protobuf=from_protobuf))

Parameters
----------
name :
Name of the property.
from_protobuf :
Function to convert the protobuf object to the type exposed by the
property.
check_optional :
If ``True``, the getter will return ``None`` if the property is not
set on the protobuf object. Otherwise, the default protobuf value
will be used.
doc :
Docstring for the property.
"""
return _wrap_doc(
_exposed_grpc_property(
grpc_data_getter(name, from_protobuf=from_protobuf, check_optional=check_optional)
),
doc=doc,
)


def grpc_link_property(name: str) -> Any:
Expand Down
7 changes: 7 additions & 0 deletions src/ansys/acp/core/_tree_objects/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
lookup_table_column_type_pb2,
mesh_query_pb2,
ply_material_pb2,
sensor_pb2,
unit_system_pb2,
)

Expand All @@ -25,6 +26,7 @@
"ElementalDataType",
"NodalDataType",
"LookUpTableColumnValueType",
"SensorType",
]

(StatusType, status_type_to_pb, status_type_from_pb) = wrap_to_string_enum(
Expand Down Expand Up @@ -155,3 +157,8 @@
lookup_table_3d_pb2.InterpolationAlgorithm,
module=__name__,
)
(
SensorType,
sensor_type_to_pb,
sensor_type_from_pb,
) = wrap_to_string_enum("SensorType", sensor_pb2.SensorType, module=__name__)
4 changes: 4 additions & 0 deletions src/ansys/acp/core/_tree_objects/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
oriented_selection_set_pb2_grpc,
parallel_selection_rule_pb2_grpc,
rosette_pb2_grpc,
sensor_pb2_grpc,
spherical_selection_rule_pb2_grpc,
stackup_pb2_grpc,
sublaminate_pb2_grpc,
Expand Down Expand Up @@ -60,6 +61,7 @@
from .oriented_selection_set import OrientedSelectionSet
from .parallel_selection_rule import ParallelSelectionRule
from .rosette import Rosette
from .sensor import Sensor
from .spherical_selection_rule import SphericalSelectionRule
from .stackup import Stackup
from .sublaminate import SubLaminate
Expand Down Expand Up @@ -339,6 +341,8 @@ def export_materials(self, path: _PATH) -> None:
ModelingGroup, modeling_group_pb2_grpc.ObjectServiceStub
)

create_sensor, sensors = define_mutable_mapping(Sensor, sensor_pb2_grpc.ObjectServiceStub)

@property
def mesh(self) -> MeshData:
mesh_query_stub = mesh_query_pb2_grpc.MeshQueryServiceStub(self._channel)
Expand Down
117 changes: 117 additions & 0 deletions src/ansys/acp/core/_tree_objects/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from __future__ import annotations

from typing import Iterable, Union, get_args

from ansys.api.acp.v0 import sensor_pb2, sensor_pb2_grpc

from .._utils.array_conversions import to_tuple_from_1D_array
from ._grpc_helpers.linked_object_list import define_polymorphic_linked_object_list
from ._grpc_helpers.property_helper import (
grpc_data_property,
grpc_data_property_read_only,
mark_grpc_properties,
)
from .base import CreatableTreeObject, IdTreeObject
from .element_set import ElementSet
from .enums import SensorType, sensor_type_from_pb, sensor_type_to_pb, status_type_from_pb
from .fabric import Fabric
from .modeling_ply import ModelingPly
from .object_registry import register
from .oriented_selection_set import OrientedSelectionSet
from .stackup import Stackup
from .sublaminate import SubLaminate

__all__ = ["Sensor"]


_LINKABLE_ENTITY_TYPES = Union[
Fabric, Stackup, SubLaminate, ElementSet, OrientedSelectionSet, ModelingPly
]


@mark_grpc_properties
@register
class Sensor(CreatableTreeObject, IdTreeObject):
"""Instantiate a Sensor.

Parameters
----------
name :
Name of the sensor.
sensor_type :
Type of sensor: The sensor can be scoped by area, material, plies,
or solid model.
active :
Inactive sensors are not evaluated.
entities :
List of entities which define the sensor's scope.
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth to document the result entities?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a doc attribute to the property helpers, to set the docstring of the properties. Opened #267 as a follow-up to enable documenting the read-write properties (the result entities are read-only) also as both parameter and attribute, without duplication.


__slots__: Iterable[str] = tuple()

_COLLECTION_LABEL = "sensors"
OBJECT_INFO_TYPE = sensor_pb2.ObjectInfo
CREATE_REQUEST_TYPE = sensor_pb2.CreateRequest

def __init__(
self,
name: str = "Sensor",
sensor_type: SensorType = SensorType.SENSOR_BY_AREA,
active: bool = True,
entities: Iterable[_LINKABLE_ENTITY_TYPES] = (),
):
super().__init__(name=name)
self.active = active
self.entities = entities
self.sensor_type = sensor_type

def _create_stub(self) -> sensor_pb2_grpc.ObjectServiceStub:
return sensor_pb2_grpc.ObjectServiceStub(self._channel)

locked = grpc_data_property_read_only("properties.locked")
sensor_type = grpc_data_property(
"properties.sensor_type", from_protobuf=sensor_type_from_pb, to_protobuf=sensor_type_to_pb
)
status = grpc_data_property_read_only("properties.status", from_protobuf=status_type_from_pb)

active = grpc_data_property("properties.active")
entities = define_polymorphic_linked_object_list(
"properties.entities", allowed_types=get_args(_LINKABLE_ENTITY_TYPES)
)

covered_area = grpc_data_property_read_only(
"properties.covered_area",
check_optional=True,
doc=(
"The surface area of a selected Element Set / Oriented Selection Set, "
"or the tooling surface area that is covered by the composite layup of "
"the selected Material or Modeling Ply."
),
)
modeling_ply_area = grpc_data_property_read_only(
"properties.modeling_ply_area",
check_optional=True,
doc="The surface area of all Modeling Plies of the selected entity.",
)
production_ply_area = grpc_data_property_read_only(
"properties.production_ply_area",
check_optional=True,
doc="The surface area of all production plies of the selected entity.",
)
price = grpc_data_property_read_only(
"properties.price",
check_optional=True,
doc=(
"The price for the composite layup of the selected entity. The price "
"per area is defined on the Fabrics or Stackups."
),
)
weight = grpc_data_property_read_only(
"properties.weight", check_optional=True, doc="The mass of the selected entity."
)
center_of_gravity = grpc_data_property_read_only(
"properties.center_of_gravity",
from_protobuf=to_tuple_from_1D_array,
check_optional=True,
doc="The center of gravity of the selected entity in the global coordinate system.",
)
Loading