カテゴリー

最新記事

FreeCADを便利に使おう AIにお願いしてPythonマクロを作成 格子状オブジェクトや6角柱ができました!

すごく楽しかったので早々にブログにすることにしました。使っていて気付いたFreeCADだとちょっと面倒な作業をなんとかしようと思ったら、AIが何とかしてくれた、というお話です。FreeCADはPythonで直接動かせるようになっており、現状ではAIと相性が良いソフトウエアではないかと思われます。

AIについて 今回利用はWindows11の Copilot GPT-5.1

どの界隈でもAIの利用には賛否があると思われますが、私個人としてはこの技術が生まれ、世に出てしまった以上は一つのツールとして上手に付き合っていきたい考えです。著作権の問題を中心に非常に複雑かつ単一な問題ではないと思いますが、どうしてもAIに何かを頼む姿勢が無理な方はこのブログ記事をスキップしたほうが精神衛生上良いと思います。よろしくお願いいたします。

なお、当ブログでは記事を適当にAIに書かせる等の行為は行っておりません。スペック表くらいは書いてもらいたいですが、嘘つくので注意が必要ですね。今回はmanusも使ってスライドも1枚作ってもらいました。

ちなみに今回FreeCADのマクロを書いてもらったのはWindows11標準搭載のCopilotです。ご存じの通り、CopilotはChatGPTをベースとしており、少なくとも現在の段階ではモデルも選べますし、課金要素がなく、FreeCADの「Free」な部分を阻害しないものになります。ただ、私はMicrosoft 365のユーザーでもあるのでもしかしたら無料部分の制限が緩い可能性もあります。ご了承ください。

ていうか、GPT-5.1ってWindows11から直接使えたんですね。私最新モデル使えるの全然知らなかったのですが・・・皆さんご存じでした?ちなみによく酷評されるMicrosoft Edgeから呼び出すと3枚目のようにGPT-5.2のモデルも使えます。個人的にはEdge(というよりはBing)でコツコツポイント集め等をしており、用途でChromeと使い分けています。

当初やりたかったこと

さて、今回これをやるきっかけになったのはちょっと大物を3Dプリンターで印刷するために、Xでお世話になっている見通し甘太郎さんのTipsでも紹介されている、底面を分割する作業をFreeCADで行っていたからです。

この作業をやる理由ですが、特にABS等収縮しやすい樹脂では長いパスや長辺部分で反りが発生しやすいからです。そこで底面にスリットを入れてあげることで収縮を分散できる、という仕組みになります。また、塗りのパスがそろっているのも原因の一つになりうるので1層目のパスを同心円パターンにするのも一定の候があると思っています。

ちょっとモデルが大きかったので今回はこの作業をやることにしたのですが、意外と手順が面倒だったんですよね・・・。

ちなみに利用しているのはABSの仲間のようですが開放型でも印刷が出来るかも?というABSのシリーズです。AmazonにあるのだとPRINSFILさんのこれとかですね。匂いは確かにABSなのですが、ノズル温度もベッド温度も低めでPLAが混ざっているのかな、と個人的には思います。おそらく温度特性も近くなると思いますのでお手軽な分注意は必要かもしれません。ちょっと試してみてはいかがでしょうか?とりあえず確かにでかいサイズでも反って割れたりせず悪くないと思いましたよ。Amazonリンクよかったらご利用ください。

[itemlink post_id="8972″]

Fusionで簡単にできる「薄い押し出し」 FreeCADだとやや面倒

Fusionユーザーはご存じだと思いますが、Fusionにはスケッチの線から直接薄いソリッドを押し出す、薄い押し出し機能があります。これを使うと格子状のスケッチを作れば一回でスリットのための形状を作成することができます。

FreeCADにはそういった機能がないので別な工夫をする必要があります。当ブログでも下記で紹介したようなやり方がありますが、今回の用途を考えるとやりすぎです。興味がある方はぜひこちらもどうぞ。ほかにも当ブログ、FreeCADの日本語情報が比較的充実していると思いますので是非ご覧いただければ幸いです。

FreeCADで格子を作ろうとした場合、おそらく一般的な方法は細長い四角を一つスケッチで書いて、それをもとの形状からくりぬいて、くりぬいたフィーチャー(操作)を配列で繰り返す、というものになると思います。こんな感じです。

これをもう一回、90度向きを変えて行えば格子状のオブジェクトができます。・・・かなり面倒ですよね。

AIにマクロ作ってもらえないかな? →出来た!手直しも最小限

ということで、もっと楽したいな、と考えた結果がAIの利用です。FreeCADにはマクロ機能があるのでこれを利用して格子状のオブジェクトを作ることに。任意の格子が作れれば後はブーリアンでくりぬけば一発で作業が完了します。

右のツイートのリプライでも鈴北組さんがopenSCADで作るというお話をされていて参考になりました。ありがとうございます。

ということで冒頭のCopilotに実装要件をお伝えしてマクロを作ってもらうことに。でもPython含めプログラムのことを私全然知らないのでついでに学びになればうれしいなと。今後スタックチャン関係でソフトウエアは色々学びたいなと思っていたので言語は違えど良い機会でした。

ということで指示を出し、ちょっと修正して出来上がったのがこちら。ちゃんとダイアログが出て、格子のサイズを規定できる格子状オブジェクト作成マクロです。

これをプリンターのビルドプレートに合わせたサイズやオブジェクト底面が覆えるサイズにすればこのまま利用できてしまいます。すごいぞAIさん!!

直ししたところといえば、ダイアログのところとオブジェクト削除部分をコメントアウトしたことくらい。問題なく動作してしまいました。初期値は格子が大きいし細かすぎるので、我が家では初期値を3枚目の設定にしています。重心で移動してブーリアンすると、高さは半分になるので1.2でもいいかも。

FreeCADでのマクロの使い方

まずはマクロです。Copilotが作ってくれたマクロがこちら。ちゃんと行っている動作をコメントとして入れてくれています。うれしい。まあ、プログラム書ける人にとってはごく簡単なお仕事なのかもしれませんが、私からすればすごいことです。数年前では考えられなくないですか?いわば「OSの標準機能」で作られているわけで、個人的にはメモ帳がワードパッドどころかワードになってしまったくらいな感じです。最初はダイアログがなかったので、修正指示でダイアログを作ってもらいました。こういう命令でダイアログって作られているんですね・・・。

import FreeCAD as App
import FreeCADGui as Gui
import Part
from PySide import QtGui, QtCore

class GridDialog(QtGui.QDialog):
    def __init__(self):
        super(GridDialog, self).__init__()
        self.setWindowTitle("格子生成パラメータ")

        layout = QtGui.QFormLayout()

        # 入力フィールド
        self.width = QtGui.QDoubleSpinBox()
        self.width.setRange(1, 10000)
        self.width.setValue(300)

        self.depth = QtGui.QDoubleSpinBox()
        self.depth.setRange(1, 10000)
        self.depth.setValue(300)

        self.height = QtGui.QDoubleSpinBox()
        self.height.setRange(1, 1000)
        self.height.setValue(1)

        self.thickness = QtGui.QDoubleSpinBox()
        self.thickness.setRange(0.1, 100)
        self.thickness.setValue(0.8)

        self.spacing = QtGui.QDoubleSpinBox()
        self.spacing.setRange(1, 1000)
        self.spacing.setValue(30)

        # フォームに追加
        layout.addRow("幅 (X)", self.width)
        layout.addRow("奥行き (Y)", self.depth)
        layout.addRow("高さ (Z)", self.height)
        layout.addRow("バーの太さ", self.thickness)
        layout.addRow("間隔(ピッチ)", self.spacing)

        # OK / Cancel ボタン
        btns = QtGui.QDialogButtonBox(
            QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel
        )
        btns.accepted.connect(self.accept)
        btns.rejected.connect(self.reject)
        layout.addRow(btns)

        self.setLayout(layout)


def create_grid(params):
    grid_width, grid_depth, grid_height, bar_thickness, spacing = params

    doc = App.ActiveDocument
    if doc is None:
        doc = App.newDocument("GridObject")

    # 既存オブジェクト削除
#    for obj in doc.Objects:
#        doc.removeObject(obj.Name)

    bars = []

    # X方向バー
    x_count = int(grid_width / spacing) + 1
    for i in range(x_count):
        x = i * spacing
        bar = Part.makeBox(bar_thickness, grid_depth, grid_height, App.Vector(x, 0, 0))
        bars.append(bar)

    # Y方向バー
    y_count = int(grid_depth / spacing) + 1
    for j in range(y_count):
        y = j * spacing
        bar = Part.makeBox(grid_width, bar_thickness, grid_height, App.Vector(0, y, 0))
        bars.append(bar)

    # 結合
    grid = bars[0]
    for b in bars[1:]:
        grid = grid.fuse(b)

    part_obj = doc.addObject("Part::Feature", "Grid")
    part_obj.Shape = grid
    doc.recompute()


# -----------------------------
# メイン処理
# -----------------------------
dialog = GridDialog()
if dialog.exec_():
    params = (
        dialog.width.value(),
        dialog.depth.value(),
        dialog.height.value(),
        dialog.thickness.value(),
        dialog.spacing.value(),
    )
    create_grid(params)
    print("格子オブジェクトを生成しました。")
else:
    print("キャンセルされました。")

どうなんでしょうね、このマクロ。なんかちょっと余計な部分がある気もしますが、私の場合は動けばよいのでこれで良しとします!。さて、ではFreeCADでの使い方です。マクロを選んで新規作成し、このコードをコピペするだけです。マクロのエディタはFreeCAD内で内部ウインドウとして開きますのでこれを保存すれば準備OKです。マクロから呼び出してそのまま使え、簡単で素晴らしいです。

これで一瞬でビルドプレートサイズの格子が完成です。原点から作られるので必要に応じてオブジェクトを移動します。現在のFreeCADは配置する際に重心を移動始点にできるので、それを底面に位置合わせすれば完成です。あとはブーリアンすれば出来上がります。カスタマイズも容易で求めていたものがここに・・・!

なお、この処理はPartワークベンチで行いました。元々の形状はPartDesignワークベンチで作ったものですが、出来る格子はマクロによりPartワークベンチのようなもので作られています。PartDesignのほうがより限定的な仕様なので、ブーリアンするときはPartでないとできません。PartDesignワークベンチで行う場合は格子をPartDesignのベースフィーチャにするか、後述するサブシェイプバインダーを使うことになります。

以前のブログでも書きましたが、FreeCADの基本はPartワークベンチでそのソリッド部分を拡張したのがPartdesignワークベンチです。出来ているソリッドの由来が異なる場合、うまくブーリアン出来ないことがあります。無難なのはPartワークベンチですが、その後またPartdesignワークベンチで作業したい場合はサブシェイプバインダーを用いる方法がたぶんよいと思います。今回はAIを利用する会なので、説明スライドをmanusに作ってもらいました。manusはスライドを作るのが上手ですよね・・・。手直しすれば普通に使えるスライド作ってくれそうです。

manusに作ってもらったFreeCADにおけるブーリアンのコツ

六角柱も作れた VORONっぽいデザイン等にも使えそう

あまりにもあっさりできたのでもう少し色々やってみました。個人的に時々あって面倒なのは6角柱な凹凸です。VORONデザインのあれです。並べて作るのがちょっと面倒には感じていましたのでやってもらいました。

最終的に出来上がったのが下記コードです。これは何回かAIとやり取りした結果になります。要件はほぼ先ほどの格子構造と同じなのですが、最初つくると6角柱のがきれいに並んでいませんでした。配列で作成してくれているので六角柱の向きを変更する必要があったんですよね。なので30度傾けるように修正してもらいました。

そうすると・・・残念、次は6角柱が重なってしまっていました。どうやらピッチの部分が間違っているようなので、その間違いを指摘してみたのですが、いまいち回転したところで指示がちぐはぐな感じに(笑)。最後は自分で数値を修正することで無事に完成。最後に辺の長さを変更しても適切に比が保たれることを確認しました。

ということで最終的なコードがこちら。これで皆さん6角柱を作り放題です!ぜひご利用ください(笑)。ただFreeCADで特に最後のような沢山のソリッドを一気に作るのは意外と時間がかかります。ご注意ください。

import FreeCAD as App
import FreeCADGui as Gui
import Part
from PySide import QtGui, QtCore
import math

class HexDialog(QtGui.QDialog):
    def __init__(self):
        super(HexDialog, self).__init__()
        self.setWindowTitle("六角形格子パラメータ")

        layout = QtGui.QFormLayout()

        # 入力フィールド
        self.width = QtGui.QDoubleSpinBox()
        self.width.setRange(1, 10000)
        self.width.setValue(100)

        self.depth = QtGui.QDoubleSpinBox()
        self.depth.setRange(1, 10000)
        self.depth.setValue(100)

        self.height = QtGui.QDoubleSpinBox()
        self.height.setRange(1, 1000)
        self.height.setValue(5)

        self.edge = QtGui.QDoubleSpinBox()
        self.edge.setRange(0.1, 1000)
        self.edge.setValue(5)

        layout.addRow("全体幅 (X)", self.width)
        layout.addRow("全体奥行き (Y)", self.depth)
        layout.addRow("高さ (Z)", self.height)
        layout.addRow("六角形の一辺の長さ", self.edge)

        btns = QtGui.QDialogButtonBox(
            QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel
        )
        btns.accepted.connect(self.accept)
        btns.rejected.connect(self.reject)
        layout.addRow(btns)

        self.setLayout(layout)


def make_hex_prism(edge, height):
    """六角形プリズム(flat-top:30°回転済み)"""
    pts = []
    for i in range(6):
        angle = math.radians(60 * i + 30)  # ← ここで flat-top にする
        x = edge * math.cos(angle)
        y = edge * math.sin(angle)
        pts.append(App.Vector(x, y, 0))

    wire = Part.makePolygon(pts + [pts[0]])
    face = Part.Face(wire)
    prism = face.extrude(App.Vector(0, 0, height))
    return prism


def create_hex_grid(params):
    grid_width, grid_depth, grid_height, edge = params

    doc = App.ActiveDocument
    if doc is None:
        doc = App.newDocument("HexGrid")

    # 既存オブジェクト削除
#    for obj in doc.Objects:
#        doc.removeObject(obj.Name)

    hex_list = []

    # flat-top 六角形の正しい中心間距離
    pitch_x = edge * 2.0
    pitch_y = edge * math.sqrt(3)

    # 配置数
    nx = int(grid_width / pitch_x) + 3
    ny = int(grid_depth / pitch_y) + 3

    for iy in range(ny):
        for ix in range(nx):
            # 奇数行は半ピッチずらす
            offset_x = (pitch_x / 2) if (iy % 2 == 1) else 0

            x = ix * pitch_x + offset_x
            y = iy * pitch_y

            if x > grid_width or y > grid_depth:
                continue

            hex_prism = make_hex_prism(edge, grid_height)
            hex_prism.translate(App.Vector(x, y, 0))
            hex_list.append(hex_prism)

    # 結合
    if not hex_list:
        return

    shape = hex_list[0]
    for h in hex_list[1:]:
        shape = shape.fuse(h)

    part_obj = doc.addObject("Part::Feature", "HexGrid")
    part_obj.Shape = shape
    doc.recompute()


# -----------------------------
# メイン処理
# -----------------------------
dialog = HexDialog()
if dialog.exec_():
    params = (
        dialog.width.value(),
        dialog.depth.value(),
        dialog.height.value(),
        dialog.edge.value(),
    )
    create_hex_grid(params)
    print("六角形格子を生成しました(flat-top 完全版)。")
else:
    print("キャンセルされました。")

ちなみに完成したパーツは全部まとめて1つのPartになっており、例えばPart designワークベンチでブーリアンしたい場合はそのままだとPart designワークベンチでエラーになることが多いです。この場合は前述のとおりPartワークベンチで作業するか、Part Designワークベンチで行うなら出来た六角柱をサブシェイプバインダーで取り込むのが良いと思います。6角柱を選んで上部メニューなどからサブシェイプバインダーを押して取り込みます。黄色くなればOKです。元々のHexGridは非表示にしておきましょう。この状態でPart designワークベンチのブーリアンを行えばOKです。

サブシェイプバインダーは仮想形状なので、Binderを選択した状態でブーリアンボタンを押し、パラメータを切り取りに変更して実行すればOKです。ボディを選択する必要はありません。そうそう、足し算の場合はボディが複数になるとおそらくエラーになるのでご注意ください。これでVORONのような形状もサイズに合わせてピッタリ作成ができますね!余談ですが、Curvesワークベンチを使うとこうやって曲面にマッピングすることもできます。意外と色々出来るんですよ、FreeCAD。

FreeCADはAIと相性がよさそう python初心者にもお勧め?

ということで、AIにお願いしてFreeCAD用のpythonを書いてもらうお話でした。私は本当にソフトウエア知識ゼロで正直何にもできないのですが、ちょっと色々学びたいなとは思っているのでこういうものを入り口に少しずつベースとなる知識をつけていければなと思っています。

pythonを直接走らせられる点でFreeCADはAI向きな印象です。なお、AIと相性がよいCADの定番はbuild 123dという100%pythonのみのソフトウエアのようです。手は出せていませんが、AIが絡んだ世界、一瞬で景色が変わるので今後どうなっていくか全くわかりません。今のところ深入りはしない予定ですが、動向は適宜追っていきたいと思っています。

今回も最後までお読みいただきありがとうございました!