Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: pyvista/setup-headless-display-action@v3
- uses: mamba-org/setup-micromamba@v2
with:
generate-run-shell: true
Expand Down
2 changes: 1 addition & 1 deletion conda.recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ requirements:
- python >=3.9,<3.13
- numpy
- vtk
- pyvista
- pyvista <0.47
- python-gmsh
- meshio
- cadquery
Expand Down
28 changes: 28 additions & 0 deletions docs/examples/implicit_shapes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.. _RST ImplicitShapes:

Implicit Shapes (F-rep)
=======================

Primitives and Boolean Operations
----------------------------------

.. include:: ../../examples/ImplicitShapes/primitives_and_booleans.py
:literal:

.. image:: ../_static/examples/implicit_booleans.png

Implicit Lattices
-----------------

.. include:: ../../examples/Lattices/implicit_lattice.py
:literal:

.. image:: ../_static/examples/implicit_lattice.png

Trabecular Bone
---------------

.. include:: ../../examples/ImplicitShapes/trabecular_bone.py
:literal:

.. image:: ../_static/examples/trabecular_bone.png
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Table of contents

examples/basic_shapes
examples/lattices
examples/implicit_shapes
examples/tpms
examples/3d_operations
examples/mesh
Expand Down
5 changes: 5 additions & 0 deletions docs/microgen.shape.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ microgen.shape package
:undoc-members:
:show-inheritance:

.. automodule:: microgen.shape.implicit_shape
:members:
:undoc-members:
:show-inheritance:

.. automodule:: microgen.shape.surface_functions
:members:
:undoc-members:
Expand Down
159 changes: 159 additions & 0 deletions docs/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,165 @@ Create TPMS on a cylindrical coordinate system:
cylindrical_gyroid.sheet.plot(color='white')


Implicit Shapes (F-rep)
-----------------------

Microgen includes an implicit (F-rep) modeling framework where shapes are
defined by scalar fields ``f(x, y, z)`` — the surface is at ``f = 0`` and the
interior is ``f < 0``. This enables smooth blending, exact booleans, and
composability that is hard to achieve with boundary meshes alone.

Converting Basic Shapes to Implicit
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Basic shapes (``Sphere``, ``Box``, ``Capsule``, ``Cylinder``, ``Ellipsoid``)
can be converted to implicit shapes with ``to_implicit()``, making it easy to
combine parametric geometry with F-rep boolean operations:

.. jupyter-execute::

import microgen
from microgen.shape.implicit_shape import implicit_box

sphere = microgen.Sphere(center=(0.3, 0, 0), radius=0.4)
sphere_imp = sphere.to_implicit()
box_imp = implicit_box(center=(-0.2, 0, 0), half_extents=(0.3, 0.3, 0.3))
result = sphere_imp.smooth_union(box_imp, k=0.2)
result.generate_vtk(resolution=60).plot(color='white')


Primitives
^^^^^^^^^^

Create basic implicit primitives and visualize them with marching cubes:

.. jupyter-execute::

from microgen.shape.implicit_shape import implicit_sphere, implicit_box

sphere = implicit_sphere(center=(0, 0, 0), radius=0.4)
sphere.generate_vtk(resolution=60).plot(color='white')

.. jupyter-execute::

box = implicit_box(center=(0, 0, 0), half_extents=(0.3, 0.3, 0.3))
box.generate_vtk(resolution=60).plot(color='white')


Boolean Operations
^^^^^^^^^^^^^^^^^^

Combine implicit shapes with standard boolean operators:

.. jupyter-execute::

from microgen.shape.implicit_shape import implicit_sphere, implicit_box

sphere = implicit_sphere(center=(0, 0, 0), radius=0.4)
box = implicit_box(center=(0, 0, 0), half_extents=(0.3, 0.3, 0.3))

union = sphere | box
union.generate_vtk(resolution=60).plot(color='steelblue')

.. jupyter-execute::

intersection = sphere & box
intersection.generate_vtk(resolution=60).plot(color='coral')

.. jupyter-execute::

difference = sphere - box
difference.generate_vtk(resolution=60).plot(color='mediumseagreen')


Smooth Blending
^^^^^^^^^^^^^^^

Smooth union produces organic-looking junctions controlled by the parameter *k*:

.. jupyter-execute::

smooth = sphere.smooth_union(box, k=0.3)
smooth.generate_vtk(resolution=60).plot(color='orchid')


Batch Smooth Union
^^^^^^^^^^^^^^^^^^

When combining many primitives, use ``batch_smooth_union`` — it evaluates all
fields in a flat loop, avoiding recursion-depth limits:

.. jupyter-execute::

import numpy as np
from microgen.shape.implicit_shape import implicit_sphere, batch_smooth_union

rng = np.random.default_rng(42)
spheres = [
implicit_sphere(center=tuple(rng.uniform(-0.5, 0.5, 3)), radius=0.15)
for _ in range(20)
]
combined = batch_smooth_union(spheres, k=0.1)
combined.generate_vtk(bounds=(-0.8, 0.8) * 3, resolution=80).plot(color='coral')


Transforms
^^^^^^^^^^

Translate, rotate, and scale implicit shapes:

.. jupyter-execute::

from microgen.shape.implicit_shape import implicit_box

box = implicit_box(center=(0, 0, 0), half_extents=(0.3, 0.2, 0.1))
rotated = box.rotate(angles=(0, 0, 45))
rotated.generate_vtk(resolution=60).plot(color='white')


Utilities: Shell and Repeat
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

``shell`` hollows a shape; ``repeat`` tiles it periodically:

.. jupyter-execute::

from microgen.shape.implicit_shape import implicit_sphere, shell

sphere = implicit_sphere(center=(0, 0, 0), radius=0.4)
hollow = shell(sphere, thickness=0.05)
hollow.generate_vtk(resolution=80).plot(color='white')

.. jupyter-execute::

from microgen.shape.implicit_shape import implicit_sphere, repeat

sphere = implicit_sphere(center=(0, 0, 0), radius=0.15)
tiled = repeat(sphere, spacing=(0.5, 0.5, 0.5))
tiled.generate_vtk(bounds=(-0.8, 0.8) * 3, resolution=80).plot(color='white')


TPMS + F-rep Composition
^^^^^^^^^^^^^^^^^^^^^^^^^

Combine TPMS implicit fields with F-rep shapes — for example, intersect a
gyroid with an implicit sphere:

.. jupyter-execute::

from microgen.shape.implicit_shape import implicit_sphere, from_field
from microgen.shape.surface_functions import gyroid
import numpy as np

gyroid_field = from_field(
lambda x, y, z: gyroid(x * 2 * np.pi, y * 2 * np.pi, z * 2 * np.pi),
bounds=(-0.6, 0.6) * 3,
)
sphere = implicit_sphere(center=(0, 0, 0), radius=0.5)
result = gyroid_field & sphere
result.generate_vtk(bounds=(-0.6, 0.6) * 3, resolution=80).plot(color='white')


3D Operations
-------------

Expand Down
5 changes: 3 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ channels:
- set3mah

dependencies:
- numpy <2
- pyvista
- numpy
- pyvista <0.47
- matplotlib
- scipy
- python-gmsh
- meshio
Expand Down
75 changes: 75 additions & 0 deletions examples/ImplicitShapes/primitives_and_booleans.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Implicit shapes: primitives, boolean operations, and smooth blending.

Demonstrates the F-rep (Function Representation) framework in microgen
for creating implicit shapes and combining them with hard and smooth
boolean operations.
"""

import pyvista as pv

from microgen.shape.implicit_shape import (
batch_smooth_union,
implicit_box,
implicit_sphere,
)

# ---------------------------------------------------------------------------
# Create primitives
# ---------------------------------------------------------------------------

sphere = implicit_sphere(center=(0, 0, 0), radius=0.45)
box = implicit_box(center=(0, 0, 0), half_extents=(0.3, 0.3, 0.3))

BOUNDS = (-0.8, 0.8) * 3
RES = 80

# ---------------------------------------------------------------------------
# Boolean operations
# ---------------------------------------------------------------------------

union = sphere | box
intersection = sphere & box
difference = sphere - box
smooth = sphere.smooth_union(box, k=0.3)

# ---------------------------------------------------------------------------
# Visualize in a 2x2 grid
# ---------------------------------------------------------------------------

plotter = pv.Plotter(shape=(2, 2), window_size=(1200, 1200))

ops = [
(0, 0, "Union (A | B)", union, "steelblue"),
(0, 1, "Intersection (A & B)", intersection, "coral"),
(1, 0, "Difference (A - B)", difference, "mediumseagreen"),
(1, 1, "Smooth Union (k=0.3)", smooth, "orchid"),
]

for row, col, title, shape, color in ops:
mesh = shape.generate_vtk(bounds=BOUNDS, resolution=RES)
plotter.subplot(row, col)
plotter.add_text(title, font_size=12)
plotter.add_mesh(mesh, color=color, show_edges=False)

plotter.link_views()
plotter.show()

# ---------------------------------------------------------------------------
# Batch smooth union — combining many primitives efficiently
# ---------------------------------------------------------------------------

import numpy as np

rng = np.random.default_rng(42)
spheres = [
implicit_sphere(center=tuple(rng.uniform(-0.5, 0.5, 3)), radius=0.15)
for _ in range(20)
]

combined = batch_smooth_union(spheres, k=0.1)
mesh = combined.generate_vtk(bounds=(-0.8, 0.8) * 3, resolution=100)

plotter2 = pv.Plotter()
plotter2.add_text("Batch smooth union (20 spheres, k=0.1)", font_size=12)
plotter2.add_mesh(mesh, color="coral", show_edges=False)
plotter2.show()
Loading
Loading