# Use the existing connected PyFluent variable solver.
import math

task_directory = r"C:\Users\13977\AppData\Local\FluentAGI\data\runs\20261018-144517-9e4450"
s = solver.settings

def checked(label, result):
    print(label, result)
    if isinstance(result, dict):
        if result.get("status") in ("error", "failed", "needs_review"):
            raise RuntimeError(str(result))
        if result.get("error") or result.get("errors"):
            raise RuntimeError(str(result))
    return result

def numeric(value):
    if isinstance(value, bool):
        return []
    if isinstance(value, (int, float)):
        return [float(value)]
    if isinstance(value, dict):
        return [n for v in value.values() for n in numeric(v)]
    if isinstance(value, (tuple, list)):
        return [n for v in value for n in numeric(v)]
    return []

def read_value(result, name):
    if isinstance(result, dict):
        if name in result:
            values = numeric(result[name])
            if values and math.isfinite(values[0]):
                return values[0]
        children = result.values()
    else:
        children = result if isinstance(result, (list, tuple)) else []
    for child in children:
        try:
            return read_value(child, name)
        except ValueError:
            pass
    raise ValueError("无法读取报告：" + name + "；返回：" + str(result))

def scalar(setting, value):
    state = setting.get_state()
    setting.set_state(
        {"option": "value", "value": value}
        if isinstance(state, dict) and "option" in state else value
    )

if s.setup.models.is_active():
    s.file.write_case_data(
        file_name=task_directory + r"\previous_analysis_backup.cas.h5"
    )

code = """
gmsh.model.add("concept_racing_aerodynamics")
occ = gmsh.model.occ
domain = occ.addBox(-1.5, 0.0, -1.8, 7.0, 2.0, 3.6)
parts = []
for c, x0, y0, span, angle in [
    (0.35, 0.0, 0.18, 1.40, 8.0),
    (0.16, 0.33, 0.255, 1.40, 20.0),
    (0.42, 2.10, 0.72, 1.20, 12.0),
    (0.16, 2.48, 0.835, 1.20, 22.0)
]:
    a = angle * math.pi / 180.0
    upper = []
    lower = []
    for i in range(25):
        u = 0.5 * (1.0 - math.cos(math.pi * i / 24))
        t = 0.6 * c * (
            0.2969 * math.sqrt(u) - 0.1260 * u
            - 0.3516 * u**2 + 0.2843 * u**3 - 0.1036 * u**4
        )
        if i == 0 or i == 24:
            t = 0.0
        for points, sign in [(upper, 1), (lower, -1)]:
            points.append(occ.addPoint(
                x0 + c * u * math.cos(a) - sign * t * math.sin(a),
                y0 + c * u * math.sin(a) + sign * t * math.cos(a),
                -span / 2
            ))
    curves = [
        occ.addSpline(upper),
        occ.addSpline([upper[-1]] + lower[1:-1][::-1] + [upper[0]])
    ]
    face = occ.addPlaneSurface([occ.addWire(curves)])
    for dim, tag in occ.extrude([(2, face)], 0, 0, span):
        if dim == 3:
            parts.append((dim, tag))
for x, y, span, length, height in [
    (-0.02, 0.10, 1.40, 0.55, 0.29),
    (2.08, 0.60, 1.20, 0.60, 0.38)
]:
    for z in [-span / 2 - 0.012, span / 2]:
        parts.append((3, occ.addBox(x, y, z, length, height, 0.012)))
for x, y, z, ax, ay, az in [
    (1.00, 0.30, 0.0, 1.05, 0.18, 0.30),
    (0.65, 0.31, -0.50, 0.38, 0.23, 0.18),
    (0.65, 0.31, 0.50, 0.38, 0.23, 0.18)
]:
    tag = occ.addSphere(x, y, z, 1.0)
    occ.dilate([(3, tag)], x, y, z, ax, ay, az)
    parts.append((3, tag))
body, mapping = occ.fuse([parts[0]], parts[1:])
fluid, mapping = occ.cut([(3, domain)], body)
occ.removeAllDuplicates()
occ.synchronize()
volumes = [tag for dim, tag in gmsh.model.getEntities(3)]
if len(volumes) != 1:
    raise RuntimeError("Expected one fluid volume")
gmsh.model.addPhysicalGroup(3, volumes, name="fluid")
groups = {"inlet": [], "outlet": [], "walls": []}
aero = []
for dim, tag in gmsh.model.getBoundary([(3, volumes[0])], oriented=False):
    b = gmsh.model.getBoundingBox(dim, tag)
    if abs(b[0] + 1.5) < 1e-6 and abs(b[3] + 1.5) < 1e-6:
        groups["inlet"].append(tag)
    elif abs(b[0] - 5.5) < 1e-6 and abs(b[3] - 5.5) < 1e-6:
        groups["outlet"].append(tag)
    else:
        groups["walls"].append(tag)
        if b[0] > -0.1 and b[3] < 2.8 and b[1] > 0.07 and b[4] < 1.05:
            aero.append(tag)
for name, tags in groups.items():
    if not tags:
        raise RuntimeError("Missing boundary group")
    gmsh.model.addPhysicalGroup(2, tags, name=name)
distance = gmsh.model.mesh.field.add("Distance")
gmsh.model.mesh.field.setNumbers(distance, "FacesList", aero)
gmsh.model.mesh.field.setNumber(distance, "Sampling", 40)
near = gmsh.model.mesh.field.add("Threshold")
for key, value in [("InField", distance), ("DistMin", 0.015), ("DistMax", 0.20)]:
    gmsh.model.mesh.field.setNumber(near, key, value)
wake = gmsh.model.mesh.field.add("Box")
for key, value in [
    ("XMin", 0.0), ("XMax", 4.0), ("YMin", 0.06),
    ("YMax", 1.10), ("ZMin", -0.85), ("ZMax", 0.85), ("Thickness", 0.20)
]:
    gmsh.model.mesh.field.setNumber(wake, key, value)
minimum = gmsh.model.mesh.field.add("Min")
gmsh.model.mesh.field.setNumbers(minimum, "FieldsList", [near, wake])
gmsh.model.mesh.field.setAsBackgroundMesh(minimum)
for key, value in [
    ("Mesh.MeshSizeFromPoints", 0), ("Mesh.MeshSizeFromCurvature", 0),
    ("Mesh.MeshSizeExtendFromBoundary", 0), ("Mesh.ElementOrder", 1)
]:
    gmsh.option.setNumber(key, value)
gmsh.write(output_dir + "/concept-racer.step")
accepted = False
for attempt in range(4):
    factor = 1.3 ** attempt
    for field, key, size in [
        (near, "SizeMin", 0.025), (near, "SizeMax", 0.35),
        (wake, "VIn", 0.10), (wake, "VOut", 0.35)
    ]:
        gmsh.model.mesh.field.setNumber(field, key, size * factor)
    gmsh.model.mesh.generate(3)
    types, tags, nodes = gmsh.model.mesh.getElements(3)
    count = sum([len(v) for v in tags])
    print("MESH_BUDGET", attempt, count, factor)
    if 0 < count <= 180000:
        accepted = True
        break
    gmsh.model.mesh.clear()
if not accepted:
    raise RuntimeError("Mesh exceeds conversion budget")
"""

mesh = checked("RACER_MESH", fluentagi.geometry_mesh(directory=task_directory, code=code))
s.file.read_mesh(file_name=mesh["mesh_file"])
s.mesh.check()
if not s.setup.models.is_active():
    raise RuntimeError("新网格未激活。")
bc = s.setup.boundary_conditions
s.mesh.modify_zones.sep_face_zone_region(face_zone_name="walls", move_faces=True)
for zone in bc.wall.get_object_names():
    s.mesh.modify_zones.sep_face_zone_angle(
        face_zone_name=zone, angle=45.0, move_faces=True
    )
s.setup.general.solver.type = "pressure-based"
s.setup.general.solver.time = "steady"
s.setup.models.energy.enabled = False
s.setup.models.viscous.model = "k-omega"
s.setup.models.viscous.k_omega_model = "sst"
s.setup.general.operating_conditions.gravity.enable = False
air = s.setup.materials.fluid["air"]
for key, value in [("density", 1.225), ("viscosity", 1.7894e-5)]:
    getattr(air, key).set_state({"option": "constant", "value": value})
for zone in s.setup.cell_zone_conditions.fluid.get_object_names():
    s.setup.cell_zone_conditions.fluid[zone].general.material = "air"
scalar(bc.velocity_inlet["inlet"].momentum.velocity, 30.0)
scalar(bc.pressure_outlet["outlet"].momentum.gauge_pressure, 0.0)
turbulence = bc.velocity_inlet["inlet"].turbulence
turbulence.turbulence_specification = "Intensity and Viscosity Ratio"
scalar(turbulence.turbulent_intensity, 0.01)
scalar(turbulence.turbulent_viscosity_ratio, 10.0)
s.solution.initialization.hybrid_initialize()
reports = s.solution.report_definitions
probes = []
for axis in ["x", "y", "z"]:
    for suffix, kind in [("min", "surface-vertexmin"), ("max", "surface-vertexmax")]:
        name = "racer_" + axis + "_" + suffix
        reports.surface[name] = {}
        report = reports.surface[name]
        report.report_type = kind
        report.field = axis + "-coordinate"
        probes.append(name)
aero, ground, outer = [], [], []
for zone in bc.wall.get_object_names():
    for name in probes:
        reports.surface[name].surface_names = [zone]
    result = reports.compute(report_defs=probes)
    x0, x1, y0, y1, z0, z1 = [read_value(result, name) for name in probes]
    if max(abs(y0), abs(y1)) < 1e-5:
        ground.append(zone)
    elif x0 > -0.11 and x1 < 2.81 and y0 > 0.069 and y1 < 1.051:
        aero.append(zone)
    else:
        outer.append(zone)
print("CLASSIFICATION", {"aero": aero, "ground": ground, "outer": outer})
if not aero or len(ground) != 1 or len(outer) != 3:
    raise RuntimeError("边界分类异常，保留网格及初始化场。")
for zone in aero:
    bc.wall[zone].momentum.wall_motion = "Stationary Wall"
    bc.wall[zone].momentum.shear_condition = "No Slip"
momentum = bc.wall[ground[0]].momentum
momentum.wall_motion = "Moving Wall"
for key in ["relative", "rotating", "components"]:
    child = getattr(momentum, key)
    if child.is_active():
        child.set_state(False)
scalar(momentum.speed, 30.0)
direction = momentum.direction.get_state()
if isinstance(direction, (list, tuple)):
    momentum.direction.set_state([1.0, 0.0, 0.0])
else:
    keys = ["x", "y", "z"] if "x" in direction else ["i", "j", "k"]
    for key, value in zip(keys, [1.0, 0.0, 0.0]):
        scalar(getattr(momentum.direction, key), value)
for zone in outer:
    bc.wall[zone].momentum.shear_condition = "Specified Shear"
force_names = []
for name, direction in [("racer_drag", [1, 0, 0]), ("racer_lift", [0, 1, 0])]:
    reports.force[name] = {}
    reports.force[name].zones = aero
    reports.force[name].force_vector = direction
    force_names.append(name)
s.file.start_transcript(file_name=task_directory + r"\racer_calculation.trn")
try:
    for block in range(8):
        s.solution.run_calculation.iterate(iter_count=50)
        print("FORCE_HISTORY", block, reports.compute(report_defs=force_names))
    s.file.write_case_data(file_name=task_directory + r"\racer_steady.cas.h5")
    section = checked("CENTRAL_SECTION", fluentagi.create_section(axis="z", position=0.0))
    s.setup.general.solver.time = "unsteady-2nd-order"
    s.solution.run_calculation.transient_controls.time_step_size = 0.001
    checked("VELOCITY_ANIMATION", fluentagi.record_animation(
        directory=task_directory, field="velocity-magnitude", frames=41,
        steps_per_frame=5, max_iter_per_step=40, surfaces=[section["surface"]]
    ))
    print("ACTUAL_TIME_SCHEME", s.setup.general.solver.time.get_state())
    print("FINAL_FORCES", reports.compute(report_defs=force_names))
    for field in ["pressure", "velocity-magnitude"]:
        checked("SECTION_EXPORT", fluentagi.export_results(
            directory=task_directory, fields=[field], surfaces=[section["surface"]]
        ))
    checked("BODY_PRESSURE_EXPORT", fluentagi.export_results(
        directory=task_directory, fields=["pressure"], surfaces=aero
    ))
    paths = s.results.graphics.pathline
    paths["racer_streamlines"] = {}
    path = paths["racer_streamlines"]
    path.field = "velocity-magnitude"
    path.release_from_surfaces = ["inlet"]
    s.results.graphics.views.restore_view(view_name="isometric")
    paths.display(object_name="racer_streamlines")
    s.results.graphics.views.auto_scale()
    s.results.graphics.picture.save_picture(
        file_name=task_directory + r"\racer_streamlines.png"
    )
    reports.surface["racer_section_pressure"] = {}
    pressure_report = reports.surface["racer_section_pressure"]
    pressure_report.report_type = "surface-areaavg"
    pressure_report.field = "pressure"
    for index in range(11):
        position = -0.25 + 0.325 * index
        cut = checked("PRESSURE_SECTION", fluentagi.create_section(axis="x", position=position))
        pressure_report.surface_names = [cut["surface"]]
        result = reports.compute(report_defs=["racer_section_pressure"])
        print("PRESSURE_CURVE_DATA", position, read_value(result, "racer_section_pressure"))
finally:
    s.file.stop_transcript()
    s.file.write_case_data(file_name=task_directory + r"\racer_final_checkpoint.cas.h5")
print("VALIDATION_PENDING", ["residuals", "force_stability", "mesh_independence", "wall_resolution"])