完善MECMAS21参数配置与分组交互
This commit is contained in:
1 parent
32f21f08ff
commit
be54070855
28 files changed
+3418
-202
No files matched your search
+108
-5
@@ -38,6 +38,8 @@ FRONTEND_DIST_DIR = Path(__file__).resolve().parent.parent / "frontend" / "dist"
|
||||
PROJECT_STORAGE_DIR = Path(__file__).parent / "data" / "reactflow-projects"
|
||||
SYSTEM_XML_SCHEMA_VERSION = "2"
|
||||
SYSTEM_XML_UNIT_SYSTEM = "SI"
|
||||
AMESIM_PARAMETER_ENCODING_VERSION = 1
|
||||
MECMAS21_LEGACY_BINARY_PARAMETERS = ("useFriction", "strib")
|
||||
|
||||
SimulationProgressEmitter = Callable[
|
||||
[int, str, str, float | None, float | None],
|
||||
@@ -137,6 +139,15 @@ class ReactFlowSimulationConfig(BaseModel):
|
||||
class ReactFlowProjectPayload(BaseModel):
|
||||
name: str = "untitled"
|
||||
mediumReferenceVersion: Literal[1] | None = None
|
||||
# Missing/None is reserved for projects written before canonical AMESim
|
||||
# option codes were introduced. Every current writer emits version 1.
|
||||
amesimParameterEncodingVersion: Literal[1] | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Version 1 is required for current writers; omission is accepted "
|
||||
"only when importing legacy MECMAS21 0/1 option codes."
|
||||
),
|
||||
)
|
||||
nodes: list[ReactFlowNodePayload] = Field(default_factory=list)
|
||||
edges: list[ReactFlowEdgePayload] = Field(default_factory=list)
|
||||
simulation: ReactFlowSimulationConfig = Field(default_factory=ReactFlowSimulationConfig)
|
||||
@@ -168,8 +179,42 @@ class SimulationCancellationPayload(BaseModel):
|
||||
|
||||
def pydantic_to_jsonable(model: BaseModel) -> dict[str, Any]:
|
||||
if hasattr(model, "model_dump"):
|
||||
return model.model_dump(mode="json")
|
||||
return model.dict()
|
||||
return model.model_dump(mode="json", exclude_none=True)
|
||||
return model.dict(exclude_none=True)
|
||||
|
||||
|
||||
def reactflow_project_storage_data(
|
||||
project: ReactFlowProjectPayload,
|
||||
) -> dict[str, Any]:
|
||||
"""Return a current, self-identifying project payload for persistence."""
|
||||
|
||||
data = pydantic_to_jsonable(project)
|
||||
if project.amesimParameterEncodingVersion is None:
|
||||
from app.simulation.registry import get_component_model_spec
|
||||
|
||||
stored_nodes = data["nodes"]
|
||||
for index, node in enumerate(project.nodes):
|
||||
if node.data.modelType != "amesim_mecmas21":
|
||||
continue
|
||||
parameter_definitions = get_component_model_spec(
|
||||
node.data.modelType
|
||||
).parameter_by_name
|
||||
stored_parameters = stored_nodes[index]["data"].setdefault(
|
||||
"parameters",
|
||||
{},
|
||||
)
|
||||
for name in MECMAS21_LEGACY_BINARY_PARAMETERS:
|
||||
stored_parameters[name] = canonical_amesim_parameter_value(
|
||||
project,
|
||||
node,
|
||||
name,
|
||||
node.data.parameters.get(
|
||||
name,
|
||||
parameter_definitions[name].default,
|
||||
),
|
||||
)
|
||||
data["amesimParameterEncodingVersion"] = AMESIM_PARAMETER_ENCODING_VERSION
|
||||
return data
|
||||
|
||||
|
||||
@app.get("/")
|
||||
@@ -367,7 +412,7 @@ def save_reactflow_project(
|
||||
) -> dict[str, object]:
|
||||
path = reactflow_project_path(project_id)
|
||||
PROJECT_STORAGE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
data = pydantic_to_jsonable(payload)
|
||||
data = reactflow_project_storage_data(payload)
|
||||
data["name"] = payload.name or project_id
|
||||
path.write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2),
|
||||
@@ -907,6 +952,9 @@ def build_reactflow_system_xml(project: ReactFlowProjectPayload) -> bytes:
|
||||
"schemaVersion": SYSTEM_XML_SCHEMA_VERSION,
|
||||
"unitSystem": SYSTEM_XML_UNIT_SYSTEM,
|
||||
"mediumReferenceVersion": "1",
|
||||
"amesimParameterEncodingVersion": str(
|
||||
AMESIM_PARAMETER_ENCODING_VERSION
|
||||
),
|
||||
},
|
||||
)
|
||||
ET.SubElement(
|
||||
@@ -963,11 +1011,30 @@ def build_reactflow_system_xml(project: ReactFlowProjectPayload) -> bytes:
|
||||
port_definition.positiveFlowDirection or "intoComponent"
|
||||
)
|
||||
ET.SubElement(component_node, "Port", port_attributes)
|
||||
parameter_values = dict(node.data.parameters)
|
||||
parameter_values = {
|
||||
name: canonical_amesim_parameter_value(project, node, name, value)
|
||||
for name, value in node.data.parameters.items()
|
||||
}
|
||||
try:
|
||||
component_spec = get_component_model_spec(node.data.modelType)
|
||||
except ValueError:
|
||||
component_spec = None
|
||||
if (
|
||||
component_spec is not None
|
||||
and project.amesimParameterEncodingVersion is None
|
||||
and node.data.modelType == "amesim_mecmas21"
|
||||
):
|
||||
parameter_definitions = {
|
||||
parameter.name: parameter for parameter in component_spec.parameters
|
||||
}
|
||||
for name in MECMAS21_LEGACY_BINARY_PARAMETERS:
|
||||
if name not in parameter_values:
|
||||
parameter_values[name] = canonical_amesim_parameter_value(
|
||||
project,
|
||||
node,
|
||||
name,
|
||||
parameter_definitions[name].default,
|
||||
)
|
||||
if (
|
||||
component_spec is not None
|
||||
and component_spec.display.role == "amesimGasMediumDefinition"
|
||||
@@ -1140,7 +1207,12 @@ def compile_reactflow_network(
|
||||
for node in project.nodes:
|
||||
spec = get_component_model_spec(node.data.modelType)
|
||||
parameter_values = {
|
||||
parameter.name: parameter_float(node, parameter.name, parameter.default)
|
||||
parameter.name: canonical_amesim_parameter_value(
|
||||
project,
|
||||
node,
|
||||
parameter.name,
|
||||
parameter_float(node, parameter.name, parameter.default),
|
||||
)
|
||||
for parameter in spec.parameters
|
||||
}
|
||||
unknown_parameters = set(node.data.parameters) - set(spec.parameter_by_name)
|
||||
@@ -1436,6 +1508,37 @@ def parameter_float(
|
||||
raise ValueError(f"Parameter '{name}' on component '{node.id}' must be numeric.")
|
||||
|
||||
|
||||
def canonical_amesim_parameter_value(
|
||||
project: ReactFlowProjectPayload,
|
||||
node: ReactFlowNodePayload,
|
||||
name: str,
|
||||
value: Any,
|
||||
) -> Any:
|
||||
"""Translate legacy app-local AMESim codes to the canonical catalog codes.
|
||||
|
||||
Projects created before ``amesimParameterEncodingVersion`` used 0/1 for
|
||||
MECMAS21 yes/no parameters. AMESim itself uses 1/2, so preserve the old
|
||||
project meaning while all newly saved projects and System XML use the
|
||||
canonical codes.
|
||||
"""
|
||||
|
||||
if (
|
||||
project.amesimParameterEncodingVersion is not None
|
||||
or node.data.modelType != "amesim_mecmas21"
|
||||
or name not in MECMAS21_LEGACY_BINARY_PARAMETERS
|
||||
):
|
||||
return value
|
||||
try:
|
||||
numeric_value = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return value
|
||||
if numeric_value == 0.0:
|
||||
return 1.0
|
||||
if numeric_value == 1.0:
|
||||
return 2.0
|
||||
return value
|
||||
|
||||
|
||||
def pipe_config_from_node(node: ReactFlowNodePayload | None, pipe_config_type):
|
||||
return pipe_config_type(
|
||||
length=parameter_float(node, "length", 5.0),
|
||||
|
||||
Reference in new issue
Block a user