xnecview-1.37/0000755000076600007660000000000013606626524011466 5ustar fwmfwmxnecview-1.37/freqplot.c0000640000076600007660000003431010412046441013445 0ustar fwmfwm/* XNECVIEW - a program for visualizing NEC2 input and output data * * Copyright (C) 2000-2006, Pieter-Tjerk de Boer -- pa3fwm@amsat.org * * Distributed on the conditions of version 2 of the GPL: see the files * README and COPYING, which accompany this source file. * * This module contains code for drawing plots of several quantities * like impedance, SWR and gain as a function of frequency. * */ #include #include #include #include #include #include "xnecview.h" int win2sizex,win2sizey; /* size of window in pixels */ int plot2_swr=1; /* show the SWR graph? */ int plot2_maxgain=1; /* show the maxgain and front/back graph? */ int plot2_vgain=0; /* show the vgain graph? */ int plot2_z=0; /* show the impedance graph? */ int plot2_z2=0; /* show the phi(z)/abs(z) graph? */ int plot2_dir=0; /* show the direction-of-maximum-gain graph? */ double r0=R0; /* reference impedance for SWR calculation */ void fixrange1(double *mi, double *ma, int *np) /* mi and ma point to minimum and maximum value of a range of values to be plotted, and np to the maximum acceptable number of subdivision. This function tries to modify the minimum and maximum and the number of subdivision such that the resulting grid lines are at "round" numbers. */ { double d,e; double a; double newmin,newmax; int i; int n=*np; /* static double acceptable[]={10, 5.0, 2.5, 2.0, 1.0, -1}; */ static double acceptable[]={10, 5.0, 2.0, 1.0, -1}; if (*ma==*mi) { if (*mi>0) {*mi=0; *ma=2* *ma;} else if (*mi<0) {*mi=2* *mi; *ma=0;} else {*mi=-10; *ma=10;} } d=(*ma-*mi)/n; e=1.0; while (ed) e/=10; a=d/e; i=0; while (acceptable[i]>a) i++; if (acceptable[i]==-1) i--; i++; do { i--; if (i<0) { e*=10; i=0; while (acceptable[i+1]>0) i++; } a=acceptable[i]; d=a*e; newmin = d*floor(*mi/d); newmax = d*ceil(*ma/d); n = (int)((newmax-newmin)/d+0.5); } while (n>*np); *np=n; *mi=newmin; *ma=newmax; } void fixrange2(double *mi1, double *ma1, double *mi2, double *ma2, int *np) /* like fixrange2(), but for two (vertical) axes simultaneously */ { static double acceptable[]={100.0, 50.0, 25.0, 20.0, 10.0, 5.0, 2.5, 2.0, 1.0, 0.5, 0.25, 0.2, 0.1, 0.05, 0.025, 0.02, 0.01, -1}; double a,d,e1,e2,s; int n=*np; int i1,i2; int i,j; int ibest,jbest; int n1[5],n2[5]; double x1[4],x2[4]; double best; if (*ma1==*mi1) { if (*mi1>0) {*mi1=0; *ma1=2* *ma1;} else if (*mi1<0) {*mi1=2* *mi1; *ma1=0;} else {*mi1=-10; *ma1=10;} } d=(*ma1-*mi1)/n; /* d is the ideal, but usually not acceptable, stepsize, for axis 1 */ *ma1-=0.00001*d; /* prevent rounding errors from causing a boundary of say 1000 to be seen as slightly larger than say 10 steps of 100 each */ *mi1+=0.00001*d; /* idem */ d-=0.00001*d; e1=1.0; while (e1d) e1/=10; /* e1 is the appropriate power of 10 to scale the steps, for axis 1 */ a=d/e1; i1=0; while (acceptable[i1+1]>=a) i1++; /* i1 is the index in the acceptable[] array of the highest acceptable stepsize, for axis 1 */ for (i=0;i<4;i++) { /* consider this and the next 3 lower stepsizes: */ s = e1*acceptable[i1-i]; n1[i] = ceil(*ma1/s) - floor(*mi1/s) ; /* minimum number of acceptable steps */ x1[i] = (*ma1-*mi1) / s; /* "usage factor": how many of these steps does the data cover? */ } /* same calculations for axis 2 */ if (*ma2==*mi2) { if (*mi2>0) {*mi2=0; *ma2=2* *ma2;} else if (*mi2<0) {*mi2=2* *mi2; *ma2=0;} else {*mi2=-10; *ma2=10;} } d=(*ma2-*mi2)/n; *ma2-=0.00001*d; *mi2+=0.00001*d; d-=0.00001*d; e2=1.0; while (e2d) e2/=10; a=d/e2; i2=0; while (acceptable[i2+1]>=a) i2++; for (i=0;i<4;i++) { s = e2*acceptable[i2-i]; n2[i] = ceil(*ma2/s) - floor(*mi2/s) ; x2[i] = (*ma2-*mi2) / s; } /* search for best combination: the combination for which the data covers as large a fraction of both axes as possible */ best=0; ibest=jbest=0; for (i=0;i<4;i++) for (j=0;j<4;j++) { double x; int n; n = n1[i]; if (n2[j]>n) n=n2[j]; x = (x1[i]/n) * (x2[j]/n); if (x>best*1.1 || (x>best && n>=*np)) { best=x; ibest=i; jbest=j; *np=n; } } n = *np; i1-=ibest; i2-=jbest; s = e1*acceptable[i1]; *mi1 = s*floor(*mi1/s); *ma1 = *mi1+n*s; s = e2*acceptable[i2]; *mi2 = s*floor(*mi2/s); *ma2 = *mi2+n*s; } double minf,maxf; int xleft, xright; #define idxOK(idx,ne) (idx>=0 && ( !ONLY_IF_RP(idx) || ne->rp ) && ne->d[idx]>-DBL_MAX) void freqplot( int idx1, /* index in neco[].d[] of quantity for left axis */ int idx2, /* index in neco[].d[] of quantity for right axis */ int idx1a, /* index in neco[].d[] of second quantity for left axis (dotted line) */ int idx2a, /* index in neco[].d[] of second quantity for right axis (dotted line) */ char *title1, char *title2, /* titles for left and right */ char *title, /* center title */ GdkColor *color1, GdkColor *color2, /* colours for both curves */ double ybotf, double ytopf /* vertical position; 0...1 = top...bottom of window */ ) { int ybot, ytop; int i; double min1,max1, min2, max2; NECoutput *ne; int ntx,nty; int xx1,xx2,yy1,yy2; int xx1a,xx2a,yy1a,yy2a; /* choose the corner points of the graph area */ ybot = ybotf*win2sizey - fontheight; ytop = ytopf*win2sizey + fontheight; xleft = 5*fontheight; xright = win2sizex - 5*fontheight; /* find the ranges */ minf=maxf=neco[0].f; min1=min2=DBL_MAX; max1=max2=-DBL_MAX; for (i=0, ne=neco; if < minf) minf=ne->f; if (ne->f > maxf) maxf=ne->f; if (idxOK(idx1,ne)) { if (ne->d[idx1] < min1) min1=ne->d[idx1]; if (ne->d[idx1] > max1) max1=ne->d[idx1]; } if (idxOK(idx1a,ne)) { if (ne->d[idx1a] < min1) min1=ne->d[idx1a]; if (ne->d[idx1a] > max1) max1=ne->d[idx1a]; } if (idxOK(idx2,ne)) { if (ne->d[idx2] < min2) min2=ne->d[idx2]; if (ne->d[idx2] > max2) max2=ne->d[idx2]; } if (idxOK(idx2a,ne)) { if (ne->d[idx2a] < min2) min2=ne->d[idx2a]; if (ne->d[idx2a] > max2) max2=ne->d[idx2a]; } } if (min1>max1) { idx1=-1; idx1a=-1; } if (min2>max2) { idx2=-1; idx2a=-1; } /* extend the ranges to have 'round' numbers at each division */ ntx=(xright-xleft)/fontheight/3; fixrange1(&minf,&maxf,&ntx); nty = (ybot-ytop)/fontheight; if (idx1>=0) { if (idx1==neco_zr) { if (max1 > 20*r0) max1 = 20*r0; } } if (idx2>=0) { if (idx2==neco_zi || idx2==neco_zabs) { if (max2 > 20*r0 && min2 < 20*r0) max2 = 20*r0; if (min2 < -20*r0 && max2 > -20*r0) min2 = -20*r0; } } if (idx1==neco_swr) { min1=0; if (max1>10) max1=9; else max1-=1; } if (idx2==neco_swr) { min2=0; if (max2>10) max2=9; else max2-=1; } if (idx1<0 && idx1a<0) { if (idx2<0 && idx2a<0) return; fixrange1(&min2,&max2,&nty); } else { if (idx2<0 && idx2a<0) fixrange1(&min1,&max1,&nty); else fixrange2(&min1,&max1,&min2,&max2,&nty); } if (idx1==neco_swr) { min1+=1; max1+=1; } if (idx2==neco_swr) { min2+=1; max2+=1; } /* macros for converting from "real" values to screen coordinates */ #define sx(f) (((f)-minf)/(maxf-minf)*(xright-xleft)+xleft) #define sy1(f) (((f)-min1)/(max1-min1)*(ytop-ybot)+ybot) #define sy2(f) (((f)-min2)/(max2-min2)*(ytop-ybot)+ybot) SetLineAttributes(0, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND); /* vertical grid lines and associated labels */ for (i=0; i<=ntx; i++) { double f; int x; char s[20]; f=minf+(maxf-minf)*((double)i)/ntx; x=sx(f); if (i>0 && i0 && i=0) { sprintf(s,"%g ",f); SetForeground(color1); DrawString(xleft,y,s,1,0.5); } if (idx2>=0) { f=min2+(max2-min2)*((double)i)/nty; if (fabs(f/(max2-min2))<0.1/nty) f=0; y=sy2(f); sprintf(s," %g",f); SetForeground(color2); DrawString(xright,y,s,0,0.5); } } SetForeground(&c_axis); /* border around the graph */ DrawLine(xleft,ybot,xright,ybot); DrawLine(xleft,ytop,xright,ytop); DrawLine(xleft,ybot,xleft,ytop); DrawLine(xright,ybot,xright,ytop); /* title(s) */ if (title) { SetForeground(&c_axis); DrawString((xleft+xright)/2, ytop-1, title, 0.5,0); } if (title1) { SetForeground(color1); DrawString(xleft-fontheight, ytop-1, title1, 0,0); } if (title2) { SetForeground(color2); DrawString(xright+fontheight, ytop-1, title2, 1,0); } /* the actual data points and connecting lines */ SetClipRectangle(xleft-2,ytop,xright+2,ybot); xx1=xx2=yy1=yy2=-1; xx1a=xx2a=yy1a=yy2a=-1; for (i=0, ne=neco; if); if (idx1a>=0 || idx2a>=0) SetLineAttributes(0, GDK_LINE_ON_OFF_DASH, GDK_CAP_ROUND, GDK_JOIN_ROUND); if (idxOK(idx1a,ne)) { y = sy1(ne->d[idx1a]); SetForeground(color1); if (numneco < win2sizex / 4) { DrawLine(x-2,y-2,x+2,y-2); DrawLine(x-2,y+2,x+2,y+2); DrawLine(x-2,y-2,x-2,y+2); DrawLine(x+2,y-2,x+2,y+2); } if (xx1a!=-1) DrawLine(xx1a,yy1a,x,y); xx1a=x; yy1a=y; } if (idxOK(idx2a,ne)) { y = sy2(ne->d[idx2a]); SetForeground(color2); if (numneco < win2sizex / 4) { DrawLine(x-3,y,x,y-3); DrawLine(x-3,y,x,y+3); DrawLine(x+3,y,x,y-3); DrawLine(x+3,y,x,y+3); } if (xx2a!=-1) DrawLine(xx2a,yy2a,x,y); xx2a=x; yy2a=y; } if (idx1a>=0 || idx2a>=0) SetLineAttributes(0, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND); if (idxOK(idx1,ne)) { y = sy1(ne->d[idx1]); SetForeground(color1); if (numneco < win2sizex / 4) { DrawLine(x-2,y-2,x+2,y-2); DrawLine(x-2,y+2,x+2,y+2); DrawLine(x-2,y-2,x-2,y+2); DrawLine(x+2,y-2,x+2,y+2); } if (xx1!=-1) DrawLine(xx1,yy1,x,y); xx1=x; yy1=y; } if (idxOK(idx2,ne)) { y = sy2(ne->d[idx2]); SetForeground(color2); if (numneco < win2sizex / 4) { DrawLine(x-3,y,x,y-3); DrawLine(x-3,y,x,y+3); DrawLine(x+3,y,x,y-3); DrawLine(x+3,y,x,y+3); } if (xx2!=-1) DrawLine(xx2,yy2,x,y); xx2=x; yy2=y; } } SetClipRectangle(0,0,win2sizex,win2sizey); } double xfreq(int x) { return ((double)x-xleft)/(xright-xleft)*(maxf-minf)+minf; } int freqx(double f) { return sx(f); } int freqindex(double f) { double d,dbest; int i,ibest; ibest=-1; dbest=DBL_MAX; for (i=0;iComplete(); } xnecview-1.37/postscript.c0000640000076600007660000001275410412032633014031 0ustar fwmfwm/* XNECVIEW - a program for visualizing NEC2 input and output data * * Copyright (C) 1998-2006, Pieter-Tjerk de Boer -- pa3fwm@amsat.org * * Distributed on the conditions of version 2 of the GPL: see the files * README and COPYING, which accompany this source file. * * This module contains all low level postscript drawing routines. */ #include #include #include #include #include "xnecview.h" FILE *psfile=NULL; int needstroke; int needmoveto; GdkColor *xcprev=NULL; void ps_DrawLine(double x1,double y1,double x2,double y2) { static double lastx=-1,lasty=-1; if (x1!=lastx || y1!=lasty || needmoveto) { if (needstroke) fprintf(psfile,"stroke\n"); fprintf(psfile,"%g %g m\n",x1,y1); needmoveto=0; } fprintf(psfile,"%g %g l\n",x2,y2); lastx=x2; lasty=y2; needstroke=1; } void ps_SetLineAttributes(unsigned line_width,int line_style,int cap_style,int join_style) { if (needstroke) { fprintf(psfile,"stroke\n"); needstroke=0; } if (line_width==0) line_width++; /* Zero width lines for X11 speed of drawing but in postscript don't really want the thinnest lines which are possible on the device! */ fprintf(psfile,"%u setlinewidth ",line_width); switch (line_style) { case GDK_LINE_SOLID: fprintf(psfile,"[] 0 setdash\n"); break; case GDK_LINE_ON_OFF_DASH: fprintf(psfile,"[5 5] 0 setdash\n"); break; } if (cap_style!=GDK_CAP_ROUND || join_style!=GDK_JOIN_ROUND || line_style==GDK_LINE_DOUBLE_DASH) { puts("Error in ps_SetLineAttributes!"); } needmoveto=1; } void ps_SetForeground(GdkColor *xc) { if (xc==xcprev) return; xcprev=xc; if (needstroke) { fprintf(psfile,"stroke\n"); needstroke=0; } fprintf(psfile,"%g %g %g setrgbcolor\n",xc->red/65535.,xc->green/65535.,xc->blue/65535.); needmoveto=1; } void ps_ClearWindow(void) { } void ps_DrawString(double a,double b,char *s,double d,double e) { fprintf(psfile,"gsave (%s) dup stringwidth pop %g mul %g add\n",s,-d,a); fprintf(psfile,"%g descent sub ascent descent add %g mul add m 1 -1 scale show grestore\n", b, e); } void ps_Complete(void) { } void ps_SetClipRectangle(double x1,double y1,double x2,double y2) { if (needstroke) { fprintf(psfile,"stroke\n"); needstroke=0; } xcprev=NULL; fprintf(psfile,"grestore gsave\n"); fprintf(psfile,"newpath %g %g moveto %g %g lineto %g %g lineto %g %g lineto closepath clip newpath\n", x1,y1, x1,y2, x2,y2, x2,y1 ); } Outdev out_ps = { ps_SetLineAttributes, ps_DrawLine, ps_SetForeground, ps_ClearWindow, ps_DrawString, ps_Complete, ps_SetClipRectangle, NULL, } ; #define PSsize 400 #define PSllx 50 #define PSlly 150 int write_postscript(const char *filename,void (*drawfn)(int),int xsize,int ysize) { Outdev *oldout; time_t ti; psfile=fopen(filename,"w"); if (!psfile) return 1; oldout=out; out=&out_ps; time(&ti); fprintf(psfile,"%%!PS-Adobe-2.0 EPSF-2.0\n" "%%%%DocumentFonts: %s\n" "%%%%Title: Xnecview output\n" "%%%%Creator: Xnecview %s\n" "%%%%CreationDate: %s" "%%%%Pages: 1\n" "%%%%BoundingBox: %g %g %g %g\n" "%%%%EndComments\n", PSFONT, VERSION, ctime(&ti), (double)PSllx,(double)PSlly,(double)PSllx+xsize,(double)PSlly+ysize); fprintf(psfile,"/xnecview 10 dict def\n xnecview begin \n"); /* use a private dictionary as recommended in EPSF-3.0 standard */ fprintf(psfile,"/l /lineto load def\n"); /* define abbreviations l and m for lineto and moveto */ fprintf(psfile,"/m /moveto load def\n"); fprintf(psfile,"end\n"); /* end use (definition) of local dictionary */ fprintf(psfile,"%%%%EndProlog\n"); fprintf(psfile,"%%%%Page: 1 1\n"); fprintf(psfile,"xnecview begin\n"); /* use local dictionary again */ fprintf(psfile,"%g %g translate 1 -1 scale\n", /* set scaling and translation such that our Xwindow coordinates can be used */ (double)PSllx, (double)PSlly+ysize ); fprintf(psfile,"newpath 0 0 moveto 0 %g lineto %g %g lineto %g 0 lineto closepath clip newpath\n", (double)ysize, (double)xsize, (double)ysize, (double)xsize ); fprintf(psfile,"1 setlinejoin 1 setlinecap\n"); fprintf(psfile,"/%s findfont %i scalefont setfont\n",PSFONT,fontheight); /* prepare the font */ fprintf(psfile,"gsave 0 0 moveto (10ALIZgjy) true charpath pathbbox grestore /ascent exch def pop -1 mul /descent exch def pop\n"); /* estimate ascent and descent of font */ fprintf(psfile,"gsave\n"); /* this gsave will be grestore'd by the first SetClipRectangle call */ needstroke=0; needmoveto=0; xcprev=NULL; drawfn(0); #if 0 fprintf(psfile,"save 0 setgray 10 %i moveto 1 -1 scale (Antenna: %s) show restore\n", fontheight,inputfilename); if (gainplot!=GPnone) { fprintf(psfile,"save 0 setgray 10 %i moveto 1 -1 scale (Scale: %s%s%s) show restore\n", 2*fontheight, GSnames[gainscale], scaleplot ? ", " : "", scaleplot ? GSnumbers[gainscale] : ""); } #endif if (needstroke) fprintf(psfile,"stroke\n"); fprintf(psfile,"grestore\n"); fprintf(psfile,"end\n"); /* end use of local dictionary */ fprintf(psfile,"showpage\n"); out=oldout; return ferror(psfile) | fclose(psfile); } xnecview-1.37/HISTORY0000640000076600007660000002374413606625175012561 0ustar fwmfwmX N E C V I E W --------------- A program for visualizing NEC2 input and output data (Unix/X-windows). Copyright (C) 1998-2001, Pieter-Tjerk de Boer ----------------------------------------------------------------------- HISTORY: -------- Version 0.1, 1998-06-06: - first public release - only few input card types supported Version 0.2, 1998-12-13: - almost all input card types supported - added plotting of gain pattern - colour added - made front and back of surfaces distinguishable (by colour) Version 0.3, 1999-1-15 - added PostScript output - added third gain scale, "log" - added buttons in the window - added plotting of gain as slices in coordinate planes - very small mouse dragging is treated as clicking - corrected handling of output files which sweep over only one angle - man page added, thanks to Joop Stakenborg, PA3ABA. Version 0.4, 1999-5-30 - fixed bug in PostScript output - added handling of GS cards - "long" options now need -- instead of - - fixed colours on 1bpp screen Version 0.5, 1999-6-13 - added double buffering support: this reduces flickering by drawing in a separate 'Pixmap' instead of on the screen - postscript now uses a private dictionary - more general NEC output reading routine - uses hinted motion events to speed up dragging - motion+first button now rotates entire gain pattern, motion + second button rotates simplified gain pattern (quicker) - fixed bug in linear gain scale - removed -q/--quick option - added a (too?) simple icon to appear when xnecview is iconified The first 5 of these changes were essentially contributed by Alan Bain, M1BXV; thanks! Version 0.6, 1999-7-4 - added display of wire numbers - added display of location of excitations and loads - added display of 'X', 'Y' and 'Z' at axes - configuration is now separate .h file - corrected "ARRL style" gain scale (now 0.89 per 2 dB, as per the "ARRL Antenna Book"; tnx Don, VE3HUR) - added handling of GM cards with non-zero ITS - xnecview no longer exits when supplied with incorrect input - an error message is printed when writing postscript fails (tnx Alan Bain) - standard X command line options like -geometry now work Version 1.0, 1999-7-25 - GW cards lacking the wire radius are now accepted - GC cards now produce a warning - added a bit of text to the postscript output (tnx to Don VE3HUR for testing and remarks resulting in the above changes) - iconifying now uses own icon again (didn't in 0.6) Version 1.01, 2000-01-21 - card types of structure input ("GW" etc.) now also accepted in lower case Version 1.02, 2000-03-25 - print not only maximum gain, but also direction of maximum gain and front-to-back-ratio. - plot radial lines every 10 degrees when looking along the X/Y/Z axis (essentially contributed by Marco Menchise, IZ8AEB; tnx!) Version 1.10, 2000-04-25 - added a window with plots of SWR, gain, etc. as a function of frequency (inspired by a preliminary implementation by Marco Menchise, IZ8AEB; tnx!) - added support for radiation diagrams at multiple frequencies - added plotting multiple sets of radiation data (= results from RP cards) for a frequency. - added reading of multiple output files. - removed unbuffered option. - minor rewrites of some parts of the code. - revised the documentation, creating new files USAGE and HISTORY, thus shortening README. Version 1.11, 2000-07-15 - added "support" for GC cards (i.e., they are ignored since xnecview does not display the wire thickness). - added calculation and plotting of gain and f/b in direction toward observer. - added a line of text showing frequency and gain in bottom of 3D plot. - added centered titles to the 2D plots. - mostly rewrote the man page on the basis of the USAGE file. - some optimizations by caching often calculated data; however, the Xserver's speed seems to be the limiting factor on my system, so this doesn't bring much. - bugfix: clicking on the gainscale button if no radiation pattern data was available produced a segmentation fault. - bugfix: reading multiple sets of radiation data sometimes gave an empty radiation pattern plot, depending on the order of the data. - bugfix: GW cards with NRPT=0 or ITS>0 were not handled correctly. Note: this version is a snapshot taken just before the GTK version started to diverge too far to keep developing both versions in parallel. Version 1.20, 2000-08-07 - user interface code converted to use the GTK library instead of Xaw/Xmu/Xt. - added display of phase and magnitude of currents in wires (based on original suggestions by Marco Menchise IZ8AEB and Francesco Messineo IW8QPI; tnx!). - changed mouse bindings for dragging and zooming - added support for TL and NT cards - dynamic memory allocation for radiation pattern data, resulting in much smaller memory usage. - dropped the USAGE file, the man page is now the only file with detailed usage information. Version 1.30, 2000-11-05 - added linear-in-voltage scale; the original linear scale was (and still is) linear in power. - animated display of currents, charges, electric and magnetic (near) field vector, Poynting vector. - command-line options; xnecview options in comment line of NEC input - fix some compiler warnings - limit display of Z to 20*Z0 - add display of phi(Z)/abs(Z) - don't draw a square at each data point if there are many (window 2) (latter four changes essentially contributed by Joerg Wunsch DL8DTL; tnx!) - number of grid lines in freqplots adapts to size of graph - output files without currents information no longer cause a segfault - some other minor fixes Version 1.31, 2001-06-24 - changed axes of frequency plots: number of subdivisions can now be variable, resulting in a more readable axis (stepsizes are now always multiples of 1, 2, 2.5, or 5; no longer 4 or 7.5). - if f/b is unknown, '-' is printed (instead of -1e-308) in table - hide 'nan' on axes (window 2) if no relevant data available - impedance (Z0) can now have up to 4 digits (instead of 2) - new option: -aupdate to set the update interval of animation - bugfix: extremely large values of Zi could hang xnecview - some small fixes in choosing which frequency is used for radiation pattern display after reloading Version 1.32, 2002-08-25 - the radiation pattern can now be displayed as a "wire grid" (like in earlier versions) or as an "opaque" surface (i.e., with hidden lines removed). The latter view is usually much easier to interpret, especially when there are many sidelobes. - the gain display can now be limited to one polarization component, or the 3D pattern can be coloured according to the dominant polarization. - changed some of the default colors, which did not contrast enough with the white background. But you can of course change them again, by editing config.h . - keypress 'z' now suspends/resumes the animation - typo & corrections in manpage - fixed bug that could cause segfault if NEC output file initially contained data at one frequency, and at more frequencies upon reread. - reduced memory usage by changing arrays from double to float - bugfix: missing lines in eps-export of freqplot - bugfix: GS card vs. currents data - if only a NEC output file is given and no input file, the structure view now defaults to 'none' rather than 'currents'. Version 1.33, 2002-12-27 - now all memory allocation is dynamic, so no limitations on model size anymore. - output is now also correctly interpreted if numerical Green's function is used (GF card). - added commandline option --pol . - changed rotation "speed": this is now independent of zoom factor - bugfix: if surfaces were present, GS cards were not interpreted properly - bugfix: segfault sometimes when selecting 'opaque' while no radiation data was available - elaborate bugfix of opaque drawing; in practice, this mostly affects drawing of points on the Z axis, when a polarization different from 'total' is selected. - fixes to the Imakefile (tnx Alan Bain) Version 1.34, 2003-09-10 - yet another opaque drawing bugfix. - bugfix: when zooming in very far, coordinates became too large for X11, causing incorrect lines to appear. - converted the last bits of Xlib code to GTK. - changed line ending style from "butt" to "round". - bugfix in parsing xnecview options embedded in NEC input file - minor fixes in handling of window (re)sizing Version 1.35, 2006-04-05 - now supports for rectangular, triangular and quadrilateral surface patches in NEC input (tnx to Predrag Miocinovic for contributing this). - now understands the "other" output format that some versions of NEC apparently produce (tnx to several people for submitting this). - now compiles against GTK+-2 and later, rather than 1.2 (again, tnx to several people). - graphs in window 2 now also have units printed at/near the axes. - main() return values now correctly represent error conditions (tnx Alan Bain) - yet another opaque drawing bugfix. - bugfix: export to PNG of window 2 didn't work - bugfix: ne->rpgpovalid was not always initialized - bugfix: animation didn't work on some systems because do_animation() didn't have an explicit return value Version 1.36, 2011-07-26 - a few minor bugfixes: - correct depth parameter in some gtk calls (tnx Alan Bain) - set numeric locale to C for correct parsing of data files - correct interpretation of last parameter in GM cards (float vs. int) (tnx Graham Seale) - support for NEC output files that do not contain 'input parameters' data (practically speaking: structures that are excited by incoming fields) - fixed a few compiler warnings Version 1.37, 2020-01-12 - a few bugfixes: - correctly recognize near field data in yet another nec2 version (Debian's) (2011) - show gain scale when --view option is used to choose a view along one of the axes. (2014-02-27) - calculation of absolute segment numbers when using GM cards was incorrect (tnx Luigi Tarenga) - updates for more modern library versions - compatibility with libpng 1.6 instead of 1.2 (tnx Luigi Tarenga) - PNG file was completely black with modern libgdk (tnx Luigi Tarenga) - fixed a few compiler warnings (one of which was probably a very subtle bug in the opaque drawing) xnecview-1.37/parse_output.c0000640000076600007660000007643613606625106014373 0ustar fwmfwm/* XNECVIEW - a program for visualizing NEC2 input and output data * * Copyright (C) 1998-2006,2011,2020 Pieter-Tjerk de Boer -- pa3fwm@amsat.org * * Distributed on the conditions of version 2 of the GPL: see the files * README and COPYING, which accompany this source file. * * This module parses NEC2's output. * Furthermore, it contains some functions for later processing of this * data. */ #include #include #include #include #include #include #include "xnecview.h" double globalmaxdb=-1e30; void calcswr(NECoutput *ne) { double zr,zi,gamma; zr=ne->d[neco_zr]; zi=ne->d[neco_zi]; gamma = sqrt( ( (zr-r0)*(zr-r0) + zi*zi )/( (zr+r0)*(zr+r0) + zi*zi ) ); ne->d[neco_swr] = (1+gamma)/(1-gamma); } void calcphiabs(NECoutput *ne) { double zr,zi; zr=ne->d[neco_zr]; zi=ne->d[neco_zi]; ne->d[neco_zabs] = sqrt(zr*zr + zi*zi); ne->d[neco_zphi] = 180 * M_1_PI * atan2(zi, zr); } void *mymalloc(size_t n) { void *p; p=malloc(n); if (p==NULL) { fprintf(stderr,"Out of memory\n"); exit(1); } return p; } void *myrealloc(void *p,size_t n) { p=realloc(p,n); if (p==NULL) { fprintf(stderr,"Out of memory\n"); exit(1); } return p; } double calc_polfactor(int pol,Gain *g) { /* note: for efficiency reasons, these formulas are also in update_maxgains() */ double a,b,f; switch (pol) { case POLhor: a=g->axial*g->axial; b=sin(g->tilt); f=(a+(1-a)*b*b)/(1+a); break; case POLvert: a=g->axial*g->axial; b=cos(g->tilt); f=(a+(1-a)*b*b)/(1+a); break; case POLlhcp: a=g->axial*g->axial; f=(1+2*g->axial+a)/2/(1+a); break; case POLrhcp: a=g->axial*g->axial; f=(1-2*g->axial+a)/2/(1+a); break; default: f=1; /* POLnone, POLcolour */ } return f; } void update_maxgains(Gain *g,NECoutput *ne,double phi,double theta) { double f; double a,b; if (g->total > ne->d[neco_maxgain]) { ne->d[neco_maxgain]=g->total; ne->d[neco_phi]=phi; ne->d[neco_theta]=theta; } a=g->axial*g->axial; f=(1+2*g->axial+a)/2/(1+a); f=g->total+10*log10(f); if (f > ne->d[neco_maxgain_lhcp]) { ne->d[neco_maxgain_lhcp]=f; ne->d[neco_phi_lhcp]=phi; ne->d[neco_theta_lhcp]=theta; } f=(1-2*g->axial+a)/2/(1+a); f=g->total+10*log10(f); if (f > ne->d[neco_maxgain_rhcp]) { ne->d[neco_maxgain_rhcp]=f; ne->d[neco_phi_rhcp]=phi; ne->d[neco_theta_rhcp]=theta; } b=sin(g->tilt); f=(a+(1-a)*b*b)/(1+a); f=g->total+10*log10(f); if (f > ne->d[neco_maxgain_hor]) { ne->d[neco_maxgain_hor]=f; ne->d[neco_phi_hor]=phi; ne->d[neco_theta_hor]=theta; } b=cos(g->tilt); f=(a+(1-a)*b*b)/(1+a); f=g->total+10*log10(f); if (f > ne->d[neco_maxgain_vert]) { ne->d[neco_maxgain_vert]=f; ne->d[neco_phi_vert]=phi; ne->d[neco_theta_vert]=theta; } } void find_fb(NECoutput *ne,Radpattern *r) { int i,j,k; double theta,phi; theta=ne->d[neco_theta]*M_PI/180; phi=ne->d[neco_phi]*M_PI/180; for (k=POLnone;k<=POLrhcp;k++) { for (i=0;inumtheta;i++) { double d; d = fmod(fabs(r->gtheta[i]+theta),2*M_PI); if (fabs(d-M_PI) < 0.00001) break; } for (j=0;jnumphi;j++) { double d; d = fmod(fabs(r->gphi[j]-phi),2*M_PI); if (fabs(d-M_PI) < 0.00001) break; } if (k==POLnone) { if (inumtheta && jnumphi) ne->d[neco_fb] = ne->d[neco_maxgain] - r->gain[j][i].total; else ne->d[neco_fb] = -DBL_MAX; } else { if (inumtheta && jnumphi) { double f; Gain *g=&r->gain[j][i]; ne->d[Neco_gsize*k+Neco_polfb2] = ne->d[Neco_gsize*k+Neco_polgain] - g->total; f=calc_polfactor(k,g); ne->d[Neco_gsize*k+Neco_polfb1] = ne->d[Neco_gsize*k+Neco_polgain] - r->gain[j][i].total + 10*log10(f); } else { ne->d[Neco_gsize*k+Neco_polfb1] = -DBL_MAX; ne->d[Neco_gsize*k+Neco_polfb2] = -DBL_MAX; } } if (kd[Neco_gsize*(k+1)+Neco_poltheta]*M_PI/180; phi=ne->d[Neco_gsize*(k+1)+Neco_polphi]*M_PI/180; } } } void read_nec_output_rad(FILE *f,NECoutput *ne) /* tries to read (far field) radiation pattern data from f */ { char s[200]; double phi,theta; float db,axial,tilt; char polsense; int i; int end=0; int maxthetas, maxphis; Radpattern *r; /* Some versions of NEC2 output extraneous data after "RADIATION PATTERNS" before the actual data * and column labels (e.g. RANGE = and EXP (-JKR) values ). Discard until * the true bottom of the column labels (look for DB) */ do fgets(s,200,f); while (!feof(f) && !strstr(s,"DB")); if (feof(f)) return; /* allocate some memory; several of these arrays may need to be reallocated later on, if it turns out that their initial size is too small */ r=mymalloc(sizeof(Radpattern)); maxphis=128; r->gain=mymalloc(maxphis*sizeof(Gain*)); r->gphi=mymalloc(maxphis*sizeof(double)); /* read the first block of radiation data, i.e., for one value of phi and the full range of theta */ /* after this, we know how many thetas to expect per phi, which makes the memory allocation simpler */ fgets(s,200,f); removecommas(s); if (sscanf(s,"%lg%lg%*g%*g%g%g%g %c",&theta,&phi,&db,&axial,&tilt,&polsense)!=6) return; r->gphi[0]=phi; r->numphi=1; r->numtheta=0; maxthetas=16; r->gtheta=mymalloc(maxthetas*sizeof(double)); r->gain[0]=mymalloc(maxthetas*sizeof(Gain)); do { Gain *g; if (r->numtheta>=maxthetas) { maxthetas*=2; r->gtheta=myrealloc(r->gtheta,maxthetas*sizeof(double)); r->gain[0]=myrealloc(r->gain[0],maxthetas*sizeof(Gain)); } g=&r->gain[r->numphi-1][r->numtheta]; g->total=db; g->tilt=tilt*M_PI/180; if (polsense=='R') axial=-axial; g->axial=axial; update_maxgains(g,ne,phi,theta); r->gtheta[r->numtheta++]=theta*M_PI/180; fgets(s,200,f); removecommas(s); if (sscanf(s,"%lg%lg%*g%*g%g%g%g %c",&theta,&phi,&db,&axial,&tilt,&polsense)!=6) { end=1; break; } } while (phi==r->gphi[0]); r->gtheta=myrealloc(r->gtheta,r->numtheta*sizeof(double)); r->gain[0]=myrealloc(r->gain[0],r->numtheta*sizeof(Gain)); r->gphi[0]*=M_PI/180; /* next, read the rest of the data, i.e., for the same values of theta and the entire range of phi */ if (!end) { i=0; while (1) { Gain *g; if (i==0) { if (r->numphi>=maxphis) { maxphis*=2; r->gain=myrealloc(r->gain,maxphis*sizeof(Gain)); r->gphi=myrealloc(r->gphi,maxphis*sizeof(double)); } r->gain[r->numphi]=mymalloc(r->numtheta*sizeof(Gain)); r->gphi[r->numphi++]=phi*M_PI/180; } g=&r->gain[r->numphi-1][i]; g->total=db; g->tilt=tilt*M_PI/180; if (polsense=='R') axial=-axial; g->axial=axial; update_maxgains(g,ne,phi,theta); if (++i==r->numtheta) i=0; fgets(s,200,f); removecommas(s); if (sscanf(s,"%lg%lg%*g%*g%g%g%g %c",&theta,&phi,&db,&axial,&tilt,&polsense)!=6) break; } } r->gain=myrealloc(r->gain,r->numphi*sizeof(Gain)); r->gphi=myrealloc(r->gphi,r->numphi*sizeof(double)); /* further processing of maximum gain info */ if (ne->d[neco_maxgain] > globalmaxdb) globalmaxdb=ne->d[neco_maxgain]; find_fb(ne,r); /* allocate arrays which will later be filled with the grid coordinates in 3D space */ r->gpo=mymalloc(r->numphi*sizeof(Point*)); for (i=0;inumphi;i++) r->gpo[i]=mymalloc(r->numtheta*sizeof(Point)); /* allocate and fill the arrays of sines and cosines of theta and phi */ r->sin_gphi=mymalloc(r->numphi*sizeof(double)); r->cos_gphi=mymalloc(r->numphi*sizeof(double)); for (i=0;inumphi;i++) { r->sin_gphi[i]=sin(r->gphi[i]); r->cos_gphi[i]=cos(r->gphi[i]); } r->sin_gtheta=mymalloc(r->numtheta*sizeof(double)); r->cos_gtheta=mymalloc(r->numtheta*sizeof(double)); for (i=0;inumtheta;i++) { r->sin_gtheta[i]=sin(r->gtheta[i]); r->cos_gtheta[i]=cos(r->gtheta[i]); } /* finally, attach the set of radiation data just read to the *ne structure */ r->next=NULL; if (!ne->rp) ne->rp=r; else { Radpattern *p; p=ne->rp; while (p->next) p=p->next; p->next=r; } } Currents *read_nec_output_seg(FILE *f) /* tries to read segmentation data from f; returns NULL if not succesful */ { char s[200]; char *p; Currents *cu; /* skip some column labels etc., until we find a line that starts with a digit */ do { fgets(s,200,f); p=s; while (*p==' ') p++; } while (!isdigit(*p) && !feof(f)); if (feof(f)) return NULL; /* allocate memory */ cu=mymalloc(sizeof(Currents)); cu->numseg=0; cu->maxseg=1; cu->s=mymalloc(cu->maxseg*sizeof(*(cu->s))); cu->maxI=0; cu->maxQ=0; /* read the segmentation geometry */ do { double x,y,z,l,alpha,beta; double dx,dy,dz; Segcur *se; if (sscanf(s,"%*d%lg%lg%lg%lg%lg%lg",&x,&y,&z,&l,&alpha,&beta)!=6) break; EXPAND_IF_NECESSARY(cu->numseg,cu->maxseg,cu->s) se=cu->s+cu->numseg; alpha*=M_PI/180; beta*=M_PI/180; l/=2; dx=l*cos(alpha)*cos(beta); dy=l*cos(alpha)*sin(beta); dz=l*sin(alpha); se->c.x=x; se->c.y=y; se->c.z=z; se->p0.x=x-dx; se->p0.y=y-dy; se->p0.z=z-dz; se->p1.x=x+dx; se->p1.y=y+dy; se->p1.z=z+dz; updateextremes(&se->c); se->re[0]=dx/l; se->re[1]=dy/l; se->re[2]=dz/l; if (cos(alpha)==0) { se->q0[0]=1; se->q0[1]=0; se->q0[2]=0; se->q1[0]=0; se->q1[1]=1; se->q1[2]=0; } else { se->q0[0]=sin(alpha)*cos(beta); se->q0[1]=sin(alpha)*sin(beta); se->q0[2]=-cos(alpha); se->q1[0]=sin(beta); se->q1[1]=-cos(beta); se->q1[2]=0; } se->qre=0; se->qim=0; cu->numseg++; fgets(s,200,f); removecommas(s); } while (!feof(f)); cu->numrealseg=cu->numseg; cu->numanimseg=cu->numseg; return cu; } void read_nec_output_patches(FILE *f,Currents *cu) /* tries to read surface patch data from f */ { char s[200]; char *p; /* skip some column labels etc., until we find a line that starts with a digit */ do { fgets(s,200,f); p=s; while (*p==' ') p++; } while (!isdigit(*p) && !feof(f)); if (feof(f)) return; /* read the surface patch geometry */ do { double x,y,z,area; if (sscanf(s,"%*d%lg%lg%lg%*g%*g%*g%lg",&x,&y,&z,&area)!=4) break; EXPAND_IF_NECESSARY(cu->numseg,cu->maxseg,cu->s) cu->s[cu->numseg].c.x=x; cu->s[cu->numseg].c.y=y; cu->s[cu->numseg].c.z=z; updateextremes(&cu->s[cu->numseg].c); cu->s[cu->numseg].area=area; cu->numseg++; fgets(s,200,f); removecommas(s); } while (!feof(f)); cu->numanimseg=cu->numseg; } Currents *read_nec_output_currents(FILE *f, Currents *cug) /* tries to read segment currents from f */ { char s[200]; char *p; Currents *cu; int i; /* skip column labels etc., until we find a line that starts with a digit */ do { fgets(s,200,f); p=s; while (*p==' ') p++; } while (!isdigit(*p) && !feof(f)); if (feof(f)) return NULL; /* allocate memory and copy the geometry info that we collected earlier: */ cu=mymalloc(sizeof(Currents)); memcpy(cu,cug,sizeof(Currents)); cu->maxseg=cu->numseg; cu->s=mymalloc(cu->maxseg*sizeof(*(cu->s))); memcpy(cu->s,cug->s,cu->maxseg*sizeof(*(cu->s))); /* read the amplitude and phase for every segment */ i=0; do { int j; if (sscanf(s,"%d%*d%*g%*g%*g%*g%*g%*g%g%g",&j,&cu->s[i].a,&cu->s[i].phase)!=3) break; if (i!=j-1) break; for (j=0;j<3;j++) { cu->s[i].im[j] = cu->s[i].re[j] * cu->s[i].a * sin(cu->s[i].phase*M_PI/180); cu->s[i].re[j] = cu->s[i].re[j] * cu->s[i].a * cos(cu->s[i].phase*M_PI/180); } if (cu->s[i].a>cu->maxI) cu->maxI=cu->s[i].a; i++; fgets(s,200,f); } while (inumrealseg && !feof(f)); if (i!=cu->numrealseg) { fprintf(stderr,"Segment currents data in output file incorrect or incomplete\n"); free(cu->s); free(cu); return NULL; } return cu; } void read_nec_output_charges(FILE *f, Currents *cu) /* tries to read segment charges from f */ { char s[200]; char *p; int i; /* skip column labels etc., until we find a line that starts with a digit */ do { fgets(s,200,f); p=s; while (*p==' ') p++; } while (!isdigit(*p) && !feof(f)); if (feof(f)) return; /* read the amplitude and phase for every segment */ i=0; do { int j; double a; double l; if (sscanf(s,"%d%*d%*g%*g%*g%*g%g%g%lg",&j,&cu->s[i].qre,&cu->s[i].qim,&a)!=4) break; if (i!=j-1) break; /* note: we read charge densities, so we need to multiply them by the segment length to obtain the charges themselves */ l=0; l += (cu->s[i].p1.x-cu->s[i].p0.x)*(cu->s[i].p1.x-cu->s[i].p0.x); l += (cu->s[i].p1.y-cu->s[i].p0.y)*(cu->s[i].p1.y-cu->s[i].p0.y); l += (cu->s[i].p1.z-cu->s[i].p0.z)*(cu->s[i].p1.z-cu->s[i].p0.z); l=sqrt(l); cu->s[i].qre*=l; cu->s[i].qim*=l; a*=l; if (a>cu->maxQ) cu->maxQ=a; i++; fgets(s,200,f); } while (inumrealseg && !feof(f)); if (i!=cu->numrealseg) { fprintf(stderr,"Segment charges data in output file incorrect or incomplete\n"); return; } return; } Currents *read_nec_output_patchcurrents(FILE *f, Currents *cu) /* tries to read surface patch currents from f */ { char s[200]; char *p; int i; int oldnumseg; /* skip some column labels etc., until we find a line that starts with a digit */ do { fgets(s,200,f); p=s; while (*p==' ') p++; } while (!isdigit(*p) && !feof(f)); if (feof(f)) return NULL; /* skip the line containing only the patch number */ fgets(s,200,f); /* read the amplitude and phase for every segment */ i=cu->numrealseg; oldnumseg=cu->numseg; EXPAND_IF_NECESSARY(2*cu->numseg-cu->numrealseg,cu->maxseg,cu->s) do { double xr,xi,yr,yi,zr,zi; /* real and imaginary components of current density in X, Y and Z direction */ double xr2,xi2,yr2,yi2,zr2,zi2; /* and their squares */ double totl; /* length of the segment that we're going to use to represent the patch current */ double dxr,dyr,dzr; double dxi,dyi,dzi; double h,hh; double ta,si,co,phi; #if 0 double dx,dy,dz; double l; #endif if (sscanf(s,"%*g%*g%*g%*g%*g%*g%*g%lg%lg%lg%lg%lg%lg",&xr,&xi,&yr,&yi,&zr,&zi)!=6) break; totl=sqrt(cu->s[i].area)/2; /* in principle we can choose any length we wish, but this one gives a nice picture */ cu->s[i].re[0]=xr; cu->s[i].im[0]=xi; cu->s[i].re[1]=yr; cu->s[i].im[1]=yi; cu->s[i].re[2]=zr; cu->s[i].im[2]=zi; #if 0 /* This (disabled) piece of code just represents the currents on the surface patch by two segment currents corresponding to the real and imaginary vectors supplied by NEC. The code is here only for testing purposes. */ xr2=xr*xr; xi2=xi*xi; yr2=yr*yr; yi2=yi*yi; zr2=zr*zr; zi2=zi*zi; cu->s[cu->numseg].c = cu->s[i].c; hh=sqrt(xr2+xi2+yr2+yi2+zr2+zi2); cu->s[i].a= hh*cu->s[i].area/totl; cu->s[cu->numseg].a=hh*cu->s[i].area/totl; h=sqrt(xr2+yr2+zr2); l=totl*h/hh; dx=xr*totl/hh/2; dy=yr*totl/hh/2; dz=zr*totl/hh/2; cu->s[i].phase=0; cu->s[i].p0.x = cu->s[i].c.x-dx; cu->s[i].p0.y = cu->s[i].c.y-dy; cu->s[i].p0.z = cu->s[i].c.z-dz; cu->s[i].p1.x = cu->s[i].c.x+dx; cu->s[i].p1.y = cu->s[i].c.y+dy; cu->s[i].p1.z = cu->s[i].c.z+dz; h=sqrt(xi2+yi2+zi2); l=totl*h/hh; dx=xi*totl/hh/2; dy=yi*totl/hh/2; dz=zi*totl/hh/2; cu->s[cu->numseg].phase=90; cu->s[cu->numseg].p0.x = cu->s[cu->numseg].c.x-dx; cu->s[cu->numseg].p0.y = cu->s[cu->numseg].c.y-dy; cu->s[cu->numseg].p0.z = cu->s[cu->numseg].c.z-dz; cu->s[cu->numseg].p1.x = cu->s[cu->numseg].c.x+dx; cu->s[cu->numseg].p1.y = cu->s[cu->numseg].c.y+dy; cu->s[cu->numseg].p1.z = cu->s[cu->numseg].c.z+dz; cu->numseg++; #endif #if 1 /* In general, the current on a surface patch can be considered as "running around" an ellipse. In many linearly polarized antennas, this ellipse collapses to a straight line. The below code represents the "elliptic" surface current by two segment currents oriented along the major and minor axes of the ellipse. If the ellipse collapses to a straight line, the minor axis component becomes zero, leaving one segment aligned according to the real direction of the current in the surface patch. Particularly in the latter case, this is much more natural than taking the "raw" real and imaginary current vectors supplied by NEC, which in this case would have the same direction and thus be indistinguishable in the picture. */ xr2=xr*xr; xi2=xi*xi; yr2=yr*yr; yi2=yi*yi; zr2=zr*zr; zi2=zi*zi; cu->s[i].a=sqrt(xr2+yr2+zr2+xi2+yi2+zi2)*cu->s[i].area/totl; if (cu->s[i].a>cu->maxI) cu->maxI=cu->s[i].a; hh=xr2+yr2+zr2-xi2-yi2-zi2; if (hh==0) { co = sqrt(0.5); si = sqrt(0.5); phi = M_PI/4; } else { ta = 2*(xr*xi+yr*yi+zr*zi)/(xr2+yr2+zr2-xi2-yi2-zi2); co = sqrt(0.5+0.5*sqrt(1/(ta*ta+1))); si = sqrt(0.5-0.5*sqrt(1/(ta*ta+1))); if (ta<0) si=-si; phi = atan(ta)/2; } dxr = xr*co + xi*si; dyr = yr*co + yi*si; dzr = zr*co + zi*si; dxi = -xr*si + xi*co; dyi = -yr*si + yi*co; dzi = -zr*si + zi*co; h = totl/sqrt(xr2+yr2+zr2+xi2+yi2+zi2)/2; cu->s[i].phase = phi*180/M_PI; cu->s[i].p0.x = cu->s[i].c.x-dxr*h; cu->s[i].p0.y = cu->s[i].c.y-dyr*h; cu->s[i].p0.z = cu->s[i].c.z-dzr*h; cu->s[i].p1.x = cu->s[i].c.x+dxr*h; cu->s[i].p1.y = cu->s[i].c.y+dyr*h; cu->s[i].p1.z = cu->s[i].c.z+dzr*h; cu->s[cu->numseg].a = cu->s[i].a; cu->s[cu->numseg].c = cu->s[i].c; cu->s[cu->numseg].phase = phi*180/M_PI+90; cu->s[cu->numseg].p0.x = cu->s[cu->numseg].c.x-dxi*h; cu->s[cu->numseg].p0.y = cu->s[cu->numseg].c.y-dyi*h; cu->s[cu->numseg].p0.z = cu->s[cu->numseg].c.z-dzi*h; cu->s[cu->numseg].p1.x = cu->s[cu->numseg].c.x+dxi*h; cu->s[cu->numseg].p1.y = cu->s[cu->numseg].c.y+dyi*h; cu->s[cu->numseg].p1.z = cu->s[cu->numseg].c.z+dzi*h; cu->numseg++; #endif i++; fgets(s,200,f); /* again, skip the line containing only the patch number */ fgets(s,200,f); } while (is); free(cu); return NULL; } return cu; } void normalize_currents(Currents *cu) { int i; double max=0; for (i=0;inumseg;i++) if (maxs[i].a) max=cu->s[i].a; for (i=0;inumseg;i++) cu->s[i].a/=max; } void read_nec_output_nearfield(FILE *f, NECoutput *ne,int magnetic) /* tries to read near electric or magnetic field from f */ { char s[200]; char *p; Nearfield *nf,*np; /* skip some column labels etc., until we find a line that starts with a digit, possibly preceeded by a minus and/or a (decimal) point */ do { fgets(s,200,f); p=s; while (*p==' ' || *p=='-' || *p=='.') p++; } while (!isdigit(*p) && !feof(f)); do { double mag[3], phase[3]; /* note: "mag" is an abbreviation for magnitude, not for magnetic... */ Point p; int i; double l; /* parse a line of data */ if (sscanf(s,"%g%g%g%lg%lg%lg%lg%lg%lg", &p.x, &p.y, &p.z, &mag[0], &phase[0], &mag[1], &phase[1], &mag[2], &phase[2])!=9) break; np=ne->nf; if (np==NULL) { /* no near field data yet? then this is the first point */ nf=mymalloc(sizeof(Nearfield)); if (magnetic) nf->evalid=0; else nf->hvalid=0; ne->nf=nf; nf->next=NULL; nf->p=p; } else { /* otherwise, search whether this point is already in the data set */ do { if (np->p.x==p.x && np->p.y==p.y && np->p.z==p.z) { /* if so, just copy the new data into the existing record */ nf=np; break; } if (!np->next) { /* otherwise, append a new record to the set */ nf=mymalloc(sizeof(Nearfield)); if (magnetic) nf->evalid=0; else nf->hvalid=0; np->next=nf; nf->next=NULL; nf->p=p; break; } np=np->next; } while(1); } if (magnetic) nf->hvalid=1; else nf->evalid=1; l=0; for (i=0;i<3;i++) { if (magnetic) { nf->hre[i] = mag[i]*cos(phase[i]*M_PI/180); l += nf->hre[i]*nf->hre[i]; nf->him[i] = mag[i]*sin(phase[i]*M_PI/180); l += nf->him[i]*nf->him[i]; } else { nf->ere[i] = mag[i]*cos(phase[i]*M_PI/180); l += nf->ere[i]*nf->ere[i]; nf->eim[i] = mag[i]*sin(phase[i]*M_PI/180); l += nf->eim[i]*nf->eim[i]; } } l=sqrt(l); if (magnetic) { if (l>ne->maxh) ne->maxh=l; } else { if (l>ne->maxe) ne->maxe=l; } /* read next line */ fgets(s,200,f); } while(1); } int read_nec_output(FILE *f) /* tries to read NEC output data from f; returns 1 if not succesful */ { char s[200]; char *p; double zr,zi; int new_rp_index=-1; int near_index=-1; int curr_index=-1; NECoutput *ne; double freq; int i; Currents *cu=NULL; int oldnumneco; oldnumneco=numneco; /* skip all lines until a line containing "STRUCTURE SPECIFICATION": this effectively skips all comments (from CM cards) and text resulting from reading Numerical Green's Function parts. Of course, if a user writes "STRUCTURE SPECIFICATION" in his comment lines, this still fails... */ do { fgets(s,200,f); } while (!feof(f) && !strstr(s,"STRUCTURE SPECIFICATION")); if (feof(f)) return 1; /* check whether segmentation and surface patch data is included, and if so, read it */ do { fgets(s,200,f); if (strstr(s,"SEGMENTATION DATA")) cu=read_nec_output_seg(f); if (cu && strstr(s,"SURFACE PATCH DATA")) read_nec_output_patches(f,cu); } while (!feof(f) && !strstr(s,"FREQUENCY")); printf("# freq. Zr Zi SWR gain f/b phi theta\n"); while (!feof(f)) { EXPAND_IF_NECESSARY(numneco,maxfreqs,neco) /* search for the frequency and read it */ do fgets(s,200,f); while (!feof(f) && !strstr(s,"FREQUENCY=") && !strstr(s,"FREQUENCY : ")); if (feof(f)) break; p = strchr(s,'='); if (!p) p = strchr(s,':'); freq = atof(p+1); /* find the right place in memory, and prepare it if needed */ if (numneco==0 || freq>neco[numneco-1].f) { ne=neco+numneco; numneco++; ne->f=0; } else { for (i=0, ne=neco; if>=freq) { if (ne->f==freq) break; memmove(ne+1,ne,(neco+numneco-ne)*sizeof(NECoutput)); numneco++; if (new_rp_index>=i) new_rp_index++; break; } } if (ne->f!=freq) { ne->f = freq; ne->rpgpovalid = 0; ne->rp = NULL; ne->cu = NULL; ne->nf = NULL; ne->maxe = ne->maxh = 0; ne->d[neco_maxgain]=-DBL_MAX; ne->d[neco_maxgain_hor]=-DBL_MAX; ne->d[neco_maxgain_vert]=-DBL_MAX; ne->d[neco_maxgain_lhcp]=-DBL_MAX; ne->d[neco_maxgain_rhcp]=-DBL_MAX; ne->d[neco_zr]=0; ne->d[neco_zi]=0; ne->d[neco_swr]=0; ne->d[neco_zphi]=0; ne->d[neco_zabs]=0; } /* find and read the input impedance, and calculate the SWR */ #if 0 do fgets(s,200,f); while (!feof(f) && !strstr(s,"ANTENNA INPUT PARAMETERS")); if (feof(f)) break; do { /* skip lines until a line starting with a digit is found */ fgets(s,200,f); p=s; while (*p==' ') p++; } while (!feof(f) && !isdigit(*p)); if ((p=strchr(s,'*'))) *p=' '; if (sscanf(s,"%*i%*i%*g%*g%*g%*g%lg%lg",&zr,&zi)!=2) break; ne->d[neco_zr]=zr; ne->d[neco_zi]=zi; calcswr(ne); calcphiabs(ne); #endif /* check whether radiation pattern data is included for this frequency, and if so, read it */ /* check also whether input impedance data is included for this frequency, and if so, read it */ /* check also whether currents and/or charge data is included for this frequency, and if so, read it */ /* check also whether near field data is included for this frequency, and if so, read it */ do { fgets(s,200,f); if (cu && strstr(s,"CURRENTS AND LOCATION")) { if (ne->cu) { fprintf(stderr,"File contains more than one set of currents at a frequency; ignoring earlier data\n"); free(ne->cu->s); free(ne->cu); } ne->cu=read_nec_output_currents(f,cu); if (ne->cu && curr_index<0) curr_index=ne-neco; } if (ne->cu && strstr(s,"SURFACE PATCH CURRENTS")) ne->cu=read_nec_output_patchcurrents(f,ne->cu); if (cu && strstr(s,"CHARGE DENSITIES")) { if (!ne->cu) fprintf(stderr,"Charge density information should come after currents information; ignoring it\n"); else read_nec_output_charges(f,ne->cu); } if (strstr(s,"NEAR ELECTRIC FIELDS")) { read_nec_output_nearfield(f,ne,0); if (near_index<0) near_index=ne-neco; } if (strstr(s,"NEAR MAGNETIC FIELDS")) { read_nec_output_nearfield(f,ne,1); if (near_index<0) near_index=ne-neco; } if (strstr(s,"RADIATION PATTERNS")) { read_nec_output_rad(f,ne); if (!ne->rp) return 1; if (new_rp_index<0) new_rp_index=ne-neco; } if (strstr(s,"ANTENNA INPUT PARAMETERS")) { do { /* skip lines until a line starting with a digit is found */ fgets(s,200,f); p=s; while (*p==' ') p++; } while (!feof(f) && !isdigit(*p)); if ((p=strchr(s,'*'))) *p=' '; if (sscanf(s,"%*i%*i%*g%*g%*g%*g%lg%lg",&zr,&zi)!=2) continue; ne->d[neco_zr]=zr; ne->d[neco_zi]=zi; calcswr(ne); calcphiabs(ne); } } while (!feof(f) && !strstr(s,"FREQUENCY")); if (ne->cu) normalize_currents(ne->cu); /* print the data */ printf("%8g %8g %8g %8g ",ne->f,zr,zi,ne->d[neco_swr]); if (ne->rp) if (ne->d[neco_fb]>-DBL_MAX) printf("%8g %8g %8g %8g\n",ne->d[neco_maxgain],ne->d[neco_fb],ne->d[neco_phi],ne->d[neco_theta]); else printf("%8g - %8g %8g\n",ne->d[neco_maxgain],ne->d[neco_phi],ne->d[neco_theta]); else printf(" - - - -\n"); } if (cu) { free(cu->s); free(cu); } if (rp_index<0 || rp_index>=numneco) { if (new_rp_index>=0) rp_index=new_rp_index; else if (near_index>=0) rp_index=near_index; else if (curr_index>=0) rp_index=curr_index; } if (neco[rp_index].rp==NULL && new_rp_index>=0) rp_index=new_rp_index; return (numneco==oldnumneco); } #define pow10(x) exp(M_LN10*(x)) void process_nec_output(NECoutput *ne) /* transform gain distribution into an array of points in 3D space */ { int i,j; double r=0; Radpattern *rp; if (ne->rpgpovalid) return; rp=ne->rp; if (extr_str==0) extr=1; else extr=extr_str*GAINSIZE; while (rp) { for (i=0;inumtheta;i++) for (j=0;jnumphi;j++) { double db; double f; Gain *g=&rp->gain[j][i]; f=calc_polfactor(polarization,g); if (f<1e-200) r=0; else { db=g->total-globalmaxdb; switch (gainscale) { case GSlinpower: r=f*pow10(db/10); break; /* linear in power */ case GSlinvolt: r=sqrt(f)*pow10(db/20); break; /* linear in voltage */ case GSarrl: r=exp(0.116534*(db+10*log10(f))/2); break; /* arrl */ case GSlog: r=db+10*log10(f); if (r<-40) r=0; else r=r/40+1; break; /* log */ } r*=extr; } if (r<1e-6) r=1e-6; /* prevent points from being just about exactly at the origin, since this confuses the edge hiding calculations */ rp->gpo[j][i].x = rp->cos_gphi[j] * rp->sin_gtheta[i] * r; rp->gpo[j][i].y = rp->sin_gphi[j] * rp->sin_gtheta[i] * r; rp->gpo[j][i].z = rp->cos_gtheta[i] * r; } rp=rp->next; } ne->rpgpovalid=1; } void calc_vgain(void) /* update the vgain records in accordance with the viewing direction */ { NECoutput *ne; int i,j,k; int i0=0,j0=0; Radpattern *rp,*rp0; double dif,d,d0; double ph,th; ph=fmod(phi,360.)*(M_PI/180.); ph+=5*M_PI; th=theta*(M_PI/180.); for (k=0, ne=neco; krp; ne->d[neco_vgain]=-DBL_MAX; ne->d[neco_vgain2]=-DBL_MAX; dif=DBL_MAX; rp0=NULL; while (rp) { d0=DBL_MAX; i0=0; for (i=0;inumtheta;i++) { d = fabs(rp->gtheta[i]-th); if (dnumphi;j++) { d = d0 + fabs(fmod(ph-rp->gphi[j],2*M_PI)-M_PI); if (dgain[j0][i0]; ne->d[neco_vgain]=g->total; ne->d[neco_vgain2]=g->total+10*log10(calc_polfactor(polarization,g)); } } } rp=rp->next; } if (rp0) { double ph0,th0; th0=rp0->gtheta[i0]; ph0=rp0->gphi[j0]; for (i=0;inumtheta;i++) { double d; d = fmod(fabs(rp0->gtheta[i]+th0),2*M_PI); if (fabs(d-M_PI) < 0.00001) break; } for (j=0;jnumphi;j++) { double d; d = fmod(fabs(rp0->gphi[j]-ph0),2*M_PI); if (fabs(d-M_PI) < 0.00001) break; } if (inumtheta && jnumphi) { Gain *g; g=&rp0->gain[j][i]; ne->d[neco_vfb] = ne->d[neco_vgain2]-g->total+10*log10(calc_polfactor(polarization,g)); ne->d[neco_vfb2] = ne->d[neco_vgain2]-g->total; } else { ne->d[neco_vfb] = -DBL_MAX; ne->d[neco_vfb2] = -DBL_MAX; } } } } void mark_gpo_invalid(void) { NECoutput *ne; int k; for (k=0, ne=neco; krpgpovalid=0; } xnecview-1.37/xnecview.c0000640000076600007660000003303413606626462013462 0ustar fwmfwm/* XNECVIEW - a program for visualizing NEC2 input and output data * * Copyright (C) 1998-2006,2011,2014,2020 Pieter-Tjerk de Boer -- pa3fwm@amsat.org * * Distributed on the conditions of version 2 of the GPL: see the files * README and COPYING, which accompany this source file. * * Main module, which mostly deals with initial setup, (re)reading of * files, and parsing command line options. */ #include #include #include #include #include #ifndef NO_GETOPT #include #endif #include "xnecview.h" double extr=0; double extr_str=0; int gainscale=0; int gainplot; int structplot; int scaleplot=0; int phasedirlock=0; double phasephi, phasetheta; int polarization=POLnone; int distcor=1; double phaseoffset=0; int maxthickness=10; double animphase=0; double animfreq=1.0; double anim_update_interval=Default_anim_update_interval; double escale=1; double hscale=1; int show_p=1; double qscale=1; double iscale=1; int g_argc; char **g_argv; char *inputfilename; int window1open=0; int window2open=0; char *expeps=NULL; char *exppng=NULL; /*---------- global variables, together containing the antenna structure -------------*/ int numwires=0; int maxwires=1; Wire *wires=NULL; int numsurfaces=0; int maxsurfaces=1; Surface *surfaces; int numexcis=0; int maxexcis=1; Exci *excis=NULL; int numloads=0; int maxloads=1; Load *loads=NULL; int numnetws=0; int maxnetws=1; Netw *netws=NULL; /*---------- global variables, together containing the gain distribution -------------*/ int numneco=0; int maxfreqs=1; NECoutput *neco=NULL; int rp_index=-1; /* index of the entry in neco[] whose radiation pattern is being shown in the 3D plot */ /*------------------------------------------------------------------------------------*/ void info() { puts("Copyright (C) 1998-2020 Pieter-Tjerk de Boer -- pa3fwm@amsat.org\n"); puts("Xnecview comes with ABSOLUTELY NO WARRANTY. This is free software, and you are\n" "welcome to redistribute it under the conditions of version 2 of the GNU GPL.\n" "For more information: see the files README and COPYING which should accompany\n" "this software, or ask the author.\n"); } void usage() { puts("Usage: xnecview [options] filenames [options]\n" "filenames : names of NEC2 input and/or output files to be displayed\n" "options:\n" " -h or --help : show this information\n" #ifndef NO_GETOPT " --struct : set structure view to 'struct'\n" " --tags : set structure view to 'struct+tags'\n" " --currents : set structure view to 'currents'\n" " --animation : set structure view to 'animation'\n" " --slice : set radiation view to 'slice'\n" " --frame : set radiation view to 'frame'\n" " --opaque : set radiation view to 'opaque'\n" " --near : set radiation view to 'near field'\n" " --linpower : set radiation scale linear in power\n" " --linvoltage : set radiation scale linear in voltage\n" " --arrl : set radiation scale to ARRL style\n" " --log : set radiation scale to logarithmic\n" " --qscale num : set charges scale (animation)\n" " --iscale num : set currents scale (animation)\n" " --escale num : set electric field scale\n" " --hscale num : set magnetic field scale\n" " --hidepoynting : hide Poynting vector in near field display\n" " --afreq num : set animation frequency (Hz)\n" " --aphase num : set animation phase (degrees)\n" " --aupdate num : set time between animation updates (milliseconds, default 100)\n" " --freq num : set frequency (MHz)\n" " --z0 num : set reference impedance (ohm)\n" " --expeps filename : only export picture to .eps-file\n" #ifdef HAVE_LIBPNG " --exppng filename : only export picture to .png-file\n" #endif " --view phi,theta,zoom,trx,try : set viewing direction and zoom\n" "Note: typing 'v' prints the current values for all of these settings,\n" "for easy copying into scripts.\n" #endif ); exit(1); } void readfile(char *s) { FILE *f; f=fopen(s,"r"); if (!f) { fprintf(stderr,"Can't open \"%s\" for reading\n",s); usage(); } if (read_nec_output(f)==1) { /* try interpreting as output data first */ rewind(f); read_nec_input(f); /* if data is not output data, try interpreting it as input data */ } } void reread(void) { int i; int old_rp_index; numwires=0; numsurfaces=0; numexcis=0; numloads=0; extr=0; globalmaxdb=-1e30; while (numneco>0) { /* delete all old output data and free the associated Radpattern arrays */ Radpattern *rp,*rp1; Nearfield *nf,*nf1; numneco--; rp=neco[numneco].rp; while (rp) { for (i=0;inumphi;i++) { free(rp->gain[i]); free(rp->gpo[i]); } free(rp->gain); free(rp->gpo); free(rp->gphi); free(rp->gtheta); free(rp->sin_gphi); free(rp->cos_gphi); free(rp->sin_gtheta); free(rp->cos_gtheta); rp1=rp; rp=rp->next; free(rp1); } if (neco[numneco].cu) { free(neco[numneco].cu->s); free(neco[numneco].cu); } nf=neco[numneco].nf; while (nf) { nf1=nf; nf=nf->next; free(nf1); } } old_rp_index=rp_index; rp_index=-1; for (i=0;i=0) { if (old_rp_index>=0 && old_rp_index