Skip to content

Commit 72c56fa

Browse files
feat: add mating conditions (align, tangent, orient) (#2069)
Co-authored-by: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com>
1 parent 1e12262 commit 72c56fa

File tree

6 files changed

+329
-1
lines changed

6 files changed

+329
-1
lines changed

doc/changelog.d/2069.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add mating conditions (align, tangent, orient)

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1541,7 +1541,15 @@ def material(self, value: Material): # noqa: D102
15411541

15421542
@property
15431543
def bounding_box(self) -> BoundingBox: # noqa: D102
1544-
return self._template.bounding_box
1544+
self._template._grpc_client.log.debug(
1545+
f"Retrieving bounding box for body {self.id} from server."
1546+
)
1547+
response = self._template._grpc_client.services.bodies.get_bounding_box(id=self.id)
1548+
return BoundingBox(
1549+
min_corner=response.get("min"),
1550+
max_corner=response.get("max"),
1551+
center=response.get("center"),
1552+
)
15451553

15461554
@ensure_design_is_active
15471555
def assign_material(self, material: Material) -> None: # noqa: D102

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

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
3131
from ansys.api.geometry.v0.commands_pb2 import (
3232
ChamferRequest,
33+
CreateAlignTangentOrientGearConditionRequest,
3334
CreateCircularPatternRequest,
3435
CreateFillPatternRequest,
3536
CreateLinearPatternRequest,
@@ -61,6 +62,11 @@
6162
point3d_to_grpc_point,
6263
unit_vector_to_grpc_direction,
6364
)
65+
from ansys.geometry.core.designer.mating_conditions import (
66+
AlignCondition,
67+
OrientCondition,
68+
TangentCondition,
69+
)
6470
from ansys.geometry.core.designer.selection import NamedSelection
6571
from ansys.geometry.core.errors import protect_grpc
6672
from ansys.geometry.core.math.plane import Plane
@@ -69,6 +75,7 @@
6975
from ansys.geometry.core.misc.auxiliary import (
7076
get_bodies_from_ids,
7177
get_design_from_body,
78+
get_design_from_component,
7279
get_design_from_edge,
7380
get_design_from_face,
7481
)
@@ -1450,3 +1457,180 @@ def offset_faces_set_radius(
14501457
)
14511458

14521459
return result.success
1460+
1461+
@protect_grpc
1462+
@min_backend_version(26, 1, 0)
1463+
def create_align_condition(
1464+
self,
1465+
parent_component: "Component",
1466+
geometry_a: Union["Body", "Face", "Edge"],
1467+
geometry_b: Union["Body", "Face", "Edge"],
1468+
) -> AlignCondition:
1469+
"""Create an align condition between two geometry objects.
1470+
1471+
This will move the objects to be aligned with each other.
1472+
1473+
Parameters
1474+
----------
1475+
parent_component : Component
1476+
The common ancestor component of the two geometry objects.
1477+
geometry_a : Body | Face | Edge
1478+
The first geometry object to align to the second.
1479+
geometry_b : Body | Face | Edge
1480+
The geometry object to be aligned to.
1481+
1482+
Returns
1483+
-------
1484+
AlignCondition
1485+
The persistent align condition that was created.
1486+
1487+
Warnings
1488+
--------
1489+
This method is only available starting on Ansys release 26R1.
1490+
"""
1491+
from ansys.geometry.core.designer.body import Body
1492+
from ansys.geometry.core.designer.component import Component
1493+
from ansys.geometry.core.designer.edge import Edge
1494+
from ansys.geometry.core.designer.face import Face
1495+
1496+
check_type(parent_component, Component)
1497+
check_type(geometry_a, (Body, Face, Edge))
1498+
check_type(geometry_b, (Body, Face, Edge))
1499+
1500+
result = self._commands_stub.CreateAlignCondition(
1501+
CreateAlignTangentOrientGearConditionRequest(
1502+
parent=parent_component._grpc_id,
1503+
geometric_a=geometry_a._grpc_id,
1504+
geometric_b=geometry_b._grpc_id,
1505+
)
1506+
)
1507+
1508+
get_design_from_component(parent_component)._update_design_inplace()
1509+
1510+
return AlignCondition(
1511+
result.condition.moniker,
1512+
result.condition.is_deleted,
1513+
result.condition.is_enabled,
1514+
result.condition.is_satisfied,
1515+
result.offset,
1516+
result.is_reversed,
1517+
result.is_valid,
1518+
)
1519+
1520+
@protect_grpc
1521+
@min_backend_version(26, 1, 0)
1522+
def create_tangent_condition(
1523+
self,
1524+
parent_component: "Component",
1525+
geometry_a: Union["Body", "Face", "Edge"],
1526+
geometry_b: Union["Body", "Face", "Edge"],
1527+
) -> TangentCondition:
1528+
"""Create a tangent condition between two geometry objects.
1529+
1530+
This aligns the objects so that they are tangent.
1531+
1532+
Parameters
1533+
----------
1534+
parent_component : Component
1535+
The common ancestor component of the two geometry objects.
1536+
geometry_a : Body | Face | Edge
1537+
The first geometry object to tangent the second.
1538+
geometry_b : Body | Face | Edge
1539+
The geometry object to be tangent with.
1540+
1541+
Returns
1542+
-------
1543+
TangentCondition
1544+
The persistent tangent condition that was created.
1545+
1546+
Warnings
1547+
--------
1548+
This method is only available starting on Ansys release 26R1.
1549+
"""
1550+
from ansys.geometry.core.designer.body import Body
1551+
from ansys.geometry.core.designer.component import Component
1552+
from ansys.geometry.core.designer.edge import Edge
1553+
from ansys.geometry.core.designer.face import Face
1554+
1555+
check_type(parent_component, Component)
1556+
check_type(geometry_a, (Body, Face, Edge))
1557+
check_type(geometry_b, (Body, Face, Edge))
1558+
1559+
result = self._commands_stub.CreateTangentCondition(
1560+
CreateAlignTangentOrientGearConditionRequest(
1561+
parent=parent_component._grpc_id,
1562+
geometric_a=geometry_a._grpc_id,
1563+
geometric_b=geometry_b._grpc_id,
1564+
)
1565+
)
1566+
1567+
get_design_from_component(parent_component)._update_design_inplace()
1568+
1569+
return TangentCondition(
1570+
result.condition.moniker,
1571+
result.condition.is_deleted,
1572+
result.condition.is_enabled,
1573+
result.condition.is_satisfied,
1574+
result.offset,
1575+
result.is_reversed,
1576+
result.is_valid,
1577+
)
1578+
1579+
@protect_grpc
1580+
@min_backend_version(26, 1, 0)
1581+
def create_orient_condition(
1582+
self,
1583+
parent_component: "Component",
1584+
geometry_a: Union["Body", "Face", "Edge"],
1585+
geometry_b: Union["Body", "Face", "Edge"],
1586+
) -> OrientCondition:
1587+
"""Create an orient condition between two geometry objects.
1588+
1589+
This rotates the objects so that they are oriented in the same direction.
1590+
1591+
Parameters
1592+
----------
1593+
parent_component : Component
1594+
The common ancestor component of the two geometry objects.
1595+
geometry_a : Body | Face | Edge
1596+
The first geometry object to orient with the second.
1597+
geometry_b : Body | Face | Edge
1598+
The geometry object to be oriented with.
1599+
1600+
Returns
1601+
-------
1602+
OrientCondition
1603+
The persistent orient condition that was created.
1604+
1605+
Warnings
1606+
--------
1607+
This method is only available starting on Ansys release 26R1.
1608+
"""
1609+
from ansys.geometry.core.designer.body import Body
1610+
from ansys.geometry.core.designer.component import Component
1611+
from ansys.geometry.core.designer.edge import Edge
1612+
from ansys.geometry.core.designer.face import Face
1613+
1614+
check_type(parent_component, Component)
1615+
check_type(geometry_a, (Body, Face, Edge))
1616+
check_type(geometry_b, (Body, Face, Edge))
1617+
1618+
result = self._commands_stub.CreateOrientCondition(
1619+
CreateAlignTangentOrientGearConditionRequest(
1620+
parent=parent_component._grpc_id,
1621+
geometric_a=geometry_a._grpc_id,
1622+
geometric_b=geometry_b._grpc_id,
1623+
)
1624+
)
1625+
1626+
get_design_from_component(parent_component)._update_design_inplace()
1627+
1628+
return OrientCondition(
1629+
result.condition.moniker,
1630+
result.condition.is_deleted,
1631+
result.condition.is_enabled,
1632+
result.condition.is_satisfied,
1633+
result.offset,
1634+
result.is_reversed,
1635+
result.is_valid,
1636+
)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
"""Provides dataclasses for mating conditions."""
23+
24+
from dataclasses import dataclass
25+
26+
27+
@dataclass
28+
class MatingCondition:
29+
"""MatingCondition dataclass."""
30+
31+
id: str # Id of the mating condition corresponding to the server-side object
32+
is_deleted: bool # True if the condition has been deleted
33+
is_enabled: bool # True if the condition is currently enabled
34+
is_satisfied: bool # True if the condition has been met
35+
36+
37+
@dataclass
38+
class AlignCondition(MatingCondition):
39+
"""AlignCondition dataclass."""
40+
41+
offset: float # Separation value (only defined if at least one geometric has a surface)
42+
is_reversed: bool # True if the condition is reversed in sense
43+
is_valid: bool # True if the condition is valid
44+
45+
46+
@dataclass
47+
class TangentCondition(MatingCondition):
48+
"""TangentCondition dataclass."""
49+
50+
offset: float # Separation value (only if both are surfaces)
51+
is_reversed: bool # True if the condition is reversed in sense
52+
is_valid: bool # True if the condition is valid
53+
54+
55+
@dataclass
56+
class OrientCondition(MatingCondition):
57+
"""OrientCondition dataclass."""
58+
59+
offset: float # Rotation angle
60+
is_reversed: bool # True if the condition is reversed in sense
61+
is_valid: bool # True if the condition is valid
68.1 KB
Binary file not shown.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
from pathlib import Path
24+
25+
import numpy as np
26+
27+
from ansys.geometry.core import Modeler
28+
29+
from .conftest import FILES_DIR
30+
31+
32+
def test_align_condition(modeler: Modeler):
33+
"""Test the creation of an align condition."""
34+
design = modeler.open_file(Path(FILES_DIR, "mating-conditions.scdocx"))
35+
face1 = next((ns for ns in design.named_selections if ns.name == "face1"), None).faces[0]
36+
face2 = next((ns for ns in design.named_selections if ns.name == "face2"), None).faces[0]
37+
38+
old_center = face1.body.bounding_box.center
39+
40+
modeler.geometry_commands.create_align_condition(design, face1, face2)
41+
42+
new_center = face1.body.bounding_box.center
43+
44+
assert not np.allclose(old_center, new_center)
45+
46+
47+
def test_tangent_condition(modeler: Modeler):
48+
"""Test the creation of a tangent condition."""
49+
design = modeler.open_file(Path(FILES_DIR, "mating-conditions.scdocx"))
50+
face1 = next((ns for ns in design.named_selections if ns.name == "face1"), None).faces[0]
51+
face2 = next((ns for ns in design.named_selections if ns.name == "face2"), None).faces[0]
52+
53+
old_center = face1.body.bounding_box.center
54+
55+
modeler.geometry_commands.create_tangent_condition(design, face1, face2)
56+
57+
new_center = face1.body.bounding_box.center
58+
59+
assert not np.allclose(old_center, new_center)
60+
61+
62+
def test_orient_condition(modeler: Modeler):
63+
"""Test the creation of an orient condition."""
64+
design = modeler.open_file(Path(FILES_DIR, "mating-conditions.scdocx"))
65+
face1 = next((ns for ns in design.named_selections if ns.name == "face3"), None).faces[0]
66+
face2 = next((ns for ns in design.named_selections if ns.name == "face2"), None).faces[0]
67+
68+
old_center = face1.body.bounding_box.center
69+
70+
modeler.geometry_commands.create_orient_condition(design, face1, face2)
71+
72+
new_center = face1.body.bounding_box.center
73+
74+
assert not np.allclose(old_center, new_center)

0 commit comments

Comments
 (0)