# Use the existing connected PyFluent variable solver.
task_directory = r"C:\Users\13977\AppData\Local\FluentAGI\data\runs\20261018-152925-f99517"
s = solver.settings
active = False
if s.setup.is_active():
    active = s.setup.models.is_active()
print({"existing_model_active": active})
if active:
    s.file.write_case_data(file_name=task_directory+"/before-repair-47d200.cas.h5")

gmsh_code = "\n".join([
    'gmsh.model.add("F1-budgeted")',
    'o=gmsh.model.occ',
    'parts=[]',
    'def ell(x,y,z,a,b,c):',
    '    t=o.addSphere(x,y,z,1)',
    '    o.dilate([(3,t)],x,y,z,a,b,c)',
    '    return t',
    'wires=[]',
    'stations=[(.28,.055,.20,.27),(.65,.12,.18,.36),(1.35,.20,.17,.48),(2.10,.34,.17,.68),(2.85,.36,.17,.72),(3.55,.28,.19,.64),(4.45,.13,.23,.46),(4.72,.07,.25,.36)]',
    'for x,w,z0,z1 in stations:',
    '    h=z1-z0',
    '    yz=[(-.7*w,z0),(.7*w,z0),(w,z0+.22*h),(w,z0+.78*h),(.7*w,z1),(-.7*w,z1),(-w,z0+.78*h),(-w,z0+.22*h)]',
    '    p=[o.addPoint(x,y,z) for y,z in yz]',
    '    wires.append(o.addWire([o.addLine(p[i],p[(i+1)%8]) for i in range(8)]))',
    'body=o.addThruSections(wires,makeSolid=True,makeRuled=False)',
    'pit=ell(2.52,0,.76,.53,.255,.40)',
    'body,_=o.cut(body,[(3,pit)],removeObject=True,removeTool=True)',
    'parts.extend(body)',
    'parts.append((3,o.addBox(1.20,-.72,.12,3.16,1.44,.07)))',
    'for side in [-1,1]:',
    '    parts.append((3,ell(3.10,side*.45,.39,.94,.27,.23)))',
    'parts.append((3,ell(3.35,0,.70,.78,.245,.43)))',
    'parts.append((3,o.addBox(3.30,-.025,.82,.95,.05,.25)))',
    'for x in [1.18,4.02]:',
    '    for side in [-1,1]:',
    '        y=-.99 if side<0 else .67',
    '        tyre=o.addCylinder(x,y,.37,0,.32,0,.35)',
    '        ry=-.995 if side<0 else .93',
    '        recess=o.addCylinder(x,ry,.37,0,.065,0,.22)',
    '        wheel,_=o.cut([(3,tyre)],[(3,recess)],removeObject=True,removeTool=True)',
    '        parts.extend(wheel)',
    '        for dx in [-.20,.20]:',
    '            for z in [.26,.48]:',
    '                parts.append((3,o.addCylinder(x+dx,side*.22,z,-dx,side*.55,.37-z,.025)))',
    'for x,z,c,b,angle in [(.02,.14,.52,1.98,-5),(.24,.26,.32,1.90,-8),(4.32,1.06,.66,1.52,-10),(4.48,.91,.39,1.48,-12)]:',
    '    t=o.addBox(x,-b/2,z,c,b,.055)',
    '    o.rotate([(3,t)],x,0,z,0,1,0,angle*math.pi/180)',
    '    parts.append((3,t))',
    'for side in [-1,1]:',
    '    for x,y,z,a,b,c in [(0,side*.975-.015,.10,.64,.03,.34),(4.25,side*.76-.02,.64,.75,.04,.56),(4.42,side*.30-.025,.36,.08,.05,.62),(.29,side*.31-.025,.18,.08,.05,.20)]:',
    '        parts.append((3,o.addBox(x,y,z,a,b,c)))',
    '    parts.append((3,ell(2.20,side*.48,.70,.14,.09,.055)))',
    'car,_=o.fuse(parts[:1],parts[1:],removeObject=True,removeTool=True)',
    'o.synchronize()',
    'gmsh.write(output_dir+"/racecar-design.step")',
    'box=o.addBox(-8,-5,0,28,10,5)',
    'fluid,_=o.cut([(3,box)],car,removeObject=True,removeTool=True)',
    'o.synchronize()',
    'vol=[t for d,t in fluid if d==3]',
    'if len(vol)!=1: raise RuntimeError("Exterior fluid is not connected")',
    'inlet,outlet,walls,near=[],[],[],[]',
    'for d,t in gmsh.model.getBoundary([(3,t) for t in vol],combined=True,oriented=False):',
    '    if d!=2: continue',
    '    b=o.getBoundingBox(d,t)',
    '    if abs(b[0]+8)<1e-5 and abs(b[3]+8)<1e-5: inlet.append(t)',
    '    elif abs(b[0]-20)<1e-5 and abs(b[3]-20)<1e-5: outlet.append(t)',
    '    else:',
    '        walls.append(t)',
    '        if b[0]>-.1 and b[3]<5.1 and b[1]>-1.2 and b[4]<1.2 and b[5]<1.4: near.append(t)',
    'if not inlet or not outlet or not near: raise RuntimeError("Boundary classification failed")',
    'for name,tags in [("inlet",inlet),("outlet",outlet),("walls",walls)]:',
    '    gmsh.model.setPhysicalName(2,gmsh.model.addPhysicalGroup(2,tags),name)',
    'gmsh.model.setPhysicalName(3,gmsh.model.addPhysicalGroup(3,vol),"fluid")',
    'gmsh.write(output_dir+"/racecar-wind-tunnel.step")',
    'f=gmsh.model.mesh.field',
    'f.add("Distance",1)',
    'f.setNumbers(1,"SurfacesList",near)',
    'f.setNumber(1,"Sampling",40)',
    'f.add("Threshold",2)',
    'for k,v in [("InField",1),("SizeMin",.06),("SizeMax",1.1),("DistMin",.035),("DistMax",.9)]: f.setNumber(2,k,v)',
    'for tag,b,size in [(3,(-.3,5.5,-1.3,1.3,0,1.5),.24),(4,(4.5,11,-1.5,1.5,0,1.8),.4)]:',
    '    f.add("Box",tag)',
    '    for k,v in zip(["XMin","XMax","YMin","YMax","ZMin","ZMax"],b): f.setNumber(tag,k,v)',
    '    for k,v in [("VIn",size),("VOut",1.1),("Thickness",.35)]: f.setNumber(tag,k,v)',
    'f.add("Min",5)',
    'f.setNumbers(5,"FieldsList",[2,3,4])',
    'f.setAsBackgroundMesh(5)',
    'for k,v in [("Mesh.MeshSizeMin",.025),("Mesh.MeshSizeMax",1.1),("Mesh.MeshSizeFromPoints",0),("Mesh.MeshSizeFromCurvature",8),("Mesh.MeshSizeExtendFromBoundary",0),("Mesh.ElementOrder",1),("Mesh.Algorithm3D",1),("Mesh.Optimize",1)]: gmsh.option.setNumber(k,v)',
    'accepted=False',
    'for attempt in range(6):',
    '    if attempt: gmsh.model.mesh.clear()',
    '    gmsh.option.setNumber("Mesh.MeshSizeFactor",1.3**attempt)',
    '    gmsh.model.mesh.generate(3)',
    '    types,tags,nodes=gmsh.model.mesh.getElements(3)',
    '    count=sum([len(t) for t in tags])',
    '    print({"attempt":attempt+1,"volume_cells":count})',
    '    if count<=175000:',
    '        accepted=True',
    '        break',
    'if not accepted: raise RuntimeError("Mesh budget not met")',
    'gmsh.model.mesh.optimize("Netgen")',
    'types,tags,nodes=gmsh.model.mesh.getElements(3)',
    'count=sum([len(t) for t in tags])',
    'q=[]',
    'for t in tags: q.extend(gmsh.model.mesh.getElementQualities(t,"minSICN"))',
    'if count>180000 or not q or min(q)<=0: raise RuntimeError("Mesh acceptance failed")',
    'print({"volume_cells":count,"minimum_minSICN":float(min(q)),"inflation_layers":False})',
    'gmsh.write(output_dir+"/racecar-gmsh.msh")'
])
mesh = fluentagi.geometry_mesh(directory=task_directory,code=gmsh_code)
print(mesh)
s.file.read_mesh(file_name=mesh["mesh_file"])
s.mesh.check()
if not s.setup.is_active():
    raise RuntimeError("Setup inactive after mesh import")
if not s.setup.models.is_active():
    raise RuntimeError("Models inactive after mesh import")

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"
air = s.setup.materials.fluid["air"]
air.density = {"option":"constant","value":1.225}
air.viscosity = {"option":"constant","value":1.7894e-5}
for name in s.setup.cell_zone_conditions.fluid.get_object_names():
    s.setup.cell_zone_conditions.fluid[name].material = "air"
bc = s.setup.boundary_conditions
inlet = bc.velocity_inlet["inlet"]
if "momentum" in inlet.get_state():
    inlet.momentum.velocity_specification_method = "Magnitude, Normal to Boundary"
    inlet.momentum.velocity.value = 30.0
else:
    inlet.vmag.constant = 30.0
print({"inlet":inlet.get_state(),"outlet":bc.pressure_outlet["outlet"].get_state()})
s.file.write_case(file_name=task_directory+"/racecar-setup.cas.h5")

g = s.results.graphics
if g.picture.use_window_resolution.is_active():
    g.picture.use_window_resolution = False
g.picture.x_resolution = 1600
g.picture.y_resolution = 1000
for name,field,value in [("racecar-midplane","y-coordinate",0),
                          ("racecar-wheel-plane","z-coordinate",.37)]:
    s.results.surfaces.iso_surface[name] = {"field":field,"iso_values":[value]}
g.mesh["racecar-section-mesh"] = {"surfaces_list":["racecar-midplane"]}
g.mesh["racecar-section-mesh"].display()
g.views.restore_view(view_name="front")
g.views.auto_scale()
g.picture.save_picture(file_name=task_directory+"/racecar-section-mesh.png")

def report_residuals(stage):
    try:
        found = False
        for name in solver.monitors.get_monitor_set_names():
            if "residual" in name.lower():
                found = True
                iterations,values = solver.monitors.get_monitor_set_data(name)
                tail = {k:float(v[-1]) for k,v in values.items() if len(v)}
                print({"stage":stage,"residuals":tail})
        if not found:
            print({"stage":stage,"review":"Inspect Fluent residual transcript"})
    except Exception as exc:
        print({"stage":stage,"monitor_error":str(exc)})

s.solution.initialization.hybrid_initialize()
for batch in range(4):
    s.solution.run_calculation.iterate(iter_count=100)
    report_residuals("steady-"+str(batch+1))
s.file.write_case_data(file_name=task_directory+"/racecar-steady.cas.h5")
s.setup.general.solver.time = "unsteady-2nd-order"
s.solution.run_calculation.transient_controls.time_step_size = .002
s.solution.run_calculation.dual_time_iterate(time_step_count=50,max_iter_per_step=30)
report_residuals("transient-warmup")
for field in ["velocity-magnitude","pressure"]:
    result = fluentagi.record_animation(
        directory=task_directory,field=field,frames=16,
        steps_per_frame=5,max_iter_per_step=30,
        surfaces=["racecar-wheel-plane"])
    print({"field":field,"animation_result":result})
    report_residuals(field)
s.file.write_case_data(file_name=task_directory+"/racecar-final.cas.h5")
g.pathline["racecar-flow-lines"] = {
    "field":"velocity-magnitude","release_from_surfaces":["inlet"],"skip":2}
g.pathline["racecar-flow-lines"].display()
g.views.restore_view(view_name="isometric")
g.views.auto_scale()
g.picture.save_picture(file_name=task_directory+"/racecar-flow-lines.png")
result = fluentagi.export_results(
    directory=task_directory,fields=["pressure","velocity-magnitude"],
    surfaces=["racecar-midplane","racecar-wheel-plane"])
print({"export_result":result})
print({"review":"Check actual mesh quality, residuals and export errors",
       "animations":"Velocity and pressure cover successive time intervals",
       "assumptions":"Fixed wheels and tunnel walls; no inflation layers"})