Skip to content

Commit

Permalink
Merge branch 'main' into 707-support-for-multiple-row-entries-in-mat_196
Browse files Browse the repository at this point in the history
  • Loading branch information
RaphaelHeiniger authored Feb 12, 2025
2 parents ba21ec6 + 69a4452 commit b54975c
Show file tree
Hide file tree
Showing 12 changed files with 538 additions and 17 deletions.
1 change: 1 addition & 0 deletions doc/changelog/709.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feat: Start to handle *INCLUDE_TRANSFORM in Deck.expand()
50 changes: 40 additions & 10 deletions src/ansys/dyna/core/lib/deck.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
import warnings

from ansys.dyna.core.lib.format_type import format_type
from ansys.dyna.core.lib.import_handler import ImportContext, ImportHandler
from ansys.dyna.core.lib.io_utils import write_or_return
from ansys.dyna.core.lib.keyword_base import KeywordBase
from ansys.dyna.core.lib.parameter_set import ParameterSet
from ansys.dyna.core.lib.transform import TransformHandler


class Deck:
Expand All @@ -43,6 +45,8 @@ def __init__(self, title: str = None, **kwargs):
self.comment_header: str = None
self.title: str = title
self.format: format_type = kwargs.get("format", format_type.default)
self._import_handlers: typing.List[ImportHandler] = list()
self._transform_handler = TransformHandler()

def __add__(self, other):
"""Add two decks together."""
Expand All @@ -58,6 +62,14 @@ def clear(self):
self.title = None
self.format = format_type.default

@property
def transform_handler(self) -> TransformHandler:
return self._transform_handler

def register_import_handler(self, import_handler: ImportHandler) -> None:
"""Registers an ImportHandler object"""
self._import_handlers.append(import_handler)

@property
def parameters(self) -> ParameterSet:
return self._parameter_set
Expand Down Expand Up @@ -174,8 +186,16 @@ def _expand_helper(self, search_paths: typing.List[str], recurse: bool) -> typin
for search_path in search_paths:
include_file = os.path.join(search_path, keyword.filename)
include_deck = Deck(format=keyword.format)
for import_handler in self._import_handlers:
include_deck.register_import_handler(import_handler)
try:
include_deck.import_file(include_file)
xform = None
if keyword.subkeyword == "TRANSFORM":
xform = keyword
include_deck.register_import_handler(self.transform_handler)
context = ImportContext(xform, self, include_file)
encoding = "utf-8" # TODO - how to control encoding in expand?
include_deck._import_file(include_file, "utf-8", context)
success = True
break
except FileNotFoundError:
Expand Down Expand Up @@ -295,7 +315,9 @@ def _write(buf):

return write_or_return(buf, _write)

def loads(self, value: str) -> "ansys.dyna.keywords.lib.deck_loader.DeckLoaderResult": # noqa: F821
def loads(
self, value: str, context: typing.Optional[ImportContext] = None
) -> "ansys.dyna.keywords.lib.deck_loader.DeckLoaderResult": # noqa: F821
"""Load all keywords from the keyword file as a string.
When adding all keywords from the file, this method
Expand All @@ -304,15 +326,17 @@ def loads(self, value: str) -> "ansys.dyna.keywords.lib.deck_loader.DeckLoaderRe
Parameters
----------
value : str
context: ImportContext
the context
"""
# import this only when loading to avoid the circular
# imports
# ansys.dyna.keywords imports deck, deck imports deck_loader,
# import deck_loader only when loading to avoid circular imports

# ansys.dyna.keywords imports deck, deck imports deck_loader
# deck_loader imports ansys.dyna.keywords
from .deck_loader import load_deck
from ansys.dyna.core.lib.deck_loader import load_deck

result = load_deck(self, value)
result = load_deck(self, value, context, self._import_handlers)
return result

def _check_unique(self, type: str, field: str) -> None:
Expand Down Expand Up @@ -404,18 +428,24 @@ def get(self, **kwargs) -> typing.List[KeywordBase]:
return [kwd for kwd in kwds if kwargs["filter"](kwd)]
return kwds

def _import_file(self, path: str, encoding: str, context: ImportContext):
with open(path, encoding=encoding) as f:
return self.loads(f.read(), context)

def import_file(
self, path: str, encoding="utf-8"
self, path: str, encoding: str = "utf-8"
) -> "ansys.dyna.keywords.lib.deck_loader.DeckLoaderResult": # noqa: F821
"""Import a keyword file.
Parameters
----------
path : str
Full path for the keyword file.
encoding: str
String encoding used to read the keyword file.
"""
with open(path, encoding=encoding) as f:
return self.loads(f.read())
context = ImportContext(None, self, path)
self._import_file(path, encoding, context)

def export_file(self, path: str, encoding="utf-8") -> None:
"""Export the keyword file to a new keyword file.
Expand Down
48 changes: 45 additions & 3 deletions src/ansys/dyna/core/lib/deck_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import ansys.dyna.core
from ansys.dyna.core.lib.format_type import format_type
from ansys.dyna.core.lib.import_handler import ImportContext, ImportHandler
from ansys.dyna.core.lib.keyword_base import KeywordBase


Expand Down Expand Up @@ -84,7 +85,13 @@ def _get_kwd_class_and_format(keyword_name: str) -> str:
return keyword_object_type, format


def _try_load_deck(deck: "ansys.dyna.core.deck.Deck", text: str, result: DeckLoaderResult) -> None:
def _try_load_deck(
deck: "ansys.dyna.core.deck.Deck",
text: str,
result: DeckLoaderResult,
context: typing.Optional[ImportContext],
import_handlers: typing.List[ImportHandler],
) -> None:
lines = text.splitlines()
iterator = iter(lines)
iterstate = IterState.USERCOMMENT
Expand Down Expand Up @@ -127,13 +134,40 @@ def update_deck_title(block: typing.List[str], deck: "ansys.dyna.core.deck.Deck"
assert len(block) == 2, "Title block can only have one line"
deck.title = block[1]

def before_import(block: typing.List[str], keyword: str, keyword_data: str) -> bool:
if len(import_handlers) == 0:
return True

assert context != None
s = io.StringIO()
s.write(keyword_data)
s.seek(0)

for handler in import_handlers:
if not handler.before_import(context, keyword, s):
return False
s.seek(0)
return True

def on_error(error):
for handler in import_handlers:
handler.on_error(error)

def after_import(keyword):
for handler in import_handlers:
handler.after_import(context, keyword)

def handle_keyword(block: typing.List[str], deck: "ansys.dyna.core.deck.Deck") -> None:
keyword = block[0].strip()
keyword_data = "\n".join(block)
do_import = before_import(block, keyword, keyword_data)
if not do_import:
return
keyword_object_type, format = _get_kwd_class_and_format(keyword)
if keyword_object_type == None:
result.add_unprocessed_keyword(keyword)
deck.append(keyword_data)
after_import(keyword_data)
else:
import ansys.dyna.core.keywords

Expand All @@ -144,9 +178,12 @@ def handle_keyword(block: typing.List[str], deck: "ansys.dyna.core.deck.Deck") -
try:
keyword_object.loads(keyword_data, deck.parameters)
deck.append(keyword_object)
after_import(keyword_object)
except Exception as e:
on_error(e)
result.add_unprocessed_keyword(keyword)
deck.append(keyword_data)
after_import(keyword_data)

def handle_block(iterstate: int, block: typing.List[str]) -> bool:
if iterstate == IterState.END:
Expand Down Expand Up @@ -185,7 +222,12 @@ def handle_block(iterstate: int, block: typing.List[str]) -> bool:
return


def load_deck(deck: "ansys.dyna.core.deck.Deck", text: str) -> DeckLoaderResult:
def load_deck(
deck: "ansys.dyna.core.deck.Deck",
text: str,
context: typing.Optional[ImportContext],
import_handlers: typing.List[ImportHandler],
) -> DeckLoaderResult:
result = DeckLoaderResult()
_try_load_deck(deck, text, result)
_try_load_deck(deck, text, result, context, import_handlers)
return result
71 changes: 71 additions & 0 deletions src/ansys/dyna/core/lib/import_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Import handler used by the import deck feature"""

import dataclasses
import typing
import warnings

from ansys.dyna.core.lib.keyword_base import KeywordBase


@dataclasses.dataclass
class ImportContext:
"""Optional transformation to apply, using type `IncludeTransform`"""

xform: typing.Any = None

"""Deck into which the import is occurring."""
deck: typing.Any = None

"""Path of file that is importing."""
path: str = None


class ImportHandler:
"""Base class for import handlers."""

def before_import(self, context: ImportContext, keyword: str, buffer: typing.TextIO):
"""Event called before reading a keyword.
`keyword` is the string label of the keyword
`buffer` is a copy of the buffer to read from.
Usage:
Return True if the keyword is to be imported as usual.
Return False if the keyword is not to be imported.
"""
return True

def after_import(self, context: ImportContext, keyword: typing.Union[str, KeywordBase]):
"""Event called after a keyword is imported.
`keyword` is the imported keyword. It could be a string or a keyword object
Depending on the `context` is a
"""
pass

def on_error(self, error):
# TODO - use logging
warnings.warn(f"error in importhandler {self}: {error}")
65 changes: 65 additions & 0 deletions src/ansys/dyna/core/lib/transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Transformation handler for INCLUDE_TRANSFORM."""
import typing
import warnings

from ansys.dyna.core.lib.import_handler import ImportContext, ImportHandler
from ansys.dyna.core.lib.keyword_base import KeywordBase
from ansys.dyna.core.lib.transforms.base_transform import Transform
from ansys.dyna.core.lib.transforms.element_transform import TransformElement
from ansys.dyna.core.lib.transforms.node_transform import TransformNode


class TransformHandler(ImportHandler):
def __init__(self):
self._handlers: typing.Dict[typing.Union[str, typing.Tuple[str, str]], Transform] = {
"NODE": TransformNode,
"ELEMENT": TransformElement,
}

def register_transform_handler(
self, identity: typing.Union[str, typing.Tuple[str, str]], handler: Transform
) -> None:
self._handlers[identity] = handler

def after_import(self, context: ImportContext, keyword: typing.Union[KeywordBase, str]) -> None:
if isinstance(keyword, str):
return
if context.xform is None:
return
print(self._handlers.keys())
# first try to get the specialized handler for the keyword + subkeyword
identity = (keyword.keyword, keyword.subkeyword)
handler = self._handlers.get(identity, None)
if handler is None:
# then try to get the handler for the keyword
identity = keyword.keyword
handler = self._handlers.get(identity, None)
if handler is None:
warnings.warn()
return

# if a handler was found, initialize it with the xform from the context
# and transform the keyword with it
handler(context.xform).transform(keyword)
Empty file.
33 changes: 33 additions & 0 deletions src/ansys/dyna/core/lib/transforms/base_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Base Transform class."""

from ansys.dyna.core import keywords as kwd


class Transform:
def __init__(self, xform: kwd.IncludeTransform):
self._xform: kwd.IncludeTransform = xform

def transform(self, keyword) -> None:
raise Exception("Implementation required for transform")
Loading

0 comments on commit b54975c

Please sign in to comment.