Skip to content

Commit 76d4eb5

Browse files
committed
Merge branch 'blitz' into feat/download_stride
2 parents d27f7e2 + ab39ca0 commit 76d4eb5

File tree

19 files changed

+828
-24
lines changed

19 files changed

+828
-24
lines changed

doc/changelog.d/1675.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
implementation of NURBS curves

doc/changelog.d/1684.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
get assigned material

doc/changelog.d/1685.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
filter set export id to only CoreService based backends

doc/changelog.d/1689.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
matrix rotation and translation

doc/changelog.d/1690.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cleanup unsupported module

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies = [
2929
"ansys-tools-visualization-interface>=0.2.6,<1",
3030
"attrs!=24.3.0",
3131
"beartype>=0.11.0,<0.20",
32+
"geomdl>=5,<6",
3233
"grpcio>=1.35.0,<1.68",
3334
"grpcio-health-checking>=1.45.0,<1.68",
3435
"numpy>=1.20.3,<3",
@@ -54,6 +55,7 @@ tests = [
5455
"ansys-tools-visualization-interface==0.8.1",
5556
"beartype==0.19.0",
5657
"docker==7.1.0",
58+
"geomdl==5.3.1",
5759
"grpcio==1.67.1",
5860
"grpcio-health-checking==1.67.1",
5961
"numpy==2.2.1",
@@ -82,6 +84,7 @@ doc = [
8284
"ansys-tools-visualization-interface==0.8.1",
8385
"beartype==0.19.0",
8486
"docker==7.1.0",
87+
"geomdl==5.3.1",
8588
"grpcio==1.67.1",
8689
"grpcio-health-checking==1.67.1",
8790
"ipyvtklink==0.2.3",

src/ansys/geometry/core/connection/conversions.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
from typing import TYPE_CHECKING
2525

26+
from pint import Quantity, UndefinedUnitError
27+
2628
from ansys.api.geometry.v0.models_pb2 import (
2729
Arc as GRPCArc,
2830
Circle as GRPCCircle,
@@ -32,6 +34,8 @@
3234
Frame as GRPCFrame,
3335
Geometries as GRPCGeometries,
3436
Line as GRPCLine,
37+
Material as GRPCMaterial,
38+
MaterialProperty as GRPCMaterialProperty,
3539
Matrix as GRPCMatrix,
3640
Plane as GRPCPlane,
3741
Point as GRPCPoint,
@@ -42,12 +46,17 @@
4246
TrimmedCurve as GRPCTrimmedCurve,
4347
TrimmedSurface as GRPCTrimmedSurface,
4448
)
49+
from ansys.geometry.core.materials.material import (
50+
Material,
51+
MaterialProperty,
52+
MaterialPropertyType,
53+
)
4554
from ansys.geometry.core.math.frame import Frame
4655
from ansys.geometry.core.math.matrix import Matrix44
4756
from ansys.geometry.core.math.plane import Plane
4857
from ansys.geometry.core.math.point import Point2D, Point3D
4958
from ansys.geometry.core.math.vector import UnitVector3D
50-
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS
59+
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, UNITS
5160
from ansys.geometry.core.shapes.curves.circle import Circle
5261
from ansys.geometry.core.shapes.curves.curve import Curve
5362
from ansys.geometry.core.shapes.curves.ellipse import Ellipse
@@ -680,3 +689,37 @@ def line_to_grpc_line(line: Line) -> GRPCLine:
680689
start = line.origin
681690
end = line.origin + line.direction
682691
return GRPCLine(start=point3d_to_grpc_point(start), end=point3d_to_grpc_point(end))
692+
693+
694+
def grpc_material_to_material(material: GRPCMaterial) -> Material:
695+
"""Convert a material gRPC message to a ``Material`` class."""
696+
properties = []
697+
density = Quantity(0, UNITS.kg / UNITS.m**3)
698+
for property in material.material_properties:
699+
mp = grpc_material_property_to_material_property(property)
700+
properties.append(mp)
701+
if mp.type == MaterialPropertyType.DENSITY:
702+
density = mp.quantity
703+
704+
return Material(material.name, density, properties)
705+
706+
707+
def grpc_material_property_to_material_property(
708+
material_property: GRPCMaterialProperty,
709+
) -> MaterialProperty:
710+
"""Convert a material property gRPC message to a ``MaterialProperty`` class."""
711+
try:
712+
mp_type = MaterialPropertyType.from_id(material_property.id)
713+
except ValueError:
714+
mp_type = material_property.id
715+
716+
try:
717+
mp_quantity = Quantity(material_property.value, material_property.units)
718+
except (
719+
UndefinedUnitError,
720+
TypeError,
721+
):
722+
mp_quantity = material_property.value
723+
724+
mp = MaterialProperty(mp_type, material_property.display_name, mp_quantity)
725+
return mp

src/ansys/geometry/core/designer/body.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
from ansys.geometry.core.connection.client import GrpcClient
6161
from ansys.geometry.core.connection.conversions import (
6262
frame_to_grpc_frame,
63+
grpc_material_to_material,
6364
plane_to_grpc_plane,
6465
point3d_to_grpc_point,
6566
sketch_shapes_to_grpc_geometries,
@@ -254,6 +255,17 @@ def assign_material(self, material: Material) -> None:
254255
"""
255256
return
256257

258+
@abstractmethod
259+
def get_assigned_material(self) -> Material:
260+
"""Get the assigned material of the body.
261+
262+
Returns
263+
-------
264+
Material
265+
Material assigned to the body.
266+
"""
267+
return
268+
257269
@abstractmethod
258270
def add_midsurface_thickness(self, thickness: Quantity) -> None:
259271
"""Add a mid-surface thickness to a surface body.
@@ -908,6 +920,13 @@ def assign_material(self, material: Material) -> None: # noqa: D102
908920
SetAssignedMaterialRequest(id=self._id, material=material.name)
909921
)
910922

923+
@property
924+
@protect_grpc
925+
def get_assigned_material(self) -> Material: # noqa: D102
926+
self._grpc_client.log.debug(f"Retrieving assigned material for body {self.id}.")
927+
material_response = self._bodies_stub.GetAssignedMaterial(self._grpc_id)
928+
return grpc_material_to_material(material_response)
929+
911930
@protect_grpc
912931
@check_input_types
913932
def add_midsurface_thickness(self, thickness: Quantity) -> None: # noqa: D102
@@ -1463,6 +1482,11 @@ def volume(self) -> Quantity: # noqa: D102
14631482
def assign_material(self, material: Material) -> None: # noqa: D102
14641483
self._template.assign_material(material)
14651484

1485+
@property
1486+
@ensure_design_is_active
1487+
def get_assigned_material(self) -> Material: # noqa: D102
1488+
return self._template.get_assigned_material
1489+
14661490
@ensure_design_is_active
14671491
def add_midsurface_thickness(self, thickness: Quantity) -> None: # noqa: D102
14681492
self._template.add_midsurface_thickness(thickness)

src/ansys/geometry/core/math/matrix.py

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
# SOFTWARE.
2222
"""Provides matrix primitive representations."""
2323

24-
from typing import Union
24+
from typing import TYPE_CHECKING, Union
25+
26+
if TYPE_CHECKING:
27+
from ansys.geometry.core.math.vector import Vector3D # For type hints
2528

2629
from beartype import beartype as check_input_types
2730
import numpy as np
@@ -129,3 +132,165 @@ def __new__(cls, input: np.ndarray | RealSequence | Matrix = DEFAULT_MATRIX44):
129132
raise ValueError("Matrix44 should only be a 2D array of shape (4,4).")
130133

131134
return obj
135+
136+
@classmethod
137+
def create_translation(cls, translation: "Vector3D") -> "Matrix44":
138+
"""Create a matrix representing the specified translation.
139+
140+
Parameters
141+
----------
142+
translation : Vector3D
143+
The translation vector representing the translation. The components of the vector
144+
should be in meters.
145+
146+
Returns
147+
-------
148+
Matrix44
149+
A 4x4 matrix representing the translation.
150+
151+
Examples
152+
--------
153+
>>> translation_vector = Vector3D(1.0, 2.0, 3.0)
154+
>>> translation_matrix = Matrix44.create_translation(translation_vector)
155+
>>> print(translation_matrix)
156+
[[1. 0. 0. 1.]
157+
[0. 1. 0. 2.]
158+
[0. 0. 1. 3.]
159+
[0. 0. 0. 1.]]
160+
"""
161+
matrix = cls(
162+
[
163+
[1, 0, 0, translation.x],
164+
[0, 1, 0, translation.y],
165+
[0, 0, 1, translation.z],
166+
[0, 0, 0, 1],
167+
]
168+
)
169+
return matrix
170+
171+
def is_translation(self, including_identity=False):
172+
"""Check if the matrix represents a translation.
173+
174+
This method checks if the matrix represents a translation transformation.
175+
A translation matrix has the following form:
176+
[1 0 0 tx]
177+
[0 1 0 ty]
178+
[0 0 1 tz]
179+
[0 0 0 1]
180+
181+
Parameters
182+
----------
183+
including_identity : bool, optional
184+
If True, the method will return True for the identity matrix as well.
185+
If False, the method will return False for the identity matrix.
186+
187+
Returns
188+
-------
189+
bool
190+
``True`` if the matrix represents a translation, ``False`` otherwise.
191+
192+
Examples
193+
--------
194+
>>> matrix = Matrix44([[1, 0, 0, 5], [0, 1, 0, 3], [0, 0, 1, 2], [0, 0, 0, 1]])
195+
>>> matrix.is_translation()
196+
True
197+
>>> identity_matrix = Matrix44([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
198+
>>> identity_matrix.is_translation()
199+
False
200+
>>> identity_matrix.is_translation(including_identity=True)
201+
True
202+
"""
203+
if not (
204+
self.__is_close(self[0][0], 1)
205+
and self.__is_close(self[0][1], 0)
206+
and self.__is_close(self[0][2], 0)
207+
):
208+
return False
209+
if not (
210+
self.__is_close(self[1][0], 0)
211+
and self.__is_close(self[1][1], 1)
212+
and self.__is_close(self[1][2], 0)
213+
):
214+
return False
215+
if not (
216+
self.__is_close(self[2][0], 0)
217+
and self.__is_close(self[2][1], 0)
218+
and self.__is_close(self[2][2], 1)
219+
):
220+
return False
221+
if not self.__is_close(self[2][2], 1):
222+
return False
223+
224+
if (
225+
not including_identity
226+
and self.__is_close(self[0][3], 0)
227+
and self.__is_close(self[1][3], 0)
228+
and self.__is_close(self[2][3], 0)
229+
):
230+
return False
231+
232+
return True
233+
234+
def __is_close(self, a, b, tol=1e-9):
235+
"""Check if two values are close to each other within a tolerance."""
236+
return np.isclose(a, b, atol=tol)
237+
238+
@classmethod
239+
def create_rotation(
240+
cls, direction_x: "Vector3D", direction_y: "Vector3D", direction_z: "Vector3D" = None
241+
) -> "Matrix44":
242+
"""Create a matrix representing the specified rotation.
243+
244+
Parameters
245+
----------
246+
direction_x : Vector3D
247+
The X direction vector.
248+
direction_y : Vector3D
249+
The Y direction vector.
250+
direction_z : Vector3D, optional
251+
The Z direction vector. If not provided, it will be calculated
252+
as the cross product of direction_x and direction_y.
253+
254+
Returns
255+
-------
256+
Matrix44
257+
A 4x4 matrix representing the rotation.
258+
259+
Examples
260+
--------
261+
>>> direction_x = Vector3D(1.0, 0.0, 0.0)
262+
>>> direction_y = Vector3D(0.0, 1.0, 0.0)
263+
>>> rotation_matrix = Matrix44.create_rotation(direction_x, direction_y)
264+
>>> print(rotation_matrix)
265+
[[1. 0. 0. 0.]
266+
[0. 1. 0. 0.]
267+
[0. 0. 1. 0.]
268+
[0. 0. 0. 1.]]
269+
"""
270+
if not direction_x.is_perpendicular_to(direction_y):
271+
raise ValueError("The provided direction vectors are not orthogonal.")
272+
273+
# Normalize the vectors
274+
direction_x = direction_x.normalize()
275+
direction_y = direction_y.normalize()
276+
277+
# Calculate the third direction vector if not provided
278+
if direction_z is None:
279+
direction_z = direction_x.cross(direction_y)
280+
else:
281+
if not (
282+
direction_x.is_perpendicular_to(direction_z)
283+
and direction_y.is_perpendicular_to(direction_z)
284+
):
285+
raise ValueError("The provided direction vectors are not orthogonal.")
286+
direction_z = direction_z.normalize()
287+
288+
matrix = cls(
289+
[
290+
[direction_x.x, direction_y.x, direction_z.x, 0],
291+
[direction_x.y, direction_y.y, direction_z.y, 0],
292+
[direction_x.z, direction_y.z, direction_z.z, 0],
293+
[0, 0, 0, 1],
294+
]
295+
)
296+
return matrix

src/ansys/geometry/core/modeler.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@
4040
from ansys.geometry.core.logger import LOG
4141
from ansys.geometry.core.misc.checks import check_type, min_backend_version
4242
from ansys.geometry.core.misc.options import ImportOptions
43-
from ansys.geometry.core.misc.unsupported import UnsupportedCommands
4443
from ansys.geometry.core.tools.measurement_tools import MeasurementTools
4544
from ansys.geometry.core.tools.prepare_tools import PrepareTools
4645
from ansys.geometry.core.tools.repair_tools import RepairTools
46+
from ansys.geometry.core.tools.unsupported import UnsupportedCommands
4747
from ansys.geometry.core.typing import Real
4848

4949
if TYPE_CHECKING: # pragma: no cover
@@ -120,6 +120,9 @@ def __init__(
120120
backend_type=backend_type,
121121
)
122122

123+
# Maintaining references to all designs within the modeler workspace
124+
self._designs: dict[str, "Design"] = {}
125+
123126
# Initialize the RepairTools - Not available on Linux
124127
# TODO: delete "if" when Linux service is able to use repair tools
125128
# https://github.com/ansys/pyansys-geometry/issues/1319
@@ -135,9 +138,6 @@ def __init__(
135138
self._geometry_commands = GeometryCommands(self._grpc_client)
136139
self._unsupported = UnsupportedCommands(self._grpc_client, self)
137140

138-
# Maintaining references to all designs within the modeler workspace
139-
self._designs: dict[str, "Design"] = {}
140-
141141
# Check if the backend allows for multiple designs and throw warning if needed
142142
if not self.client.multiple_designs_allowed:
143143
LOG.warning(

0 commit comments

Comments
 (0)