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 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…- 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 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]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:¶
AssertionError – 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()
-
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 : 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]. 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).
- 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
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:¶
NotImplementedError – If isovalue is a tuple range; only scalar thresholds are supported.
- Return type:¶
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
_MetaballSpecentries, 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:¶
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…
-
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…