Skip to content

Commit 0fb9315

Browse files
andrinrdionhaefner
andauthored
doc: Add open-source alternative to Ansys-based shapeopt to showcase folder (#408)
<!-- Please use a PR title that conforms to *conventional commits*: "<commit_type>: Describe your change"; for example: "fix: prevent race condition". Some other commit types are: fix, feat, ci, doc, refactor... For a full list of commit types visit https://www.conventionalcommits.org/en/v1.0.0/ --> #### Relevant issue or PR <!-- If the changes resolve an issue or follow some other PR, link to them here. Only link something if it is directly relevant. --> #### Description of changes <!-- Add a high-level description of changes, focusing on the *what* and *why*. --> #### Testing done <!-- Describe how the changes were tested; e.g., "CI passes", "Tested manually in stagingrepo#123", screenshots of a terminal session that verify the changes, or any other evidence of testing the changes. --> --------- Co-authored-by: Dion Häfner <dion.haefner@simulation.science>
1 parent ca9bfad commit 0fb9315

14 files changed

+2091
-0
lines changed

demo/_showcase/ansys-shapeopt/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ The main entry point of this demo is `optimization.ipynb`. The evolution of the
1313
where the loss decay is plotted here:
1414

1515
![Workflow](imgs/loss.png)
16+
17+
> [!TIP]
18+
> In `optimization_os.ipynb` we provide an alternative workflow that is relying on the open-source FEM solver [JAX-FEM](https://github.com/deepmodeling/jax-fem), and does not require an active Ansys license.
19+
20+
21+
22+
| Mesh | Loss |
23+
|----------|----------|
24+
| ![Workflow](imgs/mesh_optim.gif) | ![Workflow](imgs/loss_os.png) |
25+
26+
1627
## Get Started
1728

1829
### Prerequisites
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import numpy as np
2+
import pyvista as pv
3+
import trimesh
4+
from pydantic import BaseModel, Field
5+
6+
from tesseract_core.runtime import Array, Float32
7+
8+
#
9+
# Schemata
10+
#
11+
12+
13+
class InputSchema(BaseModel):
14+
"""Input schema for bar geometry design and SDF generation."""
15+
16+
differentiable_parameters: list[
17+
Array[
18+
(None,),
19+
Float32,
20+
]
21+
] = Field(
22+
description=(
23+
"Vertex positions of the bar geometry. "
24+
"The shape is (num_bars, num_vertices, 3), where num_bars is the number of bars "
25+
"and num_vertices is the number of vertices per bar. The last dimension represents "
26+
"the x, y, z coordinates of each vertex."
27+
)
28+
)
29+
30+
non_differentiable_parameters: list[
31+
Array[
32+
(None,),
33+
Float32,
34+
]
35+
] = Field(description="Flattened array of non-differentiable geometry parameters.")
36+
37+
static_parameters: list[list[int]] = Field(
38+
description=(
39+
"List of integers used to construct the geometry."
40+
" The first integer is the number of bars, and the second integer is the number of vertices per bar."
41+
)
42+
)
43+
44+
string_parameters: list[str] = Field(
45+
description="List of string parameters for geometry construction."
46+
)
47+
48+
49+
class TriangularMesh(BaseModel):
50+
"""Triangular mesh representation with fixed-size arrays."""
51+
52+
points: Array[(None, 3), Float32] = Field(description="Array of vertex positions.")
53+
faces: Array[(None, 3), Float32] = Field(
54+
description="Array of triangular faces defined by indices into the points array."
55+
)
56+
57+
58+
class OutputSchema(BaseModel):
59+
"""Output schema for generated geometry."""
60+
61+
meshes: list[TriangularMesh] = Field(
62+
description="Triangular meshes representing the geometries"
63+
)
64+
65+
66+
def pyvista_to_trimesh(mesh: pv.PolyData) -> trimesh.Trimesh:
67+
"""Convert a pyvista mesh to a trimesh style polygon mesh."""
68+
points = mesh.points
69+
points_per_face = mesh.faces[0]
70+
n_faces = mesh.faces.shape[0] // (points_per_face + 1)
71+
72+
faces = mesh.faces.reshape(n_faces, (points_per_face + 1))[:, 1:]
73+
74+
return trimesh.Trimesh(vertices=points, faces=faces)
75+
76+
77+
def build_geometries(
78+
differentiable_parameters: list[np.ndarray],
79+
non_differentiable_parameters: list[np.ndarray],
80+
static_parameters: list[list[int]],
81+
string_parameters: list[str],
82+
) -> list[trimesh.Trimesh]:
83+
"""Build a pyvista geometry from the parameters.
84+
85+
The parameters are expected to be of shape (n_chains, n_edges_per_chain + 1, 3),
86+
"""
87+
n_geometrics = len(differentiable_parameters)
88+
geometries = []
89+
for i in range(n_geometrics):
90+
n_chains = static_parameters[i][0]
91+
n_vertices_per_chain = static_parameters[i][1]
92+
geometry = []
93+
94+
params = differentiable_parameters[i].reshape(
95+
(n_chains, n_vertices_per_chain, 3)
96+
)
97+
98+
radius = non_differentiable_parameters[i][0]
99+
100+
for chain in range(n_chains):
101+
tube = pv.Spline(points=params[chain]).tube(
102+
radius=radius, capping=True, n_sides=30
103+
)
104+
tube = tube.triangulate()
105+
tube = pyvista_to_trimesh(tube)
106+
geometry.append(tube)
107+
108+
# convert each geometry in a trimesh style mesh and combine them
109+
mesh = geometry[0]
110+
111+
for geom in geometry[1:]:
112+
mesh = mesh.union(geom)
113+
114+
geometries.append(mesh)
115+
116+
return geometries
117+
118+
119+
#
120+
# Tesseract endpoints
121+
#
122+
123+
124+
def apply(inputs: InputSchema) -> OutputSchema:
125+
"""Generate mesh and SDF from bar geometry parameters.
126+
127+
Args:
128+
inputs: Input schema containing bar geometry parameters.
129+
130+
Returns:
131+
Output schema with generated mesh and SDF field.
132+
"""
133+
meshes = build_geometries(
134+
differentiable_parameters=inputs.differentiable_parameters,
135+
non_differentiable_parameters=inputs.non_differentiable_parameters,
136+
static_parameters=inputs.static_parameters,
137+
string_parameters=inputs.string_parameters,
138+
)
139+
140+
return OutputSchema(
141+
meshes=[
142+
TriangularMesh(
143+
points=mesh.vertices.astype(np.float32),
144+
faces=mesh.faces.astype(np.int32),
145+
)
146+
for mesh in meshes
147+
],
148+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: bars_3d
2+
version: "0.1.0"
3+
description: |
4+
Tesseract that generates 3D bar geometry.
5+
6+
Parameters are expected to define the control points and radii of piecewise linear tubes in 3D space.
7+
8+
build_config:
9+
target_platform: "linux/x86_64"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
numpy==1.26.4
2+
pyvista==0.45.2
3+
trimesh==4.9.0
4+
manifold3d==3.3.2

0 commit comments

Comments
 (0)