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
Binary file added .DS_Store
Binary file not shown.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.2
hooks:
# - id: ruff
# args: ["--fix"]
- id: ruff
args: ["--fix"]
- id: ruff-format

# - repo: https://github.com/econchick/interrogate
Expand Down
2 changes: 2 additions & 0 deletions conda.recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ requirements:
- meshio
- cadquery
- scipy
- pip:
- autograd

test:
imports:
Expand Down
2 changes: 2 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ dependencies:
- python-gmsh
- meshio
- cadquery
- pip:
- autograd
- mmg
# - neper
117 changes: 117 additions & 0 deletions examples/tpms_infill_gallery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""TPMS coordinate-frame gallery — sphere, cylinder, sweep.

Three reference geometries to show off the parametric-grid TPMS classes,
each clipped by a y-axis plane so the interior is visible:

| File | Class | What it shows |
| -------------------------- | -------------------- | ---------------------------------------------- |
| ``sphere_g4_tpms.vtk`` | ``SphericalTpms`` | gyroid wrapping a sphere of radius ``R`` |
| ``cylinder_g5_tpms.vtk`` | ``CylindricalTpms`` | gyroid wrapping a cylinder |
| ``sweep_g7_helix.vtk`` | ``Sweep`` | gyroid following an arbitrary helical curve |

Knobs:

- ``CELL_SIZE`` — uniform cubic cell edge (scalar → no stretched cells)
- ``OFFSET`` — sheet thickness
- ``SPHERE_RADIUS`` — radius of the spherical demo
- ``CYLINDER_RADIUS``, ``CYLINDER_HEIGHT`` — for the cylindrical demo

Run with ``python examples/tpms_infill_gallery.py``.
"""

from __future__ import annotations

from pathlib import Path

import numpy as np
import pyvista as pv

from microgen.shape.surface_functions import gyroid
from microgen.shape.tpms import CylindricalTpms, SphericalTpms, Sweep


# -----------------------------------------------------------------------------
# Knobs
# -----------------------------------------------------------------------------

CELL_SIZE = 1.0
OFFSET = 0.5
SPHERE_RADIUS = 3.0
CYLINDER_RADIUS = 1.5
CYLINDER_HEIGHT = 6.0

OUT = Path(__file__).parent


def clip_y(mesh: pv.DataSet, frac: float = -0.2) -> pv.DataSet:
"""Cut a mesh by a y-plane at ``frac × bbox_y`` so internals are visible."""
bb = np.array(mesh.bounds)
y_origin = bb[2] + frac * (bb[3] - bb[2])
return mesh.clip("y", origin=(0.0, y_origin, 0.0), invert=False)


def report(label: str, mesh: pv.DataSet) -> None:
print(f"{label:32s} vol={abs(mesh.volume):7.3f} n_cells={mesh.n_cells}")


# -----------------------------------------------------------------------------
# 1. SphericalTpms — gyroid wrapping a sphere (auto-fill θ + φ via repeat=0)
# -----------------------------------------------------------------------------

sph = SphericalTpms(
radius=SPHERE_RADIUS,
surface_function=gyroid,
offset=OFFSET,
cell_size=CELL_SIZE,
repeat_cell=(2, 0, 0),
)
m_sph = sph.generate_vtk(type_part="sheet")
clip_y(m_sph).save(OUT / "sphere_g4_tpms.vtk")
report("1. SphericalTpms (R=3)", m_sph)

# -----------------------------------------------------------------------------
# 2. CylindricalTpms — gyroid wrapping a cylinder (auto-fill θ via repeat=0)
# -----------------------------------------------------------------------------

cyl = CylindricalTpms(
radius=CYLINDER_RADIUS,
surface_function=gyroid,
offset=OFFSET,
cell_size=CELL_SIZE,
repeat_cell=(2, 0, int(CYLINDER_HEIGHT / CELL_SIZE)),
)
m_cyl = cyl.generate_vtk(type_part="sheet")
clip_y(m_cyl).save(OUT / "cylinder_g5_tpms.vtk")
report("2. CylindricalTpms (R=1.5)", m_cyl)

# -----------------------------------------------------------------------------
# 3. Sweep — gyroid along a helical curve (1.5 turns, height 6)
# -----------------------------------------------------------------------------


def helix(t: float) -> np.ndarray:
"""Helix: 1.5 turns, radius 2, height 6."""
theta = 2.0 * np.pi * 1.5 * t
return np.array([2.0 * np.cos(theta), 2.0 * np.sin(theta), 6.0 * (t - 0.5)])


sw = Sweep(
curve_points=helix,
surface_function=gyroid,
radial_max=0.6,
offset=OFFSET,
cell_size=CELL_SIZE,
repeat_cell=(8, 1, 6),
n_curve_samples=200,
)
m_sw = sw.generate_vtk(type_part="sheet")
clip_y(m_sw).save(OUT / "sweep_g7_helix.vtk")
report("3. Sweep along helix", m_sw)

print("\nFiles saved (clipped by y-plane), open in ParaView:")
for path in (
OUT / "sphere_g4_tpms.vtk",
OUT / "cylinder_g5_tpms.vtk",
OUT / "sweep_g7_helix.vtk",
):
print(f" {path}")
82 changes: 82 additions & 0 deletions gyroid_gd_bunny.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import numpy as np
import pyvista as pv
from pyvista import examples


def gyroid(x, y, z):
return np.sin(x) * np.cos(y) + np.sin(y) * np.cos(z) + np.sin(z) * np.cos(x)


repeat_cell = np.array([8, 8, 8])
cell_size = np.array([1, 1, 1])
center_offset = 0.5
resolution = 15

offset = np.pi / 2.0

linspaces: list[np.ndarray] = []
for repeat_cell_axis, cell_size_axis in zip(repeat_cell, cell_size, strict=False):
linspaces.append(
np.linspace(
-center_offset * cell_size_axis * repeat_cell_axis,
center_offset * cell_size_axis * repeat_cell_axis,
resolution * repeat_cell_axis,
),
)

x, y, z = np.meshgrid(*linspaces)

grid = pv.StructuredGrid(x, y, z)
kx, ky, kz = 2 * np.pi / cell_size

surface_function = gyroid(kx * x, ky * y, kz * z)

bunny = examples.download_bunny()
transform_matrix = np.array([[40, 0, 0, 0], [0, 40, 0, 0], [0, 0, 40, 0], [0, 0, 0, 1]])
bunny.transform(transform_matrix, inplace=True)
center = bunny.center_of_mass()
bunny.translate(-center, inplace=True)
print("newcenter = ", bunny.center_of_mass())
print(bunny.bounds)
print(grid.bounds)

grid.compute_implicit_distance(bunny, inplace=True)

# normalize :
dist = -1.0 * grid["implicit_distance"]
dist[dist > 0] = 0
dist_norm = (dist - min(dist)) / (max(dist) - min(dist))
x_t = 0.5
l = 0.2
reg_func = 0.6 * (1.0 + np.tanh((dist_norm - x_t) / l)) - 0.2

print(min(dist))
print(max(dist))

print(min(dist_norm))
print(max(dist_norm))

print(min(reg_func))
print(max(reg_func))

grid["lower_surface"] = surface_function.ravel(order="F") - offset * reg_func
grid["upper_surface"] = surface_function.ravel(order="F") + offset * reg_func
sheet = grid.clip_scalar(scalars="upper_surface", invert=False).clip_scalar(
scalars="lower_surface",
)
upper_skeletal = grid.clip_scalar(scalars="upper_surface")
lower_skeletal = grid.clip_scalar(scalars="lower_surface", invert=False)

clipped = sheet.clip_surface(bunny, invert=False)
clipped.compute_implicit_distance(bunny, inplace=True)

print(f"relative density = {clipped.volume / bunny.volume:.2%}")

clipped2 = clipped.clip("y", origin=(0, -0.5, 0.0), invert=False)
clipped2["dist"] = -1.0 * clipped2["implicit_distance"]

pl = pv.Plotter()
# pl.add_mesh(grid, color="b", opacity = 0.1)
pl.add_mesh(bunny, color="w", opacity=0.1)
pl.add_mesh(clipped2, scalars="dist", cmap="inferno")
pl.show()
38 changes: 19 additions & 19 deletions microgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,64 +81,64 @@
"Cuboctahedron",
"CustomLattice",
"Cylinder",
"CylindricalTpms",
"Diamond",
"Ellipsoid",
"ExtrudedPolygon",
"FaceCenteredCubic",
"Infill",
"NormedDistance",
"Mmg",
"Neper",
"NormedDistance",
"Octahedron",
"OctetTruss",
"Phase",
"Polyhedron",
"Report",
"RhombicCuboctahedron",
"RhombicDodecahedron",
"Rve",
"SingleMesh",
"Sphere",
"SphericalTpms",
"Tpms",
"TruncatedCube",
"TruncatedCuboctahedron",
"TruncatedOctahedron",
"CylindricalTpms",
"SphericalTpms",
"batch_smooth_union",
"from_field",
"implicit_ops",
"check_if_only_linear_tetrahedral",
"cut_phase_by_shape_list",
"cutPhaseByShapeList",
"cut_phases",
"cutPhases",
"cut_phases_by_shape",
"cutPhasesByShape",
"cut_shapes",
"cutShapes",
"fuse_shapes",
"cut_phase_by_shape_list",
"cut_phases",
"cut_phases_by_shape",
"cut_shapes",
"from_field",
"fuseShapes",
"fuse_shapes",
"implicit_ops",
"is_periodic",
"mesh",
"mesh_periodic",
"meshPeriodic",
"new_geometry",
"mesh_periodic",
"newGeometry",
"new_geometry",
"parseNeper",
"periodic",
"periodic_split_and_translate",
"raster_phase",
"rasterPhase",
"repeat_polydata",
"raster_phase",
"repeatPolyData",
"repeat_shape",
"repeatShape",
"repeat_polydata",
"repeat_shape",
"rescale",
"rotate",
"rotate_euler",
"rotateEuler",
"rotate_pv_euler",
"rotatePvEuler",
"Report",
"SingleMesh",
"rotate_euler",
"rotate_pv_euler",
"surface_functions",
]
Loading
Loading