OpenSCAD with clojure

この記事は高専Advent Calender 4日目の記事らしいです。 前日はykbrさんのGoでTwitter Botを改良していく記事でした。僕もそろそろコピーを作らないとですね。

Clojure始めました。 キーボードの記事という事になっていましたが、殆どOpenSCADをClojureで書く記事になります。

はじめに

皆さんEndgameしてますか?僕はしていません。 自作キーボードの界隈では至高のキーボードを手に入れることをEndgameと言います。 市販の入力デバイスの多くはテキスト入力かポインティングデバイスとしての機能に特化していることが多く、 どちらか片方ならば素晴らしいものは多いものの両方を高いレベルで満足させてくれる製品は数少ないのが現状です。 当然自作する流れになる訳ですが、ここで設計データの管理手法という問題が発生します。 3Dデータを直接GUIで構築するタイプのCADはCADベンダーが独自に提供するツールを使うかGitでバイナリとして扱うかになりがちです。 ならテキストで3Dデータを設計してやりましょう。

OpenSCAD

OpenSCAD - The Programmers Solid 3D CAD Modeller

幾何学計算にCGALを、GUIにQtを使ったテキストベースの3DCADです。部分的に翻訳された日本語のWikiチュートリアルなどがあります。 スクリプトを書いて3Dデータを作るためGitで管理しやすく複雑な形状も自動生成出来ます、がそこそこ辛いです。 トラックボールポチポチで出来ないのはトレードオフなので仕方ないですが変数が全てコンパイル時に計算される、 つまり最終的に格納された値で展開される為、スクリプトっぽい見た目に反して手でゴリゴリ書いていく羽目になります。 反復処理の抽象化すらかなり限定されてしまいます。つら...

scad.clj

なのでOpenSCADをより強力で汎用的な言語でラップするプロジェクトが幾つかあります。 紹介するのはclojureでの者ですが、RubyJavaScriptはありました。探したら他にもあるかもしれません。

プロジェクトの準備

僕はWindowsが苦手でMacOSが動くマシンも所持してないのでArch Linuxでの作業になります。 デスクトップOSのシェアはLinuxが7割以上ある(?)ので(?)多分大丈夫でしょう(???)

leiningen

clojureのパッケージマネージャです。 READMEにはstableからスクリプト落としてきてパス通せば良いと書いていますが、それやってリリースされてないパッケージダウンロードしようとしていたのでリポジトリをクローンしてリリースされたバージョンのスクリプトを使ったほうが良いです。 JREを入れていないなら先に入れておきましょう。leiningen 2.8.1だとopenjdk-8が良いらしいです。

git clone https://technomancy/leiningen && cd leiningen
git tag # 最新版を調べる
git checkout <latest version> # <latest version>は先程調べたバージョンで置換
sudo cp./bin/lein /usr/local/bin
lein # 起動してみる

プロジェクトを作る

lein new app scad-playground && cd scad-playground

プロジェクト名を変えたい場合はscad-playgroundを任意に置換してください。 project.cljを以下のように編集します。ライブラリのバージョンは適宜読み替えてください。

6,7c6
<   :dependencies [[org.clojure/clojure "1.8.0"]
<                  [scad-clj "0.5.3"]]
---
>   :dependencies [[org.clojure/clojure "1.8.0"]]

これで準備は完了です。

小さな立方体

./src/scad_playground/core.cljを以下のように編集します。

(ns scad-playground.core
  (:require [scad-clj.scad]
            [scad-clj.model])
  (:gen-class))

(def simple-cube
  (scad-clj.model/cube 10 10 10))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "simple-cube.scad" (scad-clj.scad/write-scad simple-cube)))

nsは名前空間を作るマクロで、:requireキーワードで外部ライブラリを読み込むことが出来ます。

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model]))
(def simple-cube
  (model/cube 10 10 10))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "simple-cube.scad" (scad/write-scad simple-cube)))

asキーワードを使うことでパッケージを短縮することが出来ます。以降やります。

openscad ./simple-cube.scad

小さな立方体をOpenSCADで表示したスクショ

プリミティブ図形達

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model]))

(def simple-cube
  (model/cube 10 20 30))

(def simple-sphere
  (model/sphere 10))

(def simple-cylinder
  (model/cylinder 10 30))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "simple-cube.scad" (scad/write-scad simple-cube))
  (spit "simple-sphere.scad" (scad/write-scad simple-sphere))
  (spit "simple-cylinder.scad" (scad/write-scad simple-cylinder)))

f:id:namachan10777:20181204224907p:plainf:id:namachan10777:20181204224913p:plainf:id:namachan10777:20181204224918p:plain

ブーリアン演算

OR

union関数を使います。引数に渡された図形の論理和を取ります。

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model]))

(def primitives
  (model/union
    (model/cylinder 10 50)
    (model/sphere 20)))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "primitives.scad" (scad/write-scad primitives)))

f:id:namachan10777:20181204234016p:plain
これは何?

AND

intersection関数を使います。引数に渡された図形の論理積を取ります。

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model]))

(def primitives
  (model/intersection
    (model/cube 15 15 50)
    (model/sphere 20)))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "primitives.scad" (scad/write-scad primitives)))

f:id:namachan10777:20181204230511p:plain
両方向から少しだけ使った消しゴム

差分

difference関数です。ネジ穴等を開けるときに重宝します。 第一引数から以降の引数の領域を除去した図形が出力されます。

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model]))

(def primitives
  (model/difference
    (model/cube 15 15 50)
    (model/sphere 20)))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "primitives.scad" (scad/write-scad primitives)))

f:id:namachan10777:20181204234153p:plain

基点変更

scad-cljではデフォルトでcenter=trueとなっています。これは図形の中心と現在の座標が一致するように図形を配置するという意味で、 これがtrueだと座標計算が面倒になる場合があります。

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model]))
(def simple-cube
  (model/cube 10 10 10 :center false))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "simple-cube.scad" (scad/write-scad simple-cube)))

f:id:namachan10777:20181205005330p:plain 基点が変更されていますね。ちなみに球だと何も変わりません。

なめらかさ

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model])
  (:gen-class))

(def simple-cube
  (model/union
    (->>
      (binding [model/*fn* 100] (model/sphere 10))
      (model/translate [ 20 0 0]))
    (->>
      (binding [model/*fn* 10] (model/sphere 10))
      (model/translate [-20 0 0]))))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "simple-cube.scad" (scad/write-scad simple-cube)))

bindingを使いスコープを越えて変数を書き換えることで円や球の滑らかさを変更できます。 仕様として妙な気はしますがOpenSCAD自体がこうなっている為それに合わせたのでしょう。 f:id:namachan10777:20181205005238p:plain

移動

水平移動

translate関数を使います。

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model]))

(def primitives
  (model/union
    (model/translate [20 0 0] (model/cube 15 15 50))
    (model/translate [-20 0 0] (model/sphere 20))))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "primitives.scad" (scad/write-scad primitives)))

f:id:namachan10777:20181204234210p:plain

回転移動

rotate関数を使います

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model]))

(def primitives
  (model/union
    (model/rotate [20 0 0] (model/cube 5 5 100))
    (model/rotate [0 20 0] (model/cube 5 5 100))))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "primitives.scad" (scad/write-scad primitives)))

f:id:namachan10777:20181204234223p:plain

マクロ

clojure->>を使った書き方もあります。 水平移動を例に取ると、

(ns scad-scad.core
  (:require [scad-clj.scad :as scad]
            [scad-clj.model :as model]))

(def primitives
  (model/union
    (->>
      (model/cube 15 15 50)
      (model/translate [20 0 0]))
    (->>
      (model/sphere 20)
      (model/translate [-20 0 0] ))))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (spit "primitives.scad" (scad/write-scad primitives)))

最後に

全然キーボード作ってないですね。登録詐欺か? Adc書かずにCivをしていました大変申し訳ありません。Clojure+OpenSCADで設計をする記事は今後も書きたいです。

明日はhrt9092さんの画像処理に関する記事です。アドベントカレンダーを読むのを楽しみに日々を生きている。