Converting from Custom Format#
This page shows a complete, production-ready example of converting mesh data from a custom format to StructureGene.
Overview#
This example demonstrates:
Reading mesh data from a custom format
Mapping original node IDs to array indices
Grouping elements by type
Creating mocombos from unique material/angle combinations
Validation before writing
Error handling
Example Custom Format#
Assume your custom format has this structure:
{
'nodes': {
node_id: [x, y, z],
...
},
'elements': [
{
'id': element_id,
'type': 'TRIA3' | 'QUAD4' | 'TETRA4' | 'HEXA8',
'connectivity': [node_id1, node_id2, ...],
'material': material_name,
'angle': rotation_angle
},
...
],
'materials': {
material_name: {
'E': youngs_modulus,
'nu': poisson_ratio,
'rho': density
},
...
}
}
Complete Conversion Function#
Here’s a reusable conversion function with validation:
import numpy as np
from meshio import Mesh, CellBlock
from sgio import StructureGene
from sgio.model import CauchyContinuumModel
import sgio
def convert_custom_to_sgio(mesh_data, sgdim=2, name='converted_mesh'):
"""Convert custom mesh format to StructureGene.
Parameters
----------
mesh_data : dict
Dictionary with 'nodes', 'elements', and 'materials' keys
sgdim : int
Structure dimension (1, 2, or 3)
name : str
Name for the structure
Returns
-------
StructureGene
Populated StructureGene object ready for export
Raises
------
ValueError
If mesh data is invalid or incomplete
"""
# Validate input
required_keys = ['nodes', 'elements', 'materials']
for key in required_keys:
if key not in mesh_data:
raise ValueError(f"Missing required key: '{key}'")
if not mesh_data['nodes']:
raise ValueError("No nodes defined")
if not mesh_data['elements']:
raise ValueError("No elements defined")
if not mesh_data['materials']:
raise ValueError("No materials defined")
# Create StructureGene
sg = StructureGene(name=name, sgdim=sgdim, smdim=sgdim)
print(f"Creating StructureGene: {name} (sgdim={sgdim})")
# Convert materials
print(f"\nConverting {len(mesh_data['materials'])} materials...")
for mat_name, props in mesh_data['materials'].items():
material = CauchyContinuumModel(
name=mat_name,
isotropy=2, # Assume isotropic; adjust as needed
e=props['E'],
nu=props['nu'],
density=props['rho']
)
sg.materials[mat_name] = material
print(f" + {mat_name}: E={props['E']/1e9:.1f} GPa")
# Build node ID mapping: original_id -> array index
node_ids = sorted(mesh_data['nodes'].keys())
node_id_to_index = {nid: idx for idx, nid in enumerate(node_ids)}
print(f"\nMapping {len(node_ids)} nodes (ID range: {min(node_ids)}-{max(node_ids)})")
# Create points array
points = np.array([mesh_data['nodes'][nid] for nid in node_ids])
# Element type mapping from custom format to meshio
type_map = {
'TRIA3': 'triangle',
'QUAD4': 'quad',
'TETRA4': 'tetra',
'HEXA8': 'hexahedron',
'LINE2': 'line',
}
# Build mocombos: collect unique (material, angle) combinations
print(f"\nProcessing {len(mesh_data['elements'])} elements...")
mocombo_map = {} # (material, angle) -> property_id
next_property_id = 1
for elem in mesh_data['elements']:
key = (elem['material'], elem['angle'])
if key not in mocombo_map:
mocombo_map[key] = next_property_id
sg.mocombos[next_property_id] = key
next_property_id += 1
print(f"Created {len(sg.mocombos)} material-orientation combinations:")
for prop_id, (mat, angle) in sg.mocombos.items():
print(f" Property {prop_id}: {mat} @ {angle}°")
# Group elements by type
elements_by_type = {}
for elem in mesh_data['elements']:
elem_type = elem['type']
if elem_type not in type_map:
raise ValueError(f"Unsupported element type: {elem_type}")
meshio_type = type_map[elem_type]
if meshio_type not in elements_by_type:
elements_by_type[meshio_type] = []
elements_by_type[meshio_type].append(elem)
print(f"\nElement types found:")
for etype, elems in elements_by_type.items():
print(f" {etype}: {len(elems)} elements")
# Create cells and cell_data
cells = []
property_ids = []
element_ids = []
for elem_type, elems in elements_by_type.items():
connectivity = []
props = []
elem_ids = []
for elem in elems:
# CRITICAL: Map original node IDs to 0-based array indices
try:
conn = [node_id_to_index[nid] for nid in elem['connectivity']]
except KeyError as e:
raise ValueError(
f"Element {elem['id']} references undefined node ID: {e}"
)
connectivity.append(conn)
# Get property_id for this material/angle combination
prop_id = mocombo_map[(elem['material'], elem['angle'])]
props.append(prop_id)
elem_ids.append(elem['id'])
# Create cell block
cells.append(CellBlock(type=elem_type, data=np.array(connectivity)))
property_ids.append(np.array(props))
element_ids.append(np.array(elem_ids))
# Assemble mesh
sg.mesh = Mesh(
points=points,
cells=cells,
cell_data={
'property_id': property_ids,
'element_id': element_ids
},
point_data={
'node_id': node_ids # Preserve original IDs
}
)
print(f"\nStructureGene created successfully:")
print(f" Nodes: {sg.nnodes}")
print(f" Elements: {sg.nelems}")
print(f" Materials: {len(sg.materials)}")
print(f" Property combos: {len(sg.mocombos)}")
return sg
def validate_structure_gene(sg):
"""Validate StructureGene before writing.
Parameters
----------
sg : StructureGene
Structure to validate
Returns
-------
bool
True if valid, False otherwise
"""
print("\n" + "="*60)
print("Validating StructureGene")
print("="*60)
is_valid = True
# Check basic configuration
if sg.sgdim not in [1, 2, 3]:
print(f"✗ Invalid sgdim: {sg.sgdim}")
is_valid = False
else:
print(f"✓ sgdim = {sg.sgdim}")
# Check mesh exists
if sg.mesh is None:
print(f"✗ No mesh defined")
is_valid = False
else:
print(f"✓ Mesh: {sg.nnodes} nodes, {sg.nelems} elements")
# Check materials
if not sg.materials:
print(f"✗ No materials defined")
is_valid = False
else:
print(f"✓ Materials defined: {list(sg.materials.keys())}")
# Check mocombos
if not sg.mocombos:
print(f"✗ No mocombos defined")
is_valid = False
else:
print(f"✓ {len(sg.mocombos)} mocombos defined")
if not is_valid:
return False
# Check all property IDs reference valid mocombos
print("\nChecking property IDs...")
for i, prop_array in enumerate(sg.mesh.cell_data['property_id']):
unique_props = np.unique(prop_array)
for prop_id in unique_props:
if prop_id not in sg.mocombos:
print(f"✗ Block {i}: undefined property_id {prop_id}")
is_valid = False
if is_valid:
print("✓ All property IDs valid")
# Check all mocombos reference valid materials
print("\nChecking material references...")
for prop_id, (mat_name, angle) in sg.mocombos.items():
if mat_name not in sg.materials:
print(f"✗ Property {prop_id} references undefined material '{mat_name}'")
is_valid = False
if is_valid:
print("✓ All material references valid")
print("="*60)
if is_valid:
print("✓ Validation PASSED")
else:
print("✗ Validation FAILED")
print("="*60)
return is_valid
Usage Example#
Here’s how to use the conversion function:
# Simulate reading your custom format
# In practice, replace this with actual file parsing
mesh_data = {
'nodes': {
100: [0.0, 0.0, 0.0],
101: [1.0, 0.0, 0.0],
102: [1.0, 1.0, 0.0],
103: [0.0, 1.0, 0.0],
104: [0.5, 0.5, 0.0],
},
'elements': [
{'id': 1000, 'type': 'TRIA3', 'connectivity': [100, 101, 104],
'material': 'Steel', 'angle': 0.0},
{'id': 1001, 'type': 'TRIA3', 'connectivity': [101, 102, 104],
'material': 'Steel', 'angle': 0.0},
{'id': 1002, 'type': 'TRIA3', 'connectivity': [102, 103, 104],
'material': 'Aluminum', 'angle': 0.0},
{'id': 1003, 'type': 'TRIA3', 'connectivity': [103, 100, 104],
'material': 'Aluminum', 'angle': 0.0},
],
'materials': {
'Steel': {'E': 200e9, 'nu': 0.3, 'rho': 7850},
'Aluminum': {'E': 70e9, 'nu': 0.33, 'rho': 2700},
}
}
# Convert
try:
sg = convert_custom_to_sgio(
mesh_data=mesh_data,
sgdim=2,
name='my_structure'
)
# Validate
if validate_structure_gene(sg):
# Write to file
sgio.write(
sg=sg,
fn='my_structure.sc',
file_format='swiftcomp',
format_version='2.1',
model_type='PL1'
)
print("\n✓ Successfully written to my_structure.sc")
else:
print("\n✗ Validation failed - not writing file")
except Exception as e:
print(f"\n✗ Conversion failed: {e}")
Expected Output#
Creating StructureGene: my_structure (sgdim=2)
Converting 2 materials...
+ Steel: E=200.0 GPa
+ Aluminum: E=70.0 GPa
Mapping 5 nodes (ID range: 100-104)
Processing 4 elements...
Created 2 material-orientation combinations:
Property 1: Steel @ 0.0°
Property 2: Aluminum @ 0.0°
Element types found:
triangle: 4 elements
StructureGene created successfully:
Nodes: 5
Elements: 4
Materials: 2
Property combos: 2
============================================================
Validating StructureGene
============================================================
✓ sgdim = 2
✓ Mesh: 5 nodes, 4 elements
✓ Materials defined: ['Steel', 'Aluminum']
✓ 2 mocombos defined
Checking property IDs...
✓ All property IDs valid
Checking material references...
✓ All material references valid
============================================================
✓ Validation PASSED
============================================================
✓ Successfully written to my_structure.sc
Handling Different Material Types#
If your materials have different symmetries, adjust the isotropy:
def convert_material(mat_data):
"""Convert material data with symmetry detection."""
mat_name = mat_data['name']
# Check if orthotropic
if 'E1' in mat_data and 'E2' in mat_data:
return CauchyContinuumModel(
name=mat_name,
isotropy=0, # Orthotropic
e=[mat_data['E1'], mat_data['E2'], mat_data['E3']],
g=[mat_data['G12'], mat_data['G13'], mat_data['G23']],
nu=[mat_data['nu12'], mat_data['nu13'], mat_data['nu23']],
density=mat_data['rho']
)
# Isotropic
else:
return CauchyContinuumModel(
name=mat_name,
isotropy=2, # Isotropic
e=mat_data['E'],
nu=mat_data['nu'],
density=mat_data['rho']
)
Next Steps#
For troubleshooting common issues, see Troubleshooting Common Issues
For data structure details, see Data Structure Reference
For a simpler starting point, see Quick Start: Simple Triangle Mesh