This script reads a STEP file, extracts all shapes using PythonOCC, and writes each as an STL in parallel using
multiprocessing.Pool. Unfortunately, when I lost my position at Pierer Innovation, I wasn???t able to finish the project.
It still contains some bugs related to reading the hierarchical structure of STEP files.
Currently, there is no plugin on the Unreal Marketplace that can load STEP files during runtime in a packaged Unreal Engine build.
So maybe in the future, I will finish this project ??? it has the potential to become a very useful tool, especially for the automotive industry.
import sys
import os
from multiprocessing import Pool, cpu_count
from OCC.Core.IFSelect import IFSelect_RetDone
from OCC.Core.STEPCAFControl import STEPCAFControl_Reader
from OCC.Core.TDocStd import TDocStd_Document
from OCC.Core.TDF import TDF_LabelSequence
from OCC.Core.XCAFApp import XCAFApp_Application_GetApplication
from OCC.Core.XCAFDoc import XCAFDoc_DocumentTool_ShapeTool, XCAFDoc_DocumentTool_NameTool
from OCC.Core.TCollection import TCollection_ExtendedString
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
from OCC.Core.StlAPI import StlAPI_Writer
from OCC.Core.TopoDS import TopoDS_Shape
def get_shape_name(label, name_tool):
"""Returns the name of a shape from its label."""
name = TCollection_ExtendedString()
return name.ToExtString() if name_tool.GetName(label, name) else None
def write_stl(shape, filename, linear_deflection=0.1, angular_deflection=0.5):
"""Meshes the shape and writes an STL file."""
mesh = BRepMesh_IncrementalMesh(shape, linear_deflection, False, angular_deflection, True)
mesh.Perform()
if not mesh.IsDone():
print("Error meshing shape.")
return False
writer = StlAPI_Writer()
writer.SetASCIIMode(False)
return writer.Write(shape, filename)
def process_shape(args):
"""Processes a single shape: meshes and saves it."""
shape, name, output_folder = args
safe = "".join(c if c.isalnum() or c in " _-" else "_" for c in (name or "part"))
path = os.path.join(output_folder, f"{safe}.stl")
if write_stl(shape, path):
print(f"Wrote: {path}")
else:
print(f"Failed: {name}")
def step_to_stl_parts(step_file, output_folder):
"""Main function: reads STEP, extracts shapes, and parallel-writes STLs."""
if not os.path.isfile(step_file):
print(f"File not found: {step_file}")
return False
doc = TDocStd_Document(TCollection_ExtendedString("doc"))
app = XCAFApp_Application_GetApplication()
app.NewDocument(TCollection_ExtendedString("MDTV-CAF"), doc)
shape_tool = XCAFDoc_DocumentTool_ShapeTool(doc.Main())
name_tool = XCAFDoc_DocumentTool_NameTool(doc.Main())
reader = STEPCAFControl_Reader()
if reader.ReadFile(step_file) != IFSelect_RetDone:
print("STEP read error.")
return False
if not reader.Transfer(doc):
print("STEP transfer error.")
return False
labels = TDF_LabelSequence()
shape_tool.GetFreeShapes(labels)
tasks = []
for i in range(labels.Length()):
lbl = labels.Value(i+1)
shp = shape_tool.GetShape(lbl)
nm = get_shape_name(lbl, name_tool)
if not shp.IsNull():
tasks.append((shp, nm, output_folder))
if not tasks:
print("No shapes found.")
return False
cores = cpu_count()
print(f"Using {cores} cores.")
with Pool(cores) as p:
p.map(process_shape, tasks)
return True
if __name__ == "__main__":
if len(sys.argv)<3:
print("Usage: python step_to_stl_parts.py input.step output_folder")
else:
inp, out = sys.argv[1], sys.argv[2]
os.makedirs(out, exist_ok=True)
step_to_stl_parts(inp, out)