import enum
from typing import Any, List
import functools
import types
from collections import defaultdict
from ._manager import BaseHandler, ElementManager, LoadManager, MaterialManager, NodeManager, TimeSeriesManager
[docs]
class OpenSeesCommand(enum.Enum):
# Enum members' values are the manager classes themselves.
# IMPORTANT: NodeManager, ElementManager, etc., MUST be implemented as singletons(DONE by Metaclass)
NODE = NodeManager
ELEMENT = ElementManager
MATERIAL = MaterialManager
# TIMESERIES = TimeSeriesManager # Uncomment when TimeSeriesManager is a singleton
# LOAD = LoadManager # Uncomment when LoadManager is a singleton
@property
def instance(self) -> BaseHandler:
"""
Returns the singleton instance of the manager class.
Assumes self.value (e.g., NodeManager class) is a singleton,
so calling it (e.g., NodeManager()) returns its unique instance.
"""
return self.value()
[docs]
class OpenSeesParser:
[docs]
def __init__(self, module):
self.module = module
self.call_log: defaultdict[str, List[Any]] = defaultdict(list)
self.original_functions: dict[str, Any] = {}
# self.handler_instances dictionary is no longer needed here.
# The OpenSeesCommand enum will be the source of truth for handler instances.
self.dispatch_table: dict[str, BaseHandler] = self._build_dispatch_table()
def _build_dispatch_table(self) -> dict[str, BaseHandler]:
"""Build a dispatch table mapping function names to handlers."""
dispatch_table: dict[str, BaseHandler] = {}
for command_enum_member in OpenSeesCommand: # Iterate over OpenSeesCommand enum members
handler_instance = command_enum_member.instance # Get the singleton instance
handled_funcs: List[str] = handler_instance.handles()
for func_name in handled_funcs:
if func_name in dispatch_table:
# Note: The warning now refers to the class name of the handler instance
print(f"Warning: Function '{func_name}' is handled by multiple handlers. Using the last one found: {handler_instance.__class__.__name__}")
dispatch_table[func_name] = handler_instance
return dispatch_table
[docs]
def hook_all(self, debug = False):
for name in dir(self.module):
attr = getattr(self.module, name)
if isinstance(attr, (types.FunctionType, types.BuiltinFunctionType)):
self._hook_function(name, attr, debug)
def _hook_function(self, name, func, debug = False):
self.original_functions[name] = func
@functools.wraps(func)
def wrapper(*args, **kwargs):
arg_map = {"args": args, "kwargs": kwargs}
self.call_log[name].append(arg_map)
# Dispatch to handler
handler = self.dispatch_table.get(name)
if handler:
if debug:
print(name, arg_map)
handler.handle(name, arg_map)
return func(*args, **kwargs)
setattr(self.module, name, wrapper)
[docs]
def restore_all(self):
for name, func in self.original_functions.items():
setattr(self.module, name, func)
self.original_functions.clear()
[docs]
def clear(self):
self.call_log.clear()
for command_enum_member in OpenSeesCommand: # Iterate over OpsCommand enum members
handler_instance = command_enum_member.instance # Get the singleton instance
handler_instance.clear()
if __name__ == "__main__":
import matplotlib.pyplot as plt
import openseespy.opensees as ops
from _manager import NodeManager as Node
# 假设你已经有下面这些类
# - OpenSeesParser
# - NodeManager(BaseHandler)
# - ElementManager(BaseHandler)
# ...(参考前文)
# 创建 spy 并挂钩所有命令
parser = OpenSeesParser(ops)
parser.hook_all()
# 运行 OpenSees 命令
ops.model("basic", "-ndm", 2, "-ndf", 3)
ops.node(1, 0.0, 0.0)
ops.node(2, 1.0, 0.0)
ops.node(3, 1.0, 1.0)
ops.node(4, 0.0, 1.0)
# 提取节点数据
# Directly get the NodeManager singleton instance
node_dict = Node.nodes
# 准备画图数据
x_coords = []
y_coords = []
labels = []
for tag_node in node_dict:
# Use the singleton instance to get node coordinates
coords = Node.get_node_coords(tag_node)
x_coords.append(coords[0])
y_coords.append(coords[1])
labels.append(str(tag_node))
# 绘图
plt.figure(figsize=(5, 5))
plt.scatter(x_coords, y_coords, c="blue", s=60)
# 添加标签
for i, txt in enumerate(labels):
plt.text(x_coords[i] + 0.02, y_coords[i] + 0.02, txt, fontsize=10)
plt.title("OpenSees Node Layout")
plt.xlabel("X")
plt.ylabel("Y")
plt.axis("equal")
plt.grid(True)
plt.show()