Skip to content

feat: tracking boolean operations #2153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
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/changelog.d/1753.maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
update CHANGELOG for v0.9.0
1 change: 1 addition & 0 deletions doc/changelog.d/2153.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Tracking boolean operations
27 changes: 18 additions & 9 deletions src/ansys/geometry/core/_grpc/_services/v0/bodies.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import grpc
import pint

from ansys.geometry.core import USE_TRACKER_TO_UPDATE_DESIGN
from ansys.geometry.core.errors import protect_grpc
from ansys.geometry.core.misc.auxiliary import get_design_from_body
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS

from ..base.bodies import GRPCBodyService
Expand Down Expand Up @@ -658,14 +660,21 @@ def boolean(self, **kwargs) -> dict: # noqa: D102

# Call the gRPC service and build the requests accordingly
resp = 0
serialized_tracker_response = {}
try:
resp = self.stub.Boolean(
request=BooleanRequest(
body1=kwargs["target"].id,
tool_bodies=[other.id for other in kwargs["other"]],
method=kwargs["method"],
request = BooleanRequest(
body1=kwargs["target"].id,
tool_bodies=[other.id for other in kwargs["other"]],
method=kwargs["method"],
)
if USE_TRACKER_TO_UPDATE_DESIGN:
request.keep_other = kwargs["keep_other"]
resp = self.stub.Boolean(request=request)
if USE_TRACKER_TO_UPDATE_DESIGN:
parent_design = get_design_from_body(kwargs["target"])
serialized_tracker_response = parent_design._serialize_tracker_command_response(
resp.response
)
).empty_result
except grpc.RpcError as err: # pragma: no cover
# TODO: to be deleted - old versions did not have "tool_bodies" in the request
# This is a temporary fix to support old versions of the server - should be deleted
Expand All @@ -692,15 +701,15 @@ def boolean(self, **kwargs) -> dict: # noqa: D102
body2=kwargs["other"][0].id,
method=kwargs["method"],
)
).empty_result
)
else:
raise err

if resp == 1:
if resp.empty_result == 1:
raise ValueError(
f"Boolean operation of type '{kwargs['method']}' failed: {kwargs['err_msg']}.\n"
f"Involving bodies:{kwargs['target']}, {kwargs['other']}"
)

# Return the response - formatted as a dictionary
return {}
return {"complete_command_response": serialized_tracker_response}
10 changes: 5 additions & 5 deletions src/ansys/geometry/core/_grpc/_services/v0/repair_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ def find_and_fix_simplify(self, **kwargs) -> dict: # noqa: D102
# Call the gRPC service
response = self.stub.FindAndSimplify(request)

serialized_tracker_response = self._serialize_tracker_command_response(
serialized_tracker_response = kwargs["parent_design"]._serialize_tracker_command_response(
response.complete_command_response
)

Expand Down Expand Up @@ -383,7 +383,7 @@ def find_and_fix_stitch_faces(self, **kwargs) -> dict: # noqa: D102
# Call the gRPC service
response = self.stub.FindAndFixStitchFaces(request)

serialized_tracker_response = self._serialize_tracker_command_response(
serialized_tracker_response = kwargs["parent_design"]._serialize_tracker_command_response(
response.complete_command_response
)

Expand Down Expand Up @@ -467,7 +467,7 @@ def find_and_fix_short_edges(self, **kwargs): # noqa: D102
# Call the gRPC service
response = self.stub.FindAndFixShortEdges(request)

serialized_tracker_response = self._serialize_tracker_command_response(
serialized_tracker_response = kwargs["parent_design"]._serialize_tracker_command_response(
response.complete_command_response
)

Expand All @@ -494,7 +494,7 @@ def find_and_fix_extra_edges(self, **kwargs) -> dict: # noqa: D102
# Call the gRPC service
response = self.stub.FindAndFixExtraEdges(request)

serialized_tracker_response = self._serialize_tracker_command_response(
serialized_tracker_response = kwargs["parent_design"]._serialize_tracker_command_response(
response.complete_command_response
)

Expand Down Expand Up @@ -525,7 +525,7 @@ def find_and_fix_split_edges(self, **kwargs) -> dict: # noqa: D102
# Call the gRPC service
response = self.stub.FindAndFixSplitEdges(request)

serialized_tracker_response = self._serialize_tracker_command_response(
serialized_tracker_response = kwargs["parent_design"]._serialize_tracker_command_response(
response.complete_command_response
)

Expand Down
35 changes: 22 additions & 13 deletions src/ansys/geometry/core/designer/body.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
ShellRequest,
)
from ansys.api.geometry.v0.commands_pb2_grpc import CommandsStub
from ansys.geometry.core import USE_TRACKER_TO_UPDATE_DESIGN
from ansys.geometry.core.connection.client import GrpcClient
from ansys.geometry.core.connection.conversions import (
plane_to_grpc_plane,
Expand Down Expand Up @@ -1948,7 +1949,9 @@ def __generic_boolean_command(
# If USE_TRACKER_TO_UPDATE_DESIGN is True, we serialize the response
# and update the parent design with the serialized response.
tracker_response = response.result.complete_command_response
serialized_response = self._serialize_tracker_command_response(tracker_response)
serialized_response = parent_design._serialize_tracker_command_response(
tracker_response
)
parent_design._update_from_tracker(serialized_response)

@reset_tessellation_cache
Expand All @@ -1962,20 +1965,26 @@ def __generic_boolean_op(
err_msg: str,
) -> None:
grpc_other = other if isinstance(other, Iterable) else [other]
if keep_other:
# Make a copy of the other body to keep it...
# stored temporarily in the parent component - since it will be deleted
grpc_other = [b.copy(self.parent_component, f"BoolOpCopy_{b.name}") for b in grpc_other]

self._template._grpc_client.services.bodies.boolean(
target=self,
other=grpc_other,
method=method,
err_msg=err_msg,
if not USE_TRACKER_TO_UPDATE_DESIGN:
if keep_other:
# Make a copy of the other body to keep it...
# stored temporarily in the parent component - since it will be deleted
grpc_other = [
b.copy(self.parent_component, f"BoolOpCopy_{b.name}") for b in grpc_other
]

response = self._template._grpc_client.services.bodies.boolean(
target=self, other=grpc_other, method=method, err_msg=err_msg, keep_other=keep_other
)

for b in grpc_other:
b.parent_component.delete_body(b)
if not USE_TRACKER_TO_UPDATE_DESIGN:
for b in grpc_other:
b.parent_component.delete_body(b)
else:
# If USE_TRACKER_TO_UPDATE_DESIGN is True, we serialize the response
# and update the parent design with the serialized response.
parent_design = get_design_from_body(self)
parent_design._update_from_tracker(response["complete_command_response"])

def __repr__(self) -> str:
"""Represent the ``Body`` as a string."""
Expand Down
11 changes: 9 additions & 2 deletions src/ansys/geometry/core/designer/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ def __init__(
self._name = new_component.component.name
self._instance_name = new_component.component.instance_name
else:
new_component = None
self._name = name
self._id = None
self._instance_name = instance_name
Expand Down Expand Up @@ -248,8 +249,14 @@ def __init__(

elif not read_existing_comp:
# This is an independent Component - Create new Part and MasterComponent
p = Part(uuid.uuid4(), f"p_{name}", [], [])
master = MasterComponent(uuid.uuid4(), f"master_{name}", p)
p = Part(
uuid.uuid4() if not new_component else new_component.template, f"p_{name}", [], []
)
master = MasterComponent(
uuid.uuid4() if not new_component else new_component.component.master_id,
f"master_{name}",
p,
)
self._master_component = master

self._master_component.occurrences.append(self)
Expand Down
81 changes: 73 additions & 8 deletions src/ansys/geometry/core/designer/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,55 @@ def __repr__(self) -> str:
lines.append(f" N Design Points : {len(self.design_points)}")
return "\n".join(lines)

def _serialize_tracker_command_response(self, response) -> dict:
"""Serialize a TrackerCommandResponse object into a dictionary.

Parameters
----------
response : TrackerCommandResponse
The gRPC TrackerCommandResponse object to serialize.

Returns
-------
dict
A dictionary representation of the TrackerCommandResponse object.
"""

def serialize_body(body):
return {
"id": body.id,
"name": body.name,
"can_suppress": body.can_suppress,
"transform_to_master": {
"m00": body.transform_to_master.m00,
"m11": body.transform_to_master.m11,
"m22": body.transform_to_master.m22,
"m33": body.transform_to_master.m33,
},
"master_id": body.master_id,
"parent_id": body.parent_id,
}

def serialize_entity_identifier(entity):
"""Serialize an EntityIdentifier object into a dictionary."""
return {
"id": entity.id,
}

return {
"success": response.success,
"created_bodies": [
serialize_body(body) for body in getattr(response, "created_bodies", [])
],
"modified_bodies": [
serialize_body(body) for body in getattr(response, "modified_bodies", [])
],
"deleted_bodies": [
serialize_entity_identifier(entity)
for entity in getattr(response, "deleted_bodies", [])
],
}

def __read_existing_design(self) -> None:
"""Read an existing ``Design`` located on the server."""
#
Expand Down Expand Up @@ -1298,7 +1347,12 @@ def _handle_deleted_bodies(self, deleted_bodies):
for body in self.bodies:
if body.id == body_id:
body._is_alive = False
self.bodies.remove(body)
# self.bodies.remove(body)
for bd in self._master_component.part.bodies:
if bd.id == body_id:
self._master_component.part.bodies.remove(bd)
break
self._clear_cached_bodies()
removed = True
self._grpc_client.log.info(
f"Deleted body (ID: {body_id}) removed from root level."
Expand All @@ -1325,10 +1379,12 @@ def _handle_created_bodies(self, created_bodies):
)
continue

added = any(self._find_and_add_body(body_info, self.components))
added = self._find_and_add_body(body_info, self.components)
if not added:
new_body = MasterBody(body_id, body_name, self._grpc_client, is_surface=is_surface)
self.bodies.append(new_body)
# self.bodies.append(new_body)
self._master_component.part.bodies.append(new_body)
self._clear_cached_bodies()
self._grpc_client.log.debug(
f"Added new body '{body_name}' (ID: {body_id}) to root level."
)
Expand All @@ -1343,14 +1399,17 @@ def _update_body(self, existing_body, body_info):

def _find_and_add_body(self, body_info, components):
for component in components:
if component.id == body_info["parent_id"]:
parent_id_for_body = component._master_component.part.id
if parent_id_for_body == body_info["parent_id"]:
new_body = MasterBody(
body_info["id"],
body_info["name"],
self._grpc_client,
is_surface=body_info.get("is_surface", False),
)
component.bodies.append(new_body)
# component.bodies.append(new_body)
component._master_component.part.bodies.append(new_body)
component._clear_cached_bodies()
self._grpc_client.log.debug(
f"Added new body '{new_body.name}' (ID: {new_body.id}) "
f"to component '{component.name}' (ID: {component.id})"
Expand Down Expand Up @@ -1380,11 +1439,17 @@ def _find_and_update_body(self, body_info, component):

def _find_and_remove_body(self, body_info, component):
for body in component.bodies:
if body.id == body_info["id"]:
body_info_id = body_info["id"]
if body.id == f"{component.id}/{body_info_id}":
body._is_alive = False
component.bodies.remove(body)
# component.bodies.remove(body)
for bd in component._master_component.part.bodies:
if bd.id == body_info_id:
component._master_component.part.bodies.remove(bd)
break
component._clear_cached_bodies()
self._grpc_client.log.debug(
f"Removed body '{body_info['name']}' (ID: {body_info['id']}) from component "
f"Removed body (ID: {body_info['id']}) from component "
f"'{component.name}' (ID: {component.id})"
)
return True
Expand Down
Loading
Loading