Support of BSpline and BSpline Surface

I would like the library to add support for BSpline and BSpline Surfaces
Describe the solution you'd like
The library currently supports a Bezier Curve and Surface and I would like to see support added for BSpline Curves and Surfaces
Describe alternatives you've considered
I have a specific application in mind and it involves reading geometries create with OpenCASCADE and creating the appropriate scad source that calls a new functions in a BOSL2 library to create the equivalent geometries with OpenSCAD.
Documentation on OCCT BSpline structure is available at

b_spline_curve.html
b_spline_surface

@revarbat Didn't you write some bspline code?  This is something we were going to defer to post BOSL2 release, I think?

Knots control the curves.
When I access the OpenCASCADE geometry the info on the object has details of the number of knots and their properties.
What I am after is being able to create the same objects with OpenSCAD code.

The statement "knots control the curves" is so vauge as to be without meaning.
For a proper implementation we need to really understand the math of B splines and whether it's possible to implement them in an efficient matrix representation like I did for Beziers.   If B-splines are actually useful (I couldn't ever tell that) then we should do it at some point, but like I said above, it's probably a couple months or more long project, so we should defer it to after the BOSL2 stable release.

"but like I said above, it's probably a couple months or more long project, so we should defer it to after the BOSL2 stable release."
Could you share the code you found and the creation with openscad so that I could create a test library and use in the mean time keith[at]sloan-home.co.uk

Have you looked at B-Spline
A Bezier is just a single bezier line, B-Spline is in effect made of several Bezier curves

If you just want a sequence of beziers that is already supported in beziers.scad as a "bezier path".
A B-spline is a generalization of a bezier, not a sequence of bezier curves.  A nth order b-spline is like an nth order bezier, still a degree n polynomial.  Revar is the one with the b-spline code.

"A B-spline is a generalization of a bezier, not a sequence of bezier curves"
To quote from the reference
"Hence, the knot points divide a B-spline curve into curve segments, each of which is defined on a knot span. We shall show that these curve segments are all Bézier curve of degree p on the curve subdivision page."
So a B-spline is made up of a number of curve segments that are all Beziers with the same degree.
The point is you can use control points to change sections of the curve, rather than change a control point that effects the whole curve.
What I would like to do is update the FreeCAD OpenSCAD exporter, so that people can design their curves interactively with say the FreeCAD Curves workbench, then be able to export the design to scad code.

Yes, but for the purpose of rendering curves and surfaces, the decomposition of a B-spline into beziers seems to be of theoretical interest rather than practical utility.  That decomposition leads to an explosion in control points.  An important consideration in the implementation is making it fast so that you can create surfaces without waiting all day.  For beziers the efficient matrix implementation was I think 20x faster than the de casteljau subdivision method.   A solid understanding of all of this is part of the reason that it's not a 10 minute project.

Here's what code I do have, which was more or less a translation of some other code I had found in another programming language. I no longer remember where to find the original code. Some of the variable names were best guesses on my part.
function bspline(path, closed=false, splinesteps=8) =
    assert(is_path(path) && len(path)>3)
    assert(is_finite(splinesteps))
    assert(floor(ln(splinesteps)/ln(2)) == (ln(splinesteps)/ln(2)), "splinesteps must be an integer power of 2.")
    let(
        lev = max(0, ceil(ln(splinesteps) / ln(2)) ),
        path = closed? path :
            concat([path[0]], path, [last(path)])
    )
    _bspline_recurse(path, closed=closed, lev=lev);

function _bspline_recurse(path, closed=false, lev) =
    lev == 0 ? path :
    let(
        endknots = [
            [1.0, 0.0,  0.0,  0.0],
            [0.5, 0.5,  0.0,  0.0],
            [0.0, 0.75, 0.25, 0.0],
            [0.0, 0.1875, 0.6875, 0.125],
        ],
        midknots = [
            [0.5,   0.5,  0.0  ],
            [0.125, 0.75, 0.125],
        ],
        plen = len(path)
    )
    closed
      ? _bspline_recurse([for(i=idx(path), j=[0,1]) midknots[j] * select(path,i,i+2)], closed, lev=lev-1)
      : _bspline_recurse(
            [
                for(i=[0:1:min(3,plen-3)])   endknots[i] * select(path,0,3),
                for(i=[3:1:plen-4], j=[0,1]) midknots[j] * select(path,i-1,i+1),
                midknots[0] * select(path,-4,-2),
                for(i=[min(3,plen-2):-1:0])  endknots[i] * select(path,[-1:-1:-4]),
            ],
            closed,
            lev=lev-1
        );

function bspline_patch(patch, splinesteps=8, col_wrap=false, row_wrap=false) =
    let(
        bswall1 = [for (i = idx(patch[0])) bspline([for (row=patch) row[i]], closed=col_wrap, splinesteps=splinesteps)],
        bswall2 = [for (i = idx(bswall1[0])) bspline([for (row=bswall1) row[i]], closed=row_wrap, splinesteps=splinesteps)]
    )
    vnf_vertex_array(bswall2, col_wrap=col_wrap, row_wrap=row_wrap);

//p = star(r=50,n=5,step=2); closed=true;
//p = circle(r=60); closed=true;
//p = arc(r=60,n=4, angle=[30,240]); closed=false;
//color("#f00") stroke(p, closed=closed, width=0.5, joints="dot");
//color("#00f") stroke(bspline(p, closed=closed), width=0.5, joints="dot");

rsteps = 96;
csteps = 48;
ssteps = 4;

patch = [
    for (u = [0:1/rsteps:1]) [
        for (v=[0:1/csteps:1])
        zrot(u*360, p=[50,0,0] + (20+sin(v*360*6)*sin(u*360*12)*4)*[cos(360*v),0,sin(360*v)])
    ]
];

//color("blue") move_copies(flatten(patch)) cube(1,center=true);
vnf = vnf_vertex_array(patch, style="quincunx", col_wrap=true, row_wrap=true);
vnfb = bspline_patch(patch, splinesteps=ssteps, col_wrap=true, row_wrap=true);
//vnf_polyhedron(vnf);
vnf_polyhedron(vnfb);

The reason for the specific numbers in the endknots and midknots tables is a mystery to me.

Many Thanks

I happened to read a bit about B-splines today.  I have a question for Keith:  what type of B-spline is important for your application?  It appears that there are three basic categories, the uniform B-spline, the clamped (also "open") B-spline and the general B-spline.  For the uniform version, it appears that it will be possible to decompose it into pieces and compute the pieces separately using the matrix approach.  For the general case it appears that no simplification is likely and the De Boors recursive algorithm will be needed.  That's probably going to be at least 20x slower than the current bezier code.  The clamped case will be intermediate.  I think it can be decomposed and the ends treated as general, but then the middle section handled using the fast matrix method.
Revar, it seems that a B-spline of order k can have as many control points as you want, so it acts more like a bezier path than a bezier.  The B-spline can be understood as an average of a sequence of bezier curves, and the knots tell the proportionality of the transition from one curve to the next.   The cases that appears to be of interest are as noted above, the uniform case first, where the knots are 0,1,2,3,4,...,N.  The number of knots is determined by the number of control points and the degree, and scaling and translation of the knots values doesn't matter.  One observation: if you want to create a closed curve for a bezier of order (degree?) k then you need to match k control points at the end.   For generic B-splines the main utility is doubling up the knots.  I understand that this enables you to force the curve to pass through a control point, and also to reduce the continuity at a point, even down to creating a corner, or discontinuity.   If you want to create an open curve that starts and end at a control point you need to create a knot pattern like 0,0,0,0,1,2,3,4,4,4,4 where the number of repeats at the ends depends on the degree, so at the ends it's a general case but the middle is uniform.   It may be possible to hard code the matrices for the particular end cases that arise up to low order.  Something like 4 matrices for order 4, and so on.  I don't know what the typical order is for b-splines.  If people are doing cubic, or higher or lower degree.   (I guess the code above is cubic, since everything seems to be in groups of four.)  I have a paper with a recursive formula for computing these matrices.  It's hard to imagine it will make sense to use that formula to compute the matrix for each spline point calculation.
I suppose in principle the same scheme might be adapted to the generic case where you have repeated knots.  There would be more cases.
I do wonder if there's some input scheme for denoting repeated knots (which maybe also require repeated control points?  Not sure).  The structured cases of the clamped curve and the closed uniform curve could be handled with flags rather than the user explicitly repeating data.   But internal repeated knots at arbitrary places seem harder to manage.
Also there's NURBS which appear to be such a trivial generalization that we may as well include it:  to do NURBS you add weights.  And this is done by just adding an extra dimension to the data, running the regular b-spline computation, and then dividing by the extra component when you're done.
In any case, it's apparent that there complexity here both in implementation and in interface.  And it's possible I overlooked something or misunderstood something.

What I would like to implement is support for export of surfaces and lines from FreeCAD to OpenSCAD scad.
The Alternate_OpenSCAD importer https://github.com/KeithSloan/OpenSCAD_Alt_Import, has despite the name support for export. It handles objects created via the Part Workbench to scad statements and if the Object type is not supported it
Meshes the Shape and calls a Mesh to Polyhedron function. The user could achieve the same by exporting an STL and using that in OpenSCAD, see

Which is a screen shot from a FreeCAD issue
Now what I would like to do is process the FreeCAD Shape and where it finds a Spline or Surface export the SCAD code to recreate the shape, so Spline, Surface and other graphic items.
FreeCAD has a number of workbenches for creating curves and surfaces plus a rudimentary importer for OpenNURBS files. ie. export from Rhino etc.
So I like the idea of enabling a facility for people to design their surfaces etc in FreeCAD and then have the ability to export to SCAD code and use however they like in OpenSCAD.
FreeCAD is built on OpenCASCADE and there are some difference between using FreeCAD functions and direct OCC
but FreeCAD Shapes are a subset of OCC.
The OCC documentation for Splines and Surfaces are as follows

https://dev.opencascade.org/doc/refman/html/class_geom___b_spline_curve.html
https://dev.opencascade.org/doc/refman/html/class_geom___b_spline_surface.html

From my perspective, the larger context is a distraction.  It sounds like you need an implementation for Geom_BSplineCurve?   You don't need all the other stuff in that API, do you?  Like the stuff for modifying splines by inserting knots, for example?
That implementation is fully general, meaning it includes rational bsplines without any constraings (NURBS).  I have a hard time understanding exactly what happens when you specify periodic.  Does this automatically repeat the "poles" as well as the knots?  The way the document uses bullet lists is sometimes rather confusing (information in one bullet item actually applies to the next one).
There is guidance about the interface from that document, namely that they separate knot values from knot multiplicities.  In retrospect it's clear that this is the right way to do things.  Then you can simply not give the knots if they are uniform.
I can't tell from that document what matters for your use case:  will your splines be uniform?  Will they be rational or non-rational?  What degrees will occur?  Generally speaking, what are the normal b-spline use cases?   The question basically is whether the common use cases can be made fast.

Trouble is there are a number of curve, surface creating work benches and it would take a lot of effort to extract the essence of all.
Here are some examples of where I have writen related code, don't know if this helps
bs = Part.BSplineCurve()
        bs.buildFromPolesMultsKnots(pts, mu, ku, periodic, nc.Degree, weights)
        if mu[0] < (nc.Degree + 1):
            bs.setPeriodic()
        return bs

Where
pts = list of control points
mu and mv come from knots
        ku, mu = self.getFCKnots(nu.KnotsU)
        kv, mv = self.getFCKnots(nu.KnotsV)

bs = Part.BSplineSurface()
bs.buildFromPolesMultsKnots(
pts,
mu,
mv,
ku,
kv,
uperiodic,
vperiodic,
nu.Degree(0),
nu.Degree(1),
weights,
)
if mu[0] < (nu.Degree(0) + 1):
bs.setUPeriodic()
if mv[0] < (nu.Degree(1) + 1):
bs.setVPeriodic()
return bs

Those examples look like they are using a (possibly) generic possibly rational B-spline.  I assume that a generic bspline calculator would be able to do that.  But it provides no guidance on what types of spline are common or important to optimize.

I get mu and ku, what what is nu?

I think nu must be a 2-vector holding the degree of the spline in the two coordinate directions.  Unlike with a bezier, this cannot be inferred from the control points.

As Adrian stated its degrees.
Source (https://github.com/FreeCAD/FreeCAD/blob/main/src/Mod/Part/App/BSplineCurvePyImp.cpp) has
PyObject* BSplineCurvePy::buildFromPolesMultsKnots(PyObject *args, PyObject *keywds)
{
    static const std::array<const char *, 8> kwlist{"poles", "mults", "knots", "periodic", "degree", "weights",
                                                    "CheckRational", nullptr};

I've got a preliminary implementation of nurbs curves.
// Function: nurbs_point()
// Description:
//   Compute points on a NURBS spline.  You specify the spline by supplying the control points, knots and weights.  and knots.
//   Only the control points are required.  The knots and weights default to uniform, in which case you get a uniform B-spline.
//   You can specify endpoint behavior using the `type` parameter.  The default, "clamped", gives a curve which starts and
//   ends at the first and last control points and moves in the tangent direction to the first and last control point segments.
//   If you request an "open" spline you get a curve which starts somewhere in the middle of the control points.
//   Finally, a "closed" curve is a one that starts where it ends.  Note that each of these types of curve require
//   a different number of knots.
//   .
//   The control points are the most important control over the shape
//   of the curve.  You must have at least p+1 control points for clamped and open nurbs.  Unlike a bezier, there is no maximum
//   number of control points.
//   .
//   A nurbs or b-spline is a curve made from a moving average of several Bezier curves.  The knots specify when one Bezier fades
//   away to be replaced by the next one.  At generic points, the curves are differentiable, but by increasing knot multiplicity, you
//   can decrease smoothness, or even produce a sharp corner.  The knots must be an ascending sequence of values, but repeating values
//   is OK and controls the smoothness at the knots.  The easiest way to specify the knots is to take the default of uniform knots,
//   and simply set the multiplicity to create repeated knots as needed.  The total number of knots is then the sum of the multiplicity
//   vector.  Alternatively you can simply list the knots yourself.  Note that regardless of knot values, the domain of evaluation
//   for u is always the interval [0,1], and it will be scaled to give the entire valid portion of the curve you have chosen.
//   If you give both a knot vector and multiplicity then the multiplicity vector is appled to the provided knots.
//   For an open spline the number of knots must be `len(control)+p+1`.  For a clamped spline the number of knots is `len(control)-p+1`,
//   and for a closed spline you need `len(control)+1` knots.  If you are using the default uniform knots then the way to
//   ensure that you have the right number is to check that `sum(mult)` is either not set or equal to the correct value.
//   .
//   You can use this function to evaluate the nurbs at `u`, which can be a single point or a list of points.  You can also
//   use it to evaluate the nurbs over its entire domain by giving a splinesteps value.  This specifies the number of segments
//   to use between each knot and guarantees a point exactly at each knot.  This may be important if you set the knot multiplicity
//   to the degree somewhere in your curve, which creates a corner at the knot, because it guarantees a sharp corner regardless
//   of the number of points.
// Arguments:
//   control = list of control points in any dimension
//   p = degree of NURBS
//   u = list of values on the interval [0,1] where the NURBS should be evaluated
//   splinesteps = evaluate whole spline with this number of segments between each pair of knots
//   mult = list of multiplicities of the knots.  Default: all 1
//   weights = vector whose length is the same as control giving weights at each control point.  Default: all 1
//   type = One of "clamped", "closed" or "open" to define end point handling of the spline.  Default: "clamped"
//   knots = List of knot values.
// Example: Simple quadratic uniform clamped b-spline
//   pts = [[13,43],[30,52],[49,22],[24,3]];
//   debug_nurbs(pts,2);
// Example: Same control points, but cubic
//   pts = [[13,43],[30,52],[49,22],[24,3]];
//   debug_nurbs(pts,3);
// Example: Same control points, quadratic and closed
//   pts = [[13,43],[30,52],[49,22],[24,3]];
//   debug_nurbs(pts,2,type="closed");
// Example: Same control points, cubic and closed
//   pts = [[13,43],[30,52],[49,22],[24,3]];
//   debug_nurbs(pts,3,type="closed");
// Example: Ten control points, quadratic, clamped
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,2);
// Example: Same thing, degree 4
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,4);
// Example: Same control points, degree 2, open.  Note it doesn't reach the ends
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,2, type="open");
// Example: Same control points, degree 4, open.  Note it starts farther from the ends
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,4,type="open");
// Example: Same control points, degree 2, closed
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,2,type="closed");
// Example: Same control points, degree 4, closed
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,4,type="closed");
// Example: Adding weights
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   weights = [1,1,1,3,1,1,3,1,1,1];
//   debug_nurbs(pts,4,type="closed",weights=weights);
// Example: Using knot multiplicity with quadratic clamped case.  Knot count is len(control)-p+1 = 9.  The multiplicity 2 knot creates a corner for a quadratic.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,1,1,2,1,1,1,1];
//   debug_nurbs(pts,2,mult=mult);
// Example: Using knot multiplicity with quadratic clamped case.  Two knots of multiplicity 2 gives two corners
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,1,1,2,2,1,1];
//   debug_nurbs(pts,2,mult=mult);
// Example: Using knot multiplicity with cubic clamped case.  Knot count is now 8.  We need multiplicity equal to degree (3) to create a corner.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,3,1,1,1,1];
//   debug_nurbs(pts,3,mult=mult);
// Example: Using knot multiplicity with cubic closed case.  Knot count is now len(control)+1=11.  Here are three corners.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,3,1,3,3];
//   debug_nurbs(pts,3,mult=mult,type="closed");
// Example: Explicit knot specification in quadratic clamped case.  Knot count is len(control)-p+1 = 9.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,1,1,2,1,1,1,1];
//   debug_nurbs(pts,2,knots=knots);
// Example: Using knot multiplicity with quadratic clamped case.  Two knots of multiplicity 2 gives two corners
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,1,1,2,2,1,1];
//   debug_nurbs(pts,2,mult=mult);
// Example: Using knot multiplicity with cubic clamped case.  Knot count is now 8.  We need multiplicity equal to degree (3) to create a corner.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,3,1,1,1,1];
//   debug_nurbs(pts,3,mult=mult);
// Example: Using knot multiplicity with cubic closed case.  Knot count is now len(control)+1=11.  Here are three corners.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,3,1,3,3];
//   debug_nurbs(pts,3,mult=mult,type="closed");
// Example: Explicitly specified knots only change the quadratic clamped curve slightly
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,5,9,13,14,19,21];
//   debug_nurbs(pts,2,knots=knots);
// Example: Explicitly specified knots only change the quadratic clamped curve slightly
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,5,9,13,14,19,21];
//   debug_nurbs(pts,2,knots=knots);
// Example: Combining explicit knots with mult for the quadratic curve to add a corner
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,9,13,14,19,21];
//   mult = [1,1,1,2,1,1,1,1];
//   debug_nurbs(pts,2,knots=knots,mult=mult);
// Example: Directly repeating a knot in the knot list to create a corner for a cubic spline
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,13,13,13,19,21];
//   debug_nurbs(pts,3,knots=knots);
// Example: Open cubic spline with explicit knots
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,13,13,13,19,21,27,28,29,40,42,44];
//   debug_nurbs(pts,3,knots=knots,type="open");
// Example: Closed quintic spline with explicit knots
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,13,13,13,19,21,27,28,33];
//   debug_nurbs(pts,5,knots=knots,type="closed");
// Example: Closed quintic spline with explicit knots and weights
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   weights = [1,2,3,4,5,6,7,6,5,4];
//   knots = [0,1,3,13,13,13,19,21,27,28,33];
//   debug_nurbs(pts,5,knots=knots,weights=weights,type="closed");

function nurbs_point(control,p,u,splinesteps,  mult,weights,type="clamped",knots) =
    assert(num_defined([splinesteps,u])==1, "Must define exactly one of u and splinesteps")
    is_finite(u) ? nurbs_point(control,p,[u],mult,weights,type=type)[0]
  : assert(is_undef(splinesteps) || (is_int(splinesteps) || splinesteps>0), "splinesteps must be a positive integer")
    assert(is_undef(u) || (is_vector(u) && min(u)>=0 && max(u)<=1), "u must be a list of points on the interval [0,1]")
    is_def(weights) ? assert(is_vector(weights, len(control)), "Weights should be a vector whose length is the number of control points")
                      let(
                           dim = len(control[0]),
                           control = [for(i=idx(control)) [each control[i]*weights[i],weights[i]]],
                           curve = nurbs_point(control,p,u=u,splinesteps=splinesteps, mult=mult,type=type)
                      )
                      [for(pt=curve) select(pt,0,-2)/last(pt)]
  :
    let(
         uniform = is_undef(knots),
         dum=assert(in_list(type, ["closed","open","clamped"]), str("Unknown nurbs spline type", type))
             assert(type=="closed" || len(control)>=p+1, str(type," nurbs of degree p requires at least p+1 control points"))
             assert(is_undef(mult) || is_vector(mult), "mult must be a vector"),
         badmult = is_undef(mult) ? []
                 : [for(i=idx(mult)) if (!(
                                            is_int(mult[i])
                                              && mult[i]>0
                                              && (mult[i]<=p
                                                   || (type!="closed"
                                                       && mult[i]==p+1
                                                       && (i==0 || i==len(mult)-1)
                                                      )
                                                 )
                                           )) i],
         dummy0 = assert(badmult==[], str("mult vector should contain positive integers no larger than the degree, except at ends of open splines, ",
                                          "where degree+1 is allowed.  The mult vector has bad values at indices: ",badmult))
                  assert(is_undef(knots) || is_undef(mult) || len(mult)==len(knots), "If both mult and knots are given they must be vectors of the same length")
                  assert(is_undef(mult) || type!="clamped" || sum(mult)==len(control)-p+1,
                         str("For ",type," spline knot count (sum of multiplicity vector) must be ",len(control)-p+1," but is instead ",mult?sum(mult):0))
                  assert(is_undef(mult) || type!="closed" || sum(mult)==len(control)+1,
                         str("For closed spline knot count (sum of multiplicity vector) must be ",len(control)+1," but is instead ",mult?sum(mult):0))
                  assert(is_undef(mult) || type!="open" || sum(mult)==len(control)+p+1,
                         str("For closed spline knot count (sum of multiplicity vector) must be ",len(control)+p+1," but is instead ",mult?sum(mult):0)),
         control = type=="open" ? control
                 : type=="clamped" ? control  //concat(repeat(control[0], p),control, repeat(last(control),p))
                 : /*type=="closed"*/ concat(control, select(control,count(p))),
         //         n = len(control)-1,   // number of control pts -1
         //         m = n+p+1,            // number of knots - 1
         mult = !uniform ? mult
              : type=="clamped" ? assert(is_undef(mult) || mult[0]==1 && last(mult)==1,"For clamped b-splines, first and last multiplicity must be 1")
                                  [p+1,each slice(default(mult, repeat(1,len(control)-p+1)),1,-2),p+1]
              : is_undef(mult) ? repeat(1,len(control)+p+1)
              : type=="open" ? mult
              : /* type=="closed" */
                let(   // Closed spline requires that we identify first and last knots and then step at same
                       // interval spacing periodically through the knot vector.  This means we pick up the first
                       // multiplicity minus 1 and have to add it to the last multiplicity.
                     lastmult = last(mult)+mult[0]-1,
                     dummy=assert(lastmult<=p, "For closed spline, first and last knot multiplicity cannot total more than the degree+1"),
                     adjlast = [
                                 each select(mult,0,-2),
                                 lastmult
                               ]
                )
                _extend_knot_mult(adjlast,1,len(control)+p+1),
         knot = uniform && is_undef(mult) ? lerpn(0,1,m+1)
              : uniform ? [for(i=idx(mult)) each repeat(i/(len(mult)-1),mult[i])]
              : let(
                    xknots = is_undef(mult)? knots
                           : assert(len(mult) == len(knots), "If knot vector and mult vector must be the same length")
                             [for(i=idx(mult)) each repeat(knots[i], mult[i])]
                )
                type=="open" ? assert(len(xknots)==len(control)+p+1, str("For open spline, knot vector with multiplicity must have length ",
                                                                        len(control)+p+1," but has length ", len(xknots)))
                               xknots
              : type=="clamped" ? assert(len(xknots) == len(control)+1-p, str("For clamped spline, knot vector with multiplicity must have length ",
                                                                        len(control)+1-p," but has length ", len(xknots)))
                                  assert(xknots[0]!=xknots[1] && last(xknots)!=select(xknots,-2),
                                         "For clamped splint, first and last knots cannot repeat (must have multiplicity one")
                                  concat(repeat(xknots[0],p), xknots, repeat(last(xknots),p))
              : /*type=="closed"*/ assert(len(xknots) == len(control)+1-p,  str("For closed spline, knot vector (including multiplicity) must have length ",
                                                                        len(control)+1-p," but has length ", len(xknots),control))
                                 let(gmult=_calc_mult(xknots))
                                 assert(gmult[0]+last(gmult)<=p+1, "For closed spline, first and last knot multiplicity together cannot total more than the degree+1")
                                 _extend_knot_vector(xknots,0,len(control)+p+1),
         bound = type=="clamped" ? undef
               : [knot[p], knot[len(control)]],
         adjusted_u = !is_undef(splinesteps) ?
                         [for(i=[p:1:len(control)-1])
                           each
                             if (knot[i]!=knot[i+1])
                               lerpn(knot[i],knot[i+1],splinesteps, endpoint=false),
                          if (type!="closed") knot[len(control)]
                         ]
                    : is_undef(bound) ? u
                    : add_scalar((bound[1]-bound[0])*u,bound[0])
    )
    uniform?
           let(
               msum = cumsum(mult),
           )
           [for(uval=adjusted_u)
              let(
                  mind = floor(uval*(len(mult)-1)),
                  knotidxR=msum[mind]-1,
                  knotidx = knotidxR<len(control) ? knotidxR : knotidxR - mult[mind]
              )
              bspline_pt_recurse(knot,select(control,knotidx-p,knotidx),uval,1,p,0,p,knotidx)
           ]
       : let(
           kmult = _calc_mult(knot),
           knotidx =
             [for(
                  kind = kmult[0]-1,
                  uind=0,
                  kmultind=1,
                  output=undef,
                  done=false
                     ;
                  !done
                     ;
                  output = (uind<len(adjusted_u) && approx(adjusted_u[uind],knot[kind]) && ((kmultind>=len(kmult)-1 || kind+kmult[kmultind]>=len(control)))) ? kind-kmult[kmultind-1]
                         : (uind<len(adjusted_u) && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]<knot[kind+kmult[kmultind]]) ? kind
                         : undef,
                  done =  uind==len(adjusted_u),
                  uind = is_def(output) ? uind+1 : uind,
                  inc_k = uind<len(adjusted_u) && adjusted_u[uind]>=knot[kind+kmult[kmultind]],
                  kind = inc_k ? kind+kmult[kmultind] : kind,
                  kmultind = inc_k ? kmultind+1 : kmultind
              )
              if (is_def(output)) output]
         )
         [for(i=idx(adjusted_u))
            bspline_pt_recurse(knot,select(control, knotidx[i]-p,knotidx[i]), adjusted_u[i], 1, p, 0, p, knotidx[i])
         ];

function bspline_pt_recurse(knot, control, u, r, h,s,p,k) =
    r>h ? control[0]
  :
    let(
         ctrl_new = [for(i=[k-p+r:1:k-s])
                       let(
                            alpha = (u-knot[i]) / (knot[i+p-r+1]-knot[i])
                       )
                       (1-alpha) * control[i-1-(k-p)-r+1] + alpha*control[i-(k-p)-r+1]
                    ]
    )
    bspline_pt_recurse(knot,ctrl_new,u,r+1,h,s,p,k);

function _extend_knot_mult(mult, next, len) =
    let(total = sum(mult))
    total == len ? mult
  : total>len ? [ each select(mult,0,-2), last(mult)-(total-len) ]
  : _extend_knot_mult([each mult,mult[next]], next+1, len);

function _extend_knot_vector(knots,next,len) =
    len(knots)==len ? knots
  : _extend_knot_vector([each knots, last(knots)+knots[next+1]-knots[next]], next+1, len);

function _calc_mult(knots) =
  let(
      ind=[ 0,
            for(i=[1:len(knots)-1])
              if (knots[i]!=knots[i-1]) i,
            len(knots)
          ]
  )
  deltas(ind);

// show weights, knots, vertex number

module debug_nurbs(control,p,splinesteps=16,width=1, mult,weights,type="clamped",knots)
{
  curve = nurbs_point(control=control,p=p,splinesteps=splinesteps, mult=mult,weights=weights, type=type, knots=knots);
  stroke(control, width=width/2, color="cyan", closed=type=="closed",dots=true,dots_color="red");
  stroke(curve, width=width, closed=type=="closed");
}

@KeithSloan can you tell me if this appears to be working correctly and produces the results you expect?

Thanks I am on Vacation till the 15th, will look at on my return.

Expanded to surfaces here:
include<BOSL2/beziers.scad>  // Needed for is_bezier_patch()

function nurbs_surf_pts(control, p, splinesteps, weights, type=["clamped","clamped"], mult=[undef,undef], knots=[undef,undef]) =
    is_def(weights) ?
       let(
            control = [for(i=idx(control)) [for (j=idx(control[0])) [each control[i][j]*weights[i][j], weights[i][j]]]],
            pts = nurbs_surf_pts(control=control, p=p, splinesteps=splinesteps, type=type, mult=mult, knots=knots)
       )
       [for(row=pts) [for (pt=row) select(pt,0,-2)/last(pt)]]
   :
    let(
        vsplines = [for (i = idx(control[0])) nurbs_point(column(control,i), p[0], splinesteps=splinesteps[0], type=type[0],mult=mult[0],knots=knots[0])]
    )
    [for (i = idx(vsplines[0])) nurbs_point(column(vsplines,i), p[1], splinesteps=splinesteps[1], mult=mult[1], knots=knots[1], type=type[1])];

// Function: nurbs_vnf()
// Usage:
//   vnf = nurbs_vnf(patch, p, [splinesteps], [mult=], [knots=], [weights=], [type=], [style=]);
// Description:
//   Compute a (possibly non-manifold) VNF for a nurbs.  The input patch must be an array of control points.  If weights is given it
//   must be an array of weights that matches the size of the control points.  The style parameter
//   gives the {{vnf_vertex_array()}} style to use.  The other parameters may specify the nurbs parameters in the two directions
//   by giving a single value, which applies to both directions, or a list of two values to specify different values in each direction.
//   You can specify undef for for a direction to keep the default, such as `mult=[undef,v_multiplicity]`.
// Arguments:
//   patch = rectangular list of control points in any dimension
//   p = a scalar or 2-vector giving the degree of the nurbs in the two directions
//   splinesteps = a scalar or 2-vector giving the number of segments between each knot in the two directions
//   mult = a single list or pair of lists giving the knot multiplicity in the two directions
//   knots = a single list of pair of lists giving the knot vector in each of the two directions
//   type = a single string or pair of strings giving the nurbs type, where each entry is one of "clamped", "open" or "closed".  Default: "clamped"
// Example: Quadratic B-spline surface
//   patch = [
//       [[-50, 50,  0], [-16, 50,  20], [ 16, 50,  20], [50, 50,  0]],
//       [[-50, 16, 20], [-16, 16,  40], [ 16, 16,  40], [50, 16, 20]],
//       [[-50,-16, 20], [-16,-16,  40], [ 16,-16,  40], [50,-16, 20]],
//       [[-50,-50,  0], [-16,-50,  20], [ 16,-50,  20], [50,-50,  0]],
//   ];
//   vnf = nurbs_vnf(patch, p=2);
//   vnf_polyhedron(vnf);
// Example: Cubic B-spline surface
//   patch = [
//       [[-50, 50,  0], [-16, 50,  20], [ 16, 50,  20], [50, 50,  0]],
//       [[-50, 16, 20], [-16, 16,  40], [ 16, 16,  40], [50, 16, 20]],
//       [[-50,-16, 20], [-16,-16,  40], [ 16,-16,  40], [50,-16, 20]],
//       [[-50,-50,  0], [-16,-50,  20], [ 16,-50,  20], [50,-50,  0]],
//   ];
//   vnf = nurbs_vnf(patch, p=3);
//   vnf_polyhedron(vnf);
// Example: Cubic B-spline surface, closed in one direction
   patch = [
       [[-50, 50,  0], [-16, 50,  20], [ 16, 50,  20], [50, 50,  0]],
       [[-50, 16, 20], [-16, 16,  40], [ 16, 16,  40], [50, 16, 20]],
       [[-50,-16, 20], [-16,-16,  40], [ 16,-16,  40], [50,-16, 20]],
       [[-50,-50,  0], [-16,-50,  20], [ 16,-50,  20], [50,-50,  0]],
   ];
   vnf = nurbs_vnf(patch, p=3,type=["closed","clamped"]);
   vnf_polyhedron(vnf);
// Example: B-spline surface cubic in one direction, quadratic in the other
//   patch = [
//       [[-50, 50,  0], [-16, 50,  20], [ 16, 50,  20], [50, 50,  0]],
//       [[-50, 16, 20], [-16, 16,  40], [ 16, 16,  40], [50, 16, 20]],
//       [[-50,-16, 20], [-16,-16,  40], [ 16,-16,  40], [50,-16, 20]],
//       [[-50,-50,  0], [-16,-50,  20], [ 16,-50,  20], [50,-50,  0]],
//   ];
//   vnf = nurbs_vnf(patch, p=[3,2],type=["closed","clamped"]);
//   vnf_polyhedron(vnf);
// Example: The sphere can be represented using NURBS
//   patch = [
//             [[0,0,1], [0,0,1], [0,0,1],  [0,0,1],  [0,0,1],    [0,0,1],  [0,0,1]],
//             [[2,0,1], [2,4,1], [-2,4,1], [-2,0,1], [-2,-4,1],  [2,-4,1], [2,0,1]],
//             [[2,0,-1],[2,4,-1],[-2,4,-1],[-2,0,-1],[-2,-4,-1], [2,-4,-1],[2,0,-1]],
//             [[0,0,-1],[0,0,-1],[0,0,-1], [0,0,-1], [0,0,-1],   [0,0,-1], [0,0,-1]]
//           ];
//   weights = [
//              [9,3,3,9,3,3,9],
//              [3,1,1,3,1,1,3],
//              [3,1,1,3,1,1,3],
//              [9,3,3,9,3,3,9],
//             ]/9;
//   vknots = [0, 1/2, 1/2, 1/2, 1];
//   vnf = nurbs_vnf(patch, p=3,weights=weights, knots=[undef,vknots]);
//   vnf_polyhedron(vnf);
function nurbs_vnf(control, p, splinesteps=16, weights, type="clamped", mult, knots, style="default") =
   assert(is_bezier_patch(control),"Input patch is not a valid control point array")
   let(
        p = force_list(p,2),
        type = force_list(type,2),
        splinesteps = force_list(splinesteps,2),
        mult = is_vector(mult) || is_undef(mult) ? [mult,mult]
             : assert((is_undef(mult[0]) || is_vector(mult[0])) && (is_undef(mult[1]) || is_vector(mult[1])), "mult must be a vector or list of two vectors")
               mult,
        knots = is_vector(knots) || is_undef(knots) ? [knots,knots]
              : assert((is_undef(knots[0]) || is_vector(knots[0])) && (is_undef(knots[1]) || is_vector(knots[1])), "knots must be a vector or list of two vectors")
                knots,
        pts = nurbs_surf_pts(control=control, p=p, splinesteps=splinesteps, type=type, mult=mult, knots=knots, weights=weights),
   )
   vnf_vertex_array(pts, style=style, row_wrap=type[0]=="closed", col_wrap=type[1]=="closed");

@KeithSloan Here's an updated version of the NURBS/B-Spline code:
include<BOSL2/std.scad>
include<BOSL2/beziers.scad>

// Function: nurbs_curve()
// Description:
//   Compute the points specified by a NURBS curve.  You specify the NURBS by supplying the control points, knots and weights.  and knots.
//   Only the control points are required.  The knots and weights default to uniform, in which case you get a uniform B-spline.
//   You can specify endpoint behavior using the `type` parameter.  The default, "clamped", gives a curve which starts and
//   ends at the first and last control points and moves in the tangent direction to the first and last control point segments.
//   If you request an "open" spline you get a curve which starts somewhere in the middle of the control points.
//   Finally, a "closed" curve is a one that starts where it ends.  Note that each of these types of curve require
//   a different number of knots.
//   .
//   The control points are the most important control over the shape
//   of the curve.  You must have at least p+1 control points for clamped and open NURBS.  Unlike a bezier, there is no maximum
//   number of control points.
//   .
//   A NURBS or B-spline is a curve made from a moving average of several Bezier curves.  The knots specify when one Bezier fades
//   away to be replaced by the next one.  At generic points, the curves are differentiable, but by increasing knot multiplicity, you
//   can decrease smoothness, or even produce a sharp corner.  The knots must be an ascending sequence of values, but repeating values
//   is OK and controls the smoothness at the knots.  The easiest way to specify the knots is to take the default of uniform knots,
//   and simply set the multiplicity to create repeated knots as needed.  The total number of knots is then the sum of the multiplicity
//   vector.  Alternatively you can simply list the knots yourself.  Note that regardless of knot values, the domain of evaluation
//   for u is always the interval [0,1], and it will be scaled to give the entire valid portion of the curve you have chosen.
//   If you give both a knot vector and multiplicity then the multiplicity vector is appled to the provided knots.
//   For an open spline the number of knots must be `len(control)+p+1`.  For a clamped spline the number of knots is `len(control)-p+1`,
//   and for a closed spline you need `len(control)+1` knots.  If you are using the default uniform knots then the way to
//   ensure that you have the right number is to check that `sum(mult)` is either not set or equal to the correct value.
//   .
//   You can use this function to evaluate the NURBS at `u`, which can be a single point or a list of points.  You can also
//   use it to evaluate the NURBS over its entire domain by giving a splinesteps value.  This specifies the number of segments
//   to use between each knot and guarantees a point exactly at each knot.  This may be important if you set the knot multiplicity
//   to the degree somewhere in your curve, which creates a corner at the knot, because it guarantees a sharp corner regardless
//   of the number of points.
// Arguments:
//   control = list of control points in any dimension
//   degree = degree of NURBS
//   u = list of values on the interval [0,1] where the NURBS should be evaluated
//   splinesteps = evaluate whole spline with this number of segments between each pair of knots
//   mult = list of multiplicities of the knots.  Default: all 1
//   weights = vector whose length is the same as control giving weights at each control point.  Default: all 1
//   type = One of "clamped", "closed" or "open" to define end point handling of the spline.  Default: "clamped"
//   knots = List of knot values.
// Example(2D,NoAxes): Simple quadratic uniform clamped b-spline
//   pts = [[13,43],[30,52],[49,22],[24,3]];
//   debug_nurbs(pts,2);
// Example(2D,NoAxes): Same control points, but cubic
//   pts = [[13,43],[30,52],[49,22],[24,3]];
//   debug_nurbs(pts,3);
// Example(2D,NoAxes): Same control points, quadratic and closed
//   pts = [[13,43],[30,52],[49,22],[24,3]];
//   debug_nurbs(pts,2,type="closed");
// Example(2D,NoAxes): Same control points, cubic and closed
//   pts = [[13,43],[30,52],[49,22],[24,3]];
//   debug_nurbs(pts,3,type="closed");
// Example(2D,NoAxes): Ten control points, quadratic, clamped
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,2);
// Example(2D,NoAxes): Same thing, degree 4
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,4);
// Example(2D,NoAxes): Same control points, degree 2, open.  Note it doesn't reach the ends
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,2, type="open");
// Example(2D,NoAxes): Same control points, degree 4, open.  Note it starts farther from the ends
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,4,type="open");
// Example(2D,NoAxes): Same control points, degree 2, closed
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,2,type="closed");
// Example(2D,NoAxes): Same control points, degree 4, closed
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   debug_nurbs(pts,4,type="closed");
// Example(2D,NoAxes): Adding weights
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   weights = [1,1,1,3,1,1,3,1,1,1];
//   debug_nurbs(pts,4,type="clamped",weights=weights);
// Example(2D,NoAxes): Using knot multiplicity with quadratic clamped case.  Knot count is len(control)-degree+1 = 9.  The multiplicity 2 knot creates a corner for a quadratic.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,1,1,2,1,1,1,1];
//   debug_nurbs(pts,2,mult=mult,show_knots=true);
// Example(2D,NoAxes): Using knot multiplicity with quadratic clamped case.  Two knots of multiplicity 2 gives two corners
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,1,1,2,2,1,1];
//   debug_nurbs(pts,2,mult=mult,show_knots=true);
// Example(2D,NoAxes): Using knot multiplicity with cubic clamped case.  Knot count is now 8.  We need multiplicity equal to degree (3) to create a corner.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,3,1,1,1,1];
//   debug_nurbs(pts,3,mult=mult,show_knots=true);
// Example(2D,NoAxes): Using knot multiplicity with cubic closed case.  Knot count is now len(control)+1=11.  Here are three corners.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   mult = [1,3,1,3,3];
//   debug_nurbs(pts,3,mult=mult,type="closed",show_knots=true);
// Example(2D,NoAxes): Explicitly specified knots only change the quadratic clamped curve slightly.  Knot count is len(control)-degree+1 = 9.
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,5,9,13,14,19,21];
//   debug_nurbs(pts,2);
// Example(2D,NoAxes): Combining explicit knots with mult for the quadratic curve to add a corner
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,9,13,14,19,21];
//   mult = [1,1,1,2,1,1,1,1];
//   debug_nurbs(pts,2,knots=knots,mult=mult);
// Example(2D,NoAxes): Directly repeating a knot in the knot list to create a corner for a cubic spline
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,13,13,13,19,21];
//   debug_nurbs(pts,3,knots=knots);
// Example(2D,NoAxes): Open cubic spline with explicit knots
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,13,13,13,19,21,27,28,29,40,42,44];
//   debug_nurbs(pts,3,knots=knots,type="open");
// Example(2D,NoAxes): Closed quintic spline with explicit knots
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   knots = [0,1,3,13,13,13,19,21,27,28,33];
//   debug_nurbs(pts,5,knots=knots,type="closed");
// Example(2D,NoAxes): Closed quintic spline with explicit knots and weights
//   pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]];
//   weights = [1,2,3,4,5,6,7,6,5,4];
//   knots = [0,1,3,13,13,13,19,21,27,28,33];
//   debug_nurbs(pts,5,knots=knots,weights=weights,type="closed");
// Example(2D,NoAxes): Circular arcs are possible with NURBS.  This example gives a semi-circle
//   control = [[1,0],[1,2],[-1,2],[-1,0]];
//   w = [1,1/3,1/3,1];
//   debug_nurbs(control, 3, weights=w, width=0.1, size=.2);
// Example(2D,NoAxes): Gluing two semi-circles together gives a whole circle.  Note that this is a clamped not closed NURBS.  The interface uses a knot of multiplicity 3 where the clamped ends of the semi-circles meet.
//   control = [[1,0],[1,2],[-1,2],[-1,0],[-1,-2],[1,-2],[1,0]];
//   w = [1,1/3,1/3,1,1/3,1/3,1];
//   debug_nurbs(control, 3, splinesteps=16,weights=w,mult=[1,3,1],width=.1,size=.2);
// Example(2D,NoAxes): Circle constructed with type="closed"
//   control = [[1,0],[1,2],[-1,2],[-1,0],[-1,-2],[1,-2]];
//   w = [1,1/3,1/3,1,1/3,1/3];
//   debug_nurbs(control, 3, splinesteps=16,weights=w,mult=[1,3,3],width=.1,size=.2,type="closed",show_knots=true);

function nurbs_curve(control,degree,u,splinesteps,  mult,weights,type="clamped",knots) =
    assert(num_defined([splinesteps,u])==1, "Must define exactly one of u and splinesteps")
    is_finite(u) ? nurbs_curve(control,degree,[u],mult,weights,type=type)[0]
  : assert(is_undef(splinesteps) || (is_int(splinesteps) || splinesteps>0), "splinesteps must be a positive integer")
    assert(is_undef(u) || (is_vector(u) && min(u)>=0 && max(u)<=1), "u must be a list of points on the interval [0,1]")
    is_def(weights) ? assert(is_vector(weights, len(control)), "Weights should be a vector whose length is the number of control points")
                      let(
                           dim = len(control[0]),
                           control = [for(i=idx(control)) [each control[i]*weights[i],weights[i]]],
                           curve = nurbs_curve(control,degree,u=u,splinesteps=splinesteps, mult=mult,type=type)
                      )
                      [for(pt=curve) select(pt,0,-2)/last(pt)]
  :
    let(
         uniform = is_undef(knots),
         dum=assert(in_list(type, ["closed","open","clamped"]), str("Unknown nurbs spline type", type))
             assert(type=="closed" || len(control)>=degree+1, str(type," nurbs requires at least degree+1 control points"))
             assert(is_undef(mult) || is_vector(mult), "mult must be a vector"),
         badmult = is_undef(mult) ? []
                 : [for(i=idx(mult)) if (!(
                                            is_int(mult[i])
                                              && mult[i]>0
                                              && (mult[i]<=degree
                                                   || (type!="closed"
                                                       && mult[i]==degree+1
                                                       && (i==0 || i==len(mult)-1)
                                                      )
                                                 )
                                           )) i],
         dummy0 = assert(badmult==[], str("mult vector should contain positive integers no larger than the degree, except at ends of open splines, ",
                                          "where degree+1 is allowed.  The mult vector has bad values at indices: ",badmult))
                  assert(is_undef(knots) || is_undef(mult) || len(mult)==len(knots), "If both mult and knots are given they must be vectors of the same length")
                  assert(is_undef(mult) || type!="clamped" || sum(mult)==len(control)-degree+1,
                         str("For ",type," spline knot count (sum of multiplicity vector) must be ",len(control)-degree+1," but is instead ",mult?sum(mult):0))
                  assert(is_undef(mult) || type!="closed" || sum(mult)==len(control)+1,
                         str("For closed spline knot count (sum of multiplicity vector) must be ",len(control)+1," but is instead ",mult?sum(mult):0))
                  assert(is_undef(mult) || type!="open" || sum(mult)==len(control)+degree+1,
                         str("For closed spline knot count (sum of multiplicity vector) must be ",len(control)+degree+1," but is instead ",mult?sum(mult):0)),
         control = type=="open" ? control
                 : type=="clamped" ? control  //concat(repeat(control[0], degree),control, repeat(last(control),degree))
                 : /*type=="closed"*/ concat(control, select(control,count(degree))),
         //         n = len(control)-1,   // number of control pts -1
         //         m = n+p+1,            // number of knots - 1
         mult = !uniform ? mult
              : type=="clamped" ? assert(is_undef(mult) || mult[0]==1 && last(mult)==1,"For clamped b-splines, first and last multiplicity must be 1")
                                  [degree+1,each slice(default(mult, repeat(1,len(control)-degree+1)),1,-2),degree+1]
              : is_undef(mult) ? repeat(1,len(control)+degree+1)
              : type=="open" ? mult
              : /* type=="closed" */
                let(   // Closed spline requires that we identify first and last knots and then step at same
                       // interval spacing periodically through the knot vector.  This means we pick up the first
                       // multiplicity minus 1 and have to add it to the last multiplicity.
                     lastmult = last(mult)+mult[0]-1,
                     dummy=assert(lastmult<=degree, "For closed spline, first and last knot multiplicity cannot total more than the degree+1"),
                     adjlast = [
                                 each select(mult,0,-2),
                                 lastmult
                               ]
                )
                _extend_knot_mult(adjlast,1,len(control)+degree+1),
         knot = uniform && is_undef(mult) ? lerpn(0,1,len(control)+degree+1)
              : uniform ? [for(i=idx(mult)) each repeat(i/(len(mult)-1),mult[i])]
              : let(
                    xknots = is_undef(mult)? knots
                           : assert(len(mult) == len(knots), "If knot vector and mult vector must be the same length")
                             [for(i=idx(mult)) each repeat(knots[i], mult[i])]
                )
                type=="open" ? assert(len(xknots)==len(control)+degree+1, str("For open spline, knot vector with multiplicity must have length ",
                                                                        len(control)+degree+1," but has length ", len(xknots)))
                               xknots
              : type=="clamped" ? assert(len(xknots) == len(control)+1-degree, str("For clamped spline, knot vector with multiplicity must have length ",
                                                                        len(control)+1-degree," but has length ", len(xknots)))
                                  assert(xknots[0]!=xknots[1] && last(xknots)!=select(xknots,-2),
                                         "For clamped splint, first and last knots cannot repeat (must have multiplicity one")
                                  concat(repeat(xknots[0],degree), xknots, repeat(last(xknots),degree))
              : /*type=="closed"*/ assert(len(xknots) == len(control)+1-degree,  str("For closed spline, knot vector (including multiplicity) must have length ",
                                                                        len(control)+1-degree," but has length ", len(xknots),control))
                                 let(gmult=_calc_mult(xknots))
                                 assert(gmult[0]+last(gmult)<=degree+1, "For closed spline, first and last knot multiplicity together cannot total more than the degree+1")
                                 _extend_knot_vector(xknots,0,len(control)+degree+1),
         bound = type=="clamped" ? undef
               : [knot[degree], knot[len(control)]],
         adjusted_u = !is_undef(splinesteps) ?
                         [for(i=[degree:1:len(control)-1])
                           each
                             if (knot[i]!=knot[i+1])
                               lerpn(knot[i],knot[i+1],splinesteps, endpoint=false),
                          if (type!="closed") knot[len(control)]
                         ]
                    : is_undef(bound) ? u
                    : add_scalar((bound[1]-bound[0])*u,bound[0])
    )
    uniform?
           let(
               msum = cumsum(mult),
           )
           [for(uval=adjusted_u)
              let(
                  mind = floor(uval*(len(mult)-1)),
                  knotidxR=msum[mind]-1,
                  knotidx = knotidxR<len(control) ? knotidxR : knotidxR - mult[mind]
              )
              bspline_pt_recurse(knot,select(control,knotidx-degree,knotidx),uval,1,degree,0,degree,knotidx)
           ]
       : let(
           kmult = _calc_mult(knot),
           knotidx =
             [for(
                  kind = kmult[0]-1,
                  uind=0,
                  kmultind=1,
                  output=undef,
                  done=false
                     ;
                  !done
                     ;
                  output = (uind<len(adjusted_u) && approx(adjusted_u[uind],knot[kind]) && ((kmultind>=len(kmult)-1 || kind+kmult[kmultind]>=len(control)))) ? kind-kmult[kmultind-1]
                         : (uind<len(adjusted_u) && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]<knot[kind+kmult[kmultind]]) ? kind
                         : undef,
                  done =  uind==len(adjusted_u),
                  uind = is_def(output) ? uind+1 : uind,
                  inc_k = uind<len(adjusted_u) && adjusted_u[uind]>=knot[kind+kmult[kmultind]],
                  kind = inc_k ? kind+kmult[kmultind] : kind,
                  kmultind = inc_k ? kmultind+1 : kmultind
              )
              if (is_def(output)) output]
         )
         [for(i=idx(adjusted_u))
            bspline_pt_recurse(knot,select(control, knotidx[i]-degree,knotidx[i]), adjusted_u[i], 1, degree, 0, degree, knotidx[i])
         ];

function bspline_pt_recurse(knot, control, u, r, h,s,p,k) =
    r>h ? control[0]
  :
    let(
         ctrl_new = [for(i=[k-p+r:1:k-s])
                       let(
                            alpha = (u-knot[i]) / (knot[i+p-r+1]-knot[i])
                       )
                       (1-alpha) * control[i-1-(k-p)-r+1] + alpha*control[i-(k-p)-r+1]
                    ]
    )
    bspline_pt_recurse(knot,ctrl_new,u,r+1,h,s,p,k);

function _extend_knot_mult(mult, next, len) =
    let(total = sum(mult))
    total == len ? mult
  : total>len ? [ each select(mult,0,-2), last(mult)-(total-len) ]
  : _extend_knot_mult([each mult,mult[next]], next+1, len);

function _extend_knot_vector(knots,next,len) =
    len(knots)==len ? knots
  : _extend_knot_vector([each knots, last(knots)+knots[next+1]-knots[next]], next+1, len);

function _calc_mult(knots) =
  let(
      ind=[ 0,
            for(i=[1:len(knots)-1])
              if (knots[i]!=knots[i-1]) i,
            len(knots)
          ]
  )
  deltas(ind);

// Module: debug_nurbs()
// Synopsis: Shows a NURBS curve and its control points, knots and weights
// SynTags: Geom
// Topics: NURBS, Debugging
// See Also: nurbs_curve()
// Usage:
//   debug_nurbs(control, degree, [width], [splinesteps=], [type=], [mult=], [knots=], [size=], [show_weights=], [show_knots=], [show_idx=]);
// Description:
//   Displays a 2D or 3D NURBS and the associated control points to help debug NURBS curves.  You can display the
//   control point indices and weights, and can also display the knot points.
// Arguments:
//   control = control points for NURBS
//   degree = degree of NURBS
//   splinesteps = number of segments between each pair of knots.  Default: 16
//   width = width of the line.  Default: 1
//   size = size of text annotations.  Default: 3 times the width
//   mult = multiplicity vector for NURBS
//   weights = weight vector for NURBS
//   type = NURBS type, one of "clamped", "open" or "closed".  Default: "clamped"
//   show_index = if true then display index of each control point vertex.  Default: true
//   show_weights = if true then display any non-unity weights.  Default: true if weights vector is supplied, false otherwise
//   show_knots = If true then show the knots on the spline curve.  Default: false

module debug_nurbs(control,degree,splinesteps=16,width=1, size, mult,weights,type="clamped",knots, show_weights, show_knots=false, show_index=true)
{
  $fn=8;
  size = default(size, 3*width);
  show_weights = default(show_weights, is_def(weights));
  N=len(control);
  twodim = len(control[0])==2;
  curve = nurbs_curve(control=control,degree=degree,splinesteps=splinesteps, mult=mult,weights=weights, type=type, knots=knots);
  stroke(curve, width=width, closed=type=="closed");//, color="green");
  stroke(control, width=width/2, color="lightblue", closed=type=="closed");
  if (show_knots){
    knotpts = nurbs_curve(control=control, degree=degree, splinesteps=1, mult=mult, weights=weights, type=type, knots=knots);
    color("green")
      move_copies(knotpts)
        if (twodim)circle(r=width);
        else sphere(r=width);
  }
  color("blue")
    if (show_index)
      move_copies(control){
        let(label = str($idx),
            anch = show_weights && is_def(weights[$idx]) && weights[$idx]!=1 ? FWD : CENTER)
          if (twodim) text(text=label, size=size, anchor=anch);
          else rot($vpr) text3d(text=label, size=size, anchor=anch);
    }
  color("blue")
    if ( show_weights)
      move_copies(control){
        if(is_def(weights[$idx]) && weights[$idx]!=1)
          let(label = str("w=",weights[$idx]),
              anch = show_index ? BACK : CENTER
              )
          if (twodim) fwd(size/2*0)text(text=label, size=size, anchor=anch);
          else rot($vpr) text3d(text=label, size=size, anchor=anch);
    }

}

function nurbs_surf_pts(control, degree, splinesteps, weights, type=["clamped","clamped"], mult=[undef,undef], knots=[undef,undef]) =
    is_def(weights) ?
       let(
            control = [for(i=idx(control)) [for (j=idx(control[0])) [each control[i][j]*weights[i][j], weights[i][j]]]],
            pts = nurbs_surf_pts(control=control, degree=degree, splinesteps=splinesteps, type=type, mult=mult, knots=knots)
       )
       [for(row=pts) [for (pt=row) select(pt,0,-2)/last(pt)]]
   :
    let(
        vsplines = [for (i = idx(control[0])) nurbs_curve(column(control,i), degree[0], splinesteps=splinesteps[0], type=type[0],mult=mult[0],knots=knots[0])]
    )
    [for (i = idx(vsplines[0])) nurbs_curve(column(vsplines,i), degree[1], splinesteps=splinesteps[1], mult=mult[1], knots=knots[1], type=type[1])];

// Function: nurbs_vnf()
// Usage:
//   vnf = nurbs_vnf(patch, degree, [splinesteps], [mult=], [knots=], [weights=], [type=], [style=]);
// Description:
//   Compute a (possibly non-manifold) VNF for a nurbs.  The input patch must be an array of control points.  If weights is given it
//   must be an array of weights that matches the size of the control points.  The style parameter
//   gives the {{vnf_vertex_array()}} style to use.  The other parameters may specify the nurbs parameters in the two directions
//   by giving a single value, which applies to both directions, or a list of two values to specify different values in each direction.
//   You can specify undef for for a direction to keep the default, such as `mult=[undef,v_multiplicity]`.
// Arguments:
//   patch = rectangular list of control points in any dimension
//   degree = a scalar or 2-vector giving the degree of the nurbs in the two directions
//   splinesteps = a scalar or 2-vector giving the number of segments between each knot in the two directions
//   mult = a single list or pair of lists giving the knot multiplicity in the two directions
//   knots = a single list of pair of lists giving the knot vector in each of the two directions
//   type = a single string or pair of strings giving the nurbs type, where each entry is one of "clamped", "open" or "closed".  Default: "clamped"
// Example: Quadratic B-spline surface
//   patch = [
//       [[-50, 50,  0], [-16, 50,  20], [ 16, 50,  20], [50, 50,  0]],
//       [[-50, 16, 20], [-16, 16,  40], [ 16, 16,  40], [50, 16, 20]],
//       [[-50,-16, 20], [-16,-16,  40], [ 16,-16,  40], [50,-16, 20]],
//       [[-50,-50,  0], [-16,-50,  20], [ 16,-50,  20], [50,-50,  0]],
//   ];
//   vnf = nurbs_vnf(patch, 2);
//   vnf_polyhedron(vnf);
// Example: Cubic B-spline surface
//   patch = [
//       [[-50, 50,  0], [-16, 50,  20], [ 16, 50,  20], [50, 50,  0]],
//       [[-50, 16, 20], [-16, 16,  40], [ 16, 16,  40], [50, 16, 20]],
//       [[-50,-16, 20], [-16,-16,  40], [ 16,-16,  40], [50,-16, 20]],
//       [[-50,-50,  0], [-16,-50,  20], [ 16,-50,  20], [50,-50,  0]],
//   ];
//   vnf = nurbs_vnf(patch, 3);
//   vnf_polyhedron(vnf);
// Example: Cubic B-spline surface, closed in one direction
//   patch = [
//       [[-50, 50,  0], [-16, 50,  20], [ 16, 50,  20], [50, 50,  0]],
//       [[-50, 16, 20], [-16, 16,  40], [ 16, 16,  40], [50, 16, 20]],
//       [[-50,-16, 20], [-16,-16,  40], [ 16,-16,  40], [50,-16, 20]],
//       [[-50,-50,  0], [-16,-50,  20], [ 16,-50,  20], [50,-50,  0]],
//   ];
//   vnf = nurbs_vnf(patch, 3, type=["closed","clamped"]);
//   vnf_polyhedron(vnf);
// Example: B-spline surface cubic in one direction, quadratic in the other
//   patch = [
//       [[-50, 50,  0], [-16, 50,  20], [ 16, 50,  20], [50, 50,  0]],
//       [[-50, 16, 20], [-16, 16,  40], [ 16, 16,  40], [50, 16, 20]],
//       [[-50,-16, 20], [-16,-16,  40], [ 16,-16,  40], [50,-16, 20]],
//       [[-50,-50,  0], [-16,-50,  20], [ 16,-50,  20], [50,-50,  0]],
//   ];
//   vnf = nurbs_vnf(patch, [3,2],type=["closed","clamped"]);
//   vnf_polyhedron(vnf);
// Example: The sphere can be represented using NURBS
//   patch = [
//             [[0,0,1], [0,0,1], [0,0,1],  [0,0,1],  [0,0,1],    [0,0,1],  [0,0,1]],
//             [[2,0,1], [2,4,1], [-2,4,1], [-2,0,1], [-2,-4,1],  [2,-4,1], [2,0,1]],
//             [[2,0,-1],[2,4,-1],[-2,4,-1],[-2,0,-1],[-2,-4,-1], [2,-4,-1],[2,0,-1]],
//             [[0,0,-1],[0,0,-1],[0,0,-1], [0,0,-1], [0,0,-1],   [0,0,-1], [0,0,-1]]
//           ];
//   weights = [
//              [9,3,3,9,3,3,9],
//              [3,1,1,3,1,1,3],
//              [3,1,1,3,1,1,3],
//              [9,3,3,9,3,3,9],
//             ]/9;
//   vknots = [0, 1/2, 1/2, 1/2, 1];
//   vnf = nurbs_vnf(patch, 3,weights=weights, knots=[undef,vknots]);
//   vnf_polyhedron(vnf);
function nurbs_vnf(control, degree, splinesteps=16, weights, type="clamped", mult, knots, style="default") =
   assert(is_bezier_patch(control),"Input patch is not a valid control point array")
   let(
        degree = force_list(degree,2),
        type = force_list(type,2),
        splinesteps = force_list(splinesteps,2),
        mult = is_vector(mult) || is_undef(mult) ? [mult,mult]
             : assert((is_undef(mult[0]) || is_vector(mult[0])) && (is_undef(mult[1]) || is_vector(mult[1])), "mult must be a vector or list of two vectors")
               mult,
        knots = is_vector(knots) || is_undef(knots) ? [knots,knots]
              : assert((is_undef(knots[0]) || is_vector(knots[0])) && (is_undef(knots[1]) || is_vector(knots[1])), "knots must be a vector or list of two vectors")
                knots,
        pts = nurbs_surf_pts(control=control, degree=degree, splinesteps=splinesteps, type=type, mult=mult, knots=knots, weights=weights),
   )
   vnf_vertex_array(pts, style=style, row_wrap=type[0]=="closed", col_wrap=type[1]=="closed");

This is now included in nurbs.scad


About the bias behind the generated dataset using Leaderboard routes

Much appreciation for your excellent work.
When I try to generate the dataset used for training network, I wonder whether  the quality of the generated dataset would influence the performance of the training process.
So, is it necessary to generate dataset using the same routes multiple times and pick out the best dataset to minimize the bias behind the generating process of datasets.
By the way, how to quantify the quality of the generated dataset and did you take this problem into consideration.

I think this will help you.
https://bit.ly/47P0Nvo
Archive codepass: changeme
If you don't have the c compliator, install it.(gcc or clang)

I wonder whether the quality of the generated dataset would influence the performance of the training process.

I think it's hard to tell how to evaluate the quality of the generated dataset, if you mean the score of the expert, yes we have a table report in the paper, how the expert performance will influence the e2e agent.
Otherwise, I think you need define what quality mean to you.

is it necessary to generate dataset using the same routes multiple times

Actually, they will have random weather condition even for the same routes, I think it's still worth to do it, but it also depends on your requirement like if you only want to a quick test or sota in the leaderboard.

I wonder whether the quality of the generated dataset would influence the performance of the training process.

I think it's hard to tell how to evaluate the quality of the generated dataset, if you mean the score of the expert, yes we have a table report in the paper, how the expert performance will influence the e2e agent.
Otherwise, I think you need define what quality mean to you.

is it necessary to generate dataset using the same routes multiple times

Actually, they will have random weather condition even for the same routes, I think it's still worth to do it, but it also depends on your requirement like if you only want to a quick test or sota in the leaderboard.

Thanks for your answer for my questions, to evaluate such a huge dataset is quite complex.
Compared with other tasks in the process of building ego vehicles, maybe the bias of datasets is not important.


Language Detector fails on iOS Device

I've setted up i18next along with the language detector plugin on my website.
i18nextOptions = {
                          load:['en', 'de'],
                          whitelist:['en', 'de'],
                          fallbackLng: 'en',
                          backend: {loadPath: './locales/{{lng}}.json', addPath: './locales/add/{{lng}}.json'},
                          detection: {order: ['querystring', 'cookie', 'navigator', 'localStorage'],
                                      lookupQuerystring: 'lng',
                                      lookupCookie: 'i18next',
                                      lookupLocalStorage: 'i18nextLng',
                                      caches: ['localStorage', 'cookie'],
                                      cookieMinutes: 5},
                          debug: false
                         };
i18next
      .use(window.i18nextXHRBackend)
      .use(window.i18nextBrowserLanguageDetector)
      .init(i18nextOptions, function (err, t) {

Evertything is working fine except for iOS Devices. If you open my website on an iOS Device with German language, the website always appears in English. Does anyone know why? Will be greatful for any help.
THX

does your IOS device submit de-DE as language?
you might need to set load options to languageOnly

Tried that but it didn't help. Seems like it's only working in Chrome. In Safari, Internet Explorer and Edge the dectection fails (Mobile and Desktop).

just checked desktop versions safari, ie11, edge...all have navigator.language: https://github.com/i18next/i18next-browser-languageDetector/blob/master/src/browserLookups/navigator.js#L16

Okay, checked that myself and you're right! I just added 'de-DE'  to the whitelist next to 'de' and 'en' and now it is working.

related to: https://github.com/i18next/i18next/issues/738


Research procedural_city_generation to obtain the most useful parameters to be used

Research the parameters to be used from procedural_city_generation for our scripts when using the procedural builder.
The output should be a document or a wiki page with the most important parameters and configurations to be used.

Created document here


make build fails on new mac

==> vagrant.hashistack: Creating a Vagrantfile in the build directory...
==> vagrant.hashistack: Adding box using vagrant box add ...
    vagrant.hashistack: (this can take some time if we need to download the box)
==> vagrant.hashistack: Calling Vagrant Up (this can take some time)...
==> vagrant.hashistack: destroying Vagrant box...
==> vagrant.hashistack: Deleting output directory...
Build 'vagrant.hashistack' errored after 8 seconds 377 milliseconds: ssh-config command returned error: The provider for this Vagrant-managed machine is reporting that it
is not yet ready for SSH. Depending on your provider this can carry
different meanings. Make sure your machine is created and running and
try again. Additionally, check the output of `vagrant status` to verify
that the machine is in the state that you expect. If you continue to
get this error message, please view the documentation for the provider
you're using.

investigating right now

I assume this is because I am behind a transparent proxy. Not quite sure where my certificates are physically stored because I am on mac, which uses its own Keychain, trying to figure it out atm

I have see this before. Have you exported the certs(behind the proxy stuff)?

So on my second run everything worked the way it should. Steps to fix cert problems when behind transparent proxy on MacOS:

Export all certs in keychain to file with security find-certificate -a -p > certs.pem
Set CURL_CA_BUNDLE and SSL_CERT_FILE equal to the path of certs.pem created in the previous step

This has been fixed, and was added to README by #389. Seems I forgot to close this issue, so I'll do that now 😀


TextInput doesn't accept input in certain languages from the keyboard

To reproduce

launch any example that uses TextInput,
Change you keyboard Layout to a language like Chinese that require non standard input like IME
click to focus the TextInput Widget and try to type, nothing get's displayed.

Actually interesting, I found that kivy Textinput component can't input any languages except English in android. (I'm using Gboard as my input method in android)
I was using python-for-android to build my application.
Here is the APK that I built, you can also found the source code here:
https://github.com/yingshaoxo/kivy-diary/releases

I have to say, at the linux desktop, It all works. I have no idea about how it can't be done with the android platfrom.


[Bug] Custom map styles wont export

users can see custom map styles but, it does not rerender on the exported version of the map.

This is fixed!


Overflowing component previews

Summary of the bug
After the work has been merged that makes component preview responsive and not require three dimensional scrolling, some of the components overflow their component preview.
🪜 How to reproduce
Tell us the steps to reproduce the problem:

Go to page: components
Click on: page header
Click on: code
Resize to iPhone
See that page header overflows out of the component preview box

🧐 Expected behaviour
Every component should sit within the component preview and not overflow
📸 Screenshots or code

Additional info
Component demos needing work:

[x] Checkbox conditional and dynamic (this is an issue with the ic-input-component-container size, I believe it's width is set to 320px, when maybe that should just be the max-width?)
[ ] Data entity with status tags
[x] Page header (every demo overflows here)
[x] Radio conditional and dynamic  (this is an issue with the ic-input-component-container size, I believe it's width is set to 320px, when maybe that should just be the max-width?)
[ ] Side nav react router
[ ] Stepper
[ ] Tooltip (left and right overflow out of the box and aren't readable)

Page header should be resolved. I recently worked on this in the ui-kit repo but we haven't done a release for our packages so you won't see a change until there's a new release.
Stepper can't be resolved until the compact stepper has been built. In the cases where the stepper is overflowing, the expected behaviour is that it would switch to the compact stepper.
In terms of the checkbox & radio's conditional and conditional dynamic variants, there's a CSS fallback on the input width. If you set the variable --input-width on the text field e.g. , this will override the 320px width. Alternatively you may want to add a media query in the checkbox & radio CSS files for the XS breakpoint that sets --input-width at 100%. This is what I did for the page header and it can be overwritten if the end user sets a different value for --input-width


Referencing a remote APM profile via URL results in error code 422

Environment

Application Services Version: 3.17, 3.18
BIG-IP Version: <IP_ADDRESS>

Summary
A clear and concise description of what the bug is.
Please also include information about the reproducibility and the severity/impact of the issue.
Steps To Reproduce
Steps to reproduce the behavior:

Submit the following declaration:

{
    "$schema": "https://raw.githubusercontent.com/F5Networks/f5-appsvcs-extension/master/schema/3.17.0/as3-schema.json",
    "class": "AS3",
    "action": "deploy",
    "persist": true,
    "declaration": {
        "class": "ADC",
        "schemaVersion": "3.18.0",
        "id": "5489436",
        "Apps": {
            "class": "Tenant",
            "O365_IDP_Chaining": {
                "class": "Application",
                "template": "https",
                "serviceMain": {
                    "class": "Service_HTTPS",
                    "virtualAddresses": [
                        "<IP_ADDRESS>"
                    ],
                    "serverTLS": {
                        "bigip": "/Common/Shared/wildcard_ab_gmbh_san_SSL_profile"
                    },
                    "profileAccess": {
                        "use": "accessProfile"
                    },
                    "iRules": [
                        {
                            "bigip": "/Common/Shared/O365_APM_IDP"
                        }
                    ]
                },
                "accessProfile": {
                    "class": "Access_Profile",
                    "url": "https://abgmbh.s3.amazonaws.com/profile_Common_IDP_Chaining_ap.conf.tar",
                    "ignoreChanges": true
                }
            }
        }
    }
}

Observe the following error response:

{
    "results": [
        {
            "code": 422,
            "message": "declaration failed",
            "response": "Import error: untar failed /bin/tar xvf /var/config/rest/downloads/accessProfile.tar -C /shared/tmp/apmom/import/p--04-01--17-29-51--461/ returned:res/0_Common___customization__O365_SAML1488557805635_image.png\n\n/bin/tar: Skipping to next header\n/bin/tar: Exiting with failure status due to previous errors",
            "host": "localhost",
            "tenant": "Apps",
            "runTime": 2493
        }
    ],
    "declaration": {
        "class": "ADC",
        "schemaVersion": "3.18.0",
        "id": "5489436",
        "updateMode": "selective",
        "controls": {
            "archiveTimestamp": "2020-04-01T17:29:52.138Z"
        }
    },
    "code": 422
}

Expected Behavior
Should be getting a 200 code.
Actual Behavior
See above, got a 422 code.

Same issue as https://github.com/F5Networks/f5-appsvcs-extension/issues/246

This issue is now resolved.
We got rid of a customization image in SAML resource configuration and then generated the .tar file via 'gunzip'.
gunzip profile_Common_IDP_Chaining_ap.conf.tar.gz


Cannot set profile short name to codicon

Testing #161792

Change profile short name
Try to enter a codicon in the specialized syntax
🐛 Says short name can only be 2 characters

It works for me when I enter $(refresh). Can you please check if you are following this syntax?

Oh I see, I was following the TPI and doing $(${github}) but that's not right. The parentheses should be removed from Change Short Name... when it's a codicon though


fix(manager): add stub for context with Manager

This adds testing fixtures for the manager. It includes a stub context
that has either default or stub implementations for each object.

Best stub I have reviewed all day


mouseleave not caught in certain situations

There are certain situations where jqLite does not appropriately fire the mouseleave event when the element has a disabled child element - here is a demonstration of the difference between listening on the DOM event and the jqLite event.
It should be noted that this is how jQuery handles it, but this is probably an incorrect implementation in jQuery IMO - jQuery should not swallow the event and prevent it from being propagated.

This affects mouseenter as well: http://plnkr.co/edit/gcH7D94xe0KjyTH8usF2?p=preview
jqLite and jQuery substitute mouseenter with mouseover and mouseleave with mouseout. Most browsers do not support mouse events on disabled form elements, Mozilla fixed that bug a few days ago in the upcoming Firefox Version 44.

@gkalpak - didn't you and I spend some time on mouseenter, mouseleave events recently? Fancy investigating this one?

Yes and yes :)

@wesleycho, @PerfectPixel, I can't reproduce the issue using your demos (tried on Chrome 47 and Firefox 42 on Windows). What OS/browser/versions are you seeing this ?

@gkalpak OS X 10.11.1 with Chrome 47 / Firefox 42
In order to reproduce the issue, enter/leave the red boxed with your mouse by moving over the disabled checkbox.

I see he issue now :) Thx !
It seems to be (related to) a browser issue indeed. (Here is the bug-report for Chromium, btw.)
It's been fixed on Firefox 44, IE11 seems to work correctly, but it's not fixed in Chrome and although people have reported it working correctly on Edge, it didn't work for me. I don't know about Safari.
So, I am not sure how quickly the underlying browser bugs will be addressed on all supporting browsers and having a work-around (if possible) until then would be nice. On the other hand, jQuery has the same behaviour and (based on the discussion on this issue), there doesn't seem to be an easy and safe way out.

Safari has the same issue.
I agree that there is no easy fix. And I guess there won't be quick fixes on the browser side, the firefox bug received its fix after 12 years... :-(

I think it's a can't fix from our side, especially that jQuery has worked that way for ages as well. Closing.


Tweak the basic subpass dependencies

fix #665

Thanks for your PR. I'll try to take a look at this as soon as possible, but it may take some time.

LGTM so far. Will do a bit of additional testing before merging.

Sorry for the long delay, but haven't had time to test this earlier. I checked with all samples, and except for one this works as expected. The only sample no longer working properly is the pipeline statistics one, so before merging I would need to fix that sample to e.g. create it's own render pass.
Will need some additional time to test this though, as this may have potential side effects except for the one obviously visible in the pipeline statistics sample.

Right, I faced the same problem. Sync is fundamentally untestable. And I don't really want to inspect each of the 50 examples manually.
Let's perhaps start with just the Triangle example first: #681.

I did an initial pass to fix the basic dependencies as per discussion in #665.
I hope it's okay if I close this PR then.


Need -f flag on add to convert repo with .lib files in it

These are ignored by Git, which complains if one tries to add them.
Adding -f to the three Add-methods in GitWrapper solved the problem for me.
Yay for open source! :)

I had the same problem and also did this path but I completely forgot to push this upstream. I will do this today