NURBS: curves & surfaces¶
Pure-Python port of the NURBS evaluation API from BOSL2’s nurbs.scad, as two classes:
NurbsCurve (evaluate a curve, sample it into a path, raise its degree) and
NurbsPatch (sample a surface, mesh it into a VNF). All three flavours –
CLAMPED, OPEN and CLOSED – are supported, with weights (rational NURBS), knot
multiplicities, and explicit knot vectors.
Each object owns its whole definition, so operations chain off it instead of threading six arguments through free functions:
NurbsCurve(ctrl, 3).curve(splinesteps=12).stroke(width=3)
NurbsCurve(ctrl, 3).elevate_degree().point(0.5)
NurbsPatch(patch, (3, 3)).vnf(splinesteps=(8, 8)).polyhedron()
NurbsCurve.curve() returns a Path2D (2-D control points) or a
Path3D (3-D), so the result carries the full path/extrude/stroke API, and
NurbsPatch.vnf() returns a VNF. The classic rational-NURBS sphere is
rendered and checked for real in tests/test_stl_render.py.
Every per-direction setting on a patch is a (u, v) pair: degree=(3, 3),
splinesteps=(16, 16), knots=(u_knots, v_knots), and so on. The curve/patch definition is
read-only once constructed – build a new object to change it.
Coverage of BOSL2 nurbs.scad¶
BOSL2 function |
Status |
Notes |
|---|---|---|
|
ported |
|
|
ported |
|
|
ported |
|
|
ported |
|
|
ported |
|
|
not ported |
the constrained least-squares interpolation solvers (fit a NURBS through given points with derivative/curvature/corner constraints) – thousands of lines of custom linear algebra; a large follow-up. |
|
not ported |
preview/annotation display modules. |
Examples¶
A cubic clamped NURBS curve, swept into a tube:
from pybosl2 import NurbsCurve
ctrl = [[0, 0, 0], [10, 20, 5], [30, -10, 10], [50, 20, 0], [60, 0, 15]]
NurbsCurve(ctrl, 3).curve(splinesteps=12).stroke(width=3).show()
A cubic B-spline surface patch meshed into a sheet:
from pybosl2 import NurbsPatch
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]],
]
NurbsPatch(patch, (3, 3)).vnf(splinesteps=(10, 10)).polyhedron().show()
A sphere as a rational NURBS surface (weights + repeated knots):
from pybosl2 import NurbsPatch
patch = [[[0, 0, 1]] * 7,
[[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]] * 7]
weights = [[w / 9 for w in row] for row in
[[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]]]
NurbsPatch(patch, (3, 3), weights=weights,
knots=(None, [0, 0.5, 0.5, 0.5, 1])).vnf(splinesteps=(12, 12)).polyhedron().show()
API reference¶
NURBS curve/surface evaluation and meshing (de Boor).
- class pybosl2.nurbs.NurbsType(*values)[source]¶
Bases:
EnumNURBS curve/surface boundary condition.
Determines how the knot vector is built and whether the curve/surface wraps.
-
CLAMPED =
'clamped'¶ Clamped (end-point-interpolating) — the default.
-
OPEN =
'open'¶ Open (non-interpolating) B-spline.
-
CLOSED =
'closed'¶ Closed (periodic) — start and end connect.
-
CLAMPED =
-
class pybosl2.nurbs.NurbsCurve(control, degree, nurbs_type=
NurbsType.CLAMPED, knots=None, mult=None, weights=None)[source]¶ Bases:
objectA NURBS curve: control points plus their knot structure, with every operation as a method.
The object owns its whole definition – degree, boundary condition, knot vector, knot multiplicities and rational weights – so operations chain off it instead of repeating six arguments at every call (BOSL2’s
nurbs_curve()/nurbs_elevate_degree()):NurbsCurve(ctrl, 3).curve(splinesteps=12).stroke(width=3) NurbsCurve(ctrl, 3).elevate_degree(2).point(0.5)Evaluate at chosen parameters with
point()/points(), sample the whole curve into a path withcurve(), and raise the degree (keeping the shape) withelevate_degree(). Indexing, iteration andlen()walk the control points.- Parameters:¶
- control : Path | Sequence[Sequence[float]] | np.ndarray¶
The control points – a sequence of
[x,y]or[x,y,z]points.- degree : int¶
The curve degree.
- nurbs_type : NurbsType¶
The boundary condition –
NurbsType.CLAMPED(the default),NurbsType.OPENorNurbsType.CLOSED.- knots : Sequence[float] | None¶
An explicit knot vector, or
Nonefor a uniform one.- mult : Sequence[int] | None¶
Knot multiplicities, or
None.- weights : Sequence[float] | None¶
Weights for a rational NURBS curve, or
None.
Examples
A cubic clamped NURBS curve through five control points, swept into a tube:
from pybosl2 import NurbsCurve ctrl = [[0, 0, 0], [10, 20, 5], [30, -10, 10], [50, 20, 0], [60, 0, 15]] NurbsCurve(ctrl, 3).curve(splinesteps=12).stroke(width=3).show()Loading 3-D preview…- property to_list : list[list[float]]¶
The control points as a plain list.
- property degree : int¶
The curve degree.
- property knots : list[float] | None¶
The explicit knot vector, or
Nonewhen the curve uses a uniform one.
- property weights : list[float] | None¶
The rational weights, or
Nonefor a non-rational curve.
-
curve(splinesteps=
16)[source]¶ Sample the whole curve into a path.
Takes splinesteps uniform samples between every pair of knots, plus a sample at every knot, which is BOSL2’s
nurbs_curve(..., splinesteps=)behaviour. Closed curves come back as closed paths.- Parameters:¶
- splinesteps : int¶
Number of samples per knot span (default 16).
- Returns:¶
A
Path2Dfor 2-D control points, or aPath3Dfor 3-D ones.- Return type:¶
Examples
Sampling a cubic curve and sweeping it into a tube:
from pybosl2 import NurbsCurve ctrl = [[0, 0, 0], [10, 20, 5], [30, -10, 10], [50, 20, 0], [60, 0, 15]] NurbsCurve(ctrl, 3).curve(splinesteps=12).stroke(width=3).show()Loading 3-D preview…
-
elevate_degree(times=
1)[source]¶ Raise the curve’s degree, keeping its shape.
Only
NurbsType.CLAMPEDandNurbsType.OPENcurves can be elevated (as in BOSL2). The result carries the knot vector the elevated curve needs, so it evaluates to the same points as this one.- Parameters:¶
- times : int¶
How many times to elevate (default 1); 0 returns an equivalent curve.
- Returns:¶
A new
NurbsCurveof degreeself.degree + times.- Raises:¶
AssertionError – If the curve is
NurbsType.CLOSED, or times is negative.- Return type:¶
-
class pybosl2.nurbs.NurbsPatch(control, degree=
(3, 3), nurbs_type=(NurbsType.CLAMPED, NurbsType.CLAMPED), knots=(None, None), mult=(None, None), weights=None)[source]¶ Bases:
objectA NURBS surface patch: a rectangular grid of control points, with its knot structure.
The surface counterpart of
NurbsCurve(BOSL2’snurbs_patch_points()/nurbs_vnf()). Every per-direction setting is a(u, v)pair – degree, boundary condition, knot multiplicities, knot vectors and splinesteps:NurbsPatch(patch, (3, 3)).vnf(splinesteps=(8, 8)).polyhedron()Evaluate single points with
point(), a grid of chosen parameters withpoints(), a uniformly sampled grid withsurface(), and mesh it withvnf(). Indexing, iteration andlen()walk the control-point rows.- Parameters:¶
- control : Sequence[Sequence[Sequence[float]]] | np.ndarray¶
A rectangular grid (rows of
[x,y,z]control points).- degree : tuple[int, int]¶
Per-direction degree
(u_degree, v_degree)(default(3,3)).- nurbs_type : tuple[NurbsType, NurbsType]¶
Per-direction boundary condition
(u_type, v_type).- knots : tuple[Sequence[float] | None, Sequence[float] | None]¶
Per-direction knot vectors
(u_knots, v_knots).- mult : tuple[Sequence[int] | None, Sequence[int] | None]¶
Per-direction knot multiplicities
(u_mult, v_mult).- weights : Sequence[Sequence[float]] | None¶
A weight matrix the same size as control for rational NURBS, or
None.
Examples
A cubic B-spline surface patch meshed into a solid:
from pybosl2 import NurbsPatch 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]], ] NurbsPatch(patch, (3, 3)).vnf().polyhedron().show()Loading 3-D preview…- property to_list : list[list[list[float]]]¶
The control-point grid as a plain list of rows.
- property degree : tuple[int, int]¶
The per-direction degree
(u_degree, v_degree).
- property nurbs_type : tuple[NurbsType, NurbsType]¶
The per-direction boundary condition
(u_type, v_type).
- property weights : list[list[float]] | None¶
The rational weight matrix, or
Nonefor a non-rational patch.
-
surface(splinesteps=
(16, 16))[source]¶ Sample the whole surface on a uniform grid.
-
vnf(splinesteps=
(16, 16), style=VNFStyle.DEFAULT, reverse=False, caps=None)[source]¶ Mesh the surface into a VNF.
Samples the patch with
surface()and builds the mesh withvertex_array(). Wrapping follows the boundary condition –CLOSEDdirections produce a continuous tube or torus.- Parameters:¶
- splinesteps : tuple[int, int]¶
Per-direction samples per knot span (default
(16,16)).- style : VnfStyle¶
vertex_array()triangulation style.- reverse : bool¶
If True, flip every face normal.
- caps : CapsSpec | None¶
A
CapsSpecclosing the open ends of a(CLAMPED, CLOSED)or(CLOSED, CLAMPED)surface;Nonefor no caps.
- Returns:¶
A
VNF.- Raises:¶
AssertionError – If caps are requested on a patch that isn’t paired
CLAMPED/CLOSED(or the reverse).- Return type:¶
Examples
Meshing a cubic B-spline patch into a solid:
from pybosl2 import NurbsPatch 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]], ] NurbsPatch(patch, (3, 3)).vnf(splinesteps=(10, 10)).polyhedron().show()Loading 3-D preview…