Isosurface: marching cubes & metaballs¶
Pure-Python port of the 3-D core of BOSL2’s isosurface.scad:
VNF.from_field() meshes the level set of a scalar field over a voxel grid
(marching cubes) into a VNF; the mb_* functions are metaball field
primitives; and VNF.from_metaballs() sums transformed primitives and meshes the
result into a blobby surface:
VNF.from_field(field_fn, isovalue=1, bounding_box=60, voxel_size=2)
VNF.from_metaballs([(pos1, mb_sphere(12)), (pos2, mb_sphere(12))], bounding_box=box, voxel_size=2)
A field primitive returns a value that grows toward infinity at its center and falls off with
distance; the surface is drawn where the summed field reaches isovalue (default 1). Because the
fields add, overlapping metaballs bulge together into a smooth blob. The mb_* formulas are
pinned point-for-point to real BOSL2 in tests/test_pybosl2_reorient.py; the meshes are watertight
and verified geometrically.
The mesher uses the standard Paul Bourke marching-cubes triangle table, so its triangulation isn’t vertex-identical to BOSL2’s, but the surface it encloses is the same; face winding is fixed to outward via the VNF’s signed volume.
Coverage of BOSL2 isosurface.scad¶
BOSL2 function |
Status |
Notes |
|---|---|---|
|
ported |
|
|
ported |
|
|
ported |
the 3-D metaball field primitives, with |
|
not ported |
the revolved-profile (cone/rounded) field – a follow-up. |
|
not ported |
the 2-D analogues (marching squares) – a follow-up. |
|
not ported |
preview/attachment machinery. |
Examples¶
Two spheres merging into a peanut:
from pybosl2 import VNF, mb_sphere
spec = [([-14, 0, 0], mb_sphere(12)), ([14, 0, 0], mb_sphere(12))]
VNF.from_metaballs(spec, bounding_box=[[-40, -20, -20], [40, 20, 20]], voxel_size=2).polyhedron().show()
A ring metaball plus a connecting bar:
from pybosl2 import VNF, mb_connector, mb_torus
spec = [([0, 0, 0], mb_torus(14, 4)), ([-14, 0, 0], mb_connector([-14, 0, 0], [14, 0, 0], 4))]
VNF.from_metaballs(spec, bounding_box=[[-22, -22, -10], [22, 22, 10]], voxel_size=2).polyhedron().show()
The level set of a custom field function:
from pybosl2 import VNF
def field(p):
import numpy as np
d = np.sqrt(p[:, 0]**2 + p[:, 1]**2 + p[:, 2]**2)
return 18 / d + 3 * np.sin(p[:, 0] / 3) * np.cos(p[:, 1] / 3)
VNF.from_field(field, 1, bounding_box=70, voxel_size=2.5).polyhedron().show()
API reference¶
Metaball field primitives for VNF isosurface meshing (BOSL2 metaballs3d.scad).
- pybosl2.isosurface.MetaballSpec¶
alias of
_MetaballSpec
-
pybosl2.isosurface.mb_sphere(radius=
None, cutoff=inf, influence=1, negative=False, diameter=None)[source]¶ Return a spherical metaball field.
- Parameters:¶
- radius : float | None¶
Sphere radius (mutually exclusive with diameter).
- cutoff : float¶
Distance beyond which the field is clamped to 0.
inf= no cutoff.- influence : float¶
Blending strength (smaller = sharper).
- negative : bool¶
If True, produce a subtractive metaball.
- diameter : float | None¶
Sphere diameter.
- Returns:¶
A
_Metaballprimitive.- Raises:¶
AssertionError – If no positive radius or diameter is given.
- Return type:¶
_Metaball
Examples
from pybosl2 import mb_sphere, MetaballSpec from pybosl2.bounds import Bounds3D from pybosl2.vnf import VNF spec = [MetaballSpec([0, 0, 0], mb_sphere(radius=15))] VNF.from_metaballs( spec, Bounds3D(-20, -20, -20, 20, 20, 20, 40, 40, 40), voxel_size=2 ).polyhedron().show()Loading 3-D preview…
-
pybosl2.isosurface.mb_cuboid(size, squareness=
0.5, cutoff=inf, influence=1, negative=False)[source]¶ Return a rounded-cuboid metaball field.
- Parameters:¶
- size : tuple[float, float, float] | float¶
A scalar (cube edge) or
(dx, dy, dz)tuple.- squareness : float¶
0 = fully round, 1 = sharp square edges.
- cutoff : float¶
Distance beyond which the field is clamped to 0.
- influence : float¶
Blending strength.
- negative : bool¶
If True, produce a subtractive metaball.
- Returns:¶
A
_Metaballprimitive.- Raises:¶
AssertionError – If squareness is not in
[0, 1].- Return type:¶
_Metaball
Examples
from pybosl2 import mb_cuboid, MetaballSpec from pybosl2.bounds import Bounds3D from pybosl2.vnf import VNF spec = [MetaballSpec([-12, 0, 0], mb_cuboid(size=10, squareness=0.3)), MetaballSpec([12, 0, 0], mb_cuboid(size=10, squareness=0.3))] VNF.from_metaballs( spec, Bounds3D(-25, -15, -15, 25, 15, 15, 50, 30, 30), voxel_size=2 ).polyhedron().show()Loading 3-D preview…
-
pybosl2.isosurface.mb_torus(major_radius=
None, minor_radius=None, cutoff=inf, influence=1, negative=False, major_diameter=None, minor_diameter=None)[source]¶ Return a torus metaball field.
- Parameters:¶
- major_radius : float | None¶
Distance from the origin to the tube centre.
- minor_radius : float | None¶
Tube radius.
- cutoff : float¶
Distance beyond which the field is clamped to 0.
- influence : float¶
Blending strength.
- negative : bool¶
If True, produce a subtractive metaball.
- major_diameter : float | None¶
Overrides major_radius.
- minor_diameter : float | None¶
Overrides minor_radius.
- Returns:¶
A
_Metaballprimitive.- Raises:¶
AssertionError – If either radius is missing or non-positive.
- Return type:¶
_Metaball
Examples
from pybosl2 import mb_torus, MetaballSpec from pybosl2.bounds import Bounds3D from pybosl2.vnf import VNF spec = [MetaballSpec([0, 0, 0], mb_torus(major_radius=15, minor_radius=5))] VNF.from_metaballs( spec, Bounds3D(-20, -20, -10, 20, 20, 10, 40, 40, 20), voxel_size=2 ).polyhedron().show()Loading 3-D preview…
-
pybosl2.isosurface.mb_capsule(height=
None, radius=None, cutoff=inf, influence=1, negative=False, diameter=None)[source]¶ Return a capsule (round-ended cylinder) metaball field.
-
pybosl2.isosurface.mb_disk(height=
None, radius=None, cutoff=inf, influence=1, negative=False, diameter=None)[source]¶ Return a rounded-edge disk metaball field.
-
pybosl2.isosurface.mb_octahedron(size, squareness=
0.5, cutoff=inf, influence=1, negative=False)[source]¶ Return a rounded-octahedron metaball field.
- Parameters:¶
- size : tuple[float, float, float] | float¶
A scalar (circumscribed cube edge) or
(dx, dy, dz)tuple.- squareness : float¶
0 = round, 1 = sharp octahedron edges.
- cutoff : float¶
Distance beyond which the field is clamped to 0.
- influence : float¶
Blending strength.
- negative : bool¶
If True, produce a subtractive metaball.
- Returns:¶
A
_Metaballprimitive.- Raises:¶
AssertionError – If squareness is not in
[0, 1].- Return type:¶
_Metaball
-
pybosl2.isosurface.mb_connector(p1, p2, radius=
None, cutoff=inf, influence=1, negative=False, diameter=None)[source]¶ Return a capsule metaball field spanning from p1 to p2.
- Parameters:¶
- p1 : Point¶
Start
Point.- p2 : Point¶
End
Point(must be distinct from p1).- radius : float | None¶
Shaft radius.
- cutoff : float¶
Distance beyond which the field is clamped to 0.
- influence : float¶
Blending strength.
- negative : bool¶
If True, produce a subtractive metaball.
- diameter : float | None¶
Shaft diameter.
- Returns:¶
A
_Metaballprimitive.- Raises:¶
AssertionError – If radius is missing, non-positive, or p1 equals p2.
- Return type:¶
_Metaball
-
pybosl2.isosurface.metaballs2d(spec, bounding_box, pixel_size=
None, pixel_count=None, isovalue=1, closed=True, exact_bounds=False)[source]¶ Generate 2-D contour paths from metaball field primitives.
The metaball spec uses the same 3-D transforms and field primitives as
VNF.from_metaballs(), but evaluated on the z=0 plane to produce a 2-D contour via marching squares.- Parameters:¶
- spec : list[_MetaballSpec]¶
A list of
_MetaballSpecentries, each holding a 4×4 transform and a_Metaball.- bounding_box : Bounds2D¶
A
Bounds2D.- pixel_size : float | None¶
Isotropic pixel size.
- pixel_count : int | None¶
Approximate total pixel count.
- isovalue : float¶
Field threshold. Defaults to 1.
- 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 mb_sphere, MetaballSpec, metaballs2d, Bounds2D from pybosl2.path2d import Path2D spec = [ MetaballSpec([-14, 0, 0], mb_sphere(12)), MetaballSpec([14, 0, 0], mb_sphere(12)), ] paths = metaballs2d(spec, Bounds2D(-40, -20, 40, 20, 80, 40), pixel_size=2) Path2D(paths[0]).stroke(width=0.5).linear_extrude(height=2).show()Loading 3-D preview…
- pybosl2.isosurface.Metaball¶
alias of
_Metaball