Rhino & GrasshopperPythonRhino 6–8 · built-in Python (rhinoscriptsyntax)MIT licenseIntermediate
Export Rhino curves to DXF, one file per layer
CAM software that wants each operation as a separate import turns "export the drawing to DXF" into "export the drawing N times, once per operation, hiding everything else first." This script does that automatically: it groups every curve in the document by its current layer, then runs Rhino's own Export command once per layer, so a model with roughing/finishing/engraving on separate layers comes out as three clean DXF files instead of one that needs re-splitting downstream.
Before you run it
- Rhino 6, 7, or 8 (script uses
rhinoscriptsyntaxplus the-Exportcommand line) - Curves organized onto layers that already match your intended DXF split (one layer per operation, typically)
- Run once interactively first and check the exact Export prompts your Rhino version shows — the script assumes the default prompt accepts a single Enter
The code
"""Export every curve on each layer to its own DXF file - one file per
layer, useful for CAM software that wants each operation on a separate import.
Rhino 6/7: Tools > PythonScript > Edit, paste, run.
Rhino 8: ScriptEditor.
"""
import os
import rhinoscriptsyntax as rs
OUT_FOLDER = "C:\\DXF\\" # must end with a backslash
def main():
if not os.path.isdir(OUT_FOLDER):
print("Folder not found: {}".format(OUT_FOLDER))
return
curves = rs.ObjectsByType(4, select=False) # 4 = curve objects
if not curves:
print("No curves in the document.")
return
by_layer = {}
for obj in curves:
layer = rs.ObjectLayer(obj)
by_layer.setdefault(layer, []).append(obj)
exported = 0
for layer, ids in by_layer.items():
rs.UnselectAllObjects()
rs.SelectObjects(ids)
safe_name = layer.replace("::", "_").replace(" ", "_")
path = os.path.join(OUT_FOLDER, "{}.dxf".format(safe_name))
rs.Command('_-Export "{}" _Enter'.format(path), False)
exported += 1
print("{}: {} curve(s) -> {}".format(layer, len(ids), path))
rs.UnselectAllObjects()
print("{} layer(s) exported to {}".format(exported, OUT_FOLDER))
main()What you get
What you get
C:\DXF\
├── Roughing.dxf
├── Finishing.dxf
├── Engraving.dxf
└── ...
Roughing: 12 curve(s) -> C:\DXF\Roughing.dxf
Finishing: 8 curve(s) -> C:\DXF\Finishing.dxf
Engraving: 3 curve(s) -> C:\DXF\Engraving.dxf
3 layer(s) exported to C:\DXF\How it works
rs.ObjectsByType(4, select=False)grabs every curve in the document by its object-type code (4 = curves) — no manual selection needed, unlike the curvature-finder script'srs.GetObject.- Grouping into a
{layer: [ids]}dictionary first, then looping over the groups, is what turns "all curves" into "one DXF per layer" — the export step only ever sees one layer's curves selected at a time. rs.Command('_-Export "..." _Enter', False)is the standard way to script a command that would otherwise open a dialog: the leading-suppresses the modal UI, and_Enteraccepts the follow-up prompt's default.- Layer names are sanitized (
::and spaces replaced) before becoming filenames — Rhino allows characters in layer names, especially nested-layer separators, that most filesystems don't.
Gotchas & honest limits
- Test the `-Export` prompt sequence interactively once before trusting this unattended — depending on your Rhino version and DXF export settings, there can be more than one follow-up prompt (units, ACAD version), and this script only sends one
_Enter. - Curves on a locked or hidden layer are still returned by
ObjectsByTypebut can't be selected or exported — a hidden layer will silently produce an empty or missing DXF for that layer. - Nested layers (
Parent::Child) are flattened into one filename per full layer path, not one file per parent — usually what you want for a CAM-operation split, but check it matches your layer structure. - This exports curves only — surfaces, meshes, and annotations on the same layers are ignored, by design, since DXF/CAM handoff is almost always curve geometry.
Goes deeper
Want this adapted to your shop — or built into a real tool?
Samples are the free 80%. The last 20% is the part I do for a living.