3D CADの世界ではPythonが強いですよね。CadQueryやbuild123dでパラメトリックモデリングを書いている方も多いと思います。でも、Rustで同じことができたらどうでしょう? 型安全、高速、シングルバイナリ配布、WebAssemblyへのコンパイル——こうしたRustの恩恵をCADにも持ち込めるとしたら、かなり嬉しくないですか。
cadrum は、産業用CADカーネル OpenCASCADE(OCCT 7.9.3)のミニマルなRustバインディングです。STEP/BRepの読み書き、ブーリアン演算、メッシュ生成、色付きSTEP I/O、SVGエクスポートをRustの型安全なAPIで提供します。
- 色付きSVGエクスポートの例
もともとは KatachiForm というサービスのために作ったライブラリです。KatachiFormはSTEPファイルから「寸法変更・即時見積もり・3Dプレビュー付きの発注フォーム」を自動生成するサービスで、そのバックエンドでCADデータのパラメトリック変形やメッシュ化を行うためにcadrumが必要でした。サービス固有のコードを切り離して汎用ライブラリとして公開したものがこのcrateです。
cadrumが動いている様子は以下で体験できます
30秒で動かす
# Cargo.toml
[dependencies]
cadrum = "^0.4"
use cadrum::{Color, Shape, Solid};
use glam::DVec3;
fn main() {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let box_ = Solid::box_from_corners(DVec3::ZERO, DVec3::new(10.0, 20.0, 30.0))
.color_paint(Some(Color::from_hex("#4a90d9").unwrap()));
let cylinder = Solid::cylinder(DVec3::new(30.0, 0.0, 0.0), 8.0, DVec3::Z, 30.0)
.color_paint(Some(Color::from_hex("#e67e22").unwrap()));
let sphere = Solid::sphere(DVec3::new(60.0, 0.0, 15.0), 8.0)
.color_paint(Some(Color::from_hex("#2ecc71").unwrap()));
let cone = Solid::cone(DVec3::new(90.0, 0.0, 0.0), DVec3::Z, 8.0, 0.0, 30.0)
.color_paint(Some(Color::from_hex("#e74c3c").unwrap()));
let torus = Solid::torus(DVec3::new(130.0, 0.0, 15.0), DVec3::Z, 12.0, 4.0)
.color_paint(Some(Color::from_hex("#9b59b6").unwrap()));
let shapes = vec![box_, cylinder, sphere, cone, torus];
let mut f = std::fs::File::create(format!("{example_name}.step")).expect("failed to create file");
cadrum::write_step_with_colors(&shapes, &mut f).expect("failed to write STEP");
let svg = shapes.to_svg(DVec3::new(1.0, 1.0, 1.0), 0.5).expect("failed to export SVG");
std::fs::write(format!("{example_name}.svg"), svg.as_bytes()).expect("failed to write SVG");
}
cargo run --example 01_primitives で STEP と SVG が出力されます。OCCT 7.9.3 のソースを自動ダウンロード・ビルドしてくれるので、事前インストールは不要です。C++17コンパイラとCMakeさえあれば大丈夫です。
主な機能
| 機能 | API例 |
|---|---|
| プリミティブ生成 |
Solid::box_from_corners(), Solid::cylinder()
|
| ブーリアン演算 |
Boolean::union(), subtract(), intersect()
|
| 変換 |
.translate(), .rotate(), .mirrored(), .scaled()
|
| 回転体・スイープ |
Face::revolve(), Face::helix()
|
| STEP I/O |
read_step(), write_step()
|
| 色付きSTEP (XDE) |
write_step_with_colors(), read_step_with_colors()
|
| BRep I/O |
read_brep_bin(), write_brep_bin()
|
| メッシュ化 | Solid::mesh() |
| SVGエクスポート |
.to_svg() (HLR隠線処理付き) |
| 面の色塗り | .color_paint(Some(Color::from_hex("#ff0").unwrap())) |
| トポロジ走査 |
.faces(), .edges()
|
他のライブラリとの位置づけ
Rustで3D CADをやろうとすると、いくつかの選択肢があります。それぞれの立ち位置を整理してみます。
一つ先に言っておくと、色付きSTEP I/O(XDE)に対応しているRustライブラリはcadrumだけです。Python勢(CadQuery, build123d)はAssembly経由で色付きSTEPを扱えますが、Rust側ではopencascade-rsもtruckも対応していません。面ごとに色を塗ってSTEPで書き出せるのは、地味だけど実用上かなり大きいです。
CadQuery / build123d(Python)
PythonでOpenCASCADEを叩くライブラリです。エコシステムが成熟していて、Jupyter上でインタラクティブに使えるのが強みですよね。一方で、大規模な自動設計パイプラインではPythonの速度やバイナリ配布の難しさがネックになることがあります。cadrumはRustの強みを活かして、CLIツールやサーバーサイドでの自動設計に向いています。CadQueryユーザーがRustに移行する際の受け皿になれたらいいなと思っています。
opencascade-rs
同じくOCCTのRustバインディングです。autocxxで自動生成したローレベルなAPIを提供していて、OCCTのAPIをほぼそのまま使えます。その代わり、C++のAPIがそのまま見えるので、Rustらしく書こうとすると結構つらいです。cadrumは逆のアプローチで、SolidにCloneを実装したり、ShapeトレイトでVec<Solid>にメソッドを生やしたり、OCCTの複雑さをRustの型システムで整理しています。「SolidとBooleanだけ知っていれば使える」くらいのミニマルさを目指しています。カバー範囲は狭いですが、敷居は低いです。
truck
純Rustで書かれたB-Repカーネルです。OCCT非依存なのが最大の強みです。ただ、OCCTの数十年分の堅牢なブーリアン演算やSTEP I/Oの蓄積には及ばない部分もあります。cadrumはOCCTの信頼性をRustから使えるようにする橋渡し的な存在です。
| cadrum | opencascade-rs | truck | CadQuery | |
|---|---|---|---|---|
| 言語 | Rust | Rust | Rust | Python |
| CADカーネル | OCCT 7.9.3 | OCCT | 独自 | OCCT |
| APIの方針 | ミニマル・高レベル | ローレベル・網羅的 | 純Rust | Pythonic |
| STEP I/O | ○ | ○ | △ | ○ |
| 色付きSTEP | ○ | × (XDE未公開) | × | ○ (Assembly経由) |
| ビルド | 自動(OCCT_ROOT で永続化可) | 要OCCT | 依存なし | conda推奨 |
feature構成
| feature | 説明 |
|---|---|
color(デフォルト) |
XDE経由の色付きSTEP I/O |
OCCTのビルドはfeatureではなく環境変数で制御します。OCCT_ROOT を設定すると、そこにビルド済みバイナリがあればリンクのみ、なければそこにビルドします。未設定の場合は target/occt/ にビルドします。
# ビルド済みOCCTを再利用する場合(cargo clean後も保持される)
export OCCT_ROOT=~/occt
cargo build
改名の話:chijin → cadrum と太鼓の example
自分はOSSの名前をなるべく楽器の名前から取るようにしていて、OpenAPIからRustのサーバー/クライアントコードを生成する mandolin (弦楽器のマンドリン)や、Rustのリバースプロキシ rebab(中東の弦楽器)なんかもそうです。今回も奄美大島の太鼓「チヂン」から chijin と名付けていたのですが、もっと広く使ってもらうにはCADライブラリだと伝わる名前がいいなと思い、cadrum(CAD + drum)に改名しました。
チヂン太鼓をcadrumで作る
せっかくなので、名前の由来になった太鼓をcadrumで作ってみましょう。円柱のボディ、回転体のリム、20個の締め木、穴あけ——ブーリアン演算と色塗りのフルコースです。
use cadrum::{Boolean, Color, Face, Shape, Solid};
use glam::DVec3;
use std::f64::consts::PI;
pub fn chijin() -> Solid {
// ── 胴体: 半径15、高さ8の円柱(z=-4..+4) ───────────────────────────
let cylinder: Solid =
Solid::cylinder(DVec3::new(0.0, 0.0, -4.0), 15.0, DVec3::Z, 8.0)
.color_paint(Some(Color::from_hex("#999").unwrap()));
// ── リム: x=0 平面の断面ポリゴンをZ軸周りに360°回転
// 外径17、z=3..5 のリング形状。z=0 で鏡像コピーして上下に配置。
let cross_section = Face::from_polygon(&[
DVec3::new(0.0, 0.0, 5.0),
DVec3::new(0.0, 15.0, 5.0),
DVec3::new(0.0, 17.0, 3.0),
DVec3::new(0.0, 15.0, 4.0),
DVec3::new(0.0, 0.0, 4.0),
DVec3::new(0.0, 0.0, 5.0),
])
.unwrap();
let sheet = cross_section
.revolve(DVec3::ZERO, DVec3::Z, 2.0 * PI)
.unwrap()
.color_paint(Some(Color::from_hex("#fff").unwrap()));
let sheets = [sheet.mirrored(DVec3::ZERO, DVec3::Z), sheet];
// ── 締め木: 2x8x1、Z軸周りに60°回転、y=15 に配置 ──────────────────
let block_proto = Solid::box_from_corners(DVec3::new(-1.0, -4.0, -0.5), DVec3::new(1.0, 4.0, 0.5))
.rotate(DVec3::ZERO, DVec3::Z, 60.0_f64.to_radians())
.translate(DVec3::new(0.0, 15.0, 0.0));
// ── 締め穴: 各締め木を貫く細い円柱 ─────────────────────────────────
let hole_proto = Solid::cylinder(
DVec3::new(-5.0, 16.0, -15.0),
0.7,
DVec3::new(10.0, 0.0, 30.0),
30.0,
);
// 20個をZ軸周りに等間隔配置、各締め木を虹色に着色
let n = 20usize;
let mut blocks: Vec<Solid> = Vec::with_capacity(n);
let mut holes: Vec<Solid> = Vec::with_capacity(n);
for i in 0..n {
let angle = 2.0 * PI * (i as f64) / (n as f64);
let color = Color::from_hsv(i as f32 / n as f32, 1.0, 1.0);
blocks.push(
block_proto
.clone()
.rotate(DVec3::ZERO, DVec3::Z, angle)
.color_paint(Some(color)),
);
holes.push(hole_proto.clone().rotate(DVec3::ZERO, DVec3::Z, angle));
}
let blocks = blocks
.into_iter()
.map(|v| vec![v])
.reduce(|a, b| Boolean::union(&a, &b).unwrap().solids)
.unwrap();
let holes = holes
.into_iter()
.map(|v| vec![v])
.reduce(|a, b| Boolean::union(&a, &b).unwrap().solids)
.unwrap();
// ── 組み立て: union → subtract → union ──────────────────────────────
let combined: Vec<Solid> = Boolean::union(&[cylinder], &sheets)
.expect("cylinder + sheet union failed")
.into();
let result: Vec<Solid> = Boolean::subtract(&combined, &holes).unwrap().into(); // 穴あけ
let result: Vec<Solid> = Boolean::union(&result, &blocks).unwrap().into(); // 締め木取付
assert!(result.len() == 1);
result.into_iter().next().unwrap()
}
fn main() {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let result = vec![chijin()];
// ── STEP 書き出し ────────────────────────────────────────────────────
let step_path = format!("{example_name}.step");
let mut f = std::fs::File::create(&step_path).expect("failed to create STEP file");
cadrum::write_step_with_colors(&result, &mut f).expect("failed to write STEP");
println!("wrote {}", &step_path);
// ── SVG 書き出し(等角投影、方向 (1,1,1)) ───────────────────────────
let svg_path = format!("{example_name}.svg");
let svg = result.to_svg(DVec3::new(1.0, 1.0, 1.0), 0.5).expect("failed to export SVG");
let mut f = std::fs::File::create(&svg_path).expect("failed to create SVG file");
std::io::Write::write_all(&mut f, svg.as_bytes()).expect("failed to write SVG");
println!("wrote {}", &svg_path);
}
cargo run --example chijin で色付きSTEPとSVGが出力されます。冒頭のカラフルな太鼓です。
プリミティブ生成 → 変換 → ブーリアン → 色塗り → ファイル出力という一連のワークフローが、すべてRustのコードで完結しています。
まとめ
- cadrum はOpenCASCADE 7.9.3のミニマルなRustバインディングです
-
SolidとBooleanだけ覚えれば使い始められます - 色付きSTEP I/O、SVGエクスポート、メッシュ化まで対応しています
- OCCTの事前インストール不要、
cargo buildだけで動きます - CadQueryユーザーがRustに移行する際の選択肢になれたら嬉しいです
リポジトリ: https://github.com/lzpel/cadrum
crates.io: https://crates.io/crates/cadrum
Star、Issue、PRお待ちしています。

