Shell and Fill

Shell and Fill

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.

STL file uploaded to the cloud
STL file uploaded to the cloud
Beam lattice Structures selected to fill the part
Beam lattice Structures selected to fill the part

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:

  1. GenerateSamplePoints This operator is used to define a grid or set of points at which you want to sample the volume.
  2. LoadVolume is used to load a volume asset into the Metafold computation space.
  3. 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 by GenerateSamplePoints
  4. SampleLattice: Samples beam lattices based on a grid specified by GenerateSamplePoints, creating a lattice block that matches the grid size.
  5. Shell Creates a shell with a given thickness around the center of a level set, with its position controlled by the offset parameter.
  6. 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.

image

To do this using the Metafold SDK, you will need to follow a few main steps

Steps

  1. Import all necessary libraries
  2. Insert project and token information
  3. Define the Scene parameters
  4. Import the part
  5. Choose the lattice type and parameters
  6. Infill the part with lattice
  7. Shell the part
  8. Union the shell with the lattice part
  9. 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.

image

Total running time of the script: ( 0 minutes 32.47 seconds)

Full script