openscadconcave

OpenSCAD: ConcaveHull


I'm drilling diamond shaped holes through a plate in a pattern. I use polyround to make the diamond properly rounded in X and Y and that works.

Now, I want to add rounding through the Z axis as well. I'm thinking of using a cosine shape to have it do a very smooth "C" shape.

In the image below I made many slices of the rounded shape I am trying to create, I want it to be solid and smooth and I'm having trouble achieving this:

enter image description here

The obvious solution of using hull() doesn't work because it's a convex shape, not even if I spilt the hull in half, it wants to jump over the concave hump:

enter image description here

Using linear extrusion with scale doesn't work because it does it linearly and not smoothly

linear_extrude(it/2,scale=n_min_scale)
polygon(polyRound(aan_points,n_diamond_spline));

enter image description here

Below the code that generates the slices that outline the shape I'm trying to create:


include <polyround.scad>

//precision of the rounding
n_diamond_spline = 25;

module diamond_zround( iw, ih, it, inr_rounding_factor )
{
    aan_points =
    ([
        [0, -ih/2, iw/inr_rounding_factor],
        [+iw/2, 0, ih/inr_rounding_factor],
        [0, +ih/2, iw/inr_rounding_factor],
        [-iw/2, 0, ih/inr_rounding_factor],
    ]);

    n_min_scale = 0.25;
    n_max_scale = 1.0;
    
    n_slices = 16;

    //hull()
        for (n_cnt_slice = [0 : n_slices-1])
        {
            //Smoothly decrease size
            n_scale = (n_max_scale-n_min_scale)*(0.5+cos(n_cnt_slice/n_slices*180)/2) +n_min_scale;
            
            translate([0,0,n_cnt_slice*it/2/n_slices])
            linear_extrude(0.01)
            scale([n_scale,n_scale])
            polygon(polyRound(aan_points,n_diamond_spline));
        }
    
        translate([0,0,it]) 
        rotate([180,0,00])
        for (n_cnt_slice = [0 : n_slices-1])
        {
            //Smoothly decrease size
            n_scale = (n_max_scale-n_min_scale)*(0.5+cos(n_cnt_slice/n_slices*180)/2) +n_min_scale;
            
            translate([0,0,n_cnt_slice*it/2/n_slices])
            linear_extrude(0.01)
            scale([n_scale,n_scale])
            polygon(polyRound(aan_points,n_diamond_spline));
        }


    /*
    linear_extrude(it/2,scale=0.9)
    polygon(polyRound(aan_points,n_diamond_spline));
    */
    
    
    /*
    translate([0,0,it]) 
    rotate([180,0,00])
    linear_extrude(it/2,scale=0.9)
    polygon(polyRound(aan_points,n_diamond_spline));
    */
}

diamond_zround(30, 20, 10, 10);

QUESTION: I'd like help in constructing this shape and shapes like this in OpenSCAD. I'd like it not to be too slow to render, e.g. no brute force with thousands of thin slices because this will be used in large number in a bigger geometry to make walls with holes, and it will fail to render it at some point.


Solution

  • hull by pairs of slices

    What about just hulling every pair of slices?

    Probably not the best for your "I'd like it not to be too slow to render", since I can't think of a more "brute force" solution than this one. But since I can't neither think of an easy less "brute force" solution than this one...

    Plus on my (very slow — it was slow in 2007 when I bought it, it has not gotten faster since) machine, this takes less than half a second (clearly slower than your raw code — you have time to put your finger off the F5 key before you see the rendering done), but yet, I have done way simpler code that were way slower.

    include <polyround.scad>
    
    //precision of the rounding
    n_diamond_spline = 25;
    
    module diamond_zround( iw, ih, it, inr_rounding_factor )
    {
        aan_points =
        ([
            [0, -ih/2, iw/inr_rounding_factor],
            [+iw/2, 0, ih/inr_rounding_factor],
            [0, +ih/2, iw/inr_rounding_factor],
            [-iw/2, 0, ih/inr_rounding_factor],
        ]);
    
        prPoints = polyRound(aan_points, n_diamond_spline);
        n_min_scale = 0.25;
        n_max_scale = 1.0;
        
        n_slices = 16;
    
        module oneSlice(n_cnt_slice) {
            n_scale = (n_max_scale-n_min_scale)*(0.5+cos(n_cnt_slice/n_slices*180)/2) +n_min_scale;
                
            translate([0,0,n_cnt_slice*it/2/n_slices])
            linear_extrude(0.01)
            scale([n_scale,n_scale])
            polygon(prPoints);
        }
        
        //hull()
            for (n_cnt_slice = [0 : n_slices-1])
            {
                hull(){
                    oneSlice(n_cnt_slice+1);
                    oneSlice(n_cnt_slice);
                }
            }
        
            translate([0,0,it]) 
            rotate([180,0,00])
            for (n_cnt_slice = [0 : n_slices-1])
            {
                //Smoothly decrease size
                n_scale = (n_max_scale-n_min_scale)*(0.5+cos(n_cnt_slice/n_slices*180)/2) +n_min_scale;
                
                translate([0,0,n_cnt_slice*it/2/n_slices])
                linear_extrude(0.01)
                scale([n_scale,n_scale])
                polygon(prPoints);
            }
    
    
        /*
        linear_extrude(it/2,scale=0.9)
        polygon(polyRound(aan_points,n_diamond_spline));
        */
        
        
        /*
        translate([0,0,it]) 
        rotate([180,0,00])
        linear_extrude(it/2,scale=0.9)
        polygon(polyRound(aan_points,n_diamond_spline));
        */
    }
    
    diamond_zround(30, 20, 10, 10);
    

    Edit: btw, why compute polyround at each iteration of the loop, since it is the same each times (it is only the scaling and translating that differentiate the slices). So, I edited my code to compute them only once. And that seems to be a really decent acceleration (now instead of 1/2 a second, it seems instantly computed)

    hull by pair on bottom part

    polyhedron

    A less lazy/easy way (a probably faster) to do it is, rather than building polygons from polyround, would be to use polyround outputs to build a big polyhedron. Not that complicated neither. Just a bit of work. You have to build an array of polyround outputs (so an array of array of points). And then match them indexwise

    If the points are

    p11 p12 p13 p14 p15
    p21 p22 p23 p24 p25
    p31 p32 p33 p34 p35
    

    (assuming there are 5 points in each polyround, and 3 layers), then you can just use those 15 points as points argument of polyhedron. And for faces an array of index of [p(x,y), p(x+1,y), p(x+1,y+1), p(x,y+1)], where x and y are the indexes of the layers and the point in polyround.

    The advantage of that is that it would work even if the polyround itself wasn't convex. In your case, each polyround IS convex. It is the shape of the cosine-scaled stack of them that isn't. So my "hull by pair" method works. But it wouldn't if the diamond itself wasn't convex. While this polyhedron method would.

    The drawback of the polyhedron method (and reason why I don't choose it at first—why bother if hull works?) is, leaving aside the headache for computing the faces—with the correct orientation!— is that you have to do the scaling yourself: polyround gives you the points, before the translate and scale.

    linear_extrude

    Likewise, instead of "hull by pair" you could "linear_extrude by pair". All it would take is to compute, for each pair, what would be the scale parameter

    Code for the three methods

    include <polyround.scad>
    
    //precision of the rounding
    n_diamond_spline = 25;
    
    
    module halfDiamondHull(iw, ih, it, inr_rounding_factor){
        aan_points =
        ([
            [0, -ih/2, iw/inr_rounding_factor],
            [+iw/2, 0, ih/inr_rounding_factor],
            [0, +ih/2, iw/inr_rounding_factor],
            [-iw/2, 0, ih/inr_rounding_factor],
        ]);
    
        prPoints = polyRound(aan_points, n_diamond_spline);
        n_min_scale = 0.25;
        n_max_scale = 1.0;
        
        n_slices = 16;
    
        module oneSlice(n_cnt_slice) {
            n_scale = (n_max_scale-n_min_scale)*(0.5+cos(n_cnt_slice/n_slices*180)/2) +n_min_scale;
                
            translate([0,0,n_cnt_slice*it/2/n_slices])
            linear_extrude(0.01)
            scale([n_scale,n_scale])
            polygon(prPoints);
        }
        
        for (n_cnt_slice = [0 : n_slices-1])
        {
            hull(){
                oneSlice(n_cnt_slice+1);
                oneSlice(n_cnt_slice);
            }
        }
    }
    
    module halfDiamondLinearExtrude(iw, ih, it, inr_rounding_factor){
        aan_points =
        ([
            [0, -ih/2, iw/inr_rounding_factor],
            [+iw/2, 0, ih/inr_rounding_factor],
            [0, +ih/2, iw/inr_rounding_factor],
            [-iw/2, 0, ih/inr_rounding_factor],
        ]);
    
        prPoints = polyRound(aan_points, n_diamond_spline);
        n_min_scale = 0.25;
        n_max_scale = 1.0;
        
        n_slices = 16;
    
        for (n_cnt_slice = [0 : n_slices-1]) {
            n_scale = (n_max_scale-n_min_scale)*(0.5+cos(n_cnt_slice/n_slices*180)/2) +n_min_scale;
            n_scale_next = (n_max_scale-n_min_scale)*(0.5+cos((n_cnt_slice+1)/n_slices*180)/2) +n_min_scale;
            translate([0,0,n_cnt_slice*it/2/n_slices])
            linear_extrude(it/2/n_slices, scale=n_scale_next/n_scale)
            scale([n_scale,n_scale])
            polygon(prPoints);
        }
    }
    
    module halfDiamondPolyhedron(iw, ih, it, inr_rounding_factor){   
        aan_points =
        ([
            [0, -ih/2, iw/inr_rounding_factor],
            [+iw/2, 0, ih/inr_rounding_factor],
            [0, +ih/2, iw/inr_rounding_factor],
            [-iw/2, 0, ih/inr_rounding_factor],
        ]);
    
        prPoints = polyRound(aan_points, n_diamond_spline);
        n_min_scale = 0.25;
        n_max_scale = 1.0;
        
        n_slices = 16;
        n_scales = [for(n_cnt_slice=[0:n_slices]) (n_max_scale-n_min_scale)*(0.5+cos(n_cnt_slice/n_slices*180)/2) +n_min_scale];
        tsPoints = [
            for(n_cnt_slice = [0 : n_slices])
                for(p=prPoints)
                    [p[0]*n_scales[n_cnt_slice], p[1]*n_scales[n_cnt_slice], n_cnt_slice*it/2/n_slices]
        ];
        N=len(prPoints);
        tsFaces=[
            // Faces for 2 subsequent polyround points and 2 layes    
            for(iz = [0:n_slices-1]) for(ix=[0:N-2]) [iz*N+ix, iz*N+ix+1, (iz+1)*N+ix+1, (iz+1)*N+ix],
            // Close the loop
            for(iz=[0:n_slices-1]) [iz*N+N-1, iz*N, (iz+1)*N, (iz+1)*N+N-1],
            // Let's not forget bottom face
            [for(ix=[N-1:-1:0]) ix],
            // Nor top (turning in reverse order to keep orientation)
            [for(ix=[N*n_slices:1:N*(n_slices+1)-1]) ix]
        ];
        
        polyhedron(points=tsPoints, faces=tsFaces);
    }
    halfDiamondPolyhedron(30,20,10,10);
    translate([0,0,10]) rotate([180,0,0]) halfDiamondLinearExtrude(30,20,10,10);
    

    enter image description here

    I let you benchmark (it is a bit platform dependent, and more importantly shape dependent) to know which ones forks best. I rarely go through the trouble of implementing the 2 last methods (linearExtrude and Polyhedron), because I am usually lazy (except for fun; and it was fun to trying it for this question :D). But again, the method 2 and 3 would work even if your polygon was non-convex, while the hull method works for non-convex extrusion of convex polygons only

    On your example, all 3 methods provide stricly identical result.

    Anyhow, good luck printing that :D I know my printer wouldn't.