Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions flow360/component/simulation/meshing_param/meshing_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,14 @@ class MeshingDefaults(Flow360BaseModel):
context=SURFACE_MESH,
)

target_surface_node_count: Optional[pd.PositiveInt] = ContextField(
None,
description="Target number of surface mesh nodes. When specified, the surface mesher "
"will rescale the meshing parameters to achieve approximately this number of nodes. "
"This option is only supported when using geometry AI and can not be overridden per face.",
context=SURFACE_MESH,
)
Comment thread
cursor[bot] marked this conversation as resolved.

curvature_resolution_angle: AngleType.Positive = ContextField(
12 * u.deg,
description=(
Expand Down Expand Up @@ -312,6 +320,7 @@ def invalid_geometry_accuracy(cls, value, param_info: ParamsValidationInfo):
@contextual_field_validator(
"surface_max_aspect_ratio",
"surface_max_adaptation_iterations",
"target_surface_node_count",
"resolve_face_boundaries",
"preserve_thin_geometry",
"sealing_size",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@ def _get_gai_setting_whitelist(input_params: SimulationParams) -> dict:
"preserve_thin_geometry": None,
"surface_max_aspect_ratio": None,
"surface_max_adaptation_iterations": None,
"target_surface_node_count": None,
"sealing_size": None,
"remove_hidden_geometry": None,
"min_passage_size": None,
Expand Down
112 changes: 112 additions & 0 deletions tests/simulation/translator/test_surface_meshing_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1734,3 +1734,115 @@ def test_gai_no_stationary_enclosed_entities():
for zone in volume_zones:
if zone["type"] in ("RotationVolume", "RotationCylinder"):
assert "stationary_enclosed_entities" not in zone


def test_gai_target_surface_node_count_set():
"""target_surface_node_count passes through the GAI whitelist when set."""
param_dict = {
"private_attribute_asset_cache": {
"use_inhouse_mesher": True,
"use_geometry_AI": True,
"project_entity_info": {"type_name": "GeometryEntityInfo"},
},
}

with SI_unit_system:
params = SimulationParams(
meshing=MeshingParams(
defaults=MeshingDefaults(
surface_max_edge_length=0.1,
geometry_accuracy=0.01,
target_surface_node_count=50000,
),
volume_zones=[AutomatedFarfield()],
),
private_attribute_asset_cache=AssetCache.model_validate(
param_dict["private_attribute_asset_cache"]
),
)

translated = get_surface_meshing_json(params, 1 * u.m)
assert "meshing" in translated
assert "defaults" in translated["meshing"]
assert "target_surface_node_count" in translated["meshing"]["defaults"]
assert translated["meshing"]["defaults"]["target_surface_node_count"] == 50000


def test_gai_target_surface_node_count_absent():
"""target_surface_node_count is absent from GAI JSON when not set."""
param_dict = {
"private_attribute_asset_cache": {
"use_inhouse_mesher": True,
"use_geometry_AI": True,
"project_entity_info": {"type_name": "GeometryEntityInfo"},
},
}

with SI_unit_system:
params = SimulationParams(
meshing=MeshingParams(
defaults=MeshingDefaults(
surface_max_edge_length=0.1,
geometry_accuracy=0.01,
),
volume_zones=[AutomatedFarfield()],
),
private_attribute_asset_cache=AssetCache.model_validate(
param_dict["private_attribute_asset_cache"]
),
)

translated = get_surface_meshing_json(params, 1 * u.m)
assert "meshing" in translated
assert "defaults" in translated["meshing"]
assert "target_surface_node_count" not in translated["meshing"]["defaults"]


def test_legacy_target_surface_node_count_rejected(get_om6wing_geometry):
"""target_surface_node_count is rejected for legacy (non-GAI) flows."""
my_geometry = TempGeometry("om6wing.csm")
with SI_unit_system:
params = SimulationParams(
private_attribute_asset_cache=AssetCache(
project_entity_info=my_geometry._get_entity_info()
),
meshing=MeshingParams(
defaults=MeshingDefaults(
surface_edge_growth_rate=1.2,
curvature_resolution_angle=12 * u.deg,
surface_max_edge_length=1 * u.m,
target_surface_node_count=5000,
edge_split_layers=0,
),
),
)

params, err, warnings = validate_params_with_context(params, "Geometry", "SurfaceMesh")
assert (
err is not None
), "Expected validation error for target_surface_node_count in non-GAI flow"
assert "target_surface_node_count" in str(err)


def test_legacy_target_surface_node_count_absent(get_om6wing_geometry):
"""target_surface_node_count is absent from legacy translated JSON when not set."""
my_geometry = TempGeometry("om6wing.csm")
with SI_unit_system:
params = SimulationParams(
private_attribute_asset_cache=AssetCache(
project_entity_info=my_geometry._get_entity_info()
),
meshing=MeshingParams(
defaults=MeshingDefaults(
surface_edge_growth_rate=1.2,
curvature_resolution_angle=12 * u.deg,
surface_max_edge_length=1 * u.m,
edge_split_layers=0,
),
),
)

params, err, warnings = validate_params_with_context(params, "Geometry", "SurfaceMesh")
assert err is None, f"Validation error: {err}"
translated = get_surface_meshing_json(params, mesh_unit=get_om6wing_geometry.mesh_unit)
assert "target_surface_node_count" not in translated
Comment thread
mikeparkflex marked this conversation as resolved.
Loading