modified: anasen_fem/clean.sh new file: anasen_fem/dotproduct.py new file: anasen_fem/paraview_dotproduct.py modified: anasen_fem/paraview_plotter.py modified: anasen_fem/run.py modified: anasen_fem/scalars.dat.names new file: anasen_fem/scalars_weight.dat new file: anasen_fem/scalars_weight.dat.names modified: anasen_fem/wires2d.sif new file: anasen_fem/wires2d_weight.sif modified: anasen_fem/wires_gmsh2d_bc.py
269 lines
11 KiB
Python
Executable File
269 lines
11 KiB
Python
Executable File
#!/home/vsitaraman/ParaView-6.1.0-RC1-MPI-Linux-Python3.12-x86_64/bin/pvbatch
|
|
# #######!/home/vsitaraman/ParaView-6.1.0-MPI-Linux-Python3.12-x86_64/bin/pvbatch
|
|
import numpy as np
|
|
import sys
|
|
from paraview.simple import *
|
|
|
|
# Optional argument so an archived VTU can be re-plotted without re-solving:
|
|
# ./paraview_plotter.py wires2d/vtu_files/elfield_anasen_10_0.0000.vtu
|
|
INPUT_VTU = sys.argv[1] if len(sys.argv) > 1 else "wires2d/elfield_anasen_t0001.vtu"
|
|
|
|
# --- contour settings ---
|
|
# The whole-PC view keeps the original sparse, evenly spaced levels; at that
|
|
# scale more lines just fill in solid. The quadrant gets the denser, biased
|
|
# set, since that is where the structure between wires is actually readable.
|
|
POT_MIN, POT_MAX = 0.0, 660.0
|
|
N_CONTOURS_FULL, CONTOUR_BIAS_FULL = 40, 1.0
|
|
N_CONTOURS_QUARTER, CONTOUR_BIAS_QUARTER = 80, 1.0
|
|
|
|
# Line widths. Thin lines wash out their own colour, so the quadrant views draw
|
|
# heavier than the full-PC one; tubes keep them smooth at 2000 px.
|
|
LINE_WIDTH_FULL = 3.0
|
|
LINE_WIDTH_QUARTER = 2.5
|
|
STREAM_LINE_WIDTH = 2.5
|
|
|
|
# Arrows are glyphed onto the contour points, so doubling the quadrant contour
|
|
# density doubled the arrow count too. This offsets that.
|
|
GLYPH_STRIDE = 96
|
|
|
|
# --- quadrant view ---
|
|
# Centre sits at radius 35.4 mm, near the outer edge of the wire band, which
|
|
# pushes the wires to one side and leaves the far corner empty. Pulled in to
|
|
# 31.8 mm so the band sits more centrally.
|
|
ZOOM_CENTER = [-0.0225, 0.0225, 0.0]
|
|
ZOOM_SCALE = 0.022 # half-height of the view in metres; larger = more in frame
|
|
|
|
|
|
def contour_levels(n, bias):
|
|
"""Isosurface values.
|
|
|
|
phi ~ ln(r) around a wire, so evenly spaced levels crowd onto the wire
|
|
surfaces and thin out in the gas. Raising a symmetric parameter to `bias`
|
|
pulls levels towards mid-range, where the saddles between wires are,
|
|
without adding more of them. 1.0 is plain linear spacing; much above 1.5
|
|
and the middle levels start landing on top of each other.
|
|
"""
|
|
u = np.linspace(-1.0, 1.0, n)
|
|
u = np.sign(u) * np.abs(u) ** bias
|
|
mid = 0.5 * (POT_MIN + POT_MAX)
|
|
return list(mid + 0.5 * (POT_MAX - POT_MIN) * u)
|
|
|
|
|
|
reader = XMLUnstructuredGridReader(FileName=[INPUT_VTU])
|
|
|
|
contour_filter = Contour(Input=reader,ContourBy = 'potential')
|
|
contour_filter.Isosurfaces = contour_levels(N_CONTOURS_FULL, CONTOUR_BIAS_FULL)
|
|
|
|
renderView = GetActiveViewOrCreate('RenderView')
|
|
renderView.ViewSize = [2000,2000]
|
|
renderView.OrientationAxesVisibility = 0 # Hide axis
|
|
renderView.UseColorPaletteForBackground=0
|
|
renderView.Background = [0.1, 0.1, 0.1] # Set background to dark gray (RGB 0-1)
|
|
|
|
renderView.MultiSamples = 8 # 0 disables it, 4-8 is usually sufficient
|
|
|
|
ResetCamera()
|
|
|
|
contour_display = Show(contour_filter, renderView)
|
|
contour_display.LineWidth = LINE_WIDTH_FULL
|
|
contour_display.RenderLinesAsTubes = 1 # Makes lines look smoother at high res
|
|
#colorbar
|
|
contour_display_potentialLUT = GetColorTransferFunction('potential', contour_display, separate=True)
|
|
contour_display_potentialLUT.ApplyPreset('Cool to Warm', True)
|
|
contour_display.SetScalarBarVisibility(renderView, True)
|
|
|
|
#axesGrid = renderView.AxesGridrfcxgdtv
|
|
#axesGrid.Visibility = 1
|
|
#axesGrid.XTitle = "x (mm)"
|
|
#axesGrid.YTitle = "y (mm)"
|
|
|
|
# 1. Get the active view
|
|
view = GetActiveView()
|
|
|
|
# 2. Define your desired coordinate ranges (x_min, x_max, y_min, y_max, z_min, z_max)
|
|
x_min, x_max = -0.05, 0.05
|
|
y_min, y_max = -0.05, 0.05
|
|
z_min, z_max = -0.05, 0.05
|
|
|
|
# 3. Calculate Center, Position, and Parallel Scale
|
|
center = [(x_min + x_max) / 2.0, (y_min + y_max) / 2.0, (z_min + z_max) / 2.0]
|
|
# Position the camera far away along Z to look at the center
|
|
position = [center[0], center[1], 1.0]
|
|
# Parallel scale defines how much of the scene is visible.
|
|
# It is usually half the height of the viewed area.
|
|
view.CameraParallelScale = max((x_max - x_min), (y_max - y_min))/1.6
|
|
|
|
# 4. Apply settings
|
|
view.CenterOfRotation = center
|
|
view.CameraPosition = position
|
|
view.CameraFocalPoint = center
|
|
view.CameraViewUp = [0.0, 1.0, 0.0] # Y-axis is up
|
|
|
|
# 5. Enable Parallel Projection (optional, often better for exact mapping)
|
|
view.CameraParallelProjection = 1
|
|
|
|
#ResetCamera()
|
|
Render()
|
|
|
|
SaveScreenshot("contour_output.png")
|
|
|
|
# Everything from here on is a quadrant view: denser levels, heavier lines.
|
|
contour_filter.Isosurfaces = contour_levels(N_CONTOURS_QUARTER, CONTOUR_BIAS_QUARTER)
|
|
contour_display.LineWidth = LINE_WIDTH_QUARTER
|
|
contour_display.RenderLinesAsTubes = 1
|
|
|
|
# 1. Get the active view
|
|
view = GetActiveView()
|
|
|
|
# 1. Set the Focal Point to the middle of the quadrant in metres
|
|
zoom_center = ZOOM_CENTER
|
|
|
|
# 2. Tighten the Parallel Scale
|
|
view.CameraParallelScale = ZOOM_SCALE
|
|
|
|
# 3. Position the Camera (0.5m away is fine)
|
|
view.CameraPosition = [zoom_center[0], zoom_center[1], 0.5]
|
|
view.CameraFocalPoint = zoom_center
|
|
view.CameraViewUp = [0.0, 1.0, 0.0]
|
|
|
|
# Equipotentials over one quadrant, saved before the arrows go on so this and
|
|
# Field_output.png below share a camera and overlay exactly.
|
|
Render()
|
|
SaveScreenshot("contour_quarter_output.png")
|
|
|
|
# pot_threshold = Threshold(Input=reader)
|
|
# pot_threshold.Scalars = ['POINTS', 'potential']
|
|
# pot_threshold.ThresholdMethod = 'Above Upper Threshold'
|
|
# pot_threshold.UpperThreshold = 100.0
|
|
|
|
# --- 2. Create the Glyph Filter (The Arrows) ---
|
|
# IMPORTANT: Use 'pot_threshold' as the Input, not the 'reader'
|
|
glyph = Glyph(Input=contour_filter, GlyphType='Arrow') #
|
|
# glyph = Glyph(Input=reader, GlyphType='Arrow') #this uses all field line snot just the ones from the equipotential lines shown
|
|
|
|
# Orientation Array: Use the 'electric field' vector from Elmer
|
|
glyph.OrientationArray = ['POINTS', 'electric field']
|
|
glyph.ScaleArray = ['POINTS', 'No scale array']
|
|
glyph.ScaleFactor = 0.001
|
|
|
|
glyph.GlyphMode = 'Every Nth Point'
|
|
glyph.Stride = GLYPH_STRIDE
|
|
|
|
# --- 3. Display the Glyphs ---
|
|
glyph_display = Show(glyph, renderView)
|
|
|
|
# Set the representation to Surface so we see the full arrow colors
|
|
glyph_display.Representation = 'Surface'
|
|
|
|
|
|
# This is the critical line: Color the arrows by the 'potential' scalar
|
|
ColorBy(glyph_display, ('POINTS', 'potential'))
|
|
glyph_display.LookupTable = contour_display_potentialLUT
|
|
contour_display_potentialLUT.RescaleTransferFunction(POT_MIN, POT_MAX)
|
|
|
|
# Optional: Disable the scalar bar for the arrows to avoid cluttering
|
|
# the existing 'potential' scalar bar.
|
|
glyph_display.SetScalarBarVisibility(renderView, False)
|
|
|
|
# --- 4. Final Render ---
|
|
Render()
|
|
SaveScreenshot("Field_output.png")
|
|
|
|
|
|
# --- Streamlines over the same quadrant ---
|
|
# Field lines rather than arrows: with no B field the drift velocity is
|
|
# parallel to E, so these are drift paths up to diffusion, i.e. a map of which
|
|
# wire collects charge from where. The glyphs above are fixed length, so they
|
|
# show direction only. Quadrant only; over the whole PC it is an unreadable mat.
|
|
# Equipotentials the streamlines start from. One level near the anodes seeds
|
|
# lines only in a ring around each anode, so with the lines truncated short the
|
|
# rest of the frame comes out empty. Spanning the range instead puts seeds
|
|
# right across the gas: every equipotential is a closed curve winding between
|
|
# the wires, so together they cover the volume.
|
|
SEED_POTENTIALS = [660.0, 600.0, 550.0, 500.0, 450.0, 400.0,350.0, 300.0,250.0, 250.0, 200.0, 150.0, 100.0, 50.0]
|
|
|
|
# Keep 1 seed point in N along the seed contours. Measured point counts per
|
|
# level, for working out the total:
|
|
#
|
|
# 600 V 4970 300 V 29971
|
|
# 500 V 8861 200 V 51558
|
|
# 400 V 16331 100 V 51645
|
|
# 50 V 27259
|
|
#
|
|
# 250 V and 150 V are not measured; from the trend they are of order 40000 and
|
|
# 50000, putting the nine levels near 280000 points. Stride 3 is then ~94000
|
|
# seeds, of which roughly an eighth land in the rendered quadrant. That is
|
|
# three times the previous seed count, but each line is now 4 mm instead of
|
|
# 20 mm, so the integration work is comparable.
|
|
SEED_STRIDE = 3
|
|
# Arc length each line is allowed to run, in metres. Field lines terminate on
|
|
# electrodes, so any line long enough to reach one will converge with every
|
|
# other line reaching the same wire -- that convergence is the field, not a
|
|
# plotting artefact, and the only way to avoid drawing it is to stop the lines
|
|
# short. The anode-cathode gap runs 4.3-6.2 mm depending on z, so 3 mm keeps
|
|
# every line clear of the cathodes. Raise it towards 0.005 for longer lines,
|
|
# and they will start meeting again at the wires.
|
|
# Short lines, and the gaps between them closed by seeding more planes rather
|
|
# than by making each line longer. 4 mm is just under the 4.3 mm closest
|
|
# anode-cathode approach, so no line quite reaches a wire and they stay
|
|
# separate instead of converging.
|
|
STREAM_MAX_LENGTH = 0.0005
|
|
|
|
# StreamTracer counts steps as well as length, and its step sizes are in
|
|
# cell-length units rather than metres: 0.2 of a cell by default, and cells are
|
|
# 0.05 mm at the wires. 2000 steps therefore buys about 20 mm at best and less
|
|
# wherever the mesh is fine, so the default caps the lines before
|
|
# STREAM_MAX_LENGTH ever does. Raise it well past what the length needs.
|
|
MAX_STEPS = 20000
|
|
STREAM_DIRECTION = 'BOTH' # 'BOTH' also draws the half that runs into the anode
|
|
STREAM_RANGE = [-200.0, 650.0] # full electrode span, needle to anode
|
|
|
|
# Clear the quadrant view of everything else so only the field lines show.
|
|
Hide(glyph, renderView)
|
|
Hide(contour_filter, renderView)
|
|
|
|
seed_contour = Contour(Input=reader, ContourBy=['POINTS', 'potential'])
|
|
seed_contour.Isosurfaces = SEED_POTENTIALS
|
|
|
|
# StreamTracer's own Point Cloud and Line seeds are 3D and this mesh is a
|
|
# single plane at z = 0, so they would mostly land outside it. Seeding from a
|
|
# contour of the mesh keeps every seed in the plane by construction.
|
|
seed_points = MaskPoints(Input=seed_contour)
|
|
seed_points.OnRatio = SEED_STRIDE
|
|
seed_points.RandomSampling = 0
|
|
|
|
stream = StreamTracerWithCustomSource(Input=reader, SeedSource=seed_points)
|
|
stream.Vectors = ['POINTS', 'electric field']
|
|
# FORWARD follows E, which runs from the anode outwards, so each line leaves
|
|
# its seed and heads for a cathode. With BOTH, the backward half of every line
|
|
# also climbs to the anode the seed ring encircles, and all of them meet there
|
|
# -- that is the joining. BOTH is still the honest full field line if you want
|
|
# it; this only changes what gets drawn.
|
|
stream.IntegrationDirection = STREAM_DIRECTION
|
|
stream.MaximumStreamlineLength = STREAM_MAX_LENGTH
|
|
stream.MaximumSteps = MAX_STEPS
|
|
|
|
# Keep integration in the z = 0 plane. Newer ParaView only, so tolerate it
|
|
# being absent.
|
|
try:
|
|
stream.SurfaceStreamlines = 1
|
|
except Exception as exc:
|
|
print("note: SurfaceStreamlines unavailable (%s)" % exc)
|
|
|
|
stream_display = Show(stream, renderView)
|
|
stream_display.Representation = 'Surface'
|
|
stream_display.LineWidth = STREAM_LINE_WIDTH
|
|
stream_display.RenderLinesAsTubes = 1
|
|
|
|
ColorBy(stream_display, ('POINTS', 'potential'))
|
|
streamLUT = GetColorTransferFunction('potential', stream_display, separate=True)
|
|
streamLUT.ApplyPreset('Cool to Warm', True)
|
|
streamLUT.RescaleTransferFunction(STREAM_RANGE[0], STREAM_RANGE[1])
|
|
stream_display.LookupTable = streamLUT
|
|
stream_display.SetScalarBarVisibility(renderView, True)
|
|
|
|
renderView.Background = [0.0, 0.0, 0.0]
|
|
|
|
Render()
|
|
SaveScreenshot("Streamlines_quarter_output.png")
|