Skip to content

Commit 0c1f9b8

Browse files
umutsoysalansysRobPasMuepyansys-ci-bot
authored
feat: get and set body color (#1357)
Co-authored-by: Roberto Pastor Muela <[email protected]> Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent a47c27b commit 0c1f9b8

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed

doc/changelog.d/1357.added.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
get and set body color

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

+96
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from typing import TYPE_CHECKING, Union
2929

3030
from beartype import beartype as check_input_types
31+
import matplotlib.colors as mcolors
3132
from pint import Quantity
3233

3334
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
@@ -40,6 +41,7 @@
4041
RotateRequest,
4142
ScaleRequest,
4243
SetAssignedMaterialRequest,
44+
SetColorRequest,
4345
SetFillStyleRequest,
4446
SetNameRequest,
4547
TranslateRequest,
@@ -149,6 +151,23 @@ def set_fill_style(self, fill_style: FillStyle) -> None:
149151
"""Set the fill style of the body."""
150152
return
151153

154+
@abstractmethod
155+
def color(self) -> str:
156+
"""Get the color of the body."""
157+
return
158+
159+
@abstractmethod
160+
def set_color(self, color: str | tuple[float, float, float]) -> None:
161+
"""Set the color of the body.
162+
163+
Parameters
164+
----------
165+
color : str | tuple[float, float, float]
166+
Color to set the body to. This can be a string representing a color name
167+
or a tuple of RGB values in the range [0, 1] (RGBA) or [0, 255] (pure RGB).
168+
"""
169+
return
170+
152171
@abstractmethod
153172
def faces(self) -> list[Face]:
154173
"""Get a list of all faces within the body.
@@ -687,6 +706,7 @@ def __init__(
687706
self._commands_stub = CommandsStub(self._grpc_client.channel)
688707
self._tessellation = None
689708
self._fill_style = FillStyle.DEFAULT
709+
self._color = None
690710

691711
def reset_tessellation_cache(func): # noqa: N805
692712
"""Decorate ``MasterBody`` methods that need tessellation cache update.
@@ -734,6 +754,33 @@ def fill_style(self) -> str: # noqa: D102
734754
def fill_style(self, value: FillStyle): # noqa: D102
735755
self.set_fill_style(value)
736756

757+
@property
758+
def color(self) -> str: # noqa: D102
759+
"""Get the current color of the body."""
760+
if self._color is None:
761+
if self._grpc_client.backend_version < (25, 1, 0): # pragma: no cover
762+
# Server does not support color retrieval before version 25.1.0
763+
self._grpc_client.log.warning(
764+
"Server does not support color retrieval. Assigning default."
765+
)
766+
self._color = "#000000" # Default color
767+
else:
768+
# Fetch color from the server if it's not cached
769+
color_response = self._bodies_stub.GetColor(EntityIdentifier(id=self._id))
770+
771+
if color_response.color:
772+
self._color = mcolors.to_hex(color_response.color)
773+
else: # pragma: no cover
774+
self._grpc_client.log.warning(
775+
f"Color could not be retrieved for body {self._id}. Assigning default."
776+
)
777+
self._color = "#000000" # Default color
778+
return self._color
779+
780+
@color.setter
781+
def color(self, value: str | tuple[float, float, float]): # noqa: D102
782+
self.set_color(value)
783+
737784
@property
738785
def is_surface(self) -> bool: # noqa: D102
739786
return self._is_surface
@@ -926,6 +973,43 @@ def set_fill_style( # noqa: D102
926973
)
927974
self._fill_style = fill_style
928975

976+
@protect_grpc
977+
@check_input_types
978+
@min_backend_version(25, 1, 0)
979+
def set_color(self, color: str | tuple[float, float, float]) -> None:
980+
"""Set the color of the body."""
981+
self._grpc_client.log.debug(f"Setting body color of {self.id} to {color}.")
982+
983+
try:
984+
if isinstance(color, tuple):
985+
# Ensure that all elements are within 0-1 or 0-255 range
986+
if all(0 <= c <= 1 for c in color):
987+
# Ensure they are floats if in 0-1 range
988+
if not all(isinstance(c, float) for c in color):
989+
raise ValueError("RGB values in the 0-1 range must be floats.")
990+
elif all(0 <= c <= 255 for c in color):
991+
# Ensure they are integers if in 0-255 range
992+
if not all(isinstance(c, int) for c in color):
993+
raise ValueError("RGB values in the 0-255 range must be integers.")
994+
# Normalize the 0-255 range to 0-1
995+
color = tuple(c / 255.0 for c in color)
996+
else:
997+
raise ValueError("RGB tuple contains mixed ranges or invalid values.")
998+
999+
color = mcolors.to_hex(color)
1000+
elif isinstance(color, str):
1001+
color = mcolors.to_hex(color)
1002+
except ValueError as err:
1003+
raise ValueError(f"Invalid color value: {err}")
1004+
1005+
self._bodies_stub.SetColor(
1006+
SetColorRequest(
1007+
body_id=self.id,
1008+
color=color,
1009+
)
1010+
)
1011+
self._color = color
1012+
9291013
@protect_grpc
9301014
@check_input_types
9311015
@reset_tessellation_cache
@@ -1140,6 +1224,14 @@ def fill_style(self) -> str: # noqa: D102
11401224
def fill_style(self, fill_style: FillStyle) -> str: # noqa: D102
11411225
self._template.fill_style = fill_style
11421226

1227+
@property
1228+
def color(self) -> str: # noqa: D102
1229+
return self._template.color
1230+
1231+
@color.setter
1232+
def color(self, color: str | tuple[float, float, float]) -> None: # noqa: D102
1233+
return self._template.set_color(color)
1234+
11431235
@property
11441236
def parent_component(self) -> "Component": # noqa: D102
11451237
return self._parent_component
@@ -1367,6 +1459,10 @@ def set_name(self, name: str) -> None: # noqa: D102
13671459
def set_fill_style(self, fill_style: FillStyle) -> None: # noqa: D102
13681460
return self._template.set_fill_style(fill_style)
13691461

1462+
@ensure_design_is_active
1463+
def set_color(self, color: str | tuple[float, float, float]) -> None: # noqa: D102
1464+
return self._template.set_color(color)
1465+
13701466
@ensure_design_is_active
13711467
def translate( # noqa: D102
13721468
self, direction: UnitVector3D, distance: Quantity | Distance | Real

tests/integration/test_design.py

+45
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import os
2525

26+
import matplotlib.colors as mcolors
2627
import numpy as np
2728
from pint import Quantity
2829
import pytest
@@ -2010,6 +2011,50 @@ def test_set_fill_style(modeler: Modeler):
20102011
assert box.fill_style == FillStyle.OPAQUE
20112012

20122013

2014+
def test_set_body_color(modeler: Modeler):
2015+
"""Test the getting and setting of body color."""
2016+
skip_if_linux(modeler, test_set_body_color.__name__, "set_color") # Skip test on Linux
2017+
2018+
design = modeler.create_design("RVE2")
2019+
unit = DEFAULT_UNITS.LENGTH
2020+
2021+
plane = Plane(
2022+
Point3D([1 / 2, 1 / 2, 0.0], unit=unit),
2023+
UNITVECTOR3D_X,
2024+
UNITVECTOR3D_Y,
2025+
)
2026+
box_plane = Sketch(plane)
2027+
box_plane.box(Point2D([0.0, 0.0]), width=1 * unit, height=1 * unit)
2028+
box = design.extrude_sketch("Block", box_plane, 1 * unit)
2029+
2030+
# Default body color is if it is not set on server side.
2031+
assert box.color == "#000000"
2032+
2033+
# Set the color of the body using hex code.
2034+
box.color = "#0000ff"
2035+
assert box.color == "#0000ff"
2036+
2037+
box.color = "#ffc000"
2038+
assert box.color == "#ffc000"
2039+
2040+
# Set the color of the body using color name.
2041+
box.set_color("green")
2042+
box.color == "#008000"
2043+
2044+
# Set the color of the body using RGB values between (0,1) as floats.
2045+
box.set_color((1.0, 0.0, 0.0))
2046+
box.color == "#ff0000"
2047+
2048+
# Set the color of the body using RGB values between (0,255) as integers).
2049+
box.set_color((0, 255, 0))
2050+
box.color == "#00ff00"
2051+
2052+
# Assigning color object directly
2053+
blue_color = mcolors.to_rgba("#0000FF")
2054+
box.color = blue_color
2055+
assert box.color == "#0000ff"
2056+
2057+
20132058
def test_body_scale(modeler: Modeler):
20142059
"""Verify the correct scaling of a body."""
20152060
design = modeler.create_design("BodyScale_Test")

0 commit comments

Comments
 (0)