Skip to content

Commit 2255f80

Browse files
committed
Merge branch 'ansys' of https://github.com/pasteurlabs/tesseract-jax into ansys
2 parents 10a9a86 + 30ad848 commit 2255f80

File tree

3 files changed

+167
-93
lines changed

3 files changed

+167
-93
lines changed

examples/ansys/Readme.md

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,32 +33,22 @@ Clone this repository, navigate to the `examples/ansys/spaceclaim_tess` director
3333
```bash
3434
tesseract-runtime serve --port <port_number_1>
3535
```
36-
Note that we dont build a Tesseract image for SpaceClaim in this example. This is because SpaceClaim cannot be installed in a containerized environment. You can test it using:
36+
Note that we dont build a Tesseract image for SpaceClaim in this example. This is because SpaceClaim cannot be installed in a containerized environment. You can test it using git bash and your specific Spaceclaim.exe Path:
37+
38+
```bash
39+
curl -d '{"inputs":{"differentiable_bar_parameters": [[0, 3.14], [0.39, 3.53], [0.79, 3.93], [1.18, 4.32], [1.57, 4.71], [1.96, 5.11], [2.36, 5.50], [2.75, 5.89]], "differentiable_plane_parameters": [200, 600], "non_differentiable_parameters": [800, 100], "string_parameters":["F:\\Ansys installations\\ANSYS Inc\\v241\\scdm\\SpaceClaim.exe", "geometry_generation.scscript"]}}' -H "Content-Type: application/json" http://127.0.0.1:8000/apply
40+
```
41+
42+
or the equivalent on powershell:
3743

3844
```powershell
3945
Invoke-RestMethod -Uri "http://127.0.0.1:8000/apply" -Method Post -Body (
4046
@{
4147
inputs = @{
42-
grid_parameters = @{
43-
"__params__.z2" = "200"
44-
"__params__.z3" = "600"
45-
"__params__.s1" = "0"
46-
"__params__.s2" = "1 * (math.pi / 8)"
47-
"__params__.s3" = "2 * (math.pi / 8)"
48-
"__params__.s4" = "3 * (math.pi / 8)"
49-
"__params__.s5" = "4 * (math.pi / 8)"
50-
"__params__.s6" = "5 * (math.pi / 8)"
51-
"__params__.s7" = "6 * (math.pi / 8)"
52-
"__params__.s8" = "7 * (math.pi / 8)"
53-
"__params__.e1" = "(0) + math.pi"
54-
"__params__.e2" = "(1 * (math.pi / 8)) + math.pi"
55-
"__params__.e3" = "(2 * (math.pi / 8)) + math.pi"
56-
"__params__.e4" = "(3 * (math.pi / 8)) + math.pi"
57-
"__params__.e5" = "(4 * (math.pi / 8)) + math.pi"
58-
"__params__.e6" = "(5 * (math.pi / 8)) + math.pi"
59-
"__params__.e7" = "(6 * (math.pi / 8)) + math.pi"
60-
"__params__.e8" = "(7 * (math.pi / 8)) + math.pi"
61-
}
48+
differentiable_bar_parameters = [[0, 3.14], [0.39, 3.53], [0.79, 3.93], [1.18, 4.32], [1.57, 4.71], [1.96, 5.11], [2.36, 5.50], [2.75, 5.89]]
49+
differentiable_plane_parameters = [200, 600]
50+
non_differentiable_parameters = [800, 100]
51+
string_parameters = ["F:\\Ansys installations\\ANSYS Inc\\v241\\scdm\\SpaceClaim.exe", "geometry_generation.scscript"]
6252
}
6353
} | ConvertTo-Json -Depth 10
6454
) -ContentType "application/json"
11 Bytes
Binary file not shown.

examples/ansys/spaceclaim_tess/tesseract_api.py

Lines changed: 156 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,56 +8,76 @@
88
from pathlib import Path, WindowsPath
99
from tempfile import TemporaryDirectory
1010

11+
import numpy as np
1112
import trimesh
1213
from pydantic import BaseModel, Field
13-
from tesseract_core.runtime import Array, Float32
14-
15-
# Temporary hardcoded spaceclaim .exe and script files
16-
spaceclaim_exe = "F:\\Ansys installations\\ANSYS Inc\\v241\\scdm\\SpaceClaim.exe"
17-
spaceclaim_script = "geometry_generation.scscript" # Relies on being executed in same directory as tesseract_api.py
18-
19-
"""
20-
Example dict for 8 beam start (s) and end (e) parameters and two z-plane params
21-
22-
keyvalues_test = {"__params__.z2": "200",
23-
"__params__.z3": "600",
24-
"__params__.s1": "0",
25-
"__params__.s2": "1 * (math.pi / 8)",
26-
"__params__.s3": "2 * (math.pi / 8)",
27-
"__params__.s4": "3 * (math.pi / 8)",
28-
"__params__.s5": "4 * (math.pi / 8)",
29-
"__params__.s6": "5 * (math.pi / 8)",
30-
"__params__.s7": "6 * (math.pi / 8)",
31-
"__params__.s8": "7 * (math.pi / 8)",
32-
"__params__.e1": "(0) + math.pi",
33-
"__params__.e2": "(1 * (math.pi / 8)) + math.pi",
34-
"__params__.e3": "(2 * (math.pi / 8)) + math.pi",
35-
"__params__.e4": "(3 * (math.pi / 8)) + math.pi",
36-
"__params__.e5": "(4 * (math.pi / 8)) + math.pi",
37-
"__params__.e6": "(5 * (math.pi / 8)) + math.pi",
38-
"__params__.e7": "(6 * (math.pi / 8)) + math.pi",
39-
"__params__.e8": "(7 * (math.pi / 8)) + math.pi"}
40-
"""
41-
42-
43-
class GridParameters(BaseModel):
44-
z_planes: Array[
45-
(None,),
46-
Float32,
14+
from tesseract_core.runtime import Array, Differentiable, Float32
15+
16+
# Example spaceclaim .exe and script file Paths
17+
# spaceclaim_exe = "F:\\Ansys installations\\ANSYS Inc\\v241\\scdm\\SpaceClaim.exe"
18+
# spaceclaim_script = "geometry_generation.scscript" # Relies on being executed in same directory as tesseract_api.py
19+
20+
#
21+
# Schemata
22+
#
23+
24+
25+
class InputSchema(BaseModel):
26+
"""Input schema for bar geometry design and SDF generation."""
27+
28+
differentiable_bar_parameters: Differentiable[
29+
Array[
30+
(None, None),
31+
Float32,
32+
]
4733
] = Field(
48-
description="Array of Z cutting plane locations",
34+
description=(
35+
"Angular positions around the unit circle for the bar geometry. "
36+
"The shape is (num_bars, 2), where num_bars is the number of bars "
37+
"and the second dimension has the start then end location of each bar."
38+
"The final +1 entry represents the two z height coordinates for the cutting plane which combine with "
39+
"a third fixed coordinate centered on the grid with z = grid_height / 2"
40+
)
4941
)
50-
beam_starts: Array[
51-
(None,),
52-
Float32,
42+
43+
differentiable_plane_parameters: Differentiable[
44+
Array[
45+
(None,),
46+
Float32,
47+
]
5348
] = Field(
54-
description="Array of beam start angles (radians)",
49+
description=(
50+
"Two cutting plane z point heights which combine with a fixed third point "
51+
"centered on the grid at z = grid_height / 2. "
52+
"The shape is (2) "
53+
"The two points are orthognal at the maximum extemts of the grid (+X and +Y)."
54+
)
5555
)
56-
beam_ends: Array[
56+
57+
non_differentiable_parameters: Array[
5758
(None,),
5859
Float32,
5960
] = Field(
60-
description="Array of beam end angles (radians)",
61+
description=(
62+
"Flattened array of non-differentiable geometry parameters. "
63+
"The shape is (2), the first float is the maximum height (mm) of the "
64+
"grid (pre z-plane cutting). The second is the beam thickness (mm)."
65+
)
66+
)
67+
68+
"""static_parameters: list[int] = Field(
69+
description=(
70+
"List of integers used to construct the geometry. "
71+
"The first integer is the number of bars."
72+
)
73+
)"""
74+
75+
string_parameters: list[str] = Field(
76+
description=(
77+
"Two string parameters for geometry construction. "
78+
"First str is Path to Spaceclaim executable. "
79+
"Second str is Path to Spaceclaim Script (.scscript)."
80+
)
6181
)
6282

6383

@@ -70,18 +90,87 @@ class TriangularMesh(BaseModel):
7090
)
7191

7292

73-
class InputSchema(BaseModel):
74-
grid_parameters: dict = Field(
75-
description="Parameter dictionary defining location of grid beams and Z cutting plane"
76-
)
77-
78-
7993
class OutputSchema(BaseModel):
94+
"""Output schema for generated geometry and SDF field."""
95+
8096
mesh: TriangularMesh = Field(
81-
description="Output triangular mesh of the grid fin geometry"
97+
description="Triangular mesh representation of the geometry"
8298
)
8399

84100

101+
#
102+
# Helper functions
103+
#
104+
105+
106+
def build_geometry(
107+
differentiable_bar_parameters: np.ndarray,
108+
differentiable_plane_parameters: np.ndarray,
109+
non_differentiable_parameters: np.ndarray,
110+
string_parameters: list[str],
111+
) -> list[trimesh.Trimesh]:
112+
"""Build a Spaceclaim geometry from the parameters by modifying template .scscript.
113+
114+
Return a TriangularMesh object.
115+
"""
116+
spaceclaim_exe = Path(string_parameters[0])
117+
spaceclaim_script = Path(string_parameters[1])
118+
119+
# TODO: Want to stop using TemporaryDirectory for spaceclaim script
120+
# and instead use the unique run directory created everytime the
121+
# tesseract is run (so there is history).
122+
with TemporaryDirectory() as temp_dir:
123+
prepped_script_path, output_file = _prep_scscript(
124+
temp_dir,
125+
spaceclaim_script,
126+
differentiable_bar_parameters,
127+
differentiable_plane_parameters,
128+
non_differentiable_parameters,
129+
)
130+
run_spaceclaim(spaceclaim_exe, prepped_script_path)
131+
132+
mesh = trimesh.load(output_file)
133+
134+
return mesh
135+
136+
137+
def _prep_scscript(
138+
temp_dir: TemporaryDirectory,
139+
spaceclaim_script: Path,
140+
differentiable_bar_parameters: np.ndarray,
141+
differentiable_plane_parameters: np.ndarray,
142+
non_differentiable_parameters: np.ndarray,
143+
) -> list[str]:
144+
"""Take tesseract inputs and place into a temp .scscript that will be used to run Spaceclaim.
145+
146+
Return the Path location of this script and the output .stl
147+
"""
148+
# Define output file name and location
149+
# TODO: Same as before: can we output grid_fin.stl in the tesseract
150+
# unique run directory instead of temp dir to keep history?
151+
output_file = os.path.join(temp_dir, "grid_fin.stl")
152+
prepped_script_path = os.path.join(temp_dir, os.path.basename(spaceclaim_script))
153+
shutil.copy(spaceclaim_script, prepped_script_path)
154+
155+
# Define dict used to input params to .scscript
156+
keyvalues = {}
157+
keyvalues["__output__"] = output_file
158+
keyvalues["__params__.z2"] = str(differentiable_plane_parameters[0])
159+
keyvalues["__params__.z3"] = str(differentiable_plane_parameters[1])
160+
keyvalues["__params__.height"] = non_differentiable_parameters[0]
161+
keyvalues["__params__.thickness"] = non_differentiable_parameters[1]
162+
163+
num_of_bars = len(differentiable_bar_parameters)
164+
165+
for i in range(num_of_bars):
166+
keyvalues[f"__params__.s{i + 1}"] = str(differentiable_bar_parameters[i][0])
167+
keyvalues[f"__params__.e{i + 1}"] = str(differentiable_bar_parameters[i][1])
168+
169+
_find_and_replace_keys_in_archive(prepped_script_path, keyvalues)
170+
171+
return [prepped_script_path, output_file]
172+
173+
85174
def _safereplace(filedata: str, key: str, value: str) -> str:
86175
# ensure double backspace in windows path
87176
if isinstance(value, WindowsPath):
@@ -131,13 +220,19 @@ def _find_and_replace_keys_in_archive(file: Path, keyvalues: dict) -> None:
131220
)
132221

133222

134-
def run_spaceclaim(spaceclaim_exe: Path, spaceclaim_script: Path):
223+
def run_spaceclaim(spaceclaim_exe: Path, spaceclaim_script: Path) -> None:
224+
"""Runs Spaceclaim subprocess with .exe and script Path locations.
225+
226+
Returns the subprocess return code as a placeholder.
227+
"""
135228
env = os.environ.copy()
136229
cmd = str(
137230
f'"{spaceclaim_exe}" /UseLicenseMode=True /Welcome=False /Splash=False '
138231
+ f'/RunScript="{spaceclaim_script}" /ExitAfterScript=True /Headless=True'
139232
)
140233

234+
# TODO: Not very robust, should probably try use some error handling
235+
# or timeout logic to prevent stalling if Spaceclaim fails.
141236
result = subprocess.run(
142237
cmd,
143238
shell=True,
@@ -156,31 +251,20 @@ def run_spaceclaim(spaceclaim_exe: Path, spaceclaim_script: Path):
156251

157252

158253
def apply(inputs: InputSchema) -> OutputSchema:
159-
"""Create a Spaceclaim geometry based on input parameters and export as a .stl."""
160-
cwd = os.getcwd()
161-
output_file = os.path.join(cwd, "grid_fin.stl")
162-
163-
# TODO: lets make sure the parameters are transformed into a correct dict to be used in the script
164-
keyvalues = inputs.grid_parameters.copy()
165-
keyvalues["__output__"] = output_file
166-
167-
with TemporaryDirectory() as temp_dir:
168-
# Copy spaceclaim template script to temp dir
169-
copied_file_path = os.path.join(temp_dir, os.path.basename(spaceclaim_script))
170-
shutil.copy(spaceclaim_script, copied_file_path)
171-
172-
# Update temp spaceclaim script and use to generate .stl
173-
# update_script = _find_and_replace_keys_in_archive(copied_file_path, keyvalues)
174-
# spaceclaim_result = run_spaceclaim(spaceclaim_exe, copied_file_path)
254+
"""Create a Spaceclaim geometry based on input parameters.
255+
256+
Returns TraingularMesh obj and exports a .stl.
257+
"""
258+
mesh = build_geometry(
259+
differentiable_bar_parameters=inputs.differentiable_bar_parameters,
260+
differentiable_plane_parameters=inputs.differentiable_plane_parameters,
261+
non_differentiable_parameters=inputs.non_differentiable_parameters,
262+
string_parameters=inputs.string_parameters,
263+
)
175264

176-
# TODO: lets read the .stl file using trimesh
177-
mesh = trimesh.load(output_file)
178265
return OutputSchema(
179266
mesh=TriangularMesh(
180-
points=Array(mesh.vertices.astype("float32")),
181-
faces=Array(mesh.faces.astype("float32")),
267+
points=mesh.vertices.astype(np.float32),
268+
faces=mesh.faces.astype(np.int32),
182269
)
183270
)
184-
# return OutputSchema(
185-
# placeholder_output=f"Subprocess return code: {spaceclaim_result}"
186-
# )

0 commit comments

Comments
 (0)