Simulating a 3D CAD model in a physics engine like MuJoCo can be a powerful way to test designs in a virtual environment. However, MuJoCo doesn’t directly understand CAD formats like STEP (STP) files — it needs STL meshes and an XML file to define the simulation. In this blog, I’ll walk you through the process of converting a STEP file into a MuJoCo XML file using Python, focusing on the key steps, the reasoning behind them, and a real-world issue we encountered with shells and solids. Let’s get started!

Why Do This?

MuJoCo is a physics engine used for robotics, animation, and engineering simulations. It requires:

A STEP file, on the other hand, is a standard CAD format (ISO 10303) that stores precise 3D geometry using mathematical definitions (e.g., surfaces, solids). To use it in MuJoCo, we need to:

  1. Extract the parts from the STEP file.
  2. Convert them to STL meshes.
  3. Create an XML file to tie everything together for simulation.

This process lets us take a CAD design — like our arch bridge — and simulate its physical behavior, such as how it handles stress or movement.

Key Terms: Understanding the STEP File

Before diving in, let’s clarify some terms you’ll encounter in a STEP file:

In our case, the ACDC_arch.stp file represents an arch bridge, which we expected to have 14 distinct segments (parts). However, as we’ll see, the STEP file’s structure caused an issue that we needed to resolve.

Tools You’ll Need

Install the Python libraries:

pip install pythonocc-core numpy stl

The Overall Process: Steps and Why

Here’s the high-level process to convert the STEP file to MuJoCo XML, along with why each step matters:

Extract Parts from the STEP File:

Convert Parts to STL Files:

Generate the MuJoCo XML File:

The Code: Key Steps

Here’s the simplified code to achieve this, focusing on the main ideas.

Step 1: Extract and Convert to STL

We use pythonocc-core to read the STEP file, extract solids, and convert them to STL files.

import os
from OCC.Core.STEPControl import STEPControl_Reader
from OCC.Core.TopExp import TopExp_Explorer, TopAbs_SOLID
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
from OCC.Core.StlAPI import StlAPI_Writer
from OCC.Core.TopoDS import topods
def extract_and_convert_stp_to_stl(stp_file_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
step_reader = STEPControl_Reader()
step_reader.ReadFile(stp_file_path)
step_reader.TransferRoots()

all_solids = []
part_info = []
for i in range(1, step_reader.NbRootsForTransfer() + 1):
shape = step_reader.Shape(i)
explorer = TopExp_Explorer(shape, TopAbs_SOLID)
while explorer.More():
solid = topods.Solid(explorer.Current())
if solid not in all_solids:
all_solids.append(solid)
part_info.append({'id': len(all_solids), 'name': f'part_{len(all_solids)}'})
explorer.Next()

print(f"Detected {len(all_solids)} solids in the STEP file")

successful_conversions = 0
for i, solid in enumerate(all_solids):
try:
mesh = BRepMesh_IncrementalMesh(solid, 0.01)
mesh.Perform()
if not mesh.IsDone():
continue
stl_writer = StlAPI_Writer()
stl_file = os.path.join(output_dir, f"part_{i+1}.stl")
stl_writer.Write(solid, stl_file)
successful_conversions += 1
print(f"Created {stl_file}")
except Exception as e:
print(f"Error processing part_{i+1}: {e}")

return successful_conversions, part_info[:successful_conversions]

Step 2: Generate MuJoCo XML

We create an XML file that references the STL files and sets up the simulation.

import xml.etree.ElementTree as ET
import xml.dom.minidom as minidom
import os
def create_mujoco_xml(part_info, output_file, mesh_dir):
root = ET.Element("mujoco", model="arch_bridge")
ET.SubElement(root, "option", gravity="0 0 -9.81")

asset = ET.SubElement(root, "asset")
stl_files = sorted([f for f in os.listdir(mesh_dir) if f.endswith('.stl')])
for i, stl_file in enumerate(stl_files):
ET.SubElement(asset, "mesh", name=f"mesh_{i}", file=f"meshes/bridge/{stl_file}", scale="0.001 0.001 0.001")

worldbody = ET.SubElement(root, "worldbody")
bridge_body = ET.SubElement(worldbody, "body", name="bridge", pos="0 0 0.15")
for i in range(len(stl_files)):
ET.SubElement(bridge_body, "geom", name=f"part_{i+1}", type="mesh", mesh=f"mesh_{i}")

pretty_xml = minidom.parseString(ET.tostring(root)).toprettyxml(indent=" ")
with open(output_file, 'w') as f:
f.write(pretty_xml)
print(f"Created MuJoCo XML file: {output_file}")

Step 3: Run the Process

Tie it all together in a main() function.

def main():
stp_file_path = "assembly/assets/ACDC_arch.stp"
mesh_dir = "assembly/assets/meshes/bridge"
output_xml_file = "assembly/assets/arch_bridge.xml"

num_parts, part_info = extract_and_convert_stp_to_stl(stp_file_path, mesh_dir)
if num_parts == 0:
print("Error: No parts converted. Aborting.")
return

create_mujoco_xml(part_info, output_xml_file, mesh_dir)
print(f"Conversion complete with {num_parts} parts!")

The Issue: Shells vs. Solids in the STEP File

While working on our test file, we hit a snag: the bridge visually has 14 segments, but the code only detected 13 solids. This mismatch caused the original code to add a placeholder part, which didn’t reflect the true design.

Figuring Out the Problem

To diagnose the issue, we added logging to inspect the STEP file’s structure:

However, upon re-evaluating the visual model, we confirmed the bridge indeed has 14 segments. The discrepancy arose because the four shells were meant to be four separate solids, not one composite solid.

Solving the Issue

We used FreeCAD to fix the STEP file:

  1. Opened the STP File in FreeCAD: Identified the composite solid with four shells.
  2. Converted Shells to Solids:
  1. Re-Exported the STP File: Saved the updated model with 14 solids (12 original + 2 new solids after combining some for the correct count).
  2. Updated the Code: Modified the code to handle both solids and shells, ensuring it could process the original file if needed, but the updated STP file now worked directly with solids.

After these changes, the code detected 14 solids, converted them to 14 STL files, and generated the MuJoCo XML without placeholders.

Why This Matters

This process bridges the gap between CAD and physics simulation:

Whether you’re simulating a bridge, a robot, or any CAD model, this workflow lets you bring your designs into MuJoCo for testing. Try it with your own STEP file, and if you run into issues like shells vs. solids, FreeCAD can help you resolve them!