Skip to content

Commit 1439fe4

Browse files
jacobrkerstetterpre-commit-ci[bot]pyansys-ci-botjonahrbRobPasMue
authored
feat: add face color, round info, bring measure tools to linux (#1732)
Co-authored-by: jkerstet <jacob.kerstetter@ansys.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Co-authored-by: Jonah Boling <56607167+jonahrb@users.noreply.github.com> Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com>
1 parent 0cd31db commit 1439fe4

File tree

16 files changed

+452
-52
lines changed

16 files changed

+452
-52
lines changed

doc/changelog.d/1732.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add face color, round info, bring measure tools to linux

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,25 @@ def point3d_to_grpc_point(point: Point3D) -> GRPCPoint:
322322
)
323323

324324

325+
def grpc_point_to_point3d(point: GRPCPoint) -> Point3D:
326+
"""Convert a point gRPC message class to a ``Point3D`` class.
327+
328+
Parameters
329+
----------
330+
point : GRPCPoint
331+
Source point data.
332+
333+
Returns
334+
-------
335+
Point3D
336+
Converted point.
337+
"""
338+
return Point3D(
339+
[point.x, point.y, point.z],
340+
DEFAULT_UNITS.SERVER_LENGTH,
341+
)
342+
343+
325344
def point2d_to_grpc_point(plane: Plane, point2d: Point2D) -> GRPCPoint:
326345
"""Convert a ``Point2D`` class to a point gRPC message.
327346

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

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@
7979
from ansys.geometry.core.math.plane import Plane
8080
from ansys.geometry.core.math.point import Point3D
8181
from ansys.geometry.core.math.vector import UnitVector3D
82-
from ansys.geometry.core.misc.auxiliary import get_design_from_body
82+
from ansys.geometry.core.misc.auxiliary import (
83+
convert_color_to_hex,
84+
get_design_from_body,
85+
)
8386
from ansys.geometry.core.misc.checks import (
8487
check_type,
8588
check_type_all_elements_in_iterable,
@@ -1108,28 +1111,7 @@ def set_suppressed( # noqa: D102
11081111
def set_color(self, color: str | tuple[float, float, float]) -> None:
11091112
"""Set the color of the body."""
11101113
self._grpc_client.log.debug(f"Setting body color of {self.id} to {color}.")
1111-
1112-
try:
1113-
if isinstance(color, tuple):
1114-
# Ensure that all elements are within 0-1 or 0-255 range
1115-
if all(0 <= c <= 1 for c in color):
1116-
# Ensure they are floats if in 0-1 range
1117-
if not all(isinstance(c, float) for c in color):
1118-
raise ValueError("RGB values in the 0-1 range must be floats.")
1119-
elif all(0 <= c <= 255 for c in color):
1120-
# Ensure they are integers if in 0-255 range
1121-
if not all(isinstance(c, int) for c in color):
1122-
raise ValueError("RGB values in the 0-255 range must be integers.")
1123-
# Normalize the 0-255 range to 0-1
1124-
color = tuple(c / 255.0 for c in color)
1125-
else:
1126-
raise ValueError("RGB tuple contains mixed ranges or invalid values.")
1127-
1128-
color = mcolors.to_hex(color)
1129-
elif isinstance(color, str):
1130-
color = mcolors.to_hex(color)
1131-
except ValueError as err:
1132-
raise ValueError(f"Invalid color value: {err}")
1114+
color = convert_color_to_hex(color)
11331115

11341116
self._bodies_stub.SetColor(
11351117
SetColorRequest(

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
from ansys.geometry.core.math.vector import UnitVector3D, Vector3D
8181
from ansys.geometry.core.misc.checks import ensure_design_is_active, min_backend_version
8282
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Distance
83+
from ansys.geometry.core.misc.options import ImportOptions
8384
from ansys.geometry.core.modeler import Modeler
8485
from ansys.geometry.core.parameters.parameter import Parameter, ParameterUpdateStatus
8586
from ansys.geometry.core.typing import RealSequence
@@ -958,21 +959,25 @@ def delete_beam_profile(self, beam_profile: BeamProfile | str) -> None:
958959
@check_input_types
959960
@ensure_design_is_active
960961
@min_backend_version(24, 2, 0)
961-
def insert_file(self, file_location: Path | str) -> Component:
962+
def insert_file(
963+
self, file_location: Path | str, import_options: ImportOptions = ImportOptions()
964+
) -> Component:
962965
"""Insert a file into the design.
963966
964967
Parameters
965968
----------
966969
file_location : ~pathlib.Path | str
967970
Location on disk where the file is located.
971+
import_options : ImportOptions
972+
The options to pass into upload file
968973
969974
Returns
970975
-------
971976
Component
972977
The newly inserted component.
973978
"""
974979
# Upload the file to the server
975-
filepath_server = self._modeler._upload_file(file_location)
980+
filepath_server = self._modeler._upload_file(file_location, import_options=import_options)
976981

977982
# Insert the file into the design
978983
self._design_stub.Insert(InsertRequest(filepath=filepath_server))

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
"""Module for managing a face."""
2323

2424
from enum import Enum, unique
25+
from functools import cached_property
2526
from typing import TYPE_CHECKING
2627

28+
from beartype import beartype as check_input_types
29+
import matplotlib.colors as mcolors
2730
from pint import Quantity
2831

2932
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
@@ -34,15 +37,18 @@
3437
CreateIsoParamCurvesRequest,
3538
EvaluateRequest,
3639
GetNormalRequest,
40+
SetColorRequest,
3741
)
3842
from ansys.api.geometry.v0.faces_pb2_grpc import FacesStub
3943
from ansys.api.geometry.v0.models_pb2 import Edge as GRPCEdge
4044
from ansys.geometry.core.connection.client import GrpcClient
4145
from ansys.geometry.core.connection.conversions import grpc_curve_to_curve, grpc_surface_to_surface
4246
from ansys.geometry.core.designer.edge import Edge
4347
from ansys.geometry.core.errors import GeometryRuntimeError, protect_grpc
48+
from ansys.geometry.core.math.bbox import BoundingBox2D
4449
from ansys.geometry.core.math.point import Point3D
4550
from ansys.geometry.core.math.vector import UnitVector3D
51+
from ansys.geometry.core.misc.auxiliary import convert_color_to_hex
4652
from ansys.geometry.core.misc.checks import (
4753
deprecated_method,
4854
ensure_design_is_active,
@@ -56,6 +62,7 @@
5662
ReversedTrimmedSurface,
5763
TrimmedSurface,
5864
)
65+
from ansys.tools.visualization_interface.utils.color import Color
5966

6067
if TYPE_CHECKING: # pragma: no cover
6168
from ansys.geometry.core.designer.body import Body
@@ -181,6 +188,7 @@ def __init__(
181188
self._commands_stub = CommandsStub(grpc_client.channel)
182189
self._is_reversed = is_reversed
183190
self._shape = None
191+
self._color = None
184192

185193
@property
186194
def id(self) -> str:
@@ -287,6 +295,57 @@ def loops(self) -> list[FaceLoop]:
287295

288296
return loops
289297

298+
@property
299+
@protect_grpc
300+
@min_backend_version(25, 2, 0)
301+
def color(self) -> str:
302+
"""Get the current color of the face."""
303+
if self._color is None and self.body.is_alive:
304+
# Assigning default value first
305+
self._color = Color.DEFAULT.value
306+
307+
# If color is not cached, retrieve from the server
308+
response = self._faces_stub.GetColor(EntityIdentifier(id=self.id))
309+
310+
# Return if valid color returned
311+
if response.color:
312+
self._color = mcolors.to_hex(response.color)
313+
else:
314+
self._color = Color.DEFAULT.value
315+
316+
return self._color
317+
318+
@color.setter
319+
def color(self, color: str | tuple[float, float, float]) -> None:
320+
self.set_color(color)
321+
322+
@cached_property
323+
@protect_grpc
324+
@min_backend_version(25, 2, 0)
325+
def bounding_box(self) -> BoundingBox2D:
326+
"""Get the bounding box for the face."""
327+
self._grpc_client.log.debug(f"Getting bounding box for {self.id}.")
328+
329+
result = self._faces_stub.GetBoundingBox(request=self._grpc_id)
330+
331+
return BoundingBox2D(result.min.x, result.max.x, result.min.y, result.max.y)
332+
333+
@protect_grpc
334+
@check_input_types
335+
@min_backend_version(25, 2, 0)
336+
def set_color(self, color: str | tuple[float, float, float]) -> None:
337+
"""Set the color of the face."""
338+
self._grpc_client.log.debug(f"Setting face color of {self.id} to {color}.")
339+
color = convert_color_to_hex(color)
340+
341+
self._faces_stub.SetColor(
342+
SetColorRequest(
343+
face_id=self.id,
344+
color=color,
345+
)
346+
)
347+
self._color = color
348+
290349
@protect_grpc
291350
@ensure_design_is_active
292351
def normal(self, u: float = 0.5, v: float = 0.5) -> UnitVector3D:

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
RevolveFacesByHelixRequest,
4343
RevolveFacesRequest,
4444
RevolveFacesUpToRequest,
45+
RoundInfoRequest,
4546
SplitBodyRequest,
4647
)
4748
from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub
@@ -1082,15 +1083,15 @@ def split_body(
10821083
Parameters
10831084
----------
10841085
bodies : list[Body]
1085-
Bodies to split
1086+
Bodies to split.
10861087
plane : Plane
10871088
Plane to split with
10881089
slicers : Edge | list[Edge] | Face | list[Face]
1089-
Slicers to split with
1090+
Slicers to split with.
10901091
faces : list[Face]
1091-
Faces to split with
1092+
Faces to split with.
10921093
extendFaces : bool
1093-
Extend faces if split with faces
1094+
Extend faces if split with faces.
10941095
10951096
Returns
10961097
-------
@@ -1138,3 +1139,23 @@ def split_body(
11381139
design._update_design_inplace()
11391140

11401141
return result.success
1142+
1143+
@protect_grpc
1144+
@min_backend_version(25, 2, 0)
1145+
def get_round_info(self, face: "Face") -> tuple[bool, Real]:
1146+
"""Get info on the rounding of a face.
1147+
1148+
Parameters
1149+
----------
1150+
Face
1151+
The design face to get round info on.
1152+
1153+
Returns
1154+
-------
1155+
tuple[bool, Real]
1156+
``True`` if round is aligned with face's U-parameter direction, ``False`` otherwise.
1157+
Radius of the round.
1158+
"""
1159+
result = self._commands_stub.GetRoundInfo(RoundInfoRequest(face=face._grpc_id))
1160+
1161+
return (result.along_u, result.radius)

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
"""Provides for managing a bounding box."""
2323

2424
import sys
25+
from typing import Union
2526

2627
from beartype import beartype as check_input_types
2728

29+
from ansys.geometry.core.math.misc import intersect_interval
2830
from ansys.geometry.core.math.point import Point2D
2931
from ansys.geometry.core.misc.accuracy import Accuracy
3032
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS
@@ -206,6 +208,39 @@ def __eq__(self, other: "BoundingBox2D") -> bool:
206208
and self.y_max == other.y_max
207209
)
208210

211+
@check_input_types
209212
def __ne__(self, other: "BoundingBox2D") -> bool:
210213
"""Not equals operator for the ``BoundingBox2D`` class."""
211214
return not self == other
215+
216+
@staticmethod
217+
def intersect_bboxes(
218+
box_1: "BoundingBox2D", box_2: "BoundingBox2D"
219+
) -> Union[None, "BoundingBox2D"]:
220+
"""Find the intersection of 2 BoundingBox2D objects.
221+
222+
Parameters
223+
----------
224+
box_1: BoundingBox2D
225+
The box to consider the intersection of with respect to box_2.
226+
box_2: BoundingBox2D
227+
The box to consider the intersection of with respect to box_1.
228+
229+
Returns
230+
-------
231+
BoundingBox2D:
232+
The box representing the intersection of the two passed in boxes.
233+
"""
234+
intersect, min_x, max_x = intersect_interval(
235+
box_1.x_min, box_2.x_min, box_1.x_max, box_2.x_max
236+
)
237+
if not intersect:
238+
return None
239+
240+
intersect, min_y, max_y = intersect_interval(
241+
box_1.y_min, box_2.y_min, box_1.y_max, box_2.y_max
242+
)
243+
if not intersect:
244+
return None
245+
246+
return BoundingBox2D(min_x, max_x, min_y, max_y)

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from beartype import beartype as check_input_types
2525
import numpy as np
2626

27+
from ansys.geometry.core.misc.accuracy import Accuracy
2728
from ansys.geometry.core.typing import Real
2829

2930

@@ -92,3 +93,42 @@ def get_two_circle_intersections(
9293
y4 = y2 + dy
9394

9495
return ((x3, y3), (x4, y4))
96+
97+
98+
def intersect_interval(first_min, second_min, first_max, second_max) -> tuple[bool, Real, Real]:
99+
"""Find the intersection of two intervals.
100+
101+
Parameters
102+
----------
103+
first_min : Real
104+
The minimum value of the first interval.
105+
second_min : Real
106+
The minimum value of the second interval.
107+
first_max : Real
108+
The maximum value of the first interval.
109+
second_max : Real
110+
The maximum value of the second interval.
111+
112+
Returns
113+
-------
114+
tuple[bool, Real, Real]
115+
Tuple with a boolean to indicate whether the intervals intersect,
116+
the minimum value of the intersection interval,
117+
and the maximum value of the intersection interval.
118+
If they do not intersect, then the boolean is False and the other values are 0.
119+
"""
120+
minimum = second_min
121+
if first_min > minimum:
122+
minimum = first_min
123+
124+
maximum = second_max
125+
if first_max < maximum:
126+
maximum = first_max
127+
128+
if minimum > maximum:
129+
if minimum - maximum > Accuracy.length_accuracy():
130+
return False, 0, 0
131+
132+
maximum, minimum = minimum, maximum
133+
134+
return True, minimum, maximum

0 commit comments

Comments
 (0)