VNF meshes

VNF (vertices+faces) surface structure and grid meshing (BOSL2 vnf.scad).

class pybosl2.vnf.VNF(vertices=None, faces=None)[source]

Bases: object

A VNF surface: vertices (3-D points) plus faces (index polygons into vertices).

Renders to PythonSCAD’s native polyhedron via polyhedron(). Build one from a rectangular grid of sample points with vertex_array(), merge several with union(), or mesh a scalar field with from_field() and combine metaball primitives with from_metaballs().

Parameters:
vertices : list[list[float]] | None

list of [x, y, z] points

faces : list[list[int]] | None

list of index lists (each polygon into vertices)

Examples

Meshing a bumpy grid of sample points into a surface and rendering it as a polyhedron:

import math
from pybosl2 import VNF

grid = [[[x, y, 4 * math.sin(x / 6) * math.cos(y / 6)] for y in range(0, 60, 4)]
        for x in range(0, 60, 4)]
VNF.vertex_array(grid).polyhedron().show()
Loading 3-D preview…

⬇ Download STL mesh

bounds()[source]

Axis-aligned Bounds3D of the VNF.

Return type:

Bounds3D

reverse()[source]

Return a copy with every face wound the other way (flips the surface normals).

Return type:

VNF

volume()[source]

Signed enclosed volume (BOSL2 vnf_volume()); negative when the faces wind inward.

Used to detect and fix inverted meshes (a swept/skinned surface whose winding came out inside-out): vnf if vnf.volume() >= 0 else vnf.reverse().

Return type:

float

classmethod union(vnfs)[source]

Merge a list of VNFs into one, offsetting each VNF’s face indices (BOSL2 vnf_join()).

Parameters:
vnfs : list[VNF]

Return type:

VNF

classmethod join(vnfs)[source]

Merge multiple VNFs into a single consolidated VNF with shared vertices.

Each input VNF’s vertices and faces are copied into a combined vertex array, with face indices offset appropriately. No deduplication is performed.

Parameters:
vnfs : list[VNF]

A list of VNF objects to merge.

Returns:

A new VNF containing all vertices and faces from the inputs.

Return type:

VNF

Examples: .. pythonscad-example:

from pybosl2 import VNF

a = VNF.vertex_array([[ [0,0,0],[1,0,0] ], [ [0,1,0],[1,1,0] ]])
b = VNF.vertex_array([[ [0,0,1],[1,0,1] ], [ [0,1,1],[1,1,1] ]])
VNF.join([a, b]).polyhedron().show()
halfspace(plane, keep=True, closed=True)[source]

Clip a VNF to one side of a plane, optionally closing the cut face.

A plane is defined as [A, B, C, D] for A*x + B*y + C*z = D. If keep is True, the positive halfspace (A*x + B*y + C*z > D) is retained. If keep is False, the negative halfspace is retained.

Parameters:
plane : Sequence[float]

Plane equation [A, B, C, D].

keep : bool

If True, keep the positive halfspace. Defaults to True.

closed : bool

If True, triangulate and close the cut face. Defaults to True.

Returns:

A new VNF containing only the requested halfspace.

Raises:

AssertionError – If plane does not have exactly 4 elements.

Return type:

VNF

Examples: .. pythonscad-example:

import numpy as np
from pybosl2 import VNF, Bounds3D

cube_vnf = VNF.from_field(
    lambda p: 5 - np.max(np.abs(p), axis=1),
    0, Bounds3D(-10,-10,-10,10,10,10,20,20,20), voxel_size=1
)
cut = cube_vnf.halfspace([0, 0, 1, 0], keep=True, closed=True)
cut.polyhedron().show()
slice(plane, closed=True)[source]

Slice a VNF into two VNFs along a plane, closing both cut faces.

Returns (vnf_above, vnf_below) where vnf_above is the positive halfspace and vnf_below is the negative halfspace.

Parameters:
plane : Sequence[float]

Plane equation [A, B, C, D] for A*x + B*y + C*z = D.

closed : bool

If True, close both cut faces. Defaults to True.

Returns:

A (above, below) tuple of VNF objects.

Return type:

tuple[‘VNF’, ‘VNF’]

Examples: .. pythonscad-example:

import numpy as np
from pybosl2 import VNF, Bounds3D

cube_vnf = VNF.from_field(
    lambda p: 5 - np.max(np.abs(p), axis=1),
    0, Bounds3D(-10,-10,-10,10,10,10,20,20,20), voxel_size=1
)
above, below = cube_vnf.slice([0, 0, 1, 0], closed=True)
above.polyhedron().show()
classmethod vertex_array(points, caps=None, col_wrap=False, row_wrap=False, reverse=False, style=VNFStyle.DEFAULT)[source]

Build a VNF from a rectangular grid of 3-D points (BOSL2 vnf_vertex_array()).

Each grid cell becomes triangles (or a quad) chosen by style: “default”, “alt”, “min_edge”, “min_area”, “convex”, “concave”, “quincunx”, “quad”, “flip1”, “flip2”. col_wrap/row_wrap close the grid into a tube/torus; caps closes the column-wrapped ends with CapType or CapSpec styles (see CapsSpec). reverse flips face winding. Degenerate (zero-area) faces are dropped.

Parameters:
points : Path3D | list[Path3D] | list[list[list[float]]] | list[np.ndarray] | np.ndarray

Input grid points.

caps : CapsSpec | None

Cap specification for both ends: a single CapType, CapSpec, or a two-element pair [cap_start, cap_end]. Pass None (the default) for no caps.

col_wrap : bool

Close the column direction into a tube.

row_wrap : bool

Close the row direction into a torus.

reverse : bool

Flip face winding.

style : VNFStyle | VnfStyle

Triangulation method.

Return type:

VNF

classmethod tri_array(points, caps=False, cap1=None, cap2=None, col_wrap=False, row_wrap=False, reverse=False, limit_bunching=True)[source]

Build a VNF from an array of rows whose lengths may differ (BOSL2 vnf_tri_array()).

Triangulates between adjacent rows by repeatedly adding the shortest new edge, so it meshes triangular / irregular point arrays (what the degenerate bezier patches produce).

Parameters:
points : list[list[list[float]]]

caps : bool

cap1 : bool | None

cap2 : bool | None

col_wrap : bool

row_wrap : bool

reverse : bool

limit_bunching : bool

Return type:

VNF

polyhedron()[source]

Native geometry for this VNF via PythonSCAD’s polyhedron(points=, faces=).

A VNF winds its faces counter-clockwise seen from outside (so volume() is positive for a solid); polyhedron() wants them the other way round, so each face is reversed on the way out. Handing them over as-is builds the solid inside out – it still looks right on its own, but every union or difference with it then does the opposite of what it should.

Return type:

Any

geometry()[source]

Return the VNF as native polyhedron geometry, matching Path2D/Region’s geometry() surface.

Return type:

Any

classmethod from_field(f, isovalue, bounding_box=None, voxel_size=None, voxel_count=None, closed=True, reverse=False, exact_bounds=False)[source]

Mesh a scalar field into a VNF via marching cubes.

The solid is the region where f >= isovalue.

Parameters:
f : np.ndarray | Path3D | Callable[[np.ndarray], np.ndarray] | Callable[[Path3D], np.ndarray]

A Path3D, a 3-D numpy array, a (N,3) (N,) callable, or a (:class:`~pybosl2.path3d.Path3D`) (N,) callable.

isovalue : float

Scalar threshold.

bounding_box : Bounds3D | float | Sequence[float] | Sequence[Sequence[float]] | None

A Bounds3D or None (auto-computed from array shape when f is an array).

voxel_size : float | None

Isotropic voxel size.

voxel_count : int | None

Approximate total voxel count (ignored if voxel_size given).

closed : bool

If True, pad field so mesh closes at bounding-box faces.

reverse : bool

If True, reverse inside/outside sense.

exact_bounds : bool

If True, use bounding_box exactly.

Returns:

A VNF.

Raises:

NotImplementedError – If isovalue is a tuple range; only scalar thresholds are supported.

Return type:

VNF

Examples: .. pythonscad-example:

import numpy as np
from pybosl2 import VNF, Bounds3D

def field(p):
    x, y, z = p[:, 0], p[:, 1], p[:, 2]
    return 20 / np.sqrt(x*x + y*y + z*z) + 3 * np.sin(x / 3)
VNF.from_field(
    field, 1,
    Bounds3D(-30, -30, -30, 30, 30, 30, 60, 60, 60),
    voxel_size=2,
).polyhedron().show()
classmethod from_metaballs(spec, bounding_box, voxel_size=None, voxel_count=None, isovalue=1, closed=True, exact_bounds=False)[source]

Mesh transformed metaball primitives into a blobby VNF.

Parameters:
spec : list[_MetaballSpec]

A list of _MetaballSpec entries, each holding a transform (4×4 matrix or Point position) and a _Metaball.

bounding_box : Bounds3D | float | Sequence[float] | Sequence[Sequence[float]]

A Bounds3D.

voxel_size : float | None

Isotropic voxel size.

voxel_count : int | None

Approximate total voxel count.

isovalue : float

Field threshold.

closed : bool

Close mesh at bounding-box faces.

exact_bounds : bool

Use bounding_box exactly.

Returns:

A VNF.

Return type:

VNF

Examples: .. pythonscad-example:

from pybosl2.isosurface import MetaballSpec, mb_sphere
from pybosl2 import VNF, Bounds3D

spec = [
    MetaballSpec([-14, 0, 0], mb_sphere(12)),
    MetaballSpec([14, 0, 0], mb_sphere(12)),
]
VNF.from_metaballs(
    spec,
    Bounds3D(-40, -20, -20, 40, 20, 20, 80, 40, 40),
    voxel_size=2,
).polyhedron().show()
classmethod from_skin(profiles, slices, refine=1.0, method=SkinMethod.DIRECT, sampling=None, caps='butt', closed=False, style=VNFStyle.MIN_EDGE, z=None)[source]

Blend a stack of 2-D/3-D profiles into a skinned surface, returning a VNF or Bosl2Solid.

Consecutive profiles are connected vertex-to-vertex; slices extra interpolated profiles are inserted between each pair to smooth the transition.

Parameters:
profiles : Sequence[Sequence[Sequence[float]]]

list of >= 2 closed profiles (each a list of points). If 2-D, give matching z.

slices : int

number of interpolated profiles inserted between each pair (int or per-gap list)

refine : float

subdivide every profile by this factor before skinning (default 1)

method : SkinMethod

“direct” (connect vertex i to vertex i) or “reindex” (rotate each profile to best-align with the previous).

sampling : SamplingType | None

“length” or “segment” resampling (default “length”)

caps : CapsSpec

cap the ends; supports decorative cap types

closed : bool

the stack loops back to the first profile (default False)

style : VNFStyle

vnf_vertex_array quad-subdivision style

z : Sequence[float] | None

per-profile Z heights, required when the profiles are 2-D

Return type:

VNF | Bosl2Solid

Examples

Skinning a round profile up to a square one (a lofted transition):

import math
import numpy as np
from pybosl2 import VNF
from pybosl2.enums import SkinMethod

circle = [[6 * math.cos(t), 6 * math.sin(t)] for t in np.linspace(0, 2 * math.pi, 24, endpoint=False)]
square = [[-8, -8], [8, -8], [8, 8], [-8, 8]]
VNF.from_skin([circle, square], slices=20, method=SkinMethod.REINDEX, z=[0, 25]).polyhedron().show()
Loading 3-D preview…

⬇ Download STL mesh

pybosl2.vnf.VnfStyle

alias of VNFStyle

pybosl2.vnf.contour(f, isovalue, bounding_box, pixel_size=None, pixel_count=None, closed=True, exact_bounds=False)[source]

Generate 2-D contour paths at a given isovalue from a scalar field.

Uses marching squares on a uniform 2-D grid to trace the contour where f(x, y) == isovalue. Returns a list of closed (or open) polyline paths, each being a list of [x, y] points.

Parameters:
f : np.ndarray | Callable[[np.ndarray], np.ndarray]

A 2-D numpy array or a callable (N,2)→(N,) or (x,y)→float.

isovalue : float

Scalar threshold.

bounding_box : Bounds2D

A Bounds2D.

pixel_size : float | None

Isotropic pixel size.

pixel_count : int | None

Approximate total pixel count (ignored if pixel_size given).

closed : bool

If True, return only closed contour loops.

exact_bounds : bool

If True, use bounding_box exactly.

Returns:

A list of contour paths, each a list of [x, y] points.

Return type:

list[list[list[float]]]

Examples

import numpy as np
from pybosl2 import contour, Bounds2D
from pybosl2.path2d import Path2D

def field(p):
    r = np.hypot(p[:, 0], p[:, 1])
    return r
paths = contour(field, 10, Bounds2D(-15, -15, 15, 15, 30, 30), pixel_size=0.5)
Path2D(paths[0]).stroke(width=0.5).linear_extrude(height=2).show()
Loading 3-D preview…

⬇ Download STL mesh