
12

Working on wing generator code that computes NACA airfoil sections for a planform that is inserted from an Inkscape tracing. Internal spars & ribs will be parametrically added later. But the first task is to convert the solid polyhedron into a thin 3D printable shell. The thickness needs to be precisely controlled (constant) to be able to fake out the slicer into printing 23 perimeters. The wing shown takes 6 seconds to preview as a solid. A minkowskidifferenceuniverse of the wing with a 0.8mm sphere took over three hours to preview. No hope for render with current resolution. Not sure that a hulldifference instead of a polyhedron would work with undercambered airfoils.
Airfoil polygon sections are computed on the fly and will eventually morph between root and wing tip sections, with some washout added for good measure. These polygons are stitched into the wing polyhedron. The existing OpenSCAD Offset Transform only generates a new 2D object, not a polygon, so there is no way to stitch the result back into the interior of the wing polyhedron as a void. A polygon offset function would also be useful for laser cutting curfs and gear mesh tolerances.
As a feature request, it would be very useful to have a Polygon Offset function that operates on a 2D polygon and returns 2D polygon(s). The engine has to be there already in CGAL with a point/segment representation?
Another possibility is to write a hacky offset function in Python that would return a 2D poly. Does OpenSCAD support user defined Python functions?
The last resort will be to write a hacky user offset function in OpenSCAD that is tuned for airfoils.
At some later point, it would be handy to have a fuselage to go with the wings. Hopefully the solution will be extensible. Reading posts, 3D offset is far off in the future.... Help appreciated.


You might be interested in a different approach, which is quite flexible and a lot faster as it descibes the wing from the scratch. Didn't have your data, so I quickly redefined your wing by rule of thumb using some of my libraries published earlier in thingiverseEach row in X describes an airfoil slice shifted in 3Dspace. An interpolation scheme is used to construct the inbetween slices. Then a sweep() operation is performed to get a half wing.
You are right: there is no 'inset' function in OpenSCAD. Either write one, which is quite easy for airfoil data and/or use a work around by either scaling or constructing a core pendant for each slice. Then use a difference() operation over the wing and its core.
// for libaries refer to: http://www.thingiverse.com/thing:1208001use <Naca4.scad> use <splines.scad> use <Naca_sweep.scad> // wing data  first 4 dimensions describe airfoil
// last 3 dimensions describe xyz offsets
X = [ // Naca L, dx, dy, dz
[.05,.4,.12, 1, 200, 0.5, 0],
[.05,.4,.12, 20, 198, 10, 1],
[.05,.4,.12, 40, 180, 20, 2],
[.05,.4,.12, 80, 52, 40, 20], // edge
[.05,.4,.12, 80, 50, 40, 20],
[.05,.4,.12, 80, 0, 40, 0]
];
Y = nSpline(X, 100); // interpolate wing data
sweep(gen_dat(Y,40));
mirror([1, 0, 0]) // second half
sweep(gen_dat(Y,40));
function gen_dat(X, N=100) = [ for(i=[0:len(X)1])
let(x=X[i]) // get row
let(v2 = airfoil_data(naca = [x[0], x[1], x[2]], L = x[3], N=N))
let(v3 = T_(x[4], x[5], x[6], R_(0, 90, 90,vec3D(v2)))) // rotate and translate
v3];


Could you post the code for a section of the wing?


If you want to create a 3D offset, all you need to do is scale your shape, as I have done in the code attached for an ellipsoid. As I do not have full access to all the vertices of the ellipsoid, this code produces only an approximate 3D offset. But maybe you have access, or maybe you can live with an approximation.
As given, the code renders on my system in 13 seconds.
wolf
$fn=50;
Diameter=2; // diameter of sphere from which ellipsoid is created
Offset=0.1*Diameter;
MaxDim=[3*Diameter,1*Diameter,.5*Diameter]; // long axes of ellipsoid
MakeOffset();
MakeCutout();
TestPosition();
module MakeOffset()
scale([12*Offset/MaxDim[0],12*Offset/MaxDim[1],12*Offset/MaxDim[2]]) // for a true offset, MaxDim[...] needs to be replaced with the norm of each vertex for the ellipsoid
Ellipsoid();
module TestPosition()
translate([MaxDim[0]/2Offset/2,0,0]) sphere(d=Offset);
translate([0,(MaxDim[1]/2Offset/2),0]) sphere(d=Offset);
translate([0,0,MaxDim[2]/2Offset/2]) sphere(d=Offset);
module MakeCutout()
difference()
{ Ellipsoid();
translate([0,15,15]) cube([70,30,30], center=true);
}
module Ellipsoid()
scale([MaxDim[0]/Diameter,MaxDim[1]/Diameter,MaxDim[2]/Diameter]) sphere(d=Diameter);


I am guessing that I'll need tight control on the wall thicknesses to fake out the slicers and keep a precise perimeter thickness for the outer surface. Part of the problem is that for constant wall thickness, the inner airfoil cut out is not a scaled version of the outer surface. And as the airfoil size changes on a tapered wing, you need a new unique inner cutout for each outer airfoil.
@Parkinbot  the splines will come in handy for doing fuselages, but it seems you still have the same problem I was seeing. How do you get the offset inner polygon? I am guessing the minkowski speed is a function of the number of faces and not necessarily the size  Did you try to hollow out your wing? Need to try to dynamically decrease step size where the sections change. How well does your technique deal with airfoil sections containing different numbers of polygon points? For me, stitching together internal airfoil cutouts with differing polygon vertex counts will require some special casing.
@Torsten  yes a function returning a polygon vs a transform. Any suggestion for how to code up a convex poly offset function without writing a duplicate of the CGAL routine in native OpenSCAD?
Is there a way to call python code from within OpenSCAD?
@cbernhardt  let me clean it up
@wolf  not sure that scaling works really well if we're trying to maintain uniform wall thickness. Scaling works ok on an ellipse as in your example, but consider what happens at the pointy trailing edge compared to the leading edge.

Administrator

Zappo wrote
Is there a way to call python code from within OpenSCAD?
Not directly, but you can combine command line usage with include<> or use<>, where your python script populates a scad file with variables which you include.
Admin  email* me if you need anything, or if I've done something stupid...
* click on my MichaelAtOz label, there is a link to email me.
Unless specifically shown otherwise above, my contribution is in the Public Domain; to the extent possible under law, I have waived all copyright and related or neighbouring rights to this work. Obviously inclusion of works of previous authors is not included in the above.
The TPP is no simple “trade agreement.” Fight it! http://www.ourfairdeal.org/ time is running out!


BTW, most slicers offer hollow slicing (infill = 0). Did you try this?
Didn't promise a general solution, but offered a work around. The inpolation approach uses a skeleton with 6 airfoils. Each one needs to be 'paired' with a core airfoil for difference after sweep(). Thats all. The outer one has to be placed carefully with less distance. Viable, but needs some 'tuning'.
Don't know your Naca. But try something like this. (For rotation of polygons Rx_, Ry_, Rz_ from my library may be used.)
use <Naca4.scad> difference()
{
translate([40, 5, 1])
airfoil(naca=[0.05, .4, .08], L=100);
translate([38, 4.5, 1])
rotate([0, 0, 0.4])
airfoil(naca=[0.048, .4, .06], L=90, h = 10);
}
Zappo wrote
I am guessing that I'll need tight control on the wall thicknesses to fake out the slicers and keep a precise perimeter thickness for the outer surface. Part of the problem is that for constant wall thickness, the inner airfoil cut out is not a scaled version of the outer surface. And as the airfoil size changes on a tapered wing, you need a new unique inner cutout for each outer airfoil.
@Parkinbot  the splines will come in handy for doing fuselages, but it seems you still have the same problem I was seeing. How do you get the offset inner polygon? I am guessing the minkowski speed is a function of the number of faces and not necessarily the size  Did you try to hollow out your wing? Need to try to dynamically decrease step size where the sections change. How well does your technique deal with airfoil sections containing different numbers of polygon points? For me, stitching together internal airfoil cutouts with differing polygon vertex counts will require some special casing.


This post was updated on .
Ah, I forgot to answer: I don't see any need for stitching different vertex counts, while doing a sweep().
But if you really want to do it you can use the more general function skin() instead of sweep()  their calls are interchangeable. It was discussed here.
Also union() will connect sweeps with different vertex counts. I use this e.g. to implement furcations.
Zappo wrote
How well does your technique deal with airfoil sections containing different numbers of polygon points? For me, stitching together internal airfoil cutouts with differing polygon vertex counts will require some special casing.


Good point, about the slicers. With Slic3r, one can specify no infill
and a fixed number of perimeter shells, so there would be no need to use
OpenSCAD to create a hollow shape: just create a solid one and use the
slicer to do the dirty work.
On 4/27/2016 7:28 AM, Parkinbot wrote:
> BTW, most slicers offer hollow slicing (infill = 0). Did you try this?
>
>
_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org


Zappo wrote
Any suggestion for how to code up a convex poly offset function without writing a duplicate of the CGAL routine in native OpenSCAD?
In your case, yes, use differentiation. If you take the unit normal at each point of the airfoil you only need to add a fixed scale of it to the point. If the offset is not too large it will not have selfintersections.


Here you have an idea of how to offset noncambered airfoils. The approach applies also to cambered ones.
function unit(v) = v/norm(v);
// Noncambered Naca example
function Naca_airfoil(c, t, x ) = let( xl = x/c)
5*t*c*( 0.2969*sqrt(xl)  0.126*xl  0.3516*pow(xl,2) + 0.2843*pow(xl,3)  0.1015*pow(xl,4) );
function Naca_afl_derivative(c, t, x) = let( xl = x/c)
x > 1e12*c ?
5*t*c*( 0.2969/sqrt(xl)/2/c  0.126/c  0.3516*2*xl/c + 0.2843*3*pow(xl,2)/c  0.1015*4*pow(xl,3)/c ) :
undef;
function Naca_afl_normal(c, t, x) =
x <= 1e6*c ?
[ 1, 0 ] :
unit([ Naca_afl_derivative(c, t, x), 1 ] );
function thin_airfoil(c, t, offset, n) =
// a nonlinear reparametrization to refine the discretization near 0
concat( [ for(x=[0: c/n : c]) let( xl = c*pow(x/c,3) )
[ xl, Naca_airfoil(c, t, xl ) ] ] ,
[ for(x=[c: c/n : 0]) let( xl = c*pow(x/c,3) )
[ xl, Naca_airfoil(c, t, xl ) ]
+ offset*Naca_afl_normal(c, t, xl) ] );
module thin_Naca_airfoil(c, t, offset, n) {
intersection() {
polygon( thin_airfoil(c, t, offset, n) );
square([c,c*t]);
}
}
thin_Naca_airfoil(100, 1/5, 1, 20);


@zappo said Is there a way to call python code from within OpenSCAD?
No, but you can invoke OpenSCAD from Python. Some people use SolidPython for this.
On Wednesday, 27 April 2016, Zappo < [hidden email]> wrote: I am guessing that I'll need tight control on the wall thicknesses to fake
out the slicers and keep a precise perimeter thickness for the outer
surface. Part of the problem is that for constant wall thickness, the inner
airfoil cut out is not a scaled version of the outer surface. And as the
airfoil size changes on a tapered wing, you need a new unique inner cutout
for each outer airfoil.
@Parkinbot  the splines will come in handy for doing fuselages, but it
seems you still have the same problem I was seeing. How do you get the
offset inner polygon? I am guessing the minkowski speed is a function of
the number of faces and not necessarily the size  Did you try to hollow
out your wing? Need to try to dynamically decrease step size where the
sections change. How well does your technique deal with airfoil sections
containing different numbers of polygon points? For me, stitching together
internal airfoil cutouts with differing polygon vertex counts will require
some special casing.
@Torsten  yes a function returning a polygon vs a transform. Any
suggestion for how to code up a convex poly offset function without writing
a duplicate of the CGAL routine in native OpenSCAD?
Is there a way to call python code from within OpenSCAD?
@cbernhardt  let me clean it up
@wolf  not sure that scaling works really well if we're trying to maintain
uniform wall thickness. Scaling works ok on an ellipse as in your example,
but consider what happens at the pointy trailing edge compared to the
leading edge.

View this message in context: http://forum.openscad.org/PolygonOffsetFunctiontp17186p17200.html
Sent from the OpenSCAD mailing list archive at Nabble.com.
_______________________________________________
OpenSCAD mailing list
<a href="javascript:;" onclick="_e(event, 'cvml', 'Discuss@lists.openscad.org')">Discuss@...
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org


Thanks everyone for the ideas.
@Ronaldo "If you take the unit normal at each point of the airfoil you only need to add a fixed scale of it to the point. If the offset is not too large it will not have selfintersections."  I was headed down this path as well, and calculating the derivative directly is interesting. But icky part is that at the wing tips, as the airfoil chord get smaller, the relative thickness of the skin increases relative to the chord, so you have to start playing games reducing the number of points in the airfoil polygon to prevent selfintersections.  but still doable. At some point you just go solid and don't bother with the cut out.
@Parkinbot you're right. Slic3r does exactly the function I am looking for because it could create a 3D shell N perimeters thick by using zero infill. And it is fast! I just need a way to merge in the ribbing and spars. Meshmixer can also create shells and export them as STL. It could also merge an STL of the ribbing and spars with the skin.
@Doug/MichaelAtOz I need to look at SolidPython or maybe a command line hybrid. Probably precompute the entire polyhedron skin in Python or even C.
It is not sounding like there is enough general interest in a polygon offset function that returns a polygon to request that it be added as a language feature. And there is no way to call a Python function from OpenSCAD. Then there is the desire to be able to shell a fuselage. But it does sound like there are some work around options....


"But icky part
is that at the wing tips, as the airfoil chord get smaller, the relative
thickness of the skin increases relative to the chord, so you have to start
playing games reducing the number of points in the airfoil polygon to
prevent selfintersections.  but still doable. "
My code was just a starting point. You can always clip each internal curve offset against the startend line instead of doing a overall clipping as I did. This would avoid any selfintersections.
_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org


Ronaldo  I tweaked your example a little bit. I was worried about the polygon folding back on itself and was having trouble visualizing it. At least in this example, the offset function is fairly well behaved, and the direction reversal traces out a convex tip. The clipping could obviously be done within the thin_airfoil function itself. Thanks for coding this up.
The closed form derivative get more complex when camber is introduced. https://en.wikipedia.org/wiki/NACA_airfoil, so I think the next step is to code it up approximating the derivative with a delta step off the final cambered airfoil function.
module thin_Naca_airfoil(c, t, offset, n) {
//intersection() {
polygon( thin_airfoil(c, t, offset, n) );
// square([c,c*t]);
//}
}
thin_Naca_airfoil(100, 1/5, 10, 20);


This post was updated on .
The straight forward approach would be to calculate the normal for each edge and just add up your inset along this vector to one of the vertices (or even better: to the middle vertex of the edge). The rest is eliminating self intersection (for inset only), which can get a bit nasty if you go into the details. The 'dirty' oneline approach is to use some procrustes cut from left and right.
For outset call inset() with a negative value.
use <Naca4.scad> L = 100;
p = airfoil_data(naca = [9410, N=90, L=L);
p_ = inset(p, 1);
q = cut(p_, 1, 92);
linear_extrude(height = .1) polygon(p);
color("white") linear_extrude(height = .3) polygon(q);
function inset(A, d) = [for(i = [0:len(A)2]) let (v = A[i+1]A[i]) let (n = [v[1], v[0]]/norm(v)) A[i]+d*n];
function cut(A, a, b) = [for(i = [0:len(A)2]) if (A[i][0]>=a && A[i][0]<=b) A[i]];


I'm curious about this offset function. If the offset is interpreted as an offset to the edges, then the vertices will move by a larger amount and not perpendicular to either segment when the two adjacent edges move, due to the corner. For example, at a right angle, if both edges move by x, then the corner will move diagonally by sqrt(2) * x.
To compute this, each line segment can be transformed into an equation
a*x+b*y = c
and then each equation offset using
a*x+b*y = c+offs
Given two such offset equations, the new vertex is the intersection of these two lines which is found by solving the two equations.
Here are some functions that do exactly that:
// given two points, a, b, find equation for line that is parallel to line
// segment but offset to the right by offset amount offs
// equation is of the form c*x+d*y=e
// represented as array [ c, d, e ]
function seg2eq(pa, pb, offs) =
let (ab = [pb[0]pa[0], pb[1]pa[1]])
let (abl_un = [ab[1], ab[0]])
let (abl_len = sqrt(abl_un[0]*abl_un[0] + abl_un[1]*abl_un[1]))
let (abl = [ abl_un[0]/abl_len, abl_un[1]/abl_len ])
[ abl[0], abl[1], abl[0]*pa[0] + abl[1]*pa[1]  offs ];
// given two equations for lines, solve two equations to find intersection
function solve2eq(eq1, eq2) =
let (a=eq1[0], b=eq1[1], c = eq1[2], d=eq2[0], e=eq2[1], f=eq2[2])
let (det=a*eb*d)
[ (e*cb*f)/det, (d*c+a*f)/det ];
// given a corner as two line segments, AB and BC, find the new corner B' that results
// when both line segments are offet. Works by generating two equations and then solving
function offset_corner(pa, pb, pc, offs) =
solve2eq(seg2eq(pa, pb, offs), seg2eq(pb, pc, offs));
// given a polygon, offset each vertex using the corner offset method above
// note: this can produce selfintersections depending on the 'curvature' and offset
function offset_poly(p, offs) = [
for (i=[0:len(p)1])
i == 0 ? offset_corner(p[len(p)1], p[i], p[i+1], offs) :
i == len(p)1 ? offset_corner(p[i1], p[i], p[0], offs) :
offset_corner(p[i1], p[i], p[i+1], offs)
]; For the example airfoil below it does produce selfintersections, so I am looking at trying to make a cleanup function for "simple" selfintersections like these.
// Noncambered Naca example
function Naca_airfoil(c, t, x ) = let( xl = x/c)
5*t*c*( 0.2969*sqrt(xl)  0.126*xl  0.3516*pow(xl,2) + 0.2843*pow(xl,3)  0.1015*pow(xl,4) );
Naca_poly = let (c=100, t=1/5, x=1, n=20)
[ for(x=[0: c/n : c])
let( xl = c*pow(x/c,3) )
[ xl, Naca_airfoil(c, t, xl ) ]
];
% polygon(Naca_poly);
translate([0, 0, 0.1])
polygon(offset_poly(Naca_poly, 1)); Incidentally, for this airfoil, adjusting each point perpendicular to the line segments produces a rather severe distortion, so I would recommend that approach only be used for 'organic' shapes with many small segments and gentle angles, and avoid using it on geometric shapes with large segments and sharp angles.


@Jamie K. I had proposed the use of the curve normal (computed from the curve derivative) for the airfoil because: a) the airfoil curve is differentiable, b) the offset was small and c) the curve was finely discretized. If you have a right angle in the curve it is not differentiable and that approach is not valid. Similarly if the offset is greater then curvature radius of the curve in a convex section, selfintersections will emerge. So, its only valid for offsets small enough. Finally, if the curve discretization is course, the proposed procedure would generate a crude approximation of the offset. It is not by no means a general procedure.


@Jamie K
Agreed. We need a function to "clean up" polygon self intersections. (or just use CGAL hint hint)
Also once you add in the camber equations, the final X and Y points become parametric so the first step is to identify the line segments that intersect. I am thinking to step thru the bottom polyline while doing a lookup on the other polyline for a GT or LT crossing and visaversa. Once the crossing segments are identified, the intersection point can be calculated as you indicated.
The unit normal of the original function, not the polyline, at each vertex should give the bisector between this and the next line segment. There's probably some cleaner geometric ways to calculate the bisector of two adjacent segments not using the function unit normal. I suppose you then create the offset parallelogram and solve for the offset point. But you still have the self intersection problem.
Here is a NACA 4312 airfoil with about 200 of polygon points.

12
