---
title: 3D Prep & SDF Exports
description: 'Turn a filtered ZINC SMILES set into docking-ready 3D with RDKit ETKDGv3 and MMFF — deduplicate on canonical SMILES first, then shard the SDF at 10k-20k.'
---

This page shows how to turn a filtered [ZINC](/docs/zinc) SMILES set into docking-ready 3D structures with RDKit, then deduplicate and shard the output into manageable SDF files. Ask Ivy to run this in the chat's [code workspace](/docs/sandbox), where RDKit is installed and files land under `/workspace/scratch/`, or run it on your own machine. Deduplicating on canonical SMILES before 3D generation is what most affects the result, since it avoids wasting embedding effort on redundant molecules.

## 2D → 3D with RDKit

```python
from rdkit import Chem
from rdkit.Chem import AllChem, SDWriter
import pandas as pd

df = pd.read_csv("zinc_filtered.csv")
writer = SDWriter("/workspace/scratch/zinc_3d.sdf")

for smiles in df["smiles"]:
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        continue
    mol = Chem.AddHs(mol)
    if AllChem.EmbedMolecule(mol, AllChem.ETKDGv3()) != 0:
        continue
    AllChem.MMFFOptimizeMolecule(mol)
    writer.write(mol)

writer.close()
```

## Deduplicate

- Normalize canonical SMILES and drop duplicates before 3D generation.
- For SDF outputs, store the canonical SMILES in a property (e.g., `SMILES`) for
  easy dedup later.

## Sharding SDF

- Write 10k–20k molecules per SDF to keep file sizes manageable.
- Name shards consistently: `zinc_3d_0001.sdf`, `zinc_3d_0002.sdf`, etc.

## Tips

- Set `ETKDGv3` seeds for reproducibility: `params.randomSeed = 13`.
- If MMFF fails, fall back to UFF for problematic molecules.
- Track failures (embed/optimize) to understand data quality; log counts per
  failure mode.
