Splinepy
Library for prototyping spline geometries of arbitrary dimensions and degrees, and IGA
Install / Use
/learn @tataratat/SplinepyREADME
splinepy - Library for prototyping spline geometries of arbitrary dimensions and degrees, and IGA

Install guide
splinepy wheels are available for python3.8+ for MacOS, Linux, and Windows:
# including all optional dependencies
pip install "splinepy[all]" # quotation marks required for some shells
# or
pip install splinepy
You can install it directly from the source:
git clone git@github.com:tataratat/splinepy.git
cd splinepy
git submodule update --init --recursive
pip install -e .
Documentation
Here are links to related documentation for the library:
Quick start
1. Create a spline
Here, we will create a NURBS for the following example. Alternatively, we can also create Bezier, RationalBezier, and BSpline.
import splinepy
# Initialize nurbs with any array-like input
nurbs = splinepy.NURBS(
degrees=[2, 1],
knot_vectors=[
[0, 0, 0, 1, 1, 1],
[0, 0, 1, 1],
],
control_points=[
[-1.0, 0.0],
[-1.0, 1.0],
[0.0, 1.0],
[-2.0, 0.0],
[-2.0, 2.0],
[0.0, 2.0],
],
weights=[
[1.0],
[2**-0.5],
[1.0],
[1.0],
[2**-0.5],
[1.0],
],
)
# vizusalize
nurbs.show()
<p align="center"><img src="docs/source/_static/readme_nurbs.png" width="70%" title="nurbs"></p>
2. Modifications
All the splines can be modified. For example, by
- directly accessing properties,
- elevating degrees,
- inserting knots,
- reducing degrees and removing knots with a specified tolerance
Note: currently {3, 4} are limited to BSpline families.
# start with a copy of the original spline
modified = nurbs.copy()
# manipulate control points
# 1. all at once
modified.control_points /= 2.0
# 2. indexwise (flat indexing)
modified.control_points[[3, 4, 5]] *= [1.3, 2.0]
# 3. with grid-like indexing using multi_index helper
multi_index = modified.multi_index
modified.control_points[multi_index[0, 1]] = [-1.5, -0.3]
modified.control_points[multi_index[2, :]] += [2.0, 0.1]
modified.show() # visualize Nr. 1
# elevate degrees and insert knots
modified.elevate_degrees([0, 1])
modified.show() # visualize Nr. 2
modified.insert_knots(1, [0.5])
modified.show() # visualize Nr. 3

3. Evaluate
You can evaluate spline's basis functions, mapping, and their derivatives by giving parametric coordinate queries.
They should be 2D array-like objects and functions return 2D np.ndarray.

# first, create parametric coordinate queries
queries = [
[0.1, 0.2], # first query
[0.4, 0.5], # second query
[0.1156, 0.9091], # third query
]
# evaluate basis, spline and derivatives.
# for derivatives, specify order per parametric dimension.
basis = nurbs.basis(queries)
basis_derivative = nurbs.basis_derivative(queries, [1, 1])
physical_coordinates = nurbs.evaluate(queries)
physical_derivatives = nurbs.derivative(queries, [2, 0])
Many of splinepy's multi-query functions can be executed in parallel using multithread executions on c++ side. For that, set either the global flag or pass the nthreads argument.
p_basis0 = nurbs.basis(queries, nthreads=2)
# or
splinepy.settings.NTHREADS = 3
p_basis1 = nurbs.basis(queries)
We also implemented point inversion for splines.
# see docs for options
para_coordinates = nurbs.proximities(physical_coordinates)
import numpy as np
assert np.allclose(queries, para_coordinates)
In cases, where you may have to compute derivatives at the inverted locations or you just want to know more information about the search, you can set the keyword return_verbose=True:
(
parametric_coordinates,
physical_coordindates,
physical_difference,
distance,
convergence_norm,
first_derivatives,
second_derivatives,
) = nurbs.proximities(physical_coordinates, return_verbose=True)
4. Helper Modules
There's a list of helper modules under the namespace splinepy.helpme to boost prototyping efficiencies. Please check out the full list here!
Here are some highlights.
4.1 Create
splinepy.helpme.create module can help you create several primitive shapes and another spline based on the existing spline.
# basic splines
box = splinepy.helpme.create.box(1, 2, 3) # length per dim
disk = splinepy.helpme.create.disk(
outer_radius=3, inner_radius=2, angle=256
)
torus = splinepy.helpme.create.torus(
torus_radius=3, section_outer_radius=1.5
)
splinepy.show(["box", box], ["disk", disk], ["torus", torus])
For the latter, you can directly access such functions through spline.create.
# based on existing splines
extruded = nurbs.create.extruded(extrusion_vector=[1, 2, 3])
revolved = nurbs.create.revolved(
axis=[1, 0, 0], center=[-1, -1, 0], angle=50
)
splinepy.show(["extruded", extruded], ["revolved", revolved])

4.2 Extract
Using splinepy.helpme.extract module, you can extract meshes (as a gustaf object)
# extract meshes as gustaf objects
control_mesh = nurbs.extract.control_mesh()
control_points = nurbs.extract.control_points()
mesh = nurbs.extract.faces([201, 33]) # specify sample resolutions
splinepy.show(
["control mesh", control_mesh.to_edges()],
["control points", control_points],
["spline", mesh],
)
or part of splines from an existing spline using spline.extract.
# extract splines
boundaries = nurbs.extract.boundaries()
partial = nurbs.extract.spline(0, [0.5, 0.78])
partial_partial = nurbs.extract.spline(0, [0.1, 0.3]).extract.spline(
1, [0.65, 0.9]
)
bases = nurbs.extract.bases() # basis functions as splines
# insert knots to increase number of bezier patches
inserted = nurbs.copy()
inserted.insert_knots(0, [0.13, 0.87])
beziers_patches = inserted.extract.beziers()
splinepy.show(
[
"boundaries and part of splines",
boundaries,
partial,
partial_partial,
],
["beziers", beziers_patches],
["bases", bases],
)

4.3 Free-form deformation
Together with mesh types of gustaf, we can perform free-form deformation
import gustaf as gus
# create gustaf mesh using extract.spline()
# or use gustaf's io functions (gustaf.io)
mesh = splinepy.helpme.create.torus(2, 1).extract.faces([100, 100, 100])
# initialize ffd and move control points
ffd = splinepy.FFD(mesh=mesh)
multi_index = ffd.spline.multi_index
ffd.spline.control_points[multi_index[-1, :, -1]] += [3, 0.5, 0.1]
ffd.show()
# get deformed mesh - FFD.mesh attribute deforms mesh before returning
deformed = ffd.mesh

4.4 Fitting
You can fit your point data using splines.
data = [
[-0.955, 0.293],
[-0.707, 0.707],
[-0.293, 0.955],
[-1.911, 0.587],
[-1.414, 1.414],
[-0.587, 1.911],
]
curve, residual_curve = splinepy.helpme.fit.curve(data, degree=2)
# you can also use any existing spline's basis
surface, residual_surface = splinepy.helpme.fit.surface(
data, size=[3, 2], fitting_spline=nurbs
)
# set visuals for data
d = gus.Vertic
