run_heat_transfer.py•9.43 kB
from fipy import Grid3D, CellVariable, TransientTerm, DiffusionTerm, Viewer
import numpy as np
from stl import mesh as stl_mesh
import tqdm
import pyvista as pv
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from scipy.interpolate import griddata
def visualize_3d_temperature(mesh, temperature, title="3D Temperature Distribution"):
"""
Create comprehensive 3D visualization of temperature field using PyVista
"""
# Extract mesh coordinates and temperature values
x, y, z = mesh.cellCenters
temp_values = temperature.value
# Create PyVista structured grid
nx, ny, nz = mesh.nx, mesh.ny, mesh.nz
# Create coordinate arrays for structured grid
x_coords = np.linspace(x.min(), x.max(), nx + 1)
y_coords = np.linspace(y.min(), y.max(), ny + 1)
z_coords = np.linspace(z.min(), z.max(), nz + 1)
# Create structured grid
grid = pv.StructuredGrid(*np.meshgrid(x_coords, y_coords, z_coords, indexing="ij"))
# Add temperature data (need to reshape for cell data)
temp_reshaped = temp_values.reshape((nx, ny, nz), order="F")
grid.cell_data["Temperature"] = temp_reshaped.flatten(order="F")
# Create plotter
plotter = pv.Plotter(window_size=(1200, 800))
# Add volume rendering
opacity = [0, 0.2, 0.5, 0.8, 1.0]
plotter.add_volume(
grid,
scalars="Temperature",
opacity=opacity,
cmap="coolwarm",
show_scalar_bar=True,
)
# Add outline
plotter.add_mesh(grid.outline(), color="black", line_width=2)
# Add cross-sectional slices
slices = grid.slice_orthogonal()
plotter.add_mesh(
slices,
scalars="Temperature",
cmap="coolwarm",
opacity=0.7,
show_scalar_bar=False,
)
# Set up the scene
plotter.set_background("white")
plotter.add_text(title, position="upper_left", font_size=14)
plotter.add_text(
f"Min: {temp_values.min():.1f}K, Max: {temp_values.max():.1f}K",
position="lower_left",
font_size=12,
)
# Add coordinate axes
plotter.add_axes()
# Show the plot
plotter.show()
def create_temperature_slices(mesh, temperature, output_file=None):
"""
Create 2D slice visualizations showing temperature distribution
"""
x, y, z = mesh.cellCenters
temp_values = temperature.value
nx, ny, nz = mesh.nx, mesh.ny, mesh.nz
print(f"Debug: mesh dimensions nx={nx}, ny={ny}, nz={nz}")
print(f"Debug: temp_values shape: {temp_values.shape}")
print(f"Debug: temp range: {temp_values.min():.2f} - {temp_values.max():.2f} K")
# FiPy stores data in C order, need to reshape correctly
# Create coordinate grids for proper indexing
x_coords = np.linspace(x.min(), x.max(), nx)
y_coords = np.linspace(y.min(), y.max(), ny)
z_coords = np.linspace(z.min(), z.max(), nz)
# Create meshgrid for interpolation
X, Y, Z = np.meshgrid(x_coords, y_coords, z_coords, indexing="ij")
# Interpolate temperature values on regular grid
points = np.column_stack([x.flatten(), y.flatten(), z.flatten()])
# Reshape to 3D grid
temp_3d = griddata(
points, temp_values, (X, Y, Z), method="linear", fill_value=temp_values.mean()
)
# Create figure with subplots
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
fig.suptitle("Temperature Distribution - Cross Sections", fontsize=16)
# X-Y slice (middle Z)
z_mid = nz // 2
slice_xy = temp_3d[:, :, z_mid]
im1 = axes[0, 0].imshow(
slice_xy.T,
extent=[x.min(), x.max(), y.min(), y.max()],
origin="lower",
cmap="coolwarm",
aspect="auto",
vmin=temp_values.min(),
vmax=temp_values.max(),
)
axes[0, 0].set_title(f"X-Y Slice (Z = {z_coords[z_mid]:.2f})")
axes[0, 0].set_xlabel("X (m)")
axes[0, 0].set_ylabel("Y (m)")
plt.colorbar(im1, ax=axes[0, 0], label="Temperature (K)")
# X-Z slice (middle Y)
y_mid = ny // 2
slice_xz = temp_3d[:, y_mid, :]
im2 = axes[0, 1].imshow(
slice_xz.T,
extent=[x.min(), x.max(), z.min(), z.max()],
origin="lower",
cmap="coolwarm",
aspect="auto",
vmin=temp_values.min(),
vmax=temp_values.max(),
)
axes[0, 1].set_title(f"X-Z Slice (Y = {y_coords[y_mid]:.2f})")
axes[0, 1].set_xlabel("X (m)")
axes[0, 1].set_ylabel("Z (m)")
plt.colorbar(im2, ax=axes[0, 1], label="Temperature (K)")
# Y-Z slice (middle X)
x_mid = nx // 2
slice_yz = temp_3d[x_mid, :, :]
im3 = axes[1, 0].imshow(
slice_yz.T,
extent=[y.min(), y.max(), z.min(), z.max()],
origin="lower",
cmap="coolwarm",
aspect="auto",
vmin=temp_values.min(),
vmax=temp_values.max(),
)
axes[1, 0].set_title(f"Y-Z Slice (X = {x_coords[x_mid]:.2f})")
axes[1, 0].set_xlabel("Y (m)")
axes[1, 0].set_ylabel("Z (m)")
plt.colorbar(im3, ax=axes[1, 0], label="Temperature (K)")
# Temperature histogram
axes[1, 1].hist(
temp_values, bins=50, alpha=0.7, color="steelblue", edgecolor="black"
)
axes[1, 1].set_title("Temperature Distribution")
axes[1, 1].set_xlabel("Temperature (K)")
axes[1, 1].set_ylabel("Frequency")
axes[1, 1].grid(True, alpha=0.3)
axes[1, 1].axvline(
temp_values.mean(),
color="red",
linestyle="--",
label=f"Mean: {temp_values.mean():.1f}K",
)
axes[1, 1].legend()
plt.tight_layout()
if output_file:
plt.savefig(output_file, dpi=300, bbox_inches="tight")
print(f"Temperature slices saved to {output_file}")
plt.show()
def run_single_heat_transfer():
# --- Step 1: Load STL and create approximate mesh ---
# Load the STL file
stl_data = stl_mesh.Mesh.from_file("part.stl")
# Get bounds from STL
vertices = stl_data.vectors.reshape(-1, 3)
x_min, x_max = vertices[:, 0].min(), vertices[:, 0].max()
y_min, y_max = vertices[:, 1].min(), vertices[:, 1].max()
z_min, z_max = vertices[:, 2].min(), vertices[:, 2].max()
print(
f"STL bounds: x=[{x_min:.2f}, {x_max:.2f}], y=[{y_min:.2f}, {y_max:.2f}], z=[{z_min:.2f}, {z_max:.2f}]"
)
# Create a uniform grid that covers the STL bounds
nx, ny, nz = 20, 20, 20 # mesh resolution
dx = (x_max - x_min) / nx
dy = (y_max - y_min) / ny
dz = (z_max - z_min) / nz
mesh = Grid3D(nx=nx, ny=ny, nz=nz, dx=dx, dy=dy, dz=dz)
mesh = mesh + [[x_min], [y_min], [z_min]]
# --- Step 2: Define material properties (example: aluminum) ---
rho = 2700.0 # kg/m³
cp = 900.0 # J/kgK
k = 205.0 # W/mK
alpha = k / (rho * cp) # thermal diffusivity
# --- Step 3: Define heat transfer coefficient and ambient temp ---
h = 30.0 # W/m²K (for ~3 m/s air around small cube)
T_inf = 300.0 # K
# --- Step 4: Initialize temperature field ---
# Create non-uniform initial temperature (hot spot in center)
x, y, z = mesh.cellCenters
center_x, center_y, center_z = 0.0, 0.0, 0.0
initial_temp = 300.0 + 50.0 * np.exp(
-((x - center_x) ** 2 + (y - center_y) ** 2 + (z - center_z) ** 2) / 25.0
)
temperature = CellVariable(name="T", mesh=mesh, value=initial_temp)
# --- Step 5: Define PDE (diffusion inside, convection at boundaries) ---
eq = TransientTerm(coeff=rho * cp) == DiffusionTerm(coeff=k)
# Boundary convection (Robin condition):
# FiPy doesn't directly handle Robin, so we fake it by adding a surface source term.
# Cells near boundary get extra sink term ~ h*(T - T_inf).
# Simple boundary mask: all boundary cells (close to min/max x,y,z)
tol = 1e-6
boundary_mask = (
(np.abs(x - np.min(x)) < tol)
| (np.abs(x - np.max(x)) < tol)
| (np.abs(y - np.min(y)) < tol)
| (np.abs(y - np.max(y)) < tol)
| (np.abs(z - np.min(z)) < tol)
| (np.abs(z - np.max(z)) < tol)
)
# --- Step 6: Time stepping (3 minutes = 180 s) ---
dt = 0.5 # s
steps = int(180 / dt) # 3 minutes = 180 seconds
# Create equation with implicit convection boundary condition
eq = TransientTerm(coeff=rho * cp) == DiffusionTerm(coeff=k) - h * (
temperature - T_inf
) * boundary_mask.astype(float)
for step in tqdm.trange(steps):
# Update boundary convection term each iteration
eq = TransientTerm(coeff=rho * cp) == DiffusionTerm(coeff=k) - h * (
temperature - T_inf
) * boundary_mask.astype(float)
eq.solve(var=temperature, dt=dt)
# --- Step 7: Extract results ---
core_temp = temperature((0, 0, 0)) # approximate core at cube center
surface_temp = np.mean(temperature.value[boundary_mask])
print(f"Final surface temperature ~ {surface_temp:.2f} K")
print(f"Final core temperature ~ {core_temp:.2f} K")
# 3D Visualization
print("\nGenerating 3D temperature visualization...")
visualize_3d_temperature(mesh, temperature, "Heat Transfer - 3D Temperature Field")
# 2D Cross-section visualization
print("Creating temperature cross-sections...")
create_temperature_slices(mesh, temperature, "temperature_analysis.png")
return f"Final surface temperature ~ {surface_temp:.2f} K\nFinal core temperature ~ {core_temp:.2f} K"
if __name__ == "__main__":
result = run_single_heat_transfer()
print(result)