Skip to content
Merged
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
4 changes: 4 additions & 0 deletions src/cubitpy/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
)


class CubitPyWarning(UserWarning):
"""Warning emitted by CubitPy."""


def get_path(environment_variable, test_function, *, throw_error=True):
"""Check if he environment variable is set and the path exits."""
if environment_variable in os.environ.keys():
Expand Down
96 changes: 80 additions & 16 deletions src/cubitpy/cubit_wrapper/cubit_wrapper_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,34 @@ def is_cubit_type(obj):
return False


class DefaultMessageHandler:
"""This class is a dummy class that can be overwritten later on to
intercept messages and errors from Cubit."""

def pop(self):
"""Return two empty lists for messages and errors."""
return [], []


message_handler = DefaultMessageHandler()


def channel_send(argument):
"""Wrapper for all send calls to the host interpreter.

This wrapper appends information about messages and errors from
Cubit.
"""
messages, errors = message_handler.pop()
channel.send(
{
"return_value": argument,
"messages": messages,
"errors": errors,
}
)


# All cubit items that are created are stored in this dictionary. The keys are
# the unique object ids. The items are deleted once they run out of scope in
# the host interpreter.
Expand All @@ -91,7 +119,7 @@ def is_cubit_type(obj):

# The first call are parameters needed in this script
parameters = channel.receive()
channel.send(None)
channel_send(None)
if not isinstance(parameters, dict):
raise TypeError(
"The first item should be a dictionary. Got {}!\nparameters={}".format(
Expand All @@ -115,7 +143,7 @@ def is_cubit_type(obj):
raise ValueError("Two arguments must be given to init!")
cubit.init(init[1])
cubit_objects[id(cubit)] = cubit
channel.send(object_to_id(cubit))
channel_send(object_to_id(cubit))


if parameters["is_remote"]:
Expand All @@ -128,6 +156,42 @@ def is_cubit_type(obj):
temp_dir = tempfile.TemporaryDirectory(prefix="cubitpy_temp_dir")


# Try to add a custom message handler to Cubit
try:

class MessageHandler(cubit.CubitMessageHandler):
"""This class intercepts messages and errors from Cubit."""

def setup(self):
"""Initialize the variables that track the messages and errors."""
self.messages = []
self.errors = []

def pop(self):
"""Return the stored messages and errors and reset the
variables."""
return_value = [self.messages, self.errors]
self.setup()
return return_value

def print_message(self, message):
"""Append the message to the list of messages."""
self.messages.append(message)

def print_error(self, message):
"""Append the error to the list of errors."""
self.errors.append(message)

message_handler_cubit = MessageHandler()
message_handler_cubit.setup()
cubit.set_cubit_message_handler(message_handler_cubit)

# Everything worked, so overwrite the message handler with the one linked to Cubit.
message_handler = message_handler_cubit
except Exception:
pass # nosec B110


# Now start an endless loop (until None is sent) and perform the cubit functions
while 1:
# Get input from the python host.
Expand Down Expand Up @@ -181,7 +245,7 @@ def deserialize_item(item):
# Check what to return
if is_base_type(cubit_return):
# The return item is a string, integer or float
channel.send(cubit_return)
channel_send(cubit_return)

elif isinstance(cubit_return, tuple):
# A tuple was returned, loop over each entry and check its type
Expand All @@ -198,12 +262,12 @@ def deserialize_item(item):
item
)
)
channel.send(return_list)
channel_send(return_list)

elif is_cubit_type(cubit_return):
# Store the object locally and return the id
cubit_objects[id(cubit_return)] = cubit_return
channel.send(object_to_id(cubit_return))
channel_send(object_to_id(cubit_return))

else:
raise TypeError(
Expand All @@ -214,28 +278,28 @@ def deserialize_item(item):

elif receive[0] == "iscallable":
cubit_object = cubit_objects[cubit_item_to_id(receive[1])]
channel.send(callable(getattr(cubit_object, receive[2])))
channel_send(callable(getattr(cubit_object, receive[2])))

elif receive[0] == "get_object_type":
# Get the type of the cubit object
compare_object = cubit_objects[cubit_item_to_id(receive[1])]
if isinstance(compare_object, cubit.Vertex):
channel.send(cubit_vertex)
channel_send(cubit_vertex)
elif isinstance(compare_object, cubit.Curve):
channel.send(cubit_curve)
channel_send(cubit_curve)
elif isinstance(compare_object, cubit.Surface):
channel.send(cubit_surface)
channel_send(cubit_surface)
elif isinstance(compare_object, cubit.Volume):
channel.send(cubit_volume)
channel_send(cubit_volume)
elif isinstance(compare_object, cubit.Body):
channel.send(cubit_body)
channel_send(cubit_body)
else:
channel.send(None)
channel_send(None)

elif receive[0] == "get_self_dir":
# Return a list with all callable methods of this object
cubit_object = cubit_objects[cubit_item_to_id(receive[1])]
channel.send(
channel_send(
[
[method_name, callable(getattr(cubit_object, method_name))]
for method_name in dir(cubit_object)
Expand All @@ -257,10 +321,10 @@ def deserialize_item(item):
)

# Return to python host
channel.send(None)
channel_send(None)

elif receive[0] == "get_temp_dir":
channel.send(temp_dir.name)
channel_send(temp_dir.name)

elif receive[0] == "display_in_cubit":
# receive = ["display_in_cubit", parameters]
Expand Down Expand Up @@ -332,7 +396,7 @@ def deserialize_item(item):
break

time.sleep(2)
channel.send(None)
channel_send(None)

else:
raise ValueError('The case of "{}" is not implemented!'.format(receive[0]))
Expand Down
36 changes: 28 additions & 8 deletions src/cubitpy/cubit_wrapper/cubit_wrapper_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@

import atexit
import os
import warnings
from pathlib import Path

import execnet
import numpy as np

from cubitpy.conf import GeometryType, cupy
from cubitpy.conf import CubitPyWarning, GeometryType, cupy
from cubitpy.cubit_wrapper.cubit_wrapper_utility import cubit_item_to_id, is_base_type


Expand Down Expand Up @@ -151,16 +152,35 @@ def send_and_return(self, argument_list):
arguments stored in the second entry in argument_list.
"""

# If the channel is already finalized we get a runtime error here. This happens in cases
# where we delete items after the connection has been closed. We catch this error here.
def get_log_string(log_lines: list[str], name: str) -> str:
"""Get a string from the log lines."""
text = f"The command\n {argument_list}\nraised the following {name}:"
return "\n".join([text] + log_lines)

try:
self.channel.send(argument_list)
return self.channel.receive()
except execnet.gateway_base.RemoteError:
# We still raise errors reported from the client.
return_value = self.channel.receive()
if isinstance(return_value, dict):
if len(return_value["messages"]) > 0:
warnings.warn(
get_log_string(return_value["messages"], "message(s)"),
category=CubitPyWarning,
stacklevel=3,
)
if len(return_value["errors"]) > 0:
raise RuntimeError(
get_log_string(return_value["errors"], "error(s)")
)
return return_value["return_value"]
else:
return return_value
except OSError as e:
if "cannot send" in str(e):
# If the channel is already finalized we get this error here. This
# happens in cases where we delete items after the connection has been
# closed.
return None
raise
Comment thread
isteinbrecher marked this conversation as resolved.
except Exception:
return None

def get_attribute(self, cubit_object, name):
"""Return the attribute 'name' of cubit_object. If the attribute is
Expand Down
6 changes: 4 additions & 2 deletions src/cubitpy/cubitpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

from fourcipp.fourc_input import FourCInput

from cubitpy.conf import GeometryType, cupy
from cubitpy.conf import CubitPyWarning, GeometryType, cupy
from cubitpy.cubit_group import CubitGroup
from cubitpy.cubit_to_fourc_input import (
add_exodus_geometry_section,
Expand Down Expand Up @@ -146,7 +146,9 @@ def _name_created_set(self, set_type, set_id, name, item):
warnings.warn(
'A {} is added for the group "{}" and an explicit name of "{}" is given. This might be unintended, as usually if a group is given, we expect to use the name of the group. In the current case we will use the given name.'.format(
set_type, item.name, name
)
),
category=CubitPyWarning,
stacklevel=3,
)
rename_name = name
elif group_name is not None:
Expand Down
34 changes: 26 additions & 8 deletions tests/test_cubitpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,7 @@
from fourcipp.fourc_input import FourCInput
from fourcipp.utils.dict_utils import compare_nested_dicts_or_lists

# Define the testing paths.
testing_path = os.path.abspath(os.path.dirname(__file__))
testing_input = os.path.join(testing_path, "input-files-ref")
testing_temp = os.path.join(testing_path, "testing-tmp")
testing_external_geometry = os.path.join(testing_path, "external-geometry")

# CubitPy imports.
from cubitpy.conf import cupy
from cubitpy.conf import CubitPyWarning, cupy
from cubitpy.cubit_utility import (
formatter,
get_surface_center,
Expand All @@ -56,6 +49,13 @@
)
from cubitpy.mesh_creation_functions import create_brick, extrude_mesh_normal_to_surface

# Define the testing paths.
testing_path = os.path.abspath(os.path.dirname(__file__))
testing_input = os.path.join(testing_path, "input-files-ref")
testing_temp = os.path.join(testing_path, "testing-tmp")
testing_external_geometry = os.path.join(testing_path, "external-geometry")


# Global variable if this test is run by GitLab.
if "TESTING_GITHUB" in os.environ.keys() and os.environ["TESTING_GITHUB"] == "1":
TESTING_GITHUB = True
Expand Down Expand Up @@ -2485,3 +2485,21 @@ def test_node_set_info_to_string_errors():
match="Expected string to start with",
):
string_to_node_set_info("abc")


def test_cubit_warnings_and_errors():
"""Test that cubit warnings and errors are handled correctly."""

cubit = CubitPy()

# Warning
cubit.cmd("brick x 10")
with pytest.warns(CubitPyWarning, match="is a CUBIT identifier. Please use the"):
cubit.cmd('volume 1 rename "b"')
Comment thread
isteinbrecher marked this conversation as resolved.

# Error
with pytest.raises(
RuntimeError,
match="ERROR: All dimensions must be nonzero and positive. Entered values are:",
):
cubit.cmd("brick x -10")
Loading