Shelling and filling a part is a common workflow used to create lightweight parts through additive manufacturing. In this tutorial we will shell this part than fill it with a BCC lattice.
The graph
The Metafold backend create shapes by consuming directed acyclic graphs where each node represents a series of mathematical computations or the initialization of source values. We can build graphs very quickly using the SDK. See here for details on current operations.
Shelling and filling requires the use of 6 operators:
GenerateSamplePoints
This operator is used to define a grid or set of points at which you want to sample the volume.LoadVolume
is used to load a volume asset into the Metafold computation space.SampleVolume
Samples a volume asset at specified points. To integrate the volume into the Metafold computation space, it must be sampled on the grid defined byGenerateSamplePoints
SampleLattice
: Samples beam lattices based on a grid specified byGenerateSamplePoints
, creating a lattice block that matches the grid size.Shell
Creates a shell with a giventhickness
around the center of a level set, with its position controlled by theoffset
parameter.CSG
Enables the creation of complex shapes by combining simpler primitives using Boolean operations such as union, intersection, and subtraction
The result is a new way to add topological complexity to your shape.
To do this using the Metafold SDK, you will need to follow a few main steps
Steps
- Import all necessary libraries
- Insert project and token information
- Define the Scene parameters
- Import the part
- Choose the lattice type and parameters
- Infill the part with lattice
- Shell the part
- Union the shell with the lattice part
- Evaluate metrics and export the part as STL
1- Import all necessary libraries
import pandas as pd
import numpy as np
import os
import csv
from numpy.typing import ArrayLike
from pprint import pprint
from scipy.spatial.transform import Rotation as R
from metafold import MetafoldClient
from metafold.func_types import JSONEvaluator, FuncType, Vec3f, Mat4f
from metafold.func import *
2- Insert project and token information
This step is crucial for interacting with the Metafold API. The access_token parameter should contain your authentication token, which grants you access to the Metafold service. Similarly, the project_id parameter specifies the specific project or workspace you want to work with. Make sure to replace access_token
and project_id
with your own credentials and identifiers for this to work properly. We create a MetafoldClient
instance and assign it to the variable client.
project_id = ".."
access_token = "..."
client=MetafoldClient(access_token,project_id)
3- Define the Scene parameters
This step is used to set the scene size, offset and resolution by creating a grid of sample points usingGenerateSamplePoints
. The grid is also commonly referred to as a patch.
source = GenerateSamplePoints(
{
"offset": [
-100,
-35,
-10
],
"size": [
200,
70,
120
],
"resolution": [
516,
180,
284
]
}
)
4- Import the part
In this step we will show you how to import and STL file and use it in generating Metafold objects. First using the API check if the file you are interested in uploading is already stored in your project, if not upload it using the following:
stl_filename = "rockerArm.stl"
if existing := client.assets.list(q=f"filename:{stl_filename}"):
stl_asset = client.assets.update(existing[0].id, stl_filename)
else:
stl_asset = client.assets.create(stl_filename)
*.update and *.create return a handle to the asset we just uploaded <network_asset> that exist in the cloud. We can now reference the filename of this asset using network_asset.filename We use the .stl file in the cloud to run a sample_triangle_mesh() job.
stl_params = {
"mesh_filename": stl_asset.filename,
"resolution": [256,256,256],
}
stl_job = client.jobs.run(
"sample_triangle_mesh",
stl_params
)
This job creates new assets. We can search through the assets that were created using the file extension .bin to get our .bin file.
for asset in stl_job.assets:
if asset.filename.endswith(".bin"):
bin_asset = asset
break
we input the mesh volume parameters :
mesh_volume = {
"offset": [
-97,
-30.431371688842773,
-1.1606040000915527
],
"size": [
194,
60.86274337768555,
101.18431615829468
],
"resolution": [
256,
256,
256
]
}
# Now with our .bin file in the cloud and we have the mesh volume parameters we can use it to run a graph evaluation like normal.
Part = SampleVolume(
PointSource,
LoadVolume(
volume_data={
"file_type": "Raw",
"path": scan_filename
},
parameters={
"resolution": scan_volume["resolution"],
},
),
{
"volume_size": scan_volume["size"],
"volume_offset": scan_volume["offset"],
},
)
5- Choose the lattice type and parameters
In this example we chose a BCC lattice, with a unit cell size of 10 mm and a strut thickness of 1 mm. Check more python tutorials on different beam lattice and surface lattice tutorials.
bcc_params= {
"lattice_data" : {
"edges": [
[0, 7],
[4, 7],
[1, 7],
[2, 7],
[5, 7],
[6, 7],
[3, 7],
[7, 8]
],
"nodes": [
[0, 0, 0],
[0, 1, 0],
[1, 0, 0],
[1, 1, 0],
[0, 0, 1],
[0, 1, 1],
[1, 0, 1],
[0.5, 0.5, 0.5],
[1, 1, 1]
]
},
"section_radius" : 0.1,
"scale": [10, 10, 10]
}
bcc = SampleLattice(source, parameters=bcc_params)
6-Infill the part with lattice
we can use a CSG
operator to intersect the imported part with the selected lattice
latticefill= CSG(
Part,
bcc,
parameters={"smoothing": 0.1, "operation": "Intersect"}
)
7-Shell the part
For example, a shell thickness of 2 mm was chosen and an outside offset was chosen
shell_thickness=2
shell = Shell(
Redistance(Part),
{
"thickness": shell_thickness,
"offset": 0.5*shell_thickness,
},
)
8- Union the shell with the lattice part
A CSG is used to union the shell and lattice part
shape_func= Threshold(
Redistance(
CSG(
shell,
latticefill,
parameters={"smoothing": 0.1, "operation": "Union"}
)
),
{
"width": 0.03,
},
)
9- Evaluate metrics and export
evaluator = JSONEvaluator(source)
shape_func(evaluator)
graph_json=evaluator.json()
job= client.jobs.run("evaluate_metrics", {"graph": graph_json, "point_source": 0})
pprint((job.meta["relative_density"]))
export_job = client.jobs.run("export_triangle_mesh", {
"graph": graph_json,
"point_source": 0,
})
export_asset = export_job.assets[0].id
client.assets.download_file(export_asset, "part_union.stl")
here is a section view of the STL part shelled and filled.
Total running time of the script: ( 0 minutes 32.47 seconds)