Adding Discrete sets

Load main libraries, set paths and adjust plotting theme (optional)

[1]:
from pathlib import Path
import numpy as np
import pyvista as pv
pv.set_jupyter_backend('static')
pv.global_theme.background = 'white'
pv.global_theme.color = "beige"
pv.global_theme.show_edges = True
pv.global_theme.window_size = [800, 400]

STATICS_DIR = Path("../../../../_static")
HEXBEAM_MULTIBLOCK_FILEPATH = STATICS_DIR / "hexbeam_multiblock_discrete_case_1.vtm"

Load the Mesh

In this examples, we are simply interested in manipulating the FEB file and not the mesh itself, therefore we will be used pre-defined mesh. If you wish to create the mesh using python, we recommend using the pyvista or the gmsh and pygmsh libraries.

[2]:
mb = pv.read(HEXBEAM_MULTIBLOCK_FILEPATH)
mb
[2]:
InformationBlocks
MultiBlockValues
N Blocks3
X Bounds-2.500, 2.500
Y Bounds-0.500, 0.500
Z Bounds0.000, 1.000
IndexNameType
0first_blockUnstructuredGrid
1second_blockUnstructuredGrid
2selected_load_surfaceUnstructuredGrid

Extract each block from the multiblock

[3]:
first_block = mb["first_block"]
second_block = mb["second_block"]
selected_load_surface = mb["selected_load_surface"]

Extract the field data (just to make it easier later on)

[4]:
first_block_discrete_ids = first_block.field_data["discrete_ids"]
second_block_discrete_ids = second_block.field_data["discrete_ids"]

first_block_ground_ids = first_block.field_data["ground_ids"]
second_block_ground_ids = second_block.field_data["ground_ids"]
second_block_boundary_ids = second_block.field_data["boundary_ids"]

load_surface_connectivity = selected_load_surface.field_data["load_surface_connectivity"]

Create lines (for visualization only)

[5]:
src_points = first_block.points[first_block_discrete_ids]
dst_points = second_block.points[second_block_discrete_ids]
lines = np.stack((src_points, dst_points), axis=1).reshape(-1, 3)

Plot the data

[6]:
plotter = pv.Plotter()
plotter.add_mesh(first_block, color="beige", opacity=0.7)
plotter.add_mesh(second_block, color="beige", opacity=0.7)
plotter.add_points(first_block.points[first_block_discrete_ids], color="red", point_size=5, opacity=0.7)
plotter.add_points(second_block.points[second_block_discrete_ids], color="blue", point_size=5, opacity=0.7)
plotter.add_lines(lines, color="green", width=1)
plotter.add_points(first_block.points[first_block_ground_ids], color="black", point_size=5, opacity=0.7)
plotter.add_points(second_block.points[second_block_ground_ids], color="black", point_size=5, opacity=0.7)
plotter.add_points(second_block.points[second_block_boundary_ids], color="orange", point_size=5, opacity=0.7)
plotter.add_mesh(selected_load_surface, color="purple", point_size=6)
plotter.show()
../../../../_images/modules_feb_spec_3_0_adding_discrete_sets_2_adding_discrete_sets_13_0.png

Create FEB

Create a FEB object:

[7]:
from febio_python import Feb

feb = Feb(version=3.0)

Setup basic configurations:

[8]:
feb.setup_module(module_type="solid") # default values
feb.setup_globals(T=0, R=0, Fc=0) # default values
feb.setup_controls(analysis="dynamic") # here, you can change basic settings. See docs for more info.
feb.setup_output(variables=["displacement", "Lagrange strain", "stress"]) # default values

Add materials

[9]:
from febio_python.core import Material

# Define the block materials

# first block material
material_1 = Material(
    id=1,
    type="isotropic elastic",
    name="FirstBlockMaterial",
    parameters=dict(
        E=2e3,
        v=0.3,
        density=1,
    )
)
# second block material
material_2 = Material(
    id=2,
    type="isotropic elastic",
    name="SecondBlockMaterial",
    parameters=dict(
        E=1e3,
        v=0.25,
        density=1,
    )
)

Add the material to the FEB object

[10]:
feb.add_materials([material_1, material_2])

Add mesh

[11]:
from febio_python.core import Nodes, Elements

# Define the nodes
first_block_nodes_obj = Nodes(name="FirstBlockNodes", coordinates=first_block.points)
second_block_nodes_obj = Nodes(name="SecondBlockNodes", coordinates=second_block.points)

# Define the elements
first_block_elements = Elements(
    name="FirstBlockElements",
    type="HEXAHEDRON",
    connectivity=first_block.cells_dict[pv.CellType.HEXAHEDRON],
    mat=1
)
second_block_elements = Elements(
    name="SecondBlockElements",
    type="HEXAHEDRON",
    connectivity=second_block.cells_dict[pv.CellType.HEXAHEDRON] + first_block.n_points, # we need to offset the indices
    mat=2
)

Add the nodes and elements to the FEB object

[12]:
feb.add_nodes([first_block_nodes_obj, second_block_nodes_obj])
feb.add_elements([first_block_elements, second_block_elements])

Add Mesh Domains

[13]:
from febio_python.core import SolidDomain

# create a solid domain
solid_1 = SolidDomain(
    id=1,
    name="FirstBlockElements", # this must match one of the element names
    mat="FirstBlockMaterial", # this must match one of the material names
    )
solid_2 = SolidDomain(
    id=2,
    name="SecondBlockElements", # this must match one of the element names
    mat="SecondBlockMaterial", # this must match one of the material names
    )

Add domains to the FEB object

[14]:
feb.add_mesh_domains([solid_1, solid_2])

Add Nodesets

[15]:
from febio_python.core import NodeSet

# Define the node sets
boundary_nodeset = NodeSet(
    name="BoundaryNodes",
    ids=second_block_boundary_ids + first_block.n_points  # since this is the second block, we need to offset the ids
)

first_block_ground_nodeset = NodeSet(
    name="FirstBlockGroundNodes",
    ids=first_block_ground_ids
)
second_block_ground_nodeset = NodeSet(
    name="SecondBlockGroundNodes",
    ids=second_block_ground_ids + first_block.n_points  # since this is the second block, we need to offset the ids
)

Add node sets to the FEB object

[16]:
feb.add_node_sets([boundary_nodeset, first_block_ground_nodeset, second_block_ground_nodeset])

Add Surfaces

[17]:
from febio_python.core import Surfaces

# Define the surface
surface = Surfaces(
    name="LoadSurface",
    type="QUAD",
    connectivity=load_surface_connectivity,
)

Add surface to the FEB object

[18]:
feb.add_surfaces([surface])

Add loads

[19]:
from febio_python.core import SurfaceLoad, LoadCurve

# Define the surface load
load = SurfaceLoad(
        surface="LoadSurface",  # this is the surface name (must be in the FEB object)
        load_curve=1,  # this is the load curve ID
        name="SurfaceLoad",  # optional name
        scale=-1000, # scale factor
        linear=True,  # linear pressure load
        symmetric_stiffness=True)  # symmetric stiffness matrix

# Define the load curve
lc = LoadCurve(
    id=1,
    interpolate_type="linear",
    data=np.array([[0, 0], [1, 1]]))

Add load and load curve to the FEB object

[20]:
feb.add_surface_loads([load])
feb.add_load_curves([lc])

Add Boundary conditions

[21]:
from febio_python.core import FixCondition
# create the fixed boundary condition
fixed = FixCondition(
    dof="x,y,z",  # fix only the z direction
    node_set="BoundaryNodes",
    name="Fixed")
ground_fixed_1 = FixCondition(
    dof="z",  # fix only the z direction
    node_set="FirstBlockGroundNodes",
    name="FirstBlockGroundFixed")
ground_fixed_2 = FixCondition(
    dof="z",  # fix only the z direction
    node_set="SecondBlockGroundNodes",
    name="SecondBlockGroundFixed")

Add the fixed boundary condition to the FEB object

[22]:
feb.add_boundary_conditions([fixed, ground_fixed_1, ground_fixed_2])

Add Discrete Sets

First option:

[23]:
from febio_python.core import DiscreteSet, DiscreteMaterial

# Create the discrete set object
discrete_set_obj = DiscreteSet(
        name="DiscreteSet",
        src=first_block_discrete_ids,
        dst=second_block_discrete_ids + first_block.n_points,  # since this is the second block, we need to offset the ids
        dmat=1)

# Create the discrete material object
discrete_material = DiscreteMaterial(
        id=1,
        type="linear spring",
        name="DiscreteMaterial",
        parameters=dict(
            E=1e6
        )
    )

Add discrete set, and discrete material to the FEB object

[24]:
feb.add_discrete_sets([discrete_set_obj])
feb.add_discrete_materials([discrete_material])

Second option:

Alternatively, we can also setup discrete sets based on Nodesets. This is a convinience method from febio-python. This is useful when we already have pre-defined nodesets or when we do not want to handle the coordinations of “node ids” directly. Imagine a scenario in which there are multiple meshes or multiple discrete sets; it will get harder to manually track all ids and the order they appear in the mesh. When preparing the data, we recommend creating a single mesh object, and extract all data relate to this “combined mesh” instead of manipulating ids directly, this way it will reduce chances of errors.

[25]:
feb.clear_discrete_sets()
feb.get_discrete_sets()
[25]:
[]

Define nodesets where discrete sets will be applied

[26]:
src_ds_nodeset = NodeSet(
    name="SourceNodesForDiscreteSet",
    ids=first_block_discrete_ids
)
dst_ds_nodeset = NodeSet(
    name="DestinationNodesForDiscreteSet",
    ids=second_block_discrete_ids + first_block.n_points  # since this is the second block, we need to offset the ids
)

Add node sets to the FEB object

[27]:
feb.add_node_sets([src_ds_nodeset,
                   dst_ds_nodeset])
[28]:
# Create the discrete set object
discrete_set_obj = DiscreteSet(
        name="DiscreteSet",
        src="SourceNodesForDiscreteSet",
        dst="DestinationNodesForDiscreteSet",
        dmat=1)
[29]:
feb.add_discrete_sets([discrete_set_obj])

Writing FEB

[30]:
output_file = STATICS_DIR / "sample_discrete_set_case_1_v30.feb"
feb.write(output_file)