2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RustでCADするライブラリ作ったよ — OpenCASCADEバインディング「cadrum」

2
Last updated at Posted at 2026-03-29

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エクスポートの例

chijin — a drum of Amami Oshima

もともとは KatachiForm というサービスのために作ったライブラリです。KatachiFormはSTEPファイルから「寸法変更・即時見積もり・3Dプレビュー付きの発注フォーム」を自動生成するサービスで、そのバックエンドでCADデータのパラメトリック変形やメッシュ化を行うためにcadrumが必要でした。サービス固有のコードを切り離して汎用ライブラリとして公開したものがこのcrateです。

image.png

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");
}

01_primitives

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は逆のアプローチで、SolidCloneを実装したり、ShapeトレイトでVec<Solid>にメソッドを生やしたり、OCCTの複雑さをRustの型システムで整理しています。「SolidBooleanだけ知っていれば使える」くらいのミニマルさを目指しています。カバー範囲は狭いですが、敷居は低いです。

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バインディングです
  • SolidBoolean だけ覚えれば使い始められます
  • 色付きSTEP I/O、SVGエクスポート、メッシュ化まで対応しています
  • OCCTの事前インストール不要、cargo build だけで動きます
  • CadQueryユーザーがRustに移行する際の選択肢になれたら嬉しいです

リポジトリ: https://github.com/lzpel/cadrum
crates.io: https://crates.io/crates/cadrum

Star、Issue、PRお待ちしています。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?