Coder Social home page Coder Social logo

revarbat / bosl Goto Github PK

View Code? Open in Web Editor NEW
503.0 21.0 61.0 416 KB

The Belfry OpenScad Library - A library of tools, shapes, and helpers to make OpenScad easier to use.

Home Page: https://github.com/revarbat/BOSL/wiki

License: BSD 2-Clause "Simplified" License

OpenSCAD 95.47% Python 4.37% Shell 0.16%
scad openscad openscad-library 3d-printing stl

bosl's People

Contributors

bendaja avatar esteevens avatar fe60 avatar limitz avatar mssalvatore avatar nickcoutsos avatar olikraus avatar revarbat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bosl's Issues

simplify_path not getting tail recursion?

I tried to use simplify_path on a long list and it failed with a recursion error. It looks to me like it should be getting tail recursion, so not sure what's going on there.

list_range(n,s,e) should return n items spaced exactly from s to e

The behavior of list_range when you give n, s, and e is bizzare. It uses the step size (default 1), starts at s, and goes until either reaching e or getting to n items. It's hard to see a use for this behavior.

Meanwhile, the very useful case of spanning exactly from e to s with n items seems to be missing. (Is it somewhere else?)

[BUG] xpread manual error

The manual says that p1 defaults to the origin. Wording is a bit unclear, but it says "the line" starts at p1. But I was getting my items symmetric around the origin, not starting there. When I added p1=[0,0,0] they started at the origin, so this behavior is not actually default. (Also why is "p1" the parameter name. Non-obvious. Perhaps this makes sense if I can set p2=[0,0,0] and get the items going to the left of the origin?)

convex_hull() name confusing?

Ok, so what's the difference between hull() and convex_hull()? From a user perspective, I think the name "convex_hull()" doesn't at all differentiate it.

The Linde functions return the boundary of the hull. In 3d, the face index list, in 2d, the edge index list. Since we already have "hull" adding the word "convex" doesn't really give any clarity. So I wonder if

hull_boundary, hull3d_faces, and hull2d_edges might be good names for these functions.

Almost all modules require functions from nightlies

is_list(), is_num(), is_string() and so on seem to be used everywhere. As per the docs these are only available in nightly builds, they don't even appear to be in 2019.01-RC2 as packaged for Debian Sid (afaik RC4 is the absolute bleeding edge).

I managed to get some examples to work by changing the version detection in compat.scad to look for > 20190100 (rc2) instead of 20190000.

[BUG] bezier_segment_closest_point() fails

Consider the 2D Bézier curve with the control points:

p = [[3.98743, 5.29979], [-8.21663, -2.76544], [-5.4184, -5.00586], [8.26971, -0.0435725]];

Evaluating its closest point to the origin with function bezier_segment_closest_point() from beziers.scad, we find:

umin = bezier_segment_closest_point(p, [0,0], max_err=0.01);
distmin = norm(bez_point(p, umin));
echo(umin=umin);
echo(distmin=distmin);
// ECHO: umin = 0.765625
// ECHO: distmin = 2.41428

However, if we evaluate the point at u = 0.183874 of the same curve and compute its distance to the origin, we find:

dist2 = norm(bez_point(p, 0.183874));
echo(dist2=dist2);
// ECHO: dist2 = 1.91359

a distance value 80% of the computed by function bezier_segment_closest_point(). Reducing max_err to 1e-6 doesn't change the results significantly.

I confess that I have not understood the ideas behind that function code. My own version of that function uses Bézier subdivision to recursively find the closest point. It calls the Bézier subdivision function once for each call. Bézier subdivision has a small overhead over a Bézier point evaluation but it may be faster than the three point evaluations the function bezier_segment_closest_point() requires. So, besides, I would expect a faster method using subdivision.

Bezier Patches

The bezier code could expanded for bezier patches. I'm unsure of how to best do this, though.

screw threading database

Maybe I'm missing something, but it doesn't appear that the screw threading code has a database to generate standard screw threads. So if I want to make a USA 8-32 (UNC) I have to look up the parameters. Did I miss something?

There's been talk of a library that generates screw threads by name which seems desirable. In other words, to be able to specify "8-32" or "M4" or "M4-fine".

2d mutators

I think ultimately we need a whole new slew of 2d stuff, but at the moment I'm annoyed because the mutators are 3d, but I need to clip my object before doing other 2d operations, so I can't extrude into 3d yet. In my own version of this I implemented half-spaces that I could rotate and I included an argument (plane=true) to get half-planes instead of half-spaces. So that's one approach. Alternatively there could be right_half2d() and so on.

determining angles between vectors (or points)

Would it be worth having a function that could determine the angle defined by 3 points in a row to avoid having to turn point sequences into vectors?

I'm not sure I understand the existing angle functions' intent. There is vector3d_angle (which I had a hard time remembering the name of as I tried vector_angle3d). It works on 3d or 2d angles and uses acos of inner product. Then there's vector2d_angle which uses difference of atan2.

p1 = [0,0];
p2 = [1,0];
p3 = [0,-1];
echo(vector3d_angle(p1-p2, p3-p2)); // prints 45
echo(vector2d_angle(p1-p2, p3-p2)); // prints 315

It's difficult to understand when I would want the answer to be 315. The difference of atan2 method seems subject to wraparound issues (of which this is one) that the other method doesn't have. I guess the argument could be made that if I reversed the order, then the answer 315 would make sense, namely that the angle starting at (p3-p2) and ending at (p1-p2) is 315. Unfortunately:

echo(vector2d_angle(p3-p2,p1-p2)); // prints -315

So it seems like perhaps vector3d_angle should be renamed vector_angle, maybe (since it's dimension independent and would in fact work in 4d) and the intent of vector2d_angle needs to be clarified and it needs to be fixed to match up with whatever that intent is. Maybe what you want to do is run atan2 and posmod the results into [0,360] and then force the second angle to be larger than the first by adding 360 if needed, and then take the difference? Would that always give the angle required to rotate v1 in the positive direction until it aligns with v2? And is that the intent of this function? (Maybe the intent is rotation in the negative direction as you subtract v2 from v1.)

better name for wrap_range

The name wrap_range doesn't really capture the functionality. The revised version takes various types of index sets, not just ranges. I suggest renaming (or providing an alternate name) of select for this function.

Add more intuitive edge names

I've noticed that you generally seem to favor what I think of as excessive verbosity in naming, but in the case of the EDGE* constants, instead I find a bunch of cryptic abbreviations that I don't seem to be able to remember. Is front FT? I know it's two letters....but no, that is an error? I don't know.... For me the abbreviations end up being read-only, in the sense that I know what they mean when I see them...but I can't remember them. So I'm suggesting adding full spelled out versions.

Another thing: I would like the wiki documentation to list the constants rather than just referring to constants.scad (with no hyperlink). So for example, when cuboid says to use the edge constants you should list them right there, I think. Even if this ends up being a little repetitive in the manual, it will make it easier to use.

Maybe it's possible to define all of the edge constants without actually having to state them all by explaining the system of it?

[BUG] cuboid doesn't apply trimcorners

cuboid([50, 85, 40], fillet=4, edges=EDGES_TOP, trimcorners=true);

The corners are sharp, not rounded as expected. If I remove the "edges" specification then the corners are rounded.

hex grid: beyond rectangular grids

So far I've needed a grid structure twice, and both times not a regular uniform grid but a hex grid. The first time I wanted it squeezed in one direction, so not uniform. Now I need a regular one. The existing grid module can generate a 3d grid. But the hex grid has multiple extensions into 3d. It's not obvious to me that handling these extensions is useful. One question regarding a hexgrid() module is how do you define the extent of the grid. It appears there are two basic regular cases: rectangular extent or hexagonal extent. So for example, specify a hex grid with specified side length of the hexagonal grid and a spacing or count. Or specify a rectangular extent by specifying spacing or a count and specifying a bounding rectangle. In the rectangle case there's an extra complication because there are two different kinds of rows, so which kind occurs first? I suppose the user could give a "seed" point on the grid and then the bounding rectangle. What is the most natural way to specify a grid that is nonuniform? Do you specify a squish factor, say on the X axis?

Add round2d and shell2d modules

use<BOSL/math.scad>
use<BOSL/transforms.scad>

// Round an arbitrary 2d object
//
// Specify the rounding radius as r and the function will
// round all convex and concave corners with this radius.  
//
// Specify the outer radius (for convex corners) with or
// Specify the inner radius (for concave corners) with ir
//
// The ir and or parameters override r.  You can set either
// one to zero to to round only inside or outside corners.  
//
//
// The 2d object must not have any sections narrower than
// the 2*or (twice the outer radius).  Such sections will
// disappear.  
// 

module round2d(r,or,ir){
  or = is_def(or) ? or :
       is_def(r) ? r : 0;
  ir = is_def(ir) ? ir :
       is_def(r) ? r : 0;
  offset(or){
    offset(-ir-or){
      offset(delta=ir){   // Does delta vs r make any difference?
        children();
      }
    }
  }
}


// thickness can be positive to expand outward, negative to shrink inward,
// or an array to do both.
//
// or is the outside radius, ir is the radius of concave corners on the outside
// (same as for round2d)
//
// fill is a fill radius for filling in deep corners inside the shell
// round is a rounding radius for rounding points inside the shell

module shell2d(thickness, or=0, ir=0, fill=0, round=0)
{
   thickness = is_scalar(thickness) ?
                        (thickness<0 ? [thickness,0] :
                                       [0,thickness])
                      : thickness[0]>thickness[1] ?
                                  [thickness[1],thickness[0]] :
                                  thickness;
   difference(){
     round2d(or=or,ir=ir)
       offset(delta=thickness[1])
         children(0);
     round2d(or=fill,ir=round)
       offset(delta=thickness[0])
         children(0);
   }
}


///////////////////////////////////////////////////////
// Examples

$fn=64;

points=[[-3,0],[5,3],[-1,7],[8,7],[20,20],
        [15,10],[17,0],[12,5],[10,0]]; 

ydistribute(spacing=22){
  polygon(points);
  round2d(r=1)polygon(points);
  round2d(ir=0,or=.5)polygon(points);
  round2d(ir=1)polygon(points);
  round2d(ir=2,or=1) difference() { circle(r=5); square([6,6]);}
  shell2d(.5) polygon(points);
  shell2d(.5,or=.5) polygon(points);
  shell2d(.5,ir=1) polygon(points);
  shell2d([-.5,.5])polygon(points);
  shell2d(.5,fill=.5)polygon(points);
  shell2d(-1,round=1)polygon(points);
}

I needed round2d yesterday for an actual real project. It seems like a commonly written function. I'm not sure where they should go. I was thinking this could go in a rounding.scad file along with roundcorners and the hopeful smooth rounded cube that still isn't working right. Alternatively maybe we want a file of 2d functions and they belong there?

all and any should recurse into sublists

The new functions all() and any() don't recurse down the list like the
version I supplied. I think we want

any([[false,false],[false,false]]) to be false, not true.

and similarly

all([[true,true],[true,false],[true,true]]) should be false.

array_zip not proper inverse to array_subindex

array_zip doesn't reverse the effect of array_subindex. Also I think it would be nice if it had the option of taking 2 or 3 items to zip that aren't in a list, because I think the dominant use will be with two args, as in array_zip(a,b) rather than needing array_zip([a,b]).

The current version zips [[1,2],[3,4],[5,6]] and [1,2,3] into [[[1,2],1],[[3,4],2],[[5,6],3]].

This version I think works correctly, but just bans varying lengths instead of filling in a default value.

function array_zip(vecs,v2,v3) =
v3!=undef ? array_zip([vecs,v2,v3]) :
v2!=undef ? array_zip([vecs,v2]) :
let(
length = len(vecs[0]),
samesize = [for (v=vecs) len(v)==length],
dummy=assert(all(samesize),"Input vectors must have the same length")
)
[for(i=[0:length-1])
[for(v=vecs) each v[i]]
];

add sort() and unique()

Sort should do something intelligent with vectors so that unique can work on lists of points.

// This could be folded into listcomp()
function scalarcomp(a,b) = a<b ? -1 : a>b ? 1 : 0;

function listcomp(a,b,n=0) =   !is_list(a) && !is_list(b) ? scalarcomp(a,b):
                               len(a)<=n && len(b)<=n ? 0 :
                               len(a)<=n ? -1 :
                               len(b)<=n ? 1 :
                               a[n] < b[n] ? -1 :
                               a[n] > b[n] ? 1 :
                               listcomp(a,b,n+1);

function sort(arr) =
    !(len(arr)>0) ? [] :
        let(  pivot   = arr[floor(len(arr)/2)],
              compare = [for (entry = arr) listcomp(entry, pivot)],
              lesser  = [ for (i = [0:len(arr)-1]) if (compare[i]==-1) arr[i] ],
              equal  =  [ for (i = [0:len(arr)-1]) if (compare[i]==0) arr[i] ],
              greater = [ for (i = [0:len(arr)-1]) if (compare[i]==1) arr[i] ]
        )
        concat( sort(lesser), equal, sort(greater) );


function unique(points) = let(sorted=sort(points))
    [for(i=[0:len(sorted)-1]) if (i==0 || sorted[i]!=sorted[i-1]) sorted[i]];

add array_dim function

It returns the length of the array in the requested dimension if it is consistent, undef it the dimension exists but varies (e.g. x=[[1,3],[3],[4,5]]) and 0 if the dimension doesn't exist, like you requested dimension 2 from the x and it has only dimensions 0 and 1.

// Return the array dimensions excluding the first one (which is just the length of the list)
//
function _array_dim_recurse(v) =
     // If first entry is not a list then v is either a list of singletons, in which case we return []
     // because there is no 2nd dimension, or if some entry is a list we return undef
    !is_list(v[0]) ?
        (sum( [for(entry=v) is_list(entry) ? 1 : 0]) == 0 ? [] : [undef] ) :
     // First entry is a list so check for length consistency of the list entries, and then
     // recursively call to get the rest of the dimensions
     let( firstlen = len(v[0]),
            first =  sum( [for(entry = v) len(entry) == firstlen  ? 0 : 1]   ) == 0 ? firstlen : undef,
            leveldown = flatten(v) )
     is_list(leveldown[0]) ? concat([first],_array_dim_recurse(leveldown)) : [first];

//
function array_dim(v,depth) =
    // Add the first dimension entry (the length) to the rest of the values
  depth == undef ? concat([len(v)], _array_dim_recurse(v)) :
    // A depth was requested, so compute the dimension list and then return
    // the requested entry.
  depth == 0 ? len(v) :
    let(
       dimlist = _array_dim_recurse(v)
    )
    depth > len(dimlist) ? 0 : dimlist[depth-1];

[BUG] rotate_point3d fails with errors

This code:

echo(rotate_points3d([[3,3,3]], from=[-4,0,0], to=[0,0,-1]));

is giving me

WARNING: cos() parameter could not be converted, in file lib/BOSL/math.scad, line 1288
WARNING: sin() parameter could not be converted, in file lib/BOSL/math.scad, line 1289

and then I get [[0,0,0]] as output.

Add hull_points module

This trick is very fast and works on huge point sets. It's also very non-obvious, so I think well worth including. In the past I have seen it produce warnings from degenerate input sets (but still produce correct results). I can't duplicate that behavior at the moment.

module hull_points(points){
    extra = len(points)%3;
    list = concat(
                [[for(i=[0:extra+2])i]],
                [for(i=[extra+3:3:len(points)-3])[i,i+1,i+2]]);
    hull() polyhedron(points, faces=list);
}

[BUG] sortidx produces incorrect result

data = [[-4, 0, 0], [0, 0, -4], [0, -4, 0], [-4, 0, 0], [0, -4, 0], [0, 0, 4], [0, 0, -4], [0, 4, 0], [4, 0, 0], [0, 0, 4], [0, 4, 0], [4, 0, 0]];
echo(sorted = sort(data));
sortindex = sortidx(data);
echo(sortindex = sortindex);
echo(sorted2 = select(data,sortindex));

output is:

ECHO: sorted = [[-4, 0, 0], [-4, 0, 0], [0, -4, 0], [0, -4, 0], [0, 0, -4], [0, 0, -4], [0, 0, 4], [0, 0, 4], [0, 4, 0], [0, 4, 0], [4, 0, 0], [4, 0, 0]]
ECHO: sortindex = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
ECHO: sorted2 = [[-4, 0, 0], [0, 0, -4], [0, -4, 0], [-4, 0, 0], [0, -4, 0], [0, 0, 4], [0, 0, -4], [0, 4, 0], [4, 0, 0], [0, 0, 4], [0, 4, 0], [4, 0, 0]]

grid intersection with polygon

Thinking about hexgrid() made me wonder if it would be useful to have a way of intersecting a grid with an arbitrary polygon. What if I want a grid on a circle, for example. This cannot be done after the fact because the children might overlap or have an irregular shape. It seems like if we have a function that tests whether a grid point is inside a polygon then this should be a simple addition. Is it a good idea?

For hexgrid this would provide a mechanism for controlling the extent, as default cases of rectangular or hexagonal extent could be reduced to default intersection polygons.

Add roundcorners

I haven't had the time to understand this yet, but as I understand the discussion around it, this is a way to provide corners with continuous rate of curvature change, making for prettier corners.

Code by @adrianVmariano

use<lib/BOSL/math.scad>
use<lib/BOSL/beziers.scad>

// TODO:
//   remove colinear points?
//
// roundcorners(path, curve, type, all, closed)
//
// path: a list of 2d or 3d points, possibly with an extra coordinate giving smoothing parameters, e.g.
//      ex:  [[0,0],[0,1],[1,1],[0,1]]    2d point list
//           [[0,0,0], [0,1,1], [1,1,2], [0,1,3]]    3d point list
//           [[0,0,.2],[0,1,.1],[1,1,0],[0,1,.3]]    2d point list with smoothing parameters
//           [[0,0,0,.2], [0,1,1,.1], [1,1,2,0], [0,1,3,.3]]    3d point list with smooth parameters
//           [[0,0,[.3,.5], [4,0,[.2,.6]], [4,4,0], [0,4,[
// curve: set to "smooth" to get continuous curvature 4th order bezier curves
//        set to "circle" to get circular arcs
// type: set to "cut" with either curve type to specify how much of the corner to "cut" off.  The 
//                    smoothing parameter will be the distance from the corner to the tip of the rounded curve
//       set to "radius" to use with curve=="circle" to specify the curve radius
//       set to "joint" to use with curve=="smooth" to specify the distance from the corner at which
//                    the curve joints the shape
// all: set this to a curvature parameter to apply to all points on the list.  If this is set then all
//      of the values given in the path are assumed to be geometrical coordinates.  If you don't set it
//      then the last value of each entry in path is assumed to be the smoothing parameters
// closed: set to true (the default) if the curve is closed and false if the curve is open at the ends
//
// If you select curve=="smooth" then there are two smoothing parameters.  The first one
// is the cut or joint distance as given type "type".  The second one is a curvature
// parameter which is a number in [0,1], where larger numbers have a more abrupt
// transition and smaller ones a more gradual transition.  If the curvature parameter is
// close to zero the transition is so gradual that it may take a very large distance.
//
// If you select curves that are too large to fit the code will fail with an error.  It displays a set
// of scale factors that you can apply to the (first) smoothing parameter which will reduce the size of the 
// curves so that they will fit on the shape.  If the scale factors are larger than one then they 
// indicate how much you can increase the curve size before collisions will occur.
//
// https://hackernoon.com/apples-icons-have-that-shape-for-a-very-good-reason-720d4e7c8a14


function roundcorners(path, curve, type, all=undef,  closed=true) =
 let(
   default_curvature = 0.7,   // default curvature for "smooth" curves
   typeok = type == "cut" || (curve=="circle" && type=="radius") ||
                             (curve=="smooth" && type=="joint"),
   pathdim = array_dim(path,1),
   have_all = all==undef ? 1 : 0,
   pathsize_ok = is_num(pathdim) && pathdim >= 2+have_all && pathdim <= 3+have_all
 )
 assert(curve=="smooth" || curve=="circle", "Unknown 'curve' setting in roundcorners")
 assert(typeok, curve=="circle" ? "In roundcorners curve==\"circle\" requires 'type' of 'radius' or 'cut'":
                                  "In roundcorners curve==\"smooth\" requires 'type' of 'joint' or 'cut'")
 assert(pathdim!=undef, "Input 'path' has entries with inconsistent length")
 assert(pathsize_ok, str("Input 'path' must have entries with length ",2+have_all," or ",
                         3+have_all, all==undef ? " when 'all' is not specified" : "when 'all' is specified"))
 let(
   pathfixed= all == undef ? path : array_zip([path, replist([all],len(path))]),
   dim = len(pathfixed[0])-1,
   points = array_subindex(pathfixed, [0:dim-1]),
   parm = array_subindex(pathfixed, dim),
   // dk will be a list of parameters, for the "smooth" type the distance and curvature parameter pair,
   // and for the circle type, distance and radius.  
   dk = [for(i=[0:len(points)-1]) let(  
     angle = pathangle(wrap_range(points,i-1,i+1))/2,
     parm0 = is_list(parm[i]) ? parm[i][0] : parm[i],
     k = curve=="circle" && type=="radius" ? parm0 :
         curve=="circle" && type=="cut" ? parm0 / (1/sin(angle) - 1) : 
           is_list(parm[i]) && len(parm[i])==2 ? parm[i][1] : default_curvature
     ) !closed && (i==0 || i==len(points)-1) ? [0,0] :
       curve=="circle" ? [k/tan(angle), k] :
       curve=="smooth" && type=="joint" ? [parm0,k] :
	      [8*parm0/cos(angle)/(1+4*k),k]
   ],
   lengths = [for(i=[0:len(points)]) norm(flatten(diff(wrap_range(points, i-1,i))))],
   scalefactors = [for(i=[0:len(points)-1])
     min(lengths[i]/sum(array_subindex(wrap_range(dk,i-1,i),0)),
         lengths[i+1]/sum(array_subindex(wrap_range(dk,i,i+1),0)))]
 )
 echo("Roundover scale factors:",scalefactors)
 assert(min(scalefactors)>=1,"Curves are too big for the path")
 [ for(i=[0:len(points)-1])
       each  dk[i][0] == 0 ? [points[i]] :
	      curve=="smooth" ? bezcorner(wrap_range(points,i-1,i+1), dk[i]) :
                               circlecorner(wrap_range(points,i-1,i+1), dk[i])
 ];


function bezcorner(points, parm) =
 let(
    d = parm[0],
    k = parm[1],
    prev = normalize(points[0]-points[1]),
    next = normalize(points[2]-points[1]),
    P = [points[1]+d*prev,
         points[1]+k*d*prev,
         points[1],
         points[1]+k*d*next,
         points[1]+d*next])
   bezier_curve(P,200);


function circlecorner(points, parm) =
 let(
   angle = pathangle(points)/2,
   d = parm[0],
   r = parm[1],
   prev = normalize(points[0]-points[1]),
   next = normalize(points[2]-points[1]),
   center = r/sin(angle) * normalize(prev+next)+points[1]
   )
 circular_arc(center, points[1]+prev*d, points[1]+next*d, 200);


// Compute points for the shortest circular arc that is centered at
// the specified center, starts at p1, and ends on the vector
// p2-center.  The radius is the length of (p1-center).  If (p2-center)
// has the same length then the arc will end at p2.

function circular_arc(center, p1, p2, N) = let(
  angle = pathangle([p1,center,p2]),
  v1 = p1-center,
  v2 = p2-center
  )
  len(center)==2 ?
    let(dir = sign(v1.x*v2.y-v1.y*v2.x),   // z component of cross product
        r=norm(v1))
	 assert(dir != 0, "Colinear inputs don't define a unique arc")
	 [for(i=[0:N-1])
	    let(theta=atan2(v1.y,v1.x)+i*dir*angle/(N-1))
	      r*[cos(theta),sin(theta)]+center] :
    let(axis = cross(v1,v2))
        assert( axis != [0,0,0], "Colinear inputs don't define a unique arc")
        [for(i=[0:N-1])
	    matrix3_rot_by_axis(axis, i*angle/(N-1)) * v1 + center];

function bezier_curve(P,N) =
  [for(i=[0:N-1]) bez_point(P, i/(N-1))];


// Compute the angle at a corner in a path using the law of cosines
function pathangle(pts) =
 let( d = [for(i=[0:2]) norm(flatten(diff(wrap_range(pts,i,i+1))))] )
 acos(constrain(
     (d[0]*d[0] + d[1]*d[1] - d[2]*d[2]) / 2 / d[0] / d[1], -1,1));


function array_subindex(vect, index) =
   [for(entry=vect)
        let(value=[for(i=index) entry[i]])
            len(value)==1 ? value[0] : value];


function array_zip(vecs,v2,v3) =
 v3!=undef ? array_zip([vecs,v2,v3]) :
 v2!=undef ? array_zip([vecs,v2]) :
 let(
   length = len(vecs[0]),
   samesize = [for (v=vecs) len(v)==length?0:1],
   dummy=assert(sum(samesize)==0,"Input vectors must have the same length")
 )
 [for(i=[0:length-1])
    [for(v=vecs) each v[i]]
 ];


function replist(list, N) = [for(i=[0:N-1]) list];


function diff(v) =
 [for(i=[0:len(v)-2]) v[i+1]-v[i]];

function wrap(list,index,index2) =
 let (L = len(list),
 indices = index2 == undef ? index :
            let (a=index % L + L,
                 b=index2 % L + L)
                 b<a ? [a:b+L] : [a:b])
 is_num(indices) ? list[((indices % L)+L) % L] :
                   [for(i=indices) list[((i % L)+L) % L]];

// Return the array dimensions excluding the first one (which is just the length of the list)
// 
function _array_dim_recurse(v) =
    // If first entry is not a list then v is either a list of singletons, in which case we return []
    // because there is no 2nd dimension, or if some entry is a list we return undef
   !is_list(v[0]) ?   
       (sum( [for(entry=v) is_list(entry) ? 1 : 0]) == 0 ? [] : [undef] ) : 
    // First entry is a list so check for length consistency of the list entries, and then
    // recursively call to get the rest of the dimensions
    let( firstlen = len(v[0]),
           first =  sum( [for(entry = v) len(entry) == firstlen  ? 0 : 1]   ) == 0 ? firstlen : undef,
           leveldown = flatten(v) )
    is_list(leveldown[0]) ? concat([first],_array_dim_recurse(leveldown)) : [first];

// 
function array_dim(v,depth) =
   // Add the first dimension entry (the length) to the rest of the values
 depth == undef ? concat([len(v)], _array_dim_recurse(v)) :
   // A depth was requested, so compute the dimension list and then return
   // the requested entry.  
 depth == 0 ? len(v) :
   let(
      dimlist = _array_dim_recurse(v)
   )
   depth > len(dimlist) ? 0 : dimlist[depth-1];

bottom_half: cp arg is confusing

It took me about an hour to figure out that the cp scalar argument goes in the opposite direction as cp=[0,0,z]. I find this very surprising and am not sure what the motivation is here. I expect scalar cp to be a way to specify the z coordinate without the nuisance of giving the unnecessary x and y values.

Also note that the implementation is puzzling: why do you shift the children down and then shift them up instead of just shifting the cube by the adjusted amount?

Question: how to add thread to non-nut object

Hi,

just playing with threading.scad and figured out how to add a thread to the top of a cylinder (although its kind of hovering, could do with filleting against the inside of the cylinder) using threaded_rod() but can't figure out how to add the opposite thread to the inside of the lid, which I don't want to be a threaded_nut().

Any ideas - or is this not possible?

Reverse sorting

The sort() and sortidx() functions need a reversed=true argument, or something similar to instigate reverse order sorts.

sort() is slower than the version I was using

I didn't dig in to figure out why, but I noticed that my polyhedron code was a bit slower, having gone from 3s to 6s when I deleted the local copy of unique.

I tested sort directly with 20k random scalars and found that it took 6s with my original code, and 28 s with the code in the library. I assume it has to do with the complexity of the scalar test function.

[BUG] wrong return from bezier_segment_length() in bezier.scad

The function bezier_segment_length() in bezier.scad returns what seems to be a correct result for most cases but fails in some of them. For example:

q = [[0, 0], [336, 0.016], [-15200, -0.224], [461296, -0.72]];
echo( len=bezier_segment_length(q, 0, 1/16, 0.75e-3) );
echo(len2= bezier_segment_length(q, 0, 1/16, 0.5 e-3) );
// ECHO: len = 1
// ECHO: len2 = 23.7179

For any value of max_deflect greater or equal to 0.75e-3 the returned length is 1; for smaller values of max_deflect, the returned length is 23.7179, the correct value.

That example was built in order to show that the stopping rule in bezier_segment_length() is misleading. The bezier curve in that example have a very shallow and symmetrical loop in the parameter interval [0, 1/16]. So, both conditions of the stopping rule are met in the first call and the recursion stops with a wrong result. With a tighter precision max_deflect, the rule is not met and the recursion goes on. Changing the rule to a tighter interval condition will not assert a correct result in all cases.

I approach the Bezier length computation by subdivision stopping the recursion when the Bezier control polygonal has a length near enough to the distance between the first and last point. That method, besides its correctness, is very much faster then bezier_segment_length() requiring less Bezier point evaluations.

add replist() function

Add a function to construct lists by replicating a list to a specified length:

function replist(list, N) = [for(i=[0:N-1]) list];

If we wanted to get fancy we could make a version that would make higher depth lists, e.g. the equivalent of a 2x2 matrix of all 1's, which would just be a sequence of recursive calls. I really don't know if arrays of higher than dimension 2 are useful or not. Maybe it suffices to make a version that takes a third argument and makes a N x M output.

beveled gears working correctly?

I don't really understand gears well, so this may be a false alarm. But I noticed that when you set bevelang for a gear the diameter of the gear shrinks, possibly by a significant fraction of a tooth. Is that how it is supposed to work? Is this for gears that meet at an angle? (The docs could be expanded somewhat. Also the documentation is in the wrong order: the gear() module should be documented first, followed by rack, then gear2d, and then the functions last. That's the order that users are likely to care about things.) I also found that if the bevel angle got above about 30 then the code starts making double beveled teeth, which seems wrong. So it should probably either be trapped as invalid user input or fixed, if it's supposed to work.

setting "MODEL_MAX_SIZE" for top_half() and other similar functions

It seems to me sort of annoying to have to specify the size on every call for these. Would it make sense to create some global parameter that would set this for a given model?

If I put something like:

MODEL_MAX_SIZE = 300;

at the top of my model that could be used to adjust this (and also other types of operations where one wants to, for example, subtract the whole model from a cube).

Add libfile for regular polyhedrons

As per suggestion from @adrianVmariano, We should have a regular solids libfile. At the least for the platonic solids, and typical dice polyhedra: 4, 6, 8, 10, 12, 20, 30 sides. Maybe some of the Archimedean solids.

rotate_point3d should take "from" and "to" options

I'm not quite sure where there need to be two rotate_point3d functions, but I think one of them should provide the from and to options that rot provides. I'm think I'm going to need this for polyhedron manipulations.

staggered sphere: why?

How is staggered sphere special or advantageous over the built-in sphere?

Does it have the property (as discussed recently on the forum) that it has full dimensions along the 3 axes for all values of $fn that are divisible by 4? Because that property seems nice.

[BUG] rotate_points3d fails when from==to

When from and to are multiples of each other rotate_points3d is failing:

data=[[1,2,3],[4,5,6]];

echo(rotate_pionts3d(data, from=[0,0,1],to=[0,0,4]));
echo(rotate_points3d(data, from=[3,2,1],to=[6,4,2]));
echo(rotate_points3d(data,from=[0,0,1],to=[0,0,-1]));

ECHO: [[nan, nan, nan], [nan, nan, nan]]
ECHO: [[nan, nan, nan], [nan, nan, nan]]
WARNING: Ignoring unknown variable 'V_UP', in file lib/BOSL/math.scad, line 898.
WARNING: Ignoring unknown variable 'V_RIGHT', in file lib/BOSL/math.scad, line 899.
WARNING: Invalid type of parameters for cross(), in file lib/BOSL/math.scad, line 900
ECHO: [[0, 0, 0], [0, 0, 0]]

assertion() doesn't really work

The assertion() function is using a version test that tests false on my OpenSCAD, even though my OpenSCAD supports assert. This enabled me to observe the behavior on legacy versions, which is that assertion() does nothing, at least in functions. It tests version, and then decides it can't run assert() so it calls f_echo() instead, but f_echo tests version and runs only if the same version threshold is met, which fails, so then nothing happens.

Also I think that assert_in_list should be rewritten like this:

function check_in_list(argname, val, l, idx=undef) =
let (success = in_list(val,l, idx))
success ? true : let(
	msg = str(
		"In argument '", argname, "', ",
		(is_str(val)? str("\"", val, "\"") : val),
		" must be one of ",
		(is_def(idx)? [for (v=l) v[idx]] : l)))
            echo(str("<span style=\"background-color: #ffb0b0\"><b>", "ERROR", ":</b> ", msg, " </span>"))
           false;

and then you call it like:

assert(check_in_list("parm",parm, ["abc","def","ghi"]),"")

This doesn't address the backwards compatibility issue, which seems impossible if there's no way to halt execution and no way to print a message from a function. I assume you're not going to try to maintain backwards compatibility once the 2019 reversion is officially released?

Also note that no matter what you do, the color you're using for error output is wrong. It should be ffb0b0 to match the standard error color. Your color is a much darker red and I can barely read the text. Also the use of

tags makes the color extend too far and it looks funny.

The warning color is ffffb0, which you also have wrong, it looks like.

threads for differencing?

It appears that the code doesn't let you create threads to subtract from a model. In other words, if I want a threaded hole in my model I have to make an oversized hole and union in a nut. That seems sort of clumsy. (I assume that the threads in a nut are not the same as the threads in a rod---that is, I can't just subtract the rod from my model.)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.