花を挿す、線を引く

お花とものづくりについてを中心に発信します

PythonでSTLデータを作成 その1:numpy-stlを使って植木鉢用受け皿のモデリングに挑戦! #CAD自動化

ChatGPTなどのいわゆるAIサービスが普及しつつあり、色々な事務仕事が簡単に自動化出来るようになりました。

私は普段3D CADを使って製品の形状を書いている訳ですが、一向にモデリングの自動化の気配を感じません。早く楽になって欲しいけど、実際自動化しようと思ったらどうやるんだろうという興味が湧いてきたので、簡単なもののモデルをPythonを使って書いてみようと思います。

今回書くモデルは観葉植物などの植木鉢の下に敷くあれ、受け皿のモデリングPythonを使ってトライしたのちに、鉢のサイズに合わせて後から自動で変更出来るように作ってみます。

まずはコマンドプロンプトからライブラリをインストールします。

〜
pip install numpy numpy-stl
〜

今回作成したコードはこちらです。

import numpy as np
from stl import mesh
import argparse

def create_complete_cylinder_with_depressions(diameter, height, filename='flower_saucer.stl'):
    diameter = diameter +4 #フランジ分を追加、引数には鉢底の直径を入力してほしい
    radius = diameter / 2.0
    first_offset_radius = radius - 2  # 最初のくぼみの2mmオフセット
    first_depth = 1  # 最初のくぼみの深さ1mm
    second_offset_radius = radius * 0.8  # 2番目のくぼみの半径
    second_depth = height - 2  # 全高から2mm残す深さ
    second_base_offset_radius = second_offset_radius - second_depth  # 2番目のくぼみの底面の半径
    num_points = 100

    # 円周上の点を生成
    angles = np.linspace(0, 2 * np.pi, num_points, endpoint=False)
    base_circle = np.array([np.cos(angles) * radius, np.sin(angles) * radius, np.zeros(num_points)]).T
    top_circle = np.array([np.cos(angles) * radius, np.sin(angles) * radius, np.full(num_points, height)]).T
    first_depression_top = np.array([np.cos(angles) * first_offset_radius, np.sin(angles) * first_offset_radius, np.full(num_points, height)]).T
    first_depression_base = np.array([np.cos(angles) * first_offset_radius, np.sin(angles) * first_offset_radius, np.full(num_points, height - first_depth)]).T
    second_depression_top = np.array([np.cos(angles) * second_offset_radius, np.sin(angles) * second_offset_radius, np.full(num_points, height - first_depth)]).T
    second_depression_base = np.array([np.cos(angles) * second_base_offset_radius, np.sin(angles) * second_base_offset_radius, np.full(num_points, height - second_depth)]).T

    # STLファイルに保存するためのデータ構造を作成
    data = np.zeros(num_points * 12, dtype=mesh.Mesh.dtype)
    cylinder_mesh = mesh.Mesh(data, remove_empty_areas=False)

    # 三角形を作成してメッシュに追加
    for i in range(num_points):
        # 底面の三角形
        cylinder_mesh.vectors[i * 12] = np.array([base_circle[i], base_circle[(i + 1) % num_points], [0, 0, 0]])
        # シリンダーの側面
        cylinder_mesh.vectors[i * 12 + 1] = np.array([base_circle[i], top_circle[i], base_circle[(i + 1) % num_points]])
        cylinder_mesh.vectors[i * 12 + 2] = np.array([base_circle[(i + 1) % num_points], top_circle[i], top_circle[(i + 1) % num_points]])
        # 最初のくぼみでトリムされた上面の残り
        cylinder_mesh.vectors[i * 12 + 3] = np.array([top_circle[i], first_depression_top[(i + 1) % num_points], first_depression_top[i]])
        cylinder_mesh.vectors[i * 12 + 4] = np.array([first_depression_top[(i + 1) % num_points ], top_circle[(i + 1) % num_points], top_circle[i]])
        # 最初のくぼみの側面
        cylinder_mesh.vectors[i * 12 + 5] = np.array([first_depression_top[i], first_depression_base[i], first_depression_base[(i + 1) % num_points]])
        cylinder_mesh.vectors[i * 12 + 6] = np.array([first_depression_top[(i + 1) % num_points], first_depression_top[i], first_depression_base[(i + 1) % num_points]])
        # 最初のくぼみの底面
        cylinder_mesh.vectors[i * 12 + 7] = np.array([first_depression_base[i], first_depression_base[(i + 1) % num_points], second_depression_top[i]])
        cylinder_mesh.vectors[i * 12 + 8] = np.array([first_depression_base[(i + 1) % num_points], second_depression_top[(i + 1) % num_points], second_depression_top[i]])
        # 二番目のくぼみの側面
        cylinder_mesh.vectors[i * 12 + 9] = np.array([second_depression_top[i], second_depression_base[i], second_depression_top[(i + 1) % num_points]])
        cylinder_mesh.vectors[i * 12 + 10] = np.array([second_depression_top[(i + 1) % num_points], second_depression_base[i], second_depression_base[(i + 1) % num_points]])
        # 二番目のくぼみの底面
        cylinder_mesh.vectors[i * 12 + 11] = np.array([second_depression_base[i], second_depression_base[(i + 1) % num_points], [0,0,height - second_depth]])

    # ファイルに保存
    cylinder_mesh.save(filename)

def main():
    parser = argparse.ArgumentParser(description="Generate an STL file for a cylinder with depressions.")
    parser.add_argument("diameter", type=float, help="Diameter of the cylinder")
    parser.add_argument("height", type=float, help="Height of the cylinder")
    args = parser.parse_args()

    create_complete_cylinder_with_depressions(args.diameter, args.height)

if __name__ == "__main__":
    main()


〜

こちらのコードをstl_make_flower_saucer.pyという名前を付けて保存して
コマンドプロンプトから実行します。
ファイル名の後に 直径 高さ の情報を引数として入力します。

〜
python stl_make_flower_saucer.py 100 10

stl_make_flower_saucer.pyが保存されているフォルダにflower_saucer.stlが生成されました。

Orcaスライサーで開いたflower_saucer.stl

直径100mm 高さ10mmの受け皿の3Dデータが出来ました。
引数を変えてあげると

受け皿の直径が50mmに変更されました。

Pythonを使って3Dデータを作成して
引数で形状変更をすることが出来ました。

今日は一旦ここまで。