Skip to content

Commit c1ef814

Browse files
Rename geometry workflow API to Catalyst (#1978)
1 parent 0ad7d49 commit c1ef814

4 files changed

Lines changed: 165 additions & 10 deletions

File tree

flow360/cloud/flow360_requests.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,9 @@ class NewGeometryRequest(Flow360RequestsV2):
118118
alias="lengthUnit", description="project length unit"
119119
)
120120
description: str = pd_v2.Field(default="", description="project description")
121-
use_nextflow: bool = pd_v2.Field(
121+
use_catalyst: bool = pd_v2.Field(
122122
default=False,
123-
alias="useNextflow",
124-
description="Route geometry processing through Nextflow pipeline instead of legacy system",
123+
description="Use the Catalyst workflow for geometry processing",
125124
)
126125

127126

flow360/component/geometry.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
from flow360.exceptions import Flow360FileError, Flow360ValueError
4040
from flow360.log import log
4141

42+
GeometryWorkflow = Literal["standard", "catalyst"]
43+
4244

4345
class GeometryStatus(Enum):
4446
"""Status of geometry resource, the is_final method is overloaded"""
@@ -107,7 +109,7 @@ def __init__(
107109
length_unit: LengthUnitType = "m",
108110
tags: List[str] = None,
109111
folder: Optional[Folder] = None,
110-
use_nextflow_pipelines: bool = False,
112+
workflow: GeometryWorkflow = "standard",
111113
):
112114
"""
113115
Initialize a GeometryDraft with common attributes.
@@ -139,7 +141,7 @@ def __init__(
139141
self.length_unit = length_unit
140142
self.solver_version = solver_version
141143
self.folder = folder
142-
self.use_nextflow_pipelines = use_nextflow_pipelines
144+
self.workflow = workflow
143145

144146
# pylint: disable=fixme
145147
# TODO: create a DependableResourceDraft for GeometryDraft and SurfaceMeshDraft
@@ -169,6 +171,11 @@ def _validate_geometry(self):
169171
f"specified length_unit : {self.length_unit} is invalid. "
170172
f"Valid options are: {list(LengthUnitType.__args__)}"
171173
)
174+
if self.workflow not in ("standard", "catalyst"):
175+
raise Flow360ValueError(
176+
f"specified workflow : {self.workflow} is invalid. "
177+
"Valid options are: ['standard', 'catalyst']"
178+
)
172179

173180
def _set_default_project_name(self):
174181
"""Set default project name if not provided for project creation."""
@@ -243,7 +250,7 @@ def _create_project_root_resource(
243250
parent_folder_id=self.folder.id if self.folder else "ROOT.FLOW360",
244251
length_unit=self.length_unit,
245252
description=description,
246-
use_nextflow=self.use_nextflow_pipelines,
253+
use_catalyst=self.workflow == "catalyst",
247254
)
248255

249256
resp = RestApi(GeometryInterface.endpoint).post(req.dict())
@@ -476,7 +483,7 @@ def from_file(
476483
length_unit: LengthUnitType = "m",
477484
tags: List[str] = None,
478485
folder: Optional[Folder] = None,
479-
use_nextflow_pipelines: bool = False,
486+
workflow: GeometryWorkflow = "standard",
480487
) -> GeometryDraft:
481488
return GeometryDraft(
482489
file_names=file_names,
@@ -485,7 +492,7 @@ def from_file(
485492
length_unit=length_unit,
486493
tags=tags,
487494
folder=folder,
488-
use_nextflow_pipelines=use_nextflow_pipelines,
495+
workflow=workflow,
489496
)
490497

491498
@classmethod

flow360/component/project.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
fetch_examples,
2828
find_example_by_name,
2929
)
30-
from flow360.component.geometry import Geometry
30+
from flow360.component.geometry import Geometry, GeometryWorkflow
3131
from flow360.component.interfaces import (
3232
GeometryInterface,
3333
ProjectInterface,
@@ -931,6 +931,7 @@ def _create_project_from_files(
931931
tags: List[str] = None,
932932
run_async: bool = False,
933933
folder: Optional[Folder] = None,
934+
workflow: GeometryWorkflow = "standard",
934935
):
935936
"""
936937
Initializes a project from a file.
@@ -969,7 +970,13 @@ def _create_project_from_files(
969970

970971
if isinstance(files, GeometryFiles):
971972
draft = Geometry.from_file(
972-
files.file_names, name, solver_version, length_unit, tags, folder=folder
973+
files.file_names,
974+
name,
975+
solver_version,
976+
length_unit,
977+
tags,
978+
folder=folder,
979+
workflow=workflow,
973980
)
974981
elif isinstance(files, SurfaceMeshFile):
975982
draft = SurfaceMeshV2.from_file(
@@ -1150,6 +1157,7 @@ def from_geometry(
11501157
tags: List[str] = None,
11511158
run_async: bool = False,
11521159
folder: Optional[Folder] = None,
1160+
workflow: GeometryWorkflow = "standard",
11531161
):
11541162
"""
11551163
Initializes a project from local geometry files.
@@ -1170,6 +1178,10 @@ def from_geometry(
11701178
Whether to create project asynchronously (default is False).
11711179
folder : Optional[Folder], optional
11721180
Parent folder for the project. If None, creates in root.
1181+
workflow : {"standard", "catalyst"}, optional
1182+
Workflow used for project geometry preparation. Use `"catalyst"`
1183+
for geometry preparation recommended for GAI and snappy workflows
1184+
(default is `"standard"`).
11731185
11741186
Returns
11751187
-------
@@ -1205,6 +1217,7 @@ def from_geometry(
12051217
tags=tags,
12061218
run_async=run_async,
12071219
folder=folder,
1220+
workflow=workflow,
12081221
)
12091222

12101223
@classmethod

tests/simulation/test_project.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from flow360.component.simulation.services import ValidationCalledBy, validate_model
1717
from flow360.component.simulation.utils import model_attribute_unlock
1818
from flow360.component.volume_mesh import VolumeMeshV2
19+
from flow360.examples import Cylinder3D
1920
from flow360.exceptions import Flow360ConfigurationError, Flow360ValueError
2021

2122
log.set_logging_level("DEBUG")
@@ -57,6 +58,141 @@ def test_from_cloud(mock_id, mock_response):
5758
project.get_case(asset_id=current_case_id)
5859

5960

61+
def test_from_geometry_passes_workflow(monkeypatch):
62+
Cylinder3D.get_files()
63+
captured = {}
64+
65+
class _MockDraft:
66+
def submit(self, run_async=False):
67+
assert run_async is True
68+
return MagicMock(project_id="prj-test-project-id")
69+
70+
def _mock_from_file(
71+
file_names,
72+
project_name=None,
73+
solver_version=None,
74+
length_unit="m",
75+
tags=None,
76+
folder=None,
77+
workflow="standard",
78+
):
79+
captured["file_names"] = file_names
80+
captured["project_name"] = project_name
81+
captured["solver_version"] = solver_version
82+
captured["length_unit"] = length_unit
83+
captured["workflow"] = workflow
84+
return _MockDraft()
85+
86+
monkeypatch.setattr("flow360.component.project.Geometry.from_file", _mock_from_file)
87+
88+
project_id = fl.Project.from_geometry(
89+
Cylinder3D.geometry,
90+
name="catalyst-project",
91+
solver_version="release-test",
92+
length_unit="cm",
93+
run_async=True,
94+
workflow="catalyst",
95+
)
96+
97+
assert project_id == "prj-test-project-id"
98+
assert captured["file_names"] == Cylinder3D.geometry
99+
assert captured["project_name"] == "catalyst-project"
100+
assert captured["solver_version"] == "release-test"
101+
assert captured["length_unit"] == "cm"
102+
assert captured["workflow"] == "catalyst"
103+
104+
105+
def _fake_geometry_api_response(geo_id: str = "geo-test-0001", prj_id: str = "prj-test-payload"):
106+
return {
107+
"id": geo_id,
108+
"name": "test-geo",
109+
"userId": "user-test",
110+
"status": "uploaded",
111+
"projectId": prj_id,
112+
"createdAt": "2026-01-01T00:00:00Z",
113+
"updatedAt": "2026-01-01T00:00:00Z",
114+
"deleted": False,
115+
"tags": [],
116+
}
117+
118+
119+
def _mock_upload_files(self, *args, **kwargs):
120+
geo = MagicMock()
121+
geo.short_description.return_value = "test-geo (geo-test)"
122+
geo.id = "geo-test-0001"
123+
geo.project_id = "prj-test-payload"
124+
return geo
125+
126+
127+
def test_catalyst_workflow_reaches_api_payload(monkeypatch):
128+
Cylinder3D.get_files()
129+
captured_payload: dict = {}
130+
131+
class _FakeRestApi:
132+
def __init__(self, endpoint, **kwargs):
133+
pass
134+
135+
def post(self, json_body):
136+
captured_payload.update(json_body)
137+
return _fake_geometry_api_response()
138+
139+
monkeypatch.setattr("flow360.component.geometry.RestApi", _FakeRestApi)
140+
monkeypatch.setattr("os.path.exists", lambda _: True)
141+
monkeypatch.setattr(
142+
"flow360.component.geometry.GeometryDraft._upload_files", _mock_upload_files
143+
)
144+
145+
draft = Geometry.from_file(
146+
file_names=Cylinder3D.geometry,
147+
project_name="payload-test",
148+
solver_version="release-test",
149+
length_unit="cm",
150+
workflow="catalyst",
151+
)
152+
153+
assert draft.workflow == "catalyst"
154+
draft.submit(run_async=True)
155+
156+
assert (
157+
captured_payload.get("useCatalyst") is True
158+
), f"Expected Catalyst workflow to set useCatalyst=true, got: {captured_payload}"
159+
assert set(captured_payload) >= {"useCatalyst"}
160+
161+
162+
def test_standard_workflow_is_default(monkeypatch):
163+
Cylinder3D.get_files()
164+
captured_payload: dict = {}
165+
166+
class _FakeRestApi:
167+
def __init__(self, endpoint, **kwargs):
168+
pass
169+
170+
def post(self, json_body):
171+
captured_payload.update(json_body)
172+
return _fake_geometry_api_response(geo_id="geo-test-0002", prj_id="prj-test-default")
173+
174+
monkeypatch.setattr("flow360.component.geometry.RestApi", _FakeRestApi)
175+
monkeypatch.setattr("os.path.exists", lambda _: True)
176+
monkeypatch.setattr(
177+
"flow360.component.geometry.GeometryDraft._upload_files", _mock_upload_files
178+
)
179+
180+
draft = Geometry.from_file(
181+
file_names=Cylinder3D.geometry,
182+
project_name="default-test",
183+
solver_version="release-test",
184+
length_unit="cm",
185+
)
186+
187+
assert draft.workflow == "standard"
188+
draft.submit(run_async=True)
189+
190+
assert (
191+
captured_payload.get("useCatalyst") is False
192+
), f"Expected standard workflow to keep useCatalyst=false, got: {captured_payload}"
193+
assert set(captured_payload) >= {"useCatalyst"}
194+
195+
60196
def test_root_asset_entity_change_reflection(mock_id, mock_response):
61197
project = fl.Project.from_cloud(project_id="prj-41d2333b-85fd-4bed-ae13-15dcb6da519e")
62198
geo = project.geometry

0 commit comments

Comments
 (0)