- 
                Notifications
    You must be signed in to change notification settings 
- Fork 140
feat: First tentative for Plugin Mapdl Mechanism python API #3627
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
base: main
Are you sure you want to change the base?
Changes from all commits
7a8a716
              538936d
              35bcf88
              2dea3bc
              a1d7ee2
              57e2de6
              cd63cc8
              1d2d4d5
              4afa907
              16c744d
              6eaf391
              83997de
              ff30eae
              fec2b24
              f4e9ef5
              302ecc1
              6dc488f
              d1e8471
              3c25fe9
              caf96bc
              484b4f7
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1 @@ | ||
| First tentative for Plugin Mapdl Mechanism python API | 
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|  | @@ -87,6 +87,7 @@ | |||||
| from ansys.mapdl.core.mapdl import MapdlBase | ||||||
| from ansys.mapdl.core.mapdl_geometry import Geometry, LegacyGeometry | ||||||
| from ansys.mapdl.core.parameters import Parameters | ||||||
| from ansys.mapdl.core.plugin import ansPlugin | ||||||
| from ansys.mapdl.core.solution import Solution | ||||||
| from ansys.mapdl.core.xpl import ansXpl | ||||||
|  | ||||||
|  | @@ -372,6 +373,8 @@ def __init__( | |||||
|  | ||||||
| self._xpl: Optional[ansXpl] = None # Initialized in mapdl_grpc | ||||||
|  | ||||||
| self._plugin: Optional[ansPlugin] = None # Initialized in mapdl_grpc | ||||||
|  | ||||||
| from ansys.mapdl.core.component import ComponentManager | ||||||
|  | ||||||
| self._componentmanager: ComponentManager = ComponentManager(self) | ||||||
|  | @@ -1109,6 +1112,26 @@ def graphics_backend(self, value: GraphicsBackend): | |||||
| """Set the graphics backend to be used.""" | ||||||
| self._graphics_backend = value | ||||||
|  | ||||||
| @property | ||||||
| def plugins(self) -> "ansPlugin": | ||||||
| """MAPDL plugin handler | ||||||
|  | ||||||
| Plugin Manager for MAPDL | ||||||
|  | ||||||
| Examples | ||||||
| -------- | ||||||
|  | ||||||
| >>> from ansys import Mapdl | ||||||
| >>> mapdl = Mapdl() | ||||||
| >>> plugin = mapdl.plugin | ||||||
| 
     | ||||||
| >>> plugin = mapdl.plugin | |
| >>> plugin = mapdl.plugins | 
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,244 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| # Copyright (C) 2016 - 2025 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. | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| """Contains the ansPlugin class.""" | ||||||||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||||||||
| from warnings import warn | ||||||||||||||||||||||||||||||||||||||||||||
| import weakref | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| from ansys.mapdl.core import Mapdl | ||||||||||||||||||||||||||||||||||||||||||||
| from ansys.mapdl.core.errors import PluginError, PluginLoadError, PluginUnloadError | ||||||||||||||||||||||||||||||||||||||||||||
| from ansys.mapdl.core.logging import Logger | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| class ansPlugin: | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| ANSYS MAPDL Plugin Manager. | ||||||||||||||||||||||||||||||||||||||||||||
| Examples | ||||||||||||||||||||||||||||||||||||||||||||
| -------- | ||||||||||||||||||||||||||||||||||||||||||||
| >>> from ansys.mapdl.core import launch_mapdl | ||||||||||||||||||||||||||||||||||||||||||||
| >>> mapdl = launch_mapdl() | ||||||||||||||||||||||||||||||||||||||||||||
| >>> plugin = mapdl.plugin | ||||||||||||||||||||||||||||||||||||||||||||
| Load a plugin in the MAPDL Session | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| def __init__(self, mapdl: Mapdl): | ||||||||||||||||||||||||||||||||||||||||||||
| """Initialize the class.""" | ||||||||||||||||||||||||||||||||||||||||||||
| from ansys.mapdl.core.mapdl_grpc import MapdlGrpc | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| if not isinstance(mapdl, MapdlGrpc): # pragma: no cover | ||||||||||||||||||||||||||||||||||||||||||||
| raise TypeError("Must be initialized using an 'MapdlGrpc' object") | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| self._mapdl_weakref = weakref.ref(mapdl) | ||||||||||||||||||||||||||||||||||||||||||||
| self._filename = None | ||||||||||||||||||||||||||||||||||||||||||||
| self._open = False | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||||||
| def _mapdl(self) -> Mapdl: | ||||||||||||||||||||||||||||||||||||||||||||
| """Return the weakly referenced instance of mapdl.""" | ||||||||||||||||||||||||||||||||||||||||||||
| return self._mapdl_weakref() | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||||||
| def _log(self) -> Logger: | ||||||||||||||||||||||||||||||||||||||||||||
| """Return the logger from the MAPDL instance.""" | ||||||||||||||||||||||||||||||||||||||||||||
| return self._mapdl._log | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| def _parse_commands(self, response: str) -> list[str]: | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| Parse the response string to extract commands. | ||||||||||||||||||||||||||||||||||||||||||||
| Parameters | ||||||||||||||||||||||||||||||||||||||||||||
| ---------- | ||||||||||||||||||||||||||||||||||||||||||||
| response : str | ||||||||||||||||||||||||||||||||||||||||||||
| The response string containing commands. | ||||||||||||||||||||||||||||||||||||||||||||
| Returns | ||||||||||||||||||||||||||||||||||||||||||||
| ------- | ||||||||||||||||||||||||||||||||||||||||||||
| list[str] | ||||||||||||||||||||||||||||||||||||||||||||
| A list of commands extracted from the response. | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| if not response: | ||||||||||||||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| # Assuming commands are separated by newlines | ||||||||||||||||||||||||||||||||||||||||||||
| return re.findall(r"New command \[(.*)\] registered", response) | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| def _set_commands(self, commands: list[str], plugin_name: str = "NOT_SET") -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| Set commands to be executed. | ||||||||||||||||||||||||||||||||||||||||||||
| Parameters | ||||||||||||||||||||||||||||||||||||||||||||
| ---------- | ||||||||||||||||||||||||||||||||||||||||||||
| commands : list[str] | ||||||||||||||||||||||||||||||||||||||||||||
| List of commands to be set. | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| if not commands: | ||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| mapdl = self._mapdl | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| for each_command in commands: | ||||||||||||||||||||||||||||||||||||||||||||
| each_command = each_command.replace("*", "star").replace("/", "slash") | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| if hasattr(mapdl, each_command): | ||||||||||||||||||||||||||||||||||||||||||||
| # We are allowing to overwrite existing commands | ||||||||||||||||||||||||||||||||||||||||||||
| warn(f"Command '{each_command}' already exists in the MAPDL instance.") | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| def passer(self, *args, **kwargs): | ||||||||||||||||||||||||||||||||||||||||||||
| return self.run(*args, **kwargs) | ||||||||||||||||||||||||||||||||||||||||||||
| 
      Comment on lines
    
      +108
     to 
      +109
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): The dynamically created 'passer' function may not bind correctly to the MAPDL instance. Assigning 'passer' directly may cause binding issues with 'self'. Use functools.partial or types.MethodType to properly bind the method to the MAPDL instance. | ||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||
| # Inject docstring | ||||||||||||||||||||||||||||||||||||||||||||
| passer.__doc__ = f"""Command from plugin {plugin_name}: {each_command}. | ||||||||||||||||||||||||||||||||||||||||||||
| Use this plugin documentation to understand the command and its parameters. | ||||||||||||||||||||||||||||||||||||||||||||
| Automatically generated docstring by ansPlugin. | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| 
      Comment on lines
    
      +108
     to 
      +116
    
   
     | ||||||||||||||||||||||||||||||||||||||||||||
| def passer(self, *args, **kwargs): | |
| return self.run(*args, **kwargs) | |
| # Inject docstring | |
| passer.__doc__ = f"""Command from plugin {plugin_name}: {each_command}. | |
| Use this plugin documentation to understand the command and its parameters. | |
| Automatically generated docstring by ansPlugin. | |
| """ | |
| def make_passer(docstring): | |
| def passer(self, *args, **kwargs): | |
| return self.run(*args, **kwargs) | |
| passer.__doc__ = docstring | |
| return passer | |
| docstring = f"""Command from plugin {plugin_name}: {each_command}. | |
| Use this plugin documentation to understand the command and its parameters. | |
| Automatically generated docstring by ansPlugin. | |
| """ | |
| passer = make_passer(docstring) | 
    
      
    
      Copilot
AI
    
    
    
      Oct 14, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The passer function is being reused for all commands in the loop. Since it's created once and reused, all commands will share the same method object, and the docstring will only reflect the last command processed. Create a new function instance for each command, possibly using a factory pattern or lambda with proper binding.
| def passer(self, *args, **kwargs): | |
| return self.run(*args, **kwargs) | |
| # Inject docstring | |
| passer.__doc__ = f"""Command from plugin {plugin_name}: {each_command}. | |
| Use this plugin documentation to understand the command and its parameters. | |
| Automatically generated docstring by ansPlugin. | |
| """ | |
| setattr(mapdl, each_command, passer) | |
| def make_passer(plugin_name, command_name): | |
| def passer(self, *args, **kwargs): | |
| return self.run(*args, **kwargs) | |
| passer.__doc__ = f"""Command from plugin {plugin_name}: {command_name}. | |
| Use this plugin documentation to understand the command and its parameters. | |
| Automatically generated docstring by ansPlugin. | |
| """ | |
| return passer | |
| setattr(mapdl, each_command, make_passer(plugin_name, each_command)) | 
    
      
    
      Copilot
AI
    
    
    
      Oct 14, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable names in _deleter_commands use each_command while similar loops in _set_commands also use each_command. However, the command names have already been transformed (replacing '*' and '/' with 'star' and 'slash') in _set_commands, but _deleter_commands receives the raw command names. This inconsistency could lead to commands not being properly deleted. The deletion logic should apply the same transformation as used during command setting.
| if hasattr(mapdl, each_command): | |
| delattr(mapdl, each_command) | |
| self._log.info( | |
| f"Command '{each_command}' from '{plugin_name}' deleted successfully." | |
| transformed_command = each_command.replace("*", "star").replace("/", "slash") | |
| if hasattr(mapdl, transformed_command): | |
| delattr(mapdl, transformed_command) | |
| self._log.info( | |
| f"Command '{transformed_command}' from '{plugin_name}' deleted successfully." | 
        
          
              
                  germa89 marked this conversation as resolved.
              
          
            Show resolved
            Hide resolved
        
      
    
      
    
      Copilot
AI
    
    
    
      Oct 14, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The list() method always returns an empty list instead of parsing the response to extract plugin names. This makes the method non-functional. Implement the parsing logic to extract and return loaded plugin names from the response string.
| return [] | |
| plugin_names = [] | |
| # Example response might look like: | |
| # "Loaded Plugins:\nPLUGIN1\nPLUGIN2\n" | |
| # Or it might be a table, e.g.: | |
| # "PLUGINS LOADED\n----------------\nPLUGIN1 Description\nPLUGIN2 Description\n" | |
| lines = response.strip().splitlines() | |
| for line in lines: | |
| # Skip empty lines and headers | |
| if not line.strip(): | |
| continue | |
| if "plugin" in line.lower() or "loaded" in line.lower() or "---" in line: | |
| continue | |
| # Extract the first word as the plugin name | |
| match = re.match(r"^\s*([A-Za-z0-9_]+)", line) | |
| if match: | |
| plugin_names.append(match.group(1)) | |
| return plugin_names | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The import statement is incorrect. It should be
from ansys.mapdl.core import launch_mapdlor similar, asansysis not a module that directly exportsMapdl.