bezier.surface module¶
Helper for Bézier Surfaces / Triangles.
-
class
bezier.surface.
Surface
(nodes, _copy=True)¶ Bases:
bezier._base.Base
Represents a Bézier surface.
We define a Bézier triangle as a mapping from the unit simplex in 2D (i.e. the unit triangle) onto a surface in an arbitrary dimension. We use barycentric coordinates
\[\lambda_1 = 1 - s - t, \lambda_2 = s, \lambda_3 = t\]for points in
\[\left\{(s, t) \mid 0 \leq s, t, s + t \leq 1\right\}.\]As with curves, using these weights we get convex combinations of points \(v_{i, j, k}\) in some vector space:
\[B\left(\lambda_1, \lambda_2, \lambda_3\right) = \sum_{i + j + k = d} \binom{d}{i \, j \, k} \lambda_1^i \lambda_2^j \lambda_3^k \cdot v_{i, j, k}\]Note
We assume the nodes are ordered from left-to-right and from bottom-to-top. So for example, the linear triangle:
(0,0,1) (1,0,0) (0,1,0)
is ordered as
\[\begin{split}\left[\begin{array}{c c c} v_{1,0,0} & v_{0,1,0} & v_{0,0,1} \end{array}\right]^T\end{split}\]the quadratic triangle:
(0,0,2) (1,0,1) (0,1,1) (2,0,0) (1,1,0) (0,2,0)
is ordered as
\[\begin{split}\left[\begin{array}{c c c c c c} v_{2,0,0} & v_{1,1,0} & v_{0,2,0} & v_{1,0,1} & v_{0,1,1} & v_{0,0,2} \end{array}\right]^T\end{split}\]the cubic triangle:
(0,0,3) (1,0,2) (0,1,2) (2,0,1) (1,1,1) (0,2,1) (3,0,0) (2,1,0) (1,2,0) (0,3,0)
is ordered as
\[\begin{split}\left[\begin{array}{c c c c c c c c c c} v_{3,0,0} & v_{2,1,0} & v_{1,2,0} & v_{0,3,0} & v_{2,0,1} & v_{1,1,1} & v_{0,2,1} & v_{1,0,2} & v_{0,1,2} & v_{0,0,3} \end{array}\right]^T\end{split}\]and so on.
>>> import bezier >>> nodes = np.array([ ... [0.0 , 0.0 ], ... [1.0 , 0.25], ... [0.25, 1.0 ], ... ]) >>> surface = bezier.Surface(nodes) >>> surface <Surface (degree=1, dimension=2)>
Parameters: nodes (numpy.ndarray) – The nodes in the surface. The rows represent each node while the columns are the dimension of the ambient space. -
area
¶ float: The area of the current surface.
Raises: NotImplementedError
– If the area isn’t already cached.
-
edges
¶ tuple: The edges of the surface.
>>> nodes = np.array([ ... [0.0 , 0.0 ], ... [0.5 , -0.1875], ... [1.0 , 0.0 ], ... [0.1875, 0.5 ], ... [0.625 , 0.625 ], ... [0.0 , 1.0 ], ... ]) >>> surface = bezier.Surface(nodes) >>> edge1, _, _ = surface.edges >>> edge1 <Curve (degree=2, dimension=2)> >>> edge1.nodes array([[ 0. , 0. ], [ 0.5 , -0.1875], [ 1. , 0. ]])
Returns: The edges of the surface. Return type: Tuple
[Curve
,Curve
,Curve
]
-
evaluate_barycentric
(lambda1, lambda2, lambda3)¶ Compute a point on the surface.
Evaluates \(B\left(\lambda_1, \lambda_2, \lambda_3\right)\).
>>> nodes = np.array([ ... [0.0 , 0.0 ], ... [1.0 , 0.25], ... [0.25, 1.0 ], ... ]) >>> surface = bezier.Surface(nodes) >>> surface.evaluate_barycentric(0.125, 0.125, 0.75) array([ 0.3125 , 0.78125])
However, this can’t be used for points outside the reference triangle:
>>> surface.evaluate_barycentric(-0.25, 0.75, 0.5) Traceback (most recent call last): ... ValueError: ('Parameters must be positive', -0.25, 0.75, 0.5)
or for non-Barycentric coordinates;
>>> surface.evaluate_barycentric(0.25, 0.25, 0.25) Traceback (most recent call last): ... ValueError: ('Values do not sum to 1', 0.25, 0.25, 0.25)
Parameters: Returns: The point on the surface (as a one dimensional NumPy array).
Return type: Raises: ValueError
– If the weights are not valid barycentric coordinates, i.e. they don’t sum to1
.ValueError
– If some weights are negative.
-
evaluate_cartesian
(s, t)¶ Compute a point on the surface.
Evaluates \(B\left(1 - s - t, s, t\right)\) by calling
evaluate_barycentric()
.Parameters: Returns: The point on the surface (as a one dimensional NumPy array).
Return type:
-
evaluate_multi
(param_vals)¶ Compute multiple points on the surface.
If
param_vals
has two columns, this method treats them as Cartesian:>>> nodes = np.array([ ... [ 0., 0. ], ... [ 2., 1. ], ... [-3., 2. ], ... ]) >>> surface = bezier.Surface(nodes) >>> surface <Surface (degree=1, dimension=2)> >>> param_vals = np.array([ ... [0.0, 0.0], ... [1.0, 0.0], ... [0.5, 0.5], ... ]) >>> surface.evaluate_multi(param_vals) array([[ 0. , 0. ], [ 2. , 1. ], [-0.5, 1.5]])
and if
param_vals
has three columns, treats them as Barycentric:>>> nodes = np.array([ ... [ 0. , 0. ], ... [ 1. , 0.75], ... [ 2. , 1. ], ... [-1.5, 1. ], ... [-0.5, 1.5 ], ... [-3. , 2. ], ... ]) >>> surface = bezier.Surface(nodes) >>> surface <Surface (degree=2, dimension=2)> >>> param_vals = np.array([ ... [0. , 0.25, 0.75 ], ... [1. , 0. , 0. ], ... [0.25 , 0.5 , 0.25 ], ... [0.375, 0.25, 0.375], ... ]) >>> surface.evaluate_multi(param_vals) array([[-1.75 , 1.75 ], [ 0. , 0. ], [ 0.25 , 1.0625 ], [-0.625 , 1.046875]])
Note
This currently just uses
evaluate_cartesian()
andevaluate_barycentric()
so is less performant than it could be.Parameters: param_vals (numpy.ndarray) – Array of parameter values (as a 2D array).
Returns: The point on the surface.
Return type: Raises: ValueError
– Ifparam_vals
is not a 2D array.ValueError
– Ifparam_vals
doesn’t have 2 or 3 columns.
-
plot
(pts_per_edge, ax=None, show=False)¶ Plot the current surface.
Parameters: - pts_per_edge (int) – Number of points to plot per edge.
- ax (
Optional
[matplotlib.artist.Artist
]) – matplotlib axis object to add plot to. - show (
Optional
[bool
]) – Flag indicating if the plot should be shown.
Returns: The axis containing the plot. This may be a newly created axis.
Return type: Raises: NotImplementedError
– If the surface’s dimension is not2
.
-
subdivide
()¶ Split the surface into four sub-surfaces.
Takes the reference triangle
\[T = \left\{(s, t) \mid 0 \leq s, t, s + t \leq 1\right\}\]and splits it into four sub-triangles
\[\begin{split}\begin{align*} A &= \left\{(s, t) \mid 0 \leq s, t, s + t \leq \frac{1}{2}\right\} \\ B &= -A + \left(\frac{1}{2}, \frac{1}{2}\right) \\ C &= A + \left(\frac{1}{2}, 0\right) \\ D &= A + \left(0, \frac{1}{2}\right). \end{align*}\end{split}\]These are the lower left (\(A\)), central (\(B\)), lower right (\(C\)) and upper left (\(D\)) sub-triangles.
>>> nodes = np.array([ ... [ 0. , 0. ], ... [ 2. , 0. ], ... [ 0. , 4. ], ... ]) >>> surface = bezier.Surface(nodes) >>> _, _, _, sub_surface_d = surface.subdivide() >>> sub_surface_d <Surface (degree=1, dimension=2)> >>> sub_surface_d.nodes array([[ 0., 2.], [ 1., 2.], [ 0., 4.]])
Returns: The lower left, central, lower right and upper left sub-surfaces (in that order). Return type: Tuple
[Surface
,Surface
,Surface
,Surface
]
-
is_valid
¶ bool: Flag indicating if the surface no singularites.
This checks if the Jacobian of the map from the reference triangle is nonzero. For example, a linear “surface” with collinear points is invalid:
>>> nodes = np.array([ ... [0.0, 0.0], ... [1.0, 1.0], ... [2.0, 2.0], ... ]) >>> surface = bezier.Surface(nodes) >>> surface.is_valid False
while a quadratic surface with one straight side:
>>> nodes = np.array([ ... [ 0.0 , 0.0 ], ... [ 0.5 , 0.125], ... [ 1.0 , 0.0 ], ... [-0.125, 0.5 ], ... [ 0.5 , 0.5 ], ... [ 0.0 , 1.0 ], ... ]) >>> surface = bezier.Surface(nodes) >>> surface.is_valid True
-
__delattr__
¶ x.__delattr__(‘name’) <==> del x.name
-
__format__
()¶ default object formatter
-
__getattribute__
¶ x.__getattribute__(‘name’) <==> x.name
-
__hash__
¶
-
__reduce__
()¶ helper for pickle
-
__reduce_ex__
()¶ helper for pickle
-
__setattr__
¶ x.__setattr__(‘name’, value) <==> x.name = value
-
__sizeof__
() → int¶ size of object in memory, in bytes
-
__str__
¶
-
copy
()¶ Make a copy of the current shape.
Returns: Instance of the current shape.
-
degree
¶ int: The degree of the current shape.
-
dimension
¶ int: The dimension that the shape lives in.
For example, if the shape lives in \(\mathbf{R}^3\), then the dimension is
3
.
-
nodes
¶ numpy.ndarray: The nodes that define the current shape.
-