STLからCNC用Gコードを自動生成したいけれど、3Dプリンタ用スライサーとの違いやC++実装の全体像が分かりづらい――そんな人向けに、CAMの考え方から具体的な実装手順、ライブラリ選定、注意点までをまとめます。自作CAMに挑戦したいエンジニア・メイカー向けの実践ガイドです。
STLからCNC用Gコードを作る全体像
まずは「STL → CNC用Gコード」までの全体フローを整理しておきます。ここがイメージできていると、どこを既存ツールに任せて、どこをC++で自作すべきかが見えやすくなります。
| ステップ | 役割 | 具体的な処理 | 主な実装場所 |
|---|---|---|---|
| 1. 3Dモデル準備 | 形状の定義 | CADでモデリングし、STL(ポリゴンメッシュ)として書き出す | CADソフト側(Fusion 360等) |
| 2. STL読み込み | メッシュ取得 | 三角形メッシュ(頂点座標と法線)をアプリ内にロード | C++プログラム(Assimp等) |
| 3. ツールパス生成 | 加工経路設計 | スライス、輪郭/ポケット、荒取り/仕上げを計算 | C++プログラム(自作ロジック+幾何ライブラリ) |
| 4. ポストプロセス | Gコード整形 | 機械の方言(GRBL, Fanuc, LinuxCNC等)に合わせてGコード出力 | C++プログラム(出力部分) |
| 5. CNC実行 | 実際の切削 | 生成したGコードをCNCコントローラへ送信し加工 | 機械側(CNCコントローラ) |
この記事では、とくに2〜4の「STL読み込み〜ツールパス生成〜Gコード出力」をC++でどう組み立てるかを中心に解説します。
3Dプリンタ用スライサーとCNC CAMの違い
よくある誤解として「3Dプリンタ用スライサーがGコードを出しているのだから、それをCNCにも流用できるのでは?」というものがあります。しかし、3Dプリンタ(積層造形)とCNC(減材加工)では、前提がかなり違います。
| 項目 | 3Dプリンタ用スライサー | CNC用CAM |
|---|---|---|
| 加工方式 | 材料を「積む」(樹脂を押し出し、レイヤーを重ねる) | 材料を「削る」(工具で切削し、不要部分を除去) |
| 主な制約 | オーバーハング、サポート材、押し出し量、冷却 | 工具径、工具長、切削負荷、治具干渉、主軸回転数 |
| 経路の考え方 | レイヤーごとの輪郭・インフィルを均一に塗るイメージ | 工具負荷を抑えつつ効率的に削る(荒取り→仕上げ等) |
| 必要な計算 | 押し出し量、温度制御、サポート生成など | 工具径補正、干渉判定、切込み深さ、残材計算など |
| 流用可能な部分 | レイヤーの概念、輪郭抽出、経路のGコード化などは思想レベルでは参考になるが、そのまま転用は難しい | |
つまり、単に「STLをスライスしてG1でなぞる」だけでは、安全で効率のよいCNCツールパスにはなりません。工具や材料、機械の特性を踏まえたCAMとしてのロジックが必要になります。
CNC用Gコード生成の現実的な選択肢
既存CAMソフトを使う(最も現実的で速い)
「とにかく早くCNCで削りたい」という目的なら、まずは既存のCAMソフトを使うのが圧倒的に効率的です。
- FreeCAD Path ワークベンチ:無償・OSS。GRBL / LinuxCNC / Mach3 など向けのポストプロセッサが揃っている。
- BlenderCAM:Blender上でCAM機能を提供する拡張。
- Fusion 360 (CAM機能):商用だが使いやすく学習資料も豊富。
これらを使えば、STLやSTEPを読み込んで、GUI上で工具径・切込み量・送り速度・加工パターンを指定し、そのままGコードを書き出せます。自作ツールは後回しにして、まず一度これらで切削を体験してからの方が、アルゴリズムを理解しやすいのも大きなメリットです。
OSSの実装を読む・組み込む
学習・研究や自前システムへの組込みを考えるなら、以下のようなOSSのソースコードを読むのも非常に有効です。
- Slic3r / PrusaSlicer:3Dプリンタ向けスライサー。CNC向けではないが、スライス・輪郭・Gコード出力の設計が参考になる。
- FreeCAD / BlenderCAM / HeeksCNC:CNC向けのCAM機能を備えたOSS。
- OpenCAMLib:C++ベースの高機能なツールパスライブラリ(ドロップカッター等)。
「すべてゼロから実装」よりも、OSSのコードを読みつつ、自分の用途にあった最低限の機能だけを切り出して使う方が、現実的かつ安全です。
C++で自作する場合の難易度
STLからCNC用GコードをC++で自作することは可能ですが、以下のような要素が絡むため、難易度は決して低くありません。
- メッシュ処理(平面との交差、輪郭抽出など)
- 幾何計算(オフセット、ブーリアン演算、ポリライン処理)
- 加工戦略(荒取り、仕上げ、等高線、ラスタなど)
- 機械ごとの方言を意識したGコード出力(ポストプロセス)
- 安全性(干渉/突っ込みの回避、エアカット、シミュレーション)
そこで本記事では、2.5D加工(Z一定で輪郭やポケットを加工するケース)に範囲を絞り、最小構成のC++実装のイメージを解説します。ここを押さえれば、より高度な3D加工へも拡張しやすくなります。
C++でSTL→CNC Gコードを自作する手順
前提:まずは「2.5D加工」に絞る
いきなり自由曲面の3D加工(キャビティや金型レベル)に対応しようとすると、アルゴリズムもライブラリも一気に難しくなります。最初は以下のような2.5Dの典型パターンに絞ると設計しやすくなります。
- 上面の面削り(フェイスカット)
- 矩形や任意形状のポケット加工(くり抜き)
- 外形の輪郭加工(プロファイル・カット)
2.5Dであれば、「Zは段階的に下げるが、各層の経路はほぼ2D」とみなせるため、2Dオフセットとスライス処理が中心になり、既存ライブラリの力を借りやすくなります。
ステップ1:STLファイルの読み込み
C++でメッシュを扱うなら、Assimp(Open Asset Import Library)が定番です。STL以外のフォーマットにも対応しているため、将来の拡張性も高くなります。
// 簡易的なSTL読み込み例(Assimp)
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
struct Triangle {
Eigen::Vector3d v0, v1, v2;
};
std::vector<Triangle> loadStlMesh(const std::string& path) {
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(
path,
aiProcess_Triangulate | aiProcess_JoinIdenticalVertices
);
if (!scene || !scene->HasMeshes()) {
throw std::runtime_error("STL load failed");
}
std::vector<Triangle> tris;
aiMesh* mesh = scene->mMeshes[0];
for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {
const aiFace& face = mesh->mFaces[i];
if (face.mNumIndices != 3) continue;
auto toVec = [&](unsigned int idx) {
aiVector3D v = mesh->mVertices[idx];
return Eigen::Vector3d(v.x, v.y, v.z);
};
Triangle t;
t.v0 = toVec(face.mIndices[0]);
t.v1 = toVec(face.mIndices[1]);
t.v2 = toVec(face.mIndices[2]);
tris.push_back(t);
}
return tris;
}
メッシュを読み込んだら、モデル全体のバウンディングボックス(X/Y/Zの最小値・最大値)を計算しておくと、スライスの範囲や荒取り領域の設定に利用できます。
ステップ2:Zレベルごとにメッシュをスライスして輪郭抽出
2.5D加工では、Zを一定間隔(ステップダウン)で変化させながら、各Z平面でメッシュと平面の交線を求めます。この交線が、加工対象形状の輪郭(等高線)になります。
基本的なアルゴリズムは次の通りです。
- 加工したいZの範囲を決める(例:Z = 0 から Z = -10mm まで)。
- ステップダウン(例:1mm刻み)でZを変化させる。
- 各Zについて、全ての三角形とZ平面との交差判定を行う。
- 交差する三角形ごとに線分(2点)を求め、それらをポリライン(ループ)として連結する。
平面Z = z0と三角形の交差は、3頂点のZ座標の符号を見れば判定できます。簡易的には、
- 三角形の頂点が平面をまたいでいるエッジを探す
- 線形補間で交点のXYZを求める
という処理を行います。擬似コード的には次のようになります。
struct Segment2D {
Eigen::Vector2d p0, p1;
};
std::vector<Segment2D> sliceAtZ(
const std::vector<Triangle>& tris,
double z
) {
std::vector<Segment2D> segs;
for (const auto& tri : tris) {
// 各頂点のZとインデックス
std::array<Eigen::Vector3d, 3> vs = { tri.v0, tri.v1, tri.v2 };
std::vector<Eigen::Vector3d> inter;
for (int i = 0; i < 3; ++i) {
int j = (i + 1) % 3;
double z1 = vs[i].z();
double z2 = vs[j].z();
if ((z1 - z) * (z2 - z) <= 0.0 && z1 != z2) {
// 線分が平面をまたいでいる
double t = (z - z1) / (z2 - z1);
Eigen::Vector3d p = vs[i] + t * (vs[j] - vs[i]);
inter.push_back(p);
}
}
if (inter.size() == 2) {
Segment2D s;
s.p0 = inter[0].head<2>();
s.p1 = inter[1].head<2>();
segs.push_back(s);
}
}
return segs;
}
このようにして得られた2D線分群を、端点同士がつながるようにソートしてループ化することで、そのZ高さでの輪郭ポリラインを取得できます。
ステップ3:輪郭のオフセットとツールパス生成
次に、抽出した輪郭をもとに、工具径を考慮した加工経路(ツールパス)を生成します。ここで重要なのが、
- 工具半径分のオフセット(内側/外側)
- ステップオーバー(ラスタやポケット加工のピッチ)
2Dオフセットやポケット加工には、幾何ライブラリを活用するのが現実的です。
| ライブラリ | 主な用途 | 特徴 |
|---|---|---|
| Clipper | 2Dポリゴンのオフセット・ブーリアン | 整数座標ベースで安定。ライセンスも緩く使いやすい。 |
| CGAL | 2D/3D幾何計算全般 | 高機能だがやや重め。高精度演算に向く。 |
| OpenCAMLib | 3Dツールパス(ドロップカッター等) | 本格的な3D加工に対応。自由曲面向き。 |
例えばClipperを使う場合、輪郭ポリラインをClipperのパスに変換し、
- 外形加工:外側へ工具半径分オフセット
- ポケット加工:内側へ工具半径分オフセットし、さらにステップオーバー間隔で複数オフセット
といった処理を行います。
ステップ4:安全高さと切削パスの組み立て
2Dで工具経路が得られたら、次はZ方向を含めた3軸ツールパスとして組み立てます。ここで最低限意識すべきポイントは以下の通りです。
- クランプや治具とぶつからない安全高さ(Zsafe)を常に確保する
- 切削開始前には必ずZsafeまで退避した状態からXYに移動する
- 切削中は急激なZ変化を避ける(工具に負荷がかかる)
疑似的な処理の流れは次のようになります。
struct ToolPathPoint {
double x, y, z;
double feed; // 送り速度
bool rapid; // G0かG1か
};
std::vector<ToolPathPoint> generate2_5DPaths(
const std::vector<Polyline2D>& contours,
double z,
double zSafe,
double feedCut,
double feedRapid
) {
std::vector<ToolPathPoint> pts;
for (const auto& path : contours) {
if (path.points.empty()) continue;
// 安全高さへ
pts.push_back({ path.points.front().x(), path.points.front().y(), zSafe, feedRapid, true });
// 切り込み
pts.push_back({ path.points.front().x(), path.points.front().y(), z, feedCut, false });
// 輪郭をなぞる
for (size_t i = 1; i < path.points.size(); ++i) {
pts.push_back({ path.points[i].x(), path.points[i].y(), z, feedCut, false });
}
// 終了後に安全高さへ退避
pts.push_back({ path.points.back().x(), path.points.back().y(), zSafe, feedRapid, true });
}
return pts;
}
ここで得られたToolPathPointリストを、そのままGコードへ変換していきます。
ステップ5:Gコードとして出力(ポストプロセス)
多くのCNCコントローラはGコードに対応していますが、ヘッダ・フッタや円弧指定方法などに方言があります。まずは、比較的シンプルなGRBLをターゲットにすると分かりやすいです。
典型的なヘッダ例:
(Setup)
G90 G21 G17 ; 絶対座標, mm, XY平面
G54 ; ワーク座標系
G0 Z5.000 ; 安全高さへ
M3 S12000 ; 主軸ON 回転数12000rpm
F600 ; 送り速度の初期値
ToolPathPointからGコードを出すC++コード例は次のようになります。
void emitGCode(
const std::vector<ToolPathPoint>& pts,
std::ostream& os
) {
os << "(Setup)\n";
os << "G90 G21 G17\n";
os << "G54\n";
os << "G0 Z5.000\n";
os << "M3 S12000\n";
os << "F600\n\n";
double currentFeed = 600;
for (const auto& p : pts) {
if (p.rapid) {
os << "G0";
} else {
os << "G1";
if (p.feed != currentFeed) {
os << " F" << p.feed;
currentFeed = p.feed;
}
}
os << " X" << std::fixed << std::setprecision(3) << p.x;
os << " Y" << std::fixed << std::setprecision(3) << p.y;
os << " Z" << std::fixed << std::setprecision(3) << p.z;
os << "\n";
}
os << "\n(End)\n";
os << "M5\n"; // 主軸停止
os << "G0 X0 Y0\n";
os << "M2\n";
}
このように、「内部表現(ToolPathPoint)」→「ターゲット機種ごとのテキスト出力」という二段構成にしておくと、後から他のコントローラ向けポストプロセッサを追加しやすくなります。
最小構成の擬似C++コード例(2.5Dポケット加工)
ここまでの要素をざっくり繋げた「最小構成」の流れを、疑似C++でまとめてみます。
int main() {
// 1. STL読み込み
auto mesh = loadStlMesh("part.stl");
// 2. バウンディングボックスを計算
BoundingBox bbox = computeBoundingBox(mesh);
// 3. 加工条件
double toolDiameter = 6.0;
double toolRadius = toolDiameter / 2.0;
double stepDown = 1.0; // Z方向の切込みピッチ
double stepOver = 3.0; // XY方向のピッチ
double zSafe = 5.0; // 安全高さ
double zBottom = -10.0; // 最終Z
std::vector<ToolPathPoint> allPts;
// 4. Zレベルごとにスライスしてツールパス生成
for (double z = 0.0; z >= zBottom; z -= stepDown) {
// 4-1. メッシュと平面Z=zの交差線分を取得
auto segs = sliceAtZ(mesh, z);
// 4-2. 線分群から輪郭ポリラインを組み立て
auto contours = buildPolylinesFromSegments(segs);
// 4-3. 工具半径・ステップオーバーを考慮したオフセット(ポケット加工)
auto offsetContours = generatePocketPaths(contours, toolRadius, stepOver);
// 4-4. Z高さzにおけるツールパス(安全高さへの退避を含む)を生成
auto pts = generate2_5DPaths(offsetContours, z, zSafe, /*feedCut=*/600, /*feedRapid=*/1200);
allPts.insert(allPts.end(), pts.begin(), pts.end());
}
// 5. Gコードとして出力
std::ofstream ofs("output.nc");
emitGCode(allPts, ofs);
return 0;
}
もちろん、実際にはエラー処理や輪郭の方向(時計/反時計)判定、ポケット内部の複数ループ処理など、細かい実装が多数必要です。しかし、処理の大きな流れはこのような形になります。
Gコード出力例と主要コマンドの意味
GRBLを想定した、とてもシンプルな矩形輪郭加工のGコード例を示します。
(Setup)
G90 G21 G17
G54
G0 Z5.000
M3 S12000
F600
(例: 20x50の矩形輪郭をZ=-1まで)
G0 X0 Y0
G1 Z-1.000 F200
G1 X50 Y0
G1 X50 Y20
G1 X0 Y20
G1 X0 Y0
G0 Z5.000
(End)
M5
G0 X0 Y0
M2
よく使うGコード・Mコードの意味を表にまとめます。
| コード | 意味 | 備考 |
|---|---|---|
| G90 | 絶対座標モード | G91は相対座標。初心者はG90推奨。 |
| G21 | 単位をmmに設定 | インチの場合はG20。 |
| G17 | XY平面選択 | 円弧加工(G2/G3)の計算平面。 |
| G54 | ワーク座標系1 | 原点合わせ(ワークゼロ)の指定。 |
| G0 | 早送り移動 | 切削せずに素早く移動。 |
| G1 | 直線補間 | 指定送り速度で直線切削。 |
| G2 / G3 | 円弧補間(時計 / 反時計) | IJK or R指定。実装はやや複雑。 |
| M3 / M5 | 主軸ON / OFF | Sで回転数[rpm]を指定。 |
| F | 送り速度 | 単位はmm/minが一般的。 |
最初のうちは、G0/G1のみでツールパスを構成し、円弧(G2/G3)は使わない方が実装がシンプルです。十分に動作が確認できてから、直線を円弧に近似するロジックを追加していくと安全です。
使用する主なC++ライブラリ候補
STLからCNC用GコードをC++で生成するために、よく使われるライブラリと役割を整理します。
| 用途 | ライブラリ | メリット | 注意点 |
|---|---|---|---|
| メッシュ読み込み | Assimp | 多くの3Dフォーマットに対応。STLだけでなく拡張性が高い。 | 巨大モデルの場合はメモリに注意。 |
| 線形代数 | Eigen | ベクトル・行列演算が簡潔に書ける。 | 依存ライブラリとして別途導入が必要。 |
| 2Dオフセット | Clipper | ポリゴンのオフセット・ブーリアンが安定している。 | 整数座標で扱うため、スケーリングが必要。 |
| 幾何演算全般 | CGAL | 高精度な幾何処理を網羅。 | 学習コストは高め。ライセンス要確認。 |
| 3Dツールパス | OpenCAMLib | ドロップカッターなど高度な加工戦略を実装済み。 | ビルドやバインディングに少し手間がかかる。 |
最初のプロトタイプでは、Assimp + Eigen + Clipperという組み合わせが扱いやすくおすすめです。
実装時の設計ポイントと落とし穴
座標系・単位の取り扱い
CNCでは、座標系や単位の取り違いがそのままクラッシュや材料の無駄につながります。少なくとも、次の点は明示的に扱いましょう。
- モデル座標系の原点はどこか(CAD側での原点設定)
- CNC側のワーク座標系(G54など)との対応関係
- mmかインチか(G21 / G20)
- 絶対座標か相対座標か(G90 / G91)
おすすめは、
- 内部表現はmm・絶対座標に統一
- 出力Gコードでは必ずG90 / G21をヘッダに書く
という方針です。
工具径補正(G41 / G42)は最初は使わない
多くのCNCコントローラには工具径補正(G41/G42)がありますが、これはコントローラ側が経路を変形するため、シミュレーションが複雑になります。自作CAMの初期段階では、
- オフラインで工具径補正済みのツールパス(中心軌道)を生成する
- G41/G42は使わない
という運用にしておくと、「見たままの経路がそのまま機械で動く」のでトラブルを減らせます。
干渉と安全高さの設計
クランプやバイスとの干渉判定までやろうとすると本格的な3D衝突判定になってしまいますが、最低限として以下は守るべきです。
- ワーク上面から十分な距離の安全高さ Zsafe を決める(例:ワーク上面から +5〜10mm)
- 工具がXY移動する時は必ず Zsafe 以上で移動する
- 切込みは Zsafe → 目的Z へ、Z方向にまっすぐ降りる
これだけでも、移動中にワークや治具に突っ込むリスクをかなり下げられます。
検証と運用:シミュレータ・エアカット・ログ
自作Gコードをいきなり材料に対して実行するのは非常に危険です。必ず次のステップを踏むようにしましょう。
- シミュレーションツールでの確認
CAMoticsのようなGコードシミュレータを使い、ツールパスが意図通りか確認する。 - エアカット(空切り)
主軸回転を止めた状態、または材料から浮かせた状態で実行し、怪しい動きがないかを確認。 - ログ・差分管理
Gコード生成パラメータ(工具径、stepdown、stepover等)をログに残し、問題が起きたときに再現できるようにする。
開発初期は、加工時間よりも安全性と再現性を優先した方が、結果的にスムーズに開発を進められます。
3D加工(自由曲面)へ拡張したい場合
2.5Dの基礎ができてくると、次にやりたくなるのが3D自由曲面の加工
- 荒取り:Zレベル荒取り(Zレベルごとに2D輪郭で削る)が基本。2.5Dの延長で実現しやすい。
- 仕上げ:
- 等高線仕上げ(Z一定で輪郭をなぞる)
- 平行ラスタ仕上げ(X方向やY方向に等間隔ラスタ)
- 等スキャロップ仕上げ(工具が残す段差を一定に保つ)
これらを実現するには、
- 工具形状(ボールエンド、フラットエンド)のモデル化
- 工具をモデル表面に「落とし込む」ドロップカッター処理
- 残材計算
といった高度な処理が必要になり、OpenCAMLib等の専門ライブラリを使うのが現実的です。まずは2.5Dで全体の流れに慣れ、その後にOpenCAMLibなどを取り込んでいくのがおすすめのステップです。
まとめ:まずCAMを使い、次に自作で学ぶ
本記事では、「STLからCNC用Gコードを生成する」というテーマについて、現実的な選択肢とC++による実装の流れを紹介しました。
- 実務や趣味ですぐに削りたいなら既存CAM(FreeCAD PathやFusion 360等)を使うのが最短。
- C++での自作は可能だが、2.5D加工に範囲を絞ることで現実的な難易度にできる。
- STL読み込み → Zスライス → 2Dオフセット → ツールパス生成 → Gコード出力という流れを押さえれば、自作CAMの大枠は見えてくる。
- Assimp / Eigen / Clipper / OpenCAMLibなどのライブラリを活用すると、幾何計算を一から実装しなくて済む。
- 安全性を最優先し、シミュレーションとエアカットを徹底することが、自作GコードをCNCに流すための最低条件。
最初は既存CAMを使って「正解のツールパス」を体験し、その後でC++による自作ツールと見比べながら学ぶのが、遠回りに見えて実は一番効率的なルートです。この記事の内容をベースに、自分だけのSTL→CNC Gコード生成パイプラインを構築してみてください。

コメント