VNF meshes¶
VNF (vertices+faces) surface structure and grid meshing (BOSL2 vnf.scad).
-
class pybosl2.vnf.VNF(vertices=
None, faces=None)[source]¶ Bases:
objectA VNF surface:
vertices(3-D points) plusfaces(index polygons into vertices).Renders to PythonSCAD’s native
polyhedronviapolyhedron(). Build one from a rectangular grid of sample points withvertex_array(), merge several withunion(), or mesh a scalar field withfrom_field()and combine metaball primitives withfrom_metaballs().- Parameters:¶
Examples
Meshing a bumpy grid of sample points into a surface and rendering it as a polyhedron:
import math from pybosl2 import Path3D, VNF grid = [Path3D([[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…- is_watertight()[source]¶
Whether this mesh is a closed manifold: every undirected edge shared by exactly two faces.
A watertight mesh bounds a solid, so it can be exported, unioned or measured; an open one cannot, and a slicer will either refuse it or repair it into something the caller did not ask for. The test is on topology alone – it reads faces and never vertices – so it is cheap and says nothing about self-intersection or winding.
- Returns:¶
True if every edge has exactly two incident faces, False for an open or empty mesh.
- Return type:¶
bool
Examples
A cube built as a closed grid is watertight; one open face is not:
>>> from pybosl2 import VNF >>> VNF([[0, 0, 0], [1, 0, 0], [0, 1, 0]], [[0, 1, 2]]).is_watertight() False
- 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()).
- 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.
Examples: .. pythonscad-example:
from pybosl2 import Path3D, VNF a = VNF.vertex_array([Path3D([[0, 0, 0], [1, 0, 0]]), Path3D([[0, 1, 0], [1, 1, 0]])]) b = VNF.vertex_array([Path3D([[0, 0, 1], [1, 0, 1]]), Path3D([[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]forA*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:¶
- Returns:¶
A new
VNFcontaining only the requested halfspace.- Raises:¶
ValueError – If plane does not have exactly 4 elements.
- Return type:¶
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.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()
-
export(path, *, file_format=
None, check=True)[source]¶ Write this mesh to a file (SPEC S-53).
Pure Python and numpy – no CAD runtime – so a mesh built with no kernel present can be saved with none present either (SPEC S-54, A-2).
- Parameters:¶
- path : str | os.PathLike[str]¶
destination file. Its suffix picks the format –
.stl,.obj,.off,.ply– unless file_format overrides it.- file_format : str | None¶
explicit format name (
"stl","stla"for ASCII STL,"obj","off","ply").- check : bool¶
validate the mesh first and refuse to write one that is open or wound inside out (SPEC S-55).
Falsefor a surface that is open on purpose.
- Returns:¶
The path written.
- Raises:¶
Bosl2ValueError – If the format is unknown, or check is on and the mesh is not a closed, outward-wound solid.
- Return type:¶
FilePath
Examples
from pybosl2 import Path2D bar = Path2D([[-5, -5], [5, -5], [5, 5], [-5, 5]], closed=True).linear_sweep(height=20) bar.vnf().export("bar.stl") bar.show()Loading 3-D preview…
- classmethod from_solid(solid)[source]¶
Mesh solid into a VNF (SPEC C-8).
The way back across the boundary
polyhedron()crosses the other way, so anything the library can build can also be measured, joined or exported without the caller reaching for a native handle. Faces come back wound the way the native layer wants them and are reversed on the way in, matching the conventionvolume()andpolyhedron()assume: counter-clockwise seen from outside, positive volume for a solid.- Parameters:¶
- Returns:¶
The mesh, as an ordinary
VNF.- Raises:¶
Bosl2ValueError – If the solid produced no geometry to mesh.
- Return type:¶
Examples
from pybosl2 import cuboid, VNF mesh = VNF.from_solid(cuboid([20, 20, 20])) print(mesh.volume()) # 8000.0 mesh.polyhedron().show()Loading 3-D preview…
-
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
CapTypeorCapSpecstyles (seeCapsSpec). reverse flips face winding. Degenerate (zero-area) faces are dropped.- Parameters:¶
- points : Sequence[Path3D]¶
The grid, one
Path3Dper row (SPEC C-7a). A grid is a sequence of rows and a row is an ordered set of points, so each row is a path.- caps : CapsSpec | None¶
Cap specification for both ends: a single
CapType,CapSpec, or a two-element pair[cap_start, cap_end]. PassNone(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:¶
-
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 : Sequence[Path3D]¶
The rows, one
Path3Deach; unlikevertex_array()they may differ in length (SPEC C-7a).- caps : bool¶
Close both open ends.
- cap1 : bool | None¶
Close the first end (overrides caps).
- cap2 : bool | None¶
Close the last end (overrides caps).
- col_wrap : bool¶
Wrap each row back to its own first point.
- row_wrap : bool¶
Wrap the last row back to the first.
- reverse : bool¶
Flip face winding.
- limit_bunching : bool¶
Limit how many triangles may fan from one vertex.
- Returns:¶
The triangulated mesh.
- Raises:¶
Bosl2ValueError – If points is not a sequence of Path3D, or caps are combined with row_wrap.
- Return type:¶
- polyhedron()[source]¶
Build this VNF on the active backend.
A VNF winds its faces counter-clockwise seen from outside (so
volume()is positive for a solid); the nativepolyhedron()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.This dispatches through the backend rather than calling the native directly, so a convex mesh builds on either. The SDF backend’s polyhedron is the intersection of its face half-spaces, which can only be convex, and it refuses a mesh that is not – so a concave VNF says so here instead of quietly coming back as its own hull (SPEC B-4, B-9).
-
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
VNFvia 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
Bounds3DorNone(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:¶
Bosl2NotImplementedError – If isovalue is a tuple range; only scalar thresholds are built.
- Return type:¶
Examples: .. pythonscad-example:
import numpy as np from pybosl2 import VNF, Bounds3D def field(p: np.ndarray) -> np.ndarray: x, y, z = p[:, 0], p[:, 1], p[:, 2] return np.asarray(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
MetaballSpecentries, each holding a transform (4×4 matrix or Point position) and aMetaball.- 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:¶
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[Path2D | Path3D]¶
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:¶
Examples
Skinning a round profile up to a square one (a lofted transition):
import math import numpy as np from pybosl2 import VNF, Path2D from pybosl2.enums import SkinMethod circle = Path2D( [[6 * math.cos(t), 6 * math.sin(t)] for t in np.linspace(0, 2 * math.pi, 24, endpoint=False)] ) square = Path2D([[-8, -8], [8, -8], [8, 8], [-8, 8]]) VNF.from_skin([circle, square], slices=20, method=SkinMethod.REINDEX, z=[0, 25]).show()Loading 3-D preview…
-
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: np.ndarray) -> np.ndarray: return np.asarray(np.hypot(p[:, 0], p[:, 1])) 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…