Antenna geometry export as ASCII text in *.NEC format?

Despite maintining a Rhino license since Version 4, I’m still only a sometimes user, and never yet a Rhino scripter. I was once halfway competent with Perl, even PostScript, and long, long ago also Forth. But never Python, nor yet VB. It has just now struck me, however, something for which a Rhino script might serve very well. Just maybe perhaps, a description thereof might win the interest of a Rhino scripting guru, this being, I’m sure, a completely new application: transmitting antenna design.

As a ham radio operator, call sign KY8D, my thought goes like this. Via Rhino one could generate a *.3DM file consisting of a single joined line. Said line could traverse 3D space in whatsoever arbitrary fashion as the designer might wish to model. Then, calling a script, the point-to-point coordinates of said joined line would be written out to a file as pure ASCII text.

Let me describe what that would be good for. There exists a program in Fortran called NEC, for “National Electromagnetic Code”, which is used to analyze antenna designs. A number of variants (EZNEC, etc) also exist as stand-alone daughter programs. And it is generally one of these which we ham radio operators make use of in designing antennas. They hide from us the actual Fortran and present instead a convenient GUI.

These daughter programs, younger spinoff’s of the full-fledged NEC also have rudimentary CAD design features … emphasis on ‘rudimentary’. Circles, helixes and such come already built in, but still leave a lot to be desired.

What if, for instance, one wishes to model a helix whose centerline, rather than straight, is warped into a semi-circle? Such things are called “helically wound small loop antennas”. No easy way to model one of those using just only EZNEC. In Rhino, however, it would be easy enough. Likewise many another exotic antenna concept: fractal, figure 8, etc. And yet, even these would be comprised of nothing more than (perhaps a great many) straight-line sequences joined end-to-end.

Saving from Rhino to NEC format would require a pure ASCII output as I shall now describe. My hope, very clearly, is that a capable Rhino scriptor might find the idea a fun project. Probably not much of a market, the cross-section of Rhino license holders with ham radio licenses, and who also design antennas being indeed a very few. Nevertheless…

Find my example *.nec file here: EXAMPLE

… comprised of rows of columns like so …

GW 1 5 -0.391 2.469 10 0.391 2.469 10 0.025
GW 2 5 0.391 2.469 10 1.135 2.228 10 0.025

Where the columns are…

  1. Always “GW”
  2. An integer enumerating the “wire number”, same as a line number from 1 to whatever.
  3. An integer representing “segments”, and could always be “1” just to make things easy.
  4. Coord X for start of a wire (straight line segment).
  5. Coord Y for start of a wire.
  6. Coord Z for start of a wire.
  7. Coord X for end of same wire.
  8. Coord Y for end of same wire.
  9. Coord Z for end of same wire.
  10. Diameter of said wire (not modelled, just simply stated, or even always just “1”).

… and so on for the next row, with always the starting X, Y and Z equal to the ending X, Y and Z from the “wire” above. Because, in reality, they are all straight segments of the same actual, physical wire. Delimiters are simply spaces, with CRLF for each line’s end.

Many antennas have plural straight wires, often not touching. But Rhino is hardly needed for those. It’s the curvy ones (modelled as many short, straight-line segments), whose geometries wind through loops, where Rhino can prove a most useful tool in antenna design. So much, anyhow, is my thought.

I could, of course, do the same without any script, by clicking on a line end, then copy-and-pasting the X, Y and Z. Over and over, dozens, maybe hundreds of times. Shudder…

Hello,
OK you caught my interest :smiley:
Her’s a first draft : it prints to the console not to a file - maybe someone else will add that part…?
Run it by typing _EditPythonScript, pasting the contents and hitting Run

from __future__ import print_function, division

import rhinoscriptsyntax as rs
import Rhino

linetype = 4

line = rs.GetObject("Select a curve", linetype, True)
segments = rs.GetInteger("How many segments?", 10, 0)
diameter = rs.GetReal("Wire diameter ?", 2.0)
diameter = round(diameter, 3)

start = True

for i, end_pt in enumerate(rs.DivideCurve(line, segments),1):
    end_pt = ["{:.3f}".format(coord) for coord in end_pt]
    if start:
        start_pt = end_pt
        start = False
    else:
        print ('GW', i, " ".join(start_pt +end_pt), diameter)
1 Like

Exporting point or other data from Rhino to a text or .csv file is one of the easier things to script. There are many tools available in python for example to facilitate this.

Polylines are easy to export because they are just a series of points in order from start to end. The main thing to decide then is how to tessellate (convert to discrete straight segments) non-polyline curves such as splines. Rhino also has plenty of possibilities to do that - you would need to decide whether you want to use a distance-tolerance based method, an maximum angle based method or both… Check out all the possibilities in Rhino’s Convert command, they are all scriptable.

Once the curve to export is converted into a polyline, it is just a mater of proceeding from start to end and writing the text file line by line. Looks like you have the appropriate file format mostly outlined above, one would just need to be sure about the delimiters (separators) between coordinate entries - above you used spaces - and the end of line symbol - probably just a crlf (Windows new line) will do.

I already have a few scripts to write text files, bit not that much time to modify them this morning - I can look at it more later.

–Mitch

1 Like

So quick a response! Thank you most kindly. I shall give it a try this afternoon. Immediately post completion of the weekend’s honey-do list.

Yes, it is just as you say: spaces and CRLF for delimiters. Apologies for not mentioning that. I have edited my original post to be including that information. Your explanations clarify a great deal.

OK, try this one on for size… It only takes a polyline or degree 1 polycurve as input, I didn’t add the convert function (yet) - you can just use Convert in Rhino to do that. I didn’t include any fancy stuff like saving the user input for the wire diameter for next use either… Let me know if it writes the correct format for you.

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

"""
Exports points from a single polyline in .nec data format.
Script by Mitch Heynick 28.04.19

NEC format columns:
1 Always "GW"
2 Integer enumerating the "wire number", starting with 1.
3 Integer representing number of "segments", (just 1 as it is a single polyline)
4 - 6 Coordinates X, Y, Z for start of a wire (straight line segment).
7 - 9 Coordinates X, Y, Z for end of wire ((will also be the start of next line)
10 Diameter of  wire
"""

#line, polyline or degree 1 polycurve
def deg_1_filt(rhino_object, geometry, component_index):
    return rs.CurveDegree(geometry)==1

def ExportPolylineAsNEC():
    msg="Select polyline to export"
    obj = rs.GetObject(msg,4,preselect=True,custom_filter=deg_1_filt)
    if not obj: return
    
    dia=rs.GetReal("Wire diameter?",1.0,sc.doc.ModelAbsoluteTolerance)
    if dia is None: return
    
    filter = "NEC File (*.nec)|*.nec||"
    filename = rs.SaveFileName("Save data file as", filter)
    if not filename: return
    
    #extract polyline from Rhino object
    geo=rs.coercecurve(obj)
    rc,pl=geo.TryGetPolyline()
    if not rc:
        print "Error trying to get polyline from geometry" ; return
        
    #segs will be a list of line objects composing the polyline
    segs=pl.GetSegments() 
    #seg.From = start point  seg.To = end point
    
    #create a string with empty "slots" to be filled later with format method
    #each line also includes a crlf (\n)
    e_str="GW {} 1 {} {} {} {} {} {} {}\n"
    
    #open a new file for writing
    file = open(filename, "w")
    if not file:
        print "Error creating file" ; return
        
    #now iterate through the segment list and write each line
    for i,s in enumerate(segs):
        #fill in the data from each segment
        line=e_str.format(i+1,s.From.X,s.From.Y,s.From.Z,s.To.X,s.To.Y,s.To.Z,dia)
        #write the line to the file
        file.write(line)
    #close the file
    file.close()

ExportPolylineAsNEC()

The above as a .py file:
ExportPolylineAsNEC.py (2.0 KB)

2 Likes

Hi Mitch,
Did you know you can add an optional starting point for enumerate, e.g. enumerate(segs, 1) to start enumerating from 1 ? Doesn’t save any characters in this case but I find it a bit neater than adding 1 later and it’s particularly handy when you use i multiple times.
-G

Yeah, I think I have used that in the past, thanks…!

Tried it. Works great! Thank you most kindly!

I will make a trifling edit to the “NEC format columns:” section regarding “segments”, as follows.

3 The integer 1 as default number of wire segments.

Which is only needful due to my failure to explain that “segments” is a mathematical division, not a physical one. Say you have a rectangular loop antenna, as in really stretched out. NEC will want to regard the long sides and the short sides as if (only virtually) broken into divisions of a given sub-length. That for measuring voltage and current at regular intervals along the whole loop. And changing that number on a per-wire or wire-group basis is trivial in EZNEC, something you have to do all the time … to test at a higher frequency, for instance.

So then, with that single comment change, may I have your permission to share it? All kudos, naturally, belonging to you. Not sure who, if anyone, will employ it besides myself. Anyhow, not until I do something truly useful with Rhino and EZNEC paired. My own antenna design will go into the public domain. I do like that with everything that I create not directly connected with work. Mostly those have been Perl scripts and LabVIEW subroutines (increasingly aged, for the most part).

Thanks again!

Of course. Anything I put out here is to be shared freely with the public - and I’m sure that goes for pretty much everyone who posts scripts here.

1 Like

Excellent! Thank you again.

Would it be greedy to ask if the same thing could be done in reverse? Read in a *.nec file, and replicate each line as a separate line inside Rhino? All separate (because some might not touch). Any joining could be done manually inside of Rhino. Going both ways would make the file sharing complete.

The use of that would be to to allow modifying inside of Rhno other, pre-existing antenna designs, got from public domain sources in *.NEC format. And to design support structures for them, etc.

Already, though, you’ve done plenty on my and other’s behalf. And I thank you most kindly for it. Just hoping that the concept might still hold some interest on your part. If not, then no worries at all.

And, by the way, one of my antenna projects I am hoping will be worth an article in the ham radio journal QST (which is often on-sale in better bookstores). I will make sure to mention the use I am making of Rhino and this most helpful script already written.

No, it would certainly be logical, I’ll look into it later tonight…

Great! I will post a link to some try-out *.nec files when I get home from work. That will be in about an hour.

Well, ahem. It turns out that EZNEC does not export to *.NEC but instead to its own spreadsheet. Nevertheless, I can write a Perl script to convert from that to *.NEC. Text-to-text via Perl I do all the time at work. So maybe best, I think to stick with reading *.NEC into Rhino, as planned. There are other NEC clones besides EZNEC which DO save out to the more general *.NEC format. And I will do a Perl/Tk utility, burning that into an *.EXE for the EZNEC folks. My initial link to a *.NEC file will do for your example then.

Or, if you haven’t already started, and would like a look at the EZNEC wire list, to maybe parse that in instead, I uploaded it here: EZNEC Wire List.

Sorry to have dithered. Regular *.NEC files for input are best. I’ve looked up the sister programs to EZNEC and they take *.NEC as input, not the specialized “Wire List.txt” file. So more generic is best. And I can definitely deal with anything ASCII-to-ASCII via Perl.

And if I may suggest, should the *.NEC file have any line not starting with “GW”, then it ought just be ignored. There do exist other markers, for comments and more esoteric things than lists of straight wire running point-to-point. Hope that simplifies.

Took me awhile to get back to this, too many things going on…

So, first, I re-did the export script to allow multiple polylines to be exported instead of one - also to allow me to create my own files for testing the import script. Then I hacked out an import script (which also accepts multiple segments). Give them a go and tell me what I need to fix here…

Right now there is only one wire diameter for all the exports… and it is ignored on import as all we are producing is polylines. If we wanted to be really fancy, we could attribute a diameter to each segment and on import the polylines could be piped… but that’s another bit of work.

–Mitch

ExportPolylinesAsNEC.py (2.3 KB)

ImportNEC.py (2 KB)

I downloaded and tried those. They both work fine at my end. There seems no point in handling diameters on import, as that would make surfaces, which would not be re-exported back out to *.NEC. And in the event I were making a surface model inside of Rhino, I’d build a curve (at least for circular loops) on points put at the intersections of polyline segments in any case.

Here’s an example of the sort of antenna I’m currently interested in designing and building.

KY8D Fig-8 Loop 30-06m @ 10-350pF & 0r7uH.3dm (25.4 KB)

It’s called a “figure eight small transmitting loop”. Small because the circumference of each hoop is 0.1 wavelength or less. Just like a circular type, but with certain advantages, electrically speaking. I could build it, after some tedium, in EZNEC itself, but not so easy. And particularly as I needed to know the total perimeter length (to be just barely under the 0.1 lambda criteria). Scaling in Rhino for that is a snap.

I have experimented and find that I can build pretty much anything now in Rhino using only these scripts. Where I need single lines, I just call out polylines of one segment only, and they go both out and back in.

Assigning wire diameters in EZNEC is easy peasy. So no need for you to worry about that. I don’t find any issues. I think the scripts work great as is. Sorry it took me a bit to get back, I was bogged down in a LabVIEW program for work. I thank you emensely.

1 Like