MoI discussion forum
MoI discussion forum

Full Version: NACA Airfoil script

Show messages:  1-20  21-31

From: Hamish Mead (HAIRYKIWI)
19 Feb 2015   [#1]
Hi everyone,

First off - many thanks Petr, Bemfarmer, Smirnov and Michael for all your really helpful javascript examples. Also to David Morrill for his MoI javascript reference.

I'm part way through making a script to generate NACA four- and five-digit series airfoils (wing sections). It could probably easily be extended at a later date to generate the six-digit series also.

Right now I'd like to try improving code efficiency and need some help please. Here's the problem:
Reverse the array of (x,y,z) points within one interpcurve GeometryFactory, then add to it the array of points from a second GeometryFactory - more efficiently than the method I've so far hacked together in the code below.

Where I'm at:
Two working versions -
- One generates two separate upper and lower curves - this code seems reasonably fast enough.
- The other (code snippet below) generates one continuous curve. It's significantly slower because it has to copy the x,y,z, values of points from two PointObjectLists (upper and lower) to a single 'interpcurve' factory.
The benefit of the second version is that it improves on the first by removing a subtle blip in curvature continuity at the airfoil Leading Edge in a 'two curve' solution.

MoI JavaScript code:
Update()
{
    var num_stations = moi.ui.commandUI.num_stations.numericValue;
    var cosine_spacing =  moi.ui.commandUI.cosine_spacing.value;
    var PointObjectListUpper = moi.geometryDatabase.createObjectList();
    var PointObjectListLower = moi.geometryDatabase.createObjectList();
    var FactoryAirfoil =  moi.command.createFactory( 'interpcurve' );
    var PointFactory = moi.command.createFactory( 'point' );

    for (i = 0; i < num_stations + 1; ++i)
    {
        if (cosine_spacing == 0)
        {
            x = i/num_stations;
        }
        else if (cosine_spacing == 1)
        {
            x = ( 1 - Math.cos( (i/num_stations) * Math.PI) )/2;
        }
        
        // ... airfoil upper and lower surface x y calcs happen here ...
        
        PointFactory.setInput( 0, moi.vectorMath.createPoint( x, y, z  ) );
        PointObjectListUpper.addObject( PointFactory.calculate().item(0) );
        PointFactory.reset();
        
        PointFactory.setInput( 0, moi.vectorMath.createPoint( x, y, z  ) );
        PointObjectListLower.addObject( PointFactory.calculate().item(0) );
        PointFactory.reset();
    }
    
    // Create one continuous airfoil curve using the values from the PointObjectLists calculated above.
    // Step 1: Copy points from PointObjectListUpper in REVERSE order into FactoryAirfoil
    // i.e. startpoint of airfoil curve is at the trailing edge and proceeds forward over the airfoil upper surface:
    for ( g = 0; g < num_stations + 1; ++g )
    {
        FactoryAirfoil.createInput( 'point' );
        FactoryAirfoil.setInput( g, moi.vectorMath.createPoint( PointObjectListUpper.item(num_stations - g).pt.x, \
        PointObjectListUpper.item(num_stations - g).pt.y, PointObjectListUpper.item(num_stations - g).pt.z ) );
    }
    
    // Continue along the airfoil lower surface:
    // Step 2: Copy points from PointObjectListLower directly to (end of) FactoryAirfoil
    for ( g = 1; g < num_stations + 1; ++g )
    {
        FactoryAirfoil.createInput( 'point' );
        FactoryAirfoil.setInput( num_stations + g, moi.vectorMath.createPoint( PointObjectListLower.item(g).pt.x, \
        PointObjectListLower.item(g).pt.y, PointObjectListLower.item(g).pt.z ) );
    }

    FactoryAirfoil.update();
    PointFactory.cancel();
    
    // return airfoil profile and points to main function to enable either or both to be committed
    return { 'FactoryAirfoil':FactoryAirfoil, 'PointObjectListLower': PointObjectListLower, \
    'PointObjectListUpper': PointObjectListUpper };
}


I'll post at least one working airfoil generating script when I've cleaned up the comments and aligned variable names with the 'standard' described in the pdf's below:
http://www.pdas.com/refs/tm4741.pdf
http://www.dept.aoe.vt.edu/~mason/Mason_f/CAtxtAppA.pdf
http://www.pdas.com/refs/aiaa5235.pdf
http://www.pdas.com/naca456.html
http://airfoiltools.com/airfoil/naca5digit

Cheers,
Hamish
From: Michael Gibson
19 Feb 2015   [#2] In reply to [#1]
Hi Hamish, I don't see anything obvious that would easily be changed to speed that up - what you're probably running into is overhead from inter-process communication. Command scripts run in a separate process called moi_commandprocessor.exe and every property access or method call into a MoI object involve some communication between moi_commandprocessor.exe and the main moi.exe process. Most regular Moi commands only really control high level program flow like showing UI, setting up point pickers, stuff like that which does not involve any significant loops. So the overhead for the interprocess communication is not really noticeable in those cases. But for a script that's doing some kind of loop that per-call overhead can add up.

One thing you can do to improve performance is to move chunks of your script code into the .htm file, scripts that run in that script context are running inside of MoI.exe directly and so have a much lower per-call overhead. But the downside of that is that it's also then more possible for those scripts to lock up MoI if they go into an infinite loop or do heavy processing. One of the main reasons why the scripts run in a separate process normally is so they can't lock up the main process and can be forcibly terminated more easily.

In order to run script code directly in MoI.exe you put the script code inside the .htm part of your command, instead of a <script> block in the <head> of the HTML document, then inside of your .js command file you can call into that .htm script by accessing the HTML global object by using moi.ui.commandUI . So for example if there is a script function called MyScript inside the .htm file it can be run from the regular command script by calling moi.ui.commandUI.MyScript();

That's probably what would be involved to get more performance out of your script, it's difficult to do much to improve loop performance on out of process scripts.

In the future I want to try and improve how remote process access works by kind of batching up property accesses and maintaining a local version of the object being accessed rather than having inter-process calls for each individual property accesses as it currently does. That will probably involve a fair amount of work though.

- Michael
From: bemfarmer
19 Feb 2015   [#3] In reply to [#1]
Hi Hamish

Are you coding for one more station than the number of stations, with index i ?
for example, if num_stations = 5, index i has the values 0, 1, 2, 3, 4, 5, which are six values.

I do not see all of the code. Maybe the second curve stations can be calculated in reverse order?

I've found .htm scripting to be more challenging, but the most recent methods I've used, (borrowed from MaxS), can really speed things up.

(Have not read your pdf's yet.)

- Brian
From: Hamish Mead (HAIRYKIWI)
19 Feb 2015   [#4] In reply to [#2]
Hi Michael, Many thanks for your reply.

I'd read about the method you're suggesting in another thread, so I'll definitely try it out. Sorry to take your time explaining it again.

I wrote for help initially thinking I was just overlooking a method for getting a complete point array, and that inherently it would be more efficient. I had overlooked it, (just discovered a few moments ago). It isn't noticeably any more efficient execution time-wise, for the same reasons you already gave I guess. But it is MUCH easier to read, so I'll be content with that:

(1) code:
FactoryAirfoil.setInput( g, PointObjectListUpper.item(num_stations - g).pt.get);

(2) code:
FactoryAirfoil.setInput( g, moi.vectorMath.createPoint( PointObjectListUpper.item(num_stations - g).pt.x, PointObjectListUpper.item(num_stations - g).pt.y, PointObjectListUpper.item(num_stations - g).pt.z ) );

Method (1) works equally as well as my original (2).

I'll try to post the script tomorrow.

Cheers,
Hamish
From: Hamish Mead (HAIRYKIWI)
19 Feb 2015   [#5] In reply to [#3]
Hi Brian,

I'm glad you spotted this thread :)

I'm pretty certain the index numbering isn't causing any problems. Please do take a look when I post the full initial script later today.

I did think about calculating the airfoil upper surface curve in reverse order, and then adding the lower surface curve, but gave up on it as wasteful to do a moderately complex and identical airfoil thickness distribution calculation twice. It would be an interesting exercise to try out though and not difficult.

As I said, I'm glad you spotted this thread because I'd be interested to see if some of the work you've done on generating some of the more specialised (spiral) curves might be applied to describing the thickness-distribution of NACA's five-digit series airfoil odd-ball: the 43012A.

It has it's very own thickness distribution that was never formalised by any equation; certainly none that I can find. Even the seminal NACA Technical Report 610 of 1936 only talks of "filling out the concave portion of the lower surface near the nose of the NACA 43012 airfoil and thickening the upper surfaces so that the mean [chord] line is unchanged".

I've already created a 14 coefficient, almost oscillation-free, polynomial to describe the 43012A thickness distribution (using desmos.com to do some pretty cool interactive regression), and it's a pretty close fit. But I wonder if log aesthetic curve segments might fit even better. Or it may be easier still and just as good/accurate just to store ordinates of a best-fit to points, faired NURBS curve. I'd be interested to discuss it some more, after I post the script so far.

Cheers,
Hamish
From: Hamish Mead (HAIRYKIWI)
21 Feb 2015   [#6]
Here's the first release of the NACA Airfoil Generating script :)



Release notes copied here for convenience:

REVISION HISTORY
DATE CHANGES
2015-02-21 Initial ALPHA release.

RELEASE NOTE:
This version only generates a NACA five-digit series airfoil and
the associated airfoil mean chord line. Output has NOT YET BEEN VERIFIED
against official NACA ordinates available in the pdf documents below.

KNOWN LIMITATIONS:
- T.E. currently only created as in the 'open' style (as described by NACA).
A line segment closes the otherwise open T.E., however it and the airfoil
profile are not joined by the script, (for want of MoI geometry database
method knowledge); do it manually for the meantime if required.
- If coefficient A4 were to be edited to generate a closed T.E., the
T.E. shape is incorrect. (as permitted by NACA documentation)
TODO: make closed T.E. curve start(?) and endpoints corner points.
TODO: make T.E. thickness user variable (var A4).
- Reflex camber lines are not yet implemented.
- TODO: Implement four-digit series, 43012A, six-digit series; in roughly
that order.


I got a little distracted playing with hiding/unhiding divs in the UI (to enable separate four-digit and five-digit series UIs). It only partially worked, so I returned the htm to an earlier version for the moment.

I also played with joining a line segment to a curve, before each is committed - it's not working yet - I just don't have a grasp on the construct of a GeometryFactory vs a GeometryDatabase.
I got stuck trying to get (convert?) the ObjectList objects from an interpcurve factory into a GeomObject object for the join factory. I'm not sure it's even possible, or a correct approach.

Brian - you experienced similar problems with your prop generating script: http://moi3d.com/forum/index.php?webtag=MOI&msg=4801.159 - but I can't see how you implemented the methods from the SpurGearProfile in PropDesignGeoThru_1_7_2012.7z - if that even is the version in which you got the join working.

The ideal solution would actually be to forget about a join and simply make the first and last points corner points. Is it even possible to specify that points in a GeometryFactory are corner points? I need the last point of the airfoil curve to be a corner point in any case, if the airfoil profile is to be a closed one. Equally, there would be two corner points if I was to close the otherwise open, (NACA terminology for a trailing edge (T.E.) edge with finite thickness) trailing edge with a line segment.

I also found some potentially useful extra methods not yet documented in David Morrill's 'Moment of Inspiration Javascript Documentation' in the moi.idl file:
[GeometryFactory].getInputObjects() and [GeometryFactory].getCreatedObjects() - are these of any use or am I barking up the wrong tree? Where might they be used anyway?

Thanks for any further help or comments - and please tell me about any bugs (I expect many).

BTW, if you're not into building wings, some of the thicker profiles this script generates make a good budgerigar-like profile for further lofting.

Cheers,
Hamish

Attachments:
NACAAirfoilGenerator-2015-02-21.zip

Image Attachments:
NACAAirfoilGenerator-2015-02-21-800px.png 


From: Frenchy Pilou (PILOU)
21 Feb 2015   [#7]
French Version ;)
http://moiscript.weebly.com/aile-naca.html


From: Michael Gibson
21 Feb 2015   [#8] In reply to [#6]
Hi Hamish, some great progress!

> I also played with joining a line segment to a curve, before each is committed - it's not
> working yet - I just don't have a grasp on the construct of a GeometryFactory vs a
> GeometryDatabase.
> I got stuck trying to get (convert?) the ObjectList objects from an interpcurve factory into
> a GeomObject object for the join factory. I'm not sure it's even possible, or a correct approach.

One thing you might be running into is if a factory does asynchronous object generation (meaning it actually does its calculation in a separate moi_commandprocessor.exe process, generally used for possibly long running calculations) the inputs you give to it need to be active in the geometry database and not a "loose object" that has just been dynamically created but not added to the geometry database yet. You get a "loose object" if you use factory.calculate() to generate it. These objects currently have to be added in to the geometry database in order for them to be used as inputs to an async factory and Join is one of these types of factories. I'd like to change how this works in the future so as not to have this requirement but it will take some work since some parts of object serialization and deserialization are based off of a unique object id number that is set when objects get added to the geometry database.


> I just don't have a grasp on the construct of a GeometryFactory vs a GeometryDatabase.

A GeometryFactory is something that you use to generate objects, it has inputs that can be set on it, and then for regular commands a call to factory.update() will cause the factory to generate objects and put them in the GeometryDatabase. The GeometryDatabase is not something you create yourself, it's basically the master list of all objects in the model, for example when the screen draws it's every object in the GeometryDatabase that gets drawn.

> [GeometryFactory].getInputObjects() and [GeometryFactory].getCreatedObjects() - are these of any
> use or am I barking up the wrong tree? Where might they be used anyway?

You can call these if you use factory.update() - the call to factory.update() will generate objects and put them into the GeometryDatabase automatically, if you then want to get a hold of an object list of what was actually generated then the .getCreatedObjects() will do that.


Most things in the current scripting environment are heavily oriented towards creating things in the same way as a regular command would, basically by setting some options and then having the objects show up on screen, so usually a regular MoI command just calls factory.update() and does not deal with the generated objects directly itself after that.

- Michael
From: bemfarmer
22 Feb 2015   [#9] In reply to [#8]
I'll be looking at the script over the next few days...

- Brian
From: Hamish Mead (HAIRYKIWI)
23 Feb 2015   [#10]
Hi Guys,

Pilou - merci beaucoup! :)

Brian - Thanks for taking a look.

Michael, Thanks for your explanations - that's helped a lot, conceptually.

As for putting it into practise, I'm still missing something.

I tried:
code:
var objectstojoin = moi.geometryDatabase.createObjectList();
objectstojoin.addObject( FactoryAirfoil.update().getCreatedObjects() );
objectstojoin.addObject( FactoryLine.update().getCreatedObjects() );
var FactoryJoin = moi.command.createFactory( 'join' );
FactoryJoin.setInput( 0, objectstojoin );


This was the result:



In future, can I somehow capture the MoI javascript error code as text instead of a screen capture?

Cheers,
Hamish

Edit: code now in code block

Image Attachments:
naca-airfoil-generator-error201502231123.png 


From: ttype (STRUBE)
23 Feb 2015   [#11] In reply to [#10]
I don't know if it works for your script, but do you know this very simple script console?

https://sites.google.com/site/nakanosoramemo/software/moi3d/script-console
From: Hamish Mead (HAIRYKIWI)
23 Feb 2015   [#12] In reply to [#11]
Hi ttype,
Thanks! That's definitely faster for testing parts of a script.

- Hamish
From: Max Smirnov (SMIRNOV)
23 Feb 2015   [#13] In reply to [#12]
Hi Hamish,

>>objectstojoin.addObject( FactoryAirfoil.update().getCreatedObjects() );
You are trying to use ObjectList instead of Object.

var objects = FactoryAirfoil.update().getCreatedObjects();
for (var i=0; i<objects.length; i++) objectstojoin.addObject(objects.item(i));
From: Hamish Mead (HAIRYKIWI)
23 Feb 2015   [#14] In reply to [#13]
Thanks Max!

Conceptually that's really helpful, but now the .update() method returns nothing usable. so I tried:

var objectlist = FactoryAirfoil.calculate().getCurves(); // should return an objectlist of curve items within the objectlist returned by the .calculate???
for (var i=0; i<objectlist.length; i++) objectstojoin.addObject(objectlist.item(i));

Now no error is thrown - but also there is no output - but I'm in a rush.

Will have time to look into it later today.

Cheers,
Hamish
From: Michael Gibson
23 Feb 2015   [#15] In reply to [#14]
Hi Hamish, to put in a code block here in the forum use <code></code> make sure you're not using something like [code] instead.

re:
> objectstojoin.addObject( FactoryAirfoil.update().getCreatedObjects() );
> objectstojoin.addObject( FactoryLine.update().getCreatedObjects() );

The problem here is factory.update() does not return anything directly itself, it just causes the geometry factory to calculate the objects and update the factory's own internal lists of objects for what was created. If you then want to get at those created objects you need to call factory.getCreatedObjects().

That's different than factory.calculate() - calling factory.calculate() does directly return an object list to the caller and generates "loose objects" which are not yet part of the geometry database main object list of objects to draw and save in the file and so forth.

So your code would need to be changed to have the .update call on a previous line before you call factory.getCreatedObjects(), something like this instead (and also be modified to deal with the ObjectList that is returned from getCreatedObjects(), you are treating it as a single object being returned rather than an object list), something like:

FactoryAirfoil.update();
var objectlist = Factory.getCreatedObjects();
for ( var i = 0; i < objectlist.length; i++ ) objectstojoin.addObject( objectlist.item(i) );

- Michael
From: bemfarmer
23 Feb 2015   [#16] In reply to [#14]
Hi Hamish,

There is some Joining done in the chain script, and more recently in the _elastica2 script.

- Brian
From: Hamish Mead (HAIRYKIWI)
23 Feb 2015   [#17] In reply to [#15]
Hi Michael,

Thanks for your suggestions and further explanations.

In a hurry, I was using <code><\code> - i.e. wrong slash direction. :/

I found the .update() .getCreatedObjects() approach you suggested 'sort of works', but leaves 'orphaned display objects'; see my note at the end of the first code block below.

Here's are three 'Join Two Line Segment' examples (can be copy pasted into the script console that ttype mentioned earlier):
Partially working code:
var line_a = moi.command.createFactory( 'line' );
line_a.setInput( 0, moi.vectorMath.createPoint( 0, 0, 0 ));
line_a.setInput( 1, moi.vectorMath.createPoint( 1, 1, 0 ));
line_a.update();
var objectlist_a = line_a.getCreatedObjects();

var line_b = moi.command.createFactory( 'line' );
line_b.setInput( 0, moi.vectorMath.createPoint( 0, 0, 0 ));
line_b.setInput( 1, moi.vectorMath.createPoint( 1, -1, 0));
line_b.update();
var objectlist_b = line_a.getCreatedObjects();

var objectstojoin = moi.geometryDatabase.createObjectList();
for ( var i = 0; i < objectlist_a.length; i++ ) objectstojoin.addObject( objectlist_a.item(i));
for ( var i = 0; i < objectlist_b.length; i++ ) objectstojoin.addObject( objectlist_b.item(i));

moi.geometryDatabase.addobjects(objectstojoin);

var factoryjoin_ab = moi.command.createFactory( 'join' );
factoryjoin_ab.setInput( 0, objectstojoin );
factoryjoin_ab.commit();


//  line_a.update() and line_b.update() displays those objects, but of course doesn't commit them,
//  leaving 'orphaned display objects' visible: i.e. objects that are displayed, but never selectable after the script completes.


In the end I discovered that using the factory.calculate().item() approach - without using .getCreatedObjects() - does actually work as required.
Here are two examples:

Working code:
var line_a = moi.command.createFactory( 'line' );
line_a.setInput( 0, moi.vectorMath.createPoint( 0, 0, 0 ));
line_a.setInput( 1, moi.vectorMath.createPoint( 1, 1, 0 ));
var line_a_objectlist = line_a.calculate();

var line_b = moi.command.createFactory( 'line' );
line_b.setInput( 0, moi.vectorMath.createPoint( 0, 0, 0 ));
line_b.setInput( 1, moi.vectorMath.createPoint( 1, -1, 0 ));
var line_b_objectlist = line_b.calculate();

var objectstojoin = moi.geometryDatabase.createObjectList();
for ( var i = 0; i < line_b_objectlist.length; i++ ) objectstojoin.addObject(line_a_objectlist.item(i));
for ( var i = 0; i < line_b_objectlist.length; i++ ) objectstojoin.addObject(line_b_objectlist.item(i));

moi.geometryDatabase.addobjects(objectstojoin);

var factoryjoin_ab = moi.command.createFactory( 'join' );
factoryjoin_ab.setInput( 0, objectstojoin );
factoryjoin_ab.commit();

// Notes:
// .item(i) returns the i'th GeomObject object
// .calculate() returns an ObjectList of objects
// .addobject ONLY takes a GeomObject object
// .addobjects ONLY takes an objectlist



Working code:
var line_a = moi.command.createFactory( 'line' );
line_a.setInput( 0, moi.vectorMath.createPoint( 0, 0, 0 ));
line_a.setInput( 1, moi.vectorMath.createPoint( 1, 1, 0 ));

var line_b = moi.command.createFactory( 'line' );
line_b.setInput( 0, moi.vectorMath.createPoint( 0, 0, 0 ));
line_b.setInput( 1, moi.vectorMath.createPoint( 1, -1, 0 ));

var objectstojoin = moi.geometryDatabase.createObjectList();

objectstojoin.addObject(line_a.calculate().item(0));
objectstojoin.addObject(line_b.calculate().item(0));

moi.geometryDatabase.addobjects(objectstojoin);

var factoryjoin_ab = moi.command.createFactory( 'join' );
factoryjoin_ab.setInput( 0, objectstojoin );
factoryjoin_ab.commit();

// Notes:
// .item(i) returns the i'th GeomObject object
// .calculate() returns an ObjectList of objects
// .addobject ONLY takes a GeomObject object
// .addobjects ONLY takes an objectlist


I'll update the NACA Airfoil script when I get a spare moment.

Good night, and thank you all your help :)

- Hamish
From: Hamish Mead (HAIRYKIWI)
23 Feb 2015   [#18] In reply to [#16]
Thanks Brian. The more scripts to review the better!
From: Michael Gibson
23 Feb 2015   [#19] In reply to [#17]
Hi Hamish,

> I found the .update() .getCreatedObjects() approach you suggested 'sort of works', but leaves
> 'orphaned display objects'; see my note at the end of the first code block below.

You could try putting in a call to factory.cancel(); to have the factory remove the created objects.

But it sounds like you already have a solution!

- Michael
From: Hamish Mead (HAIRYKIWI)
24 Feb 2015   [#20] In reply to [#19]
Hi Michael,

Yeah, I did try factory.cancel() , factory.reset() and even factory.removeLastInput() but none of those methods removed the intermediate objects.

Cheers,
Hamish

Show messages:  1-20  21-31