4クロックで支度しな

脊髄反射で思ったことを書き垂れます。68060とか名乗っときながら4クロックはヌルいとか言わないで。

FreeCAD + 3Dプリンタでプレピー万年筆を改造してみた

できるだけ線の細い万年筆が欲しいと思い、昨年プラチナ万年筆のセンチュリー3776のUEFを購入したのですが、どうも線の入りと抜きが上手くないので、もっと細く線の制御が容易なものをと思い作ってみました。
要件としては、以下の通りです。

  • 丸ペン先が無改造ではまる
  • コンバータが使用可能
  • 丸ペン先を強固に固定し、たわまないようにする
  • 3Dプリンタで容易に作成可能(誰でも作れる)
  • 市販のプレピー万年筆の部品が流用可能(特にコンバータとスリップシール機構付きのキャップ)

設計には、FreeCAD-0.16-6712(git) x64、Windows7 Professional 64bitを使用しました。
部品の形状は以下の画像の通りです。
f:id:Lil68060:20180408181850j:plain
これをSTLにエクスポートしたものを、DMMの3Dプリントサービスで出力しました。
インクはプラチナ万年筆のブランセピア(顔料)を入れています。
f:id:Lil68060:20180408190633j:plain
f:id:Lil68060:20180408190721j:plain
f:id:Lil68060:20180408190739j:plain
f:id:Lil68060:20180408191136j:plain
キャップやコンバータは、緩くも固くもなく、丁度良いはめ具合です。
線の細さは、筆圧の強弱によって容易に変えることができ、UEFより細い線を書くことができます。
f:id:Lil68060:20180408191757j:plain

Python+GTK+3(pygobject)での、テキスト変更シグナルについて

この記事の目的

GTK+3でのテキスト入力用ウィジェットの1つにGtk.Entryというクラスがあります。
このEntryウィジェットへの入力に応じて何か動作(整形や入力のバリデーションなど)を行いたいことがあります。
そのためには、Entryへはどのような経路でテキストが入力されるかを知っておく必要があります。

どういう経路があるか

大きく分けて4つです。

  1. キーボードからの直接入力
  2. IMEからの入力
  3. コンテキストメニューからの「貼り付け」による入力
  4. Gtk.Entry.set_text()関数による変更

キーボードからの直接入力

この入力は「key-press-event」でキャッチできます(Gtk.Widgetに属するシグナルです)。
起こる状況は

  • IMEがオフの状態で
  • Entryにフォーカスがあり
  • キーボードが押された時(key-press-event)

または、

  • IMEがオンの状態で
  • Entryにフォーカスがあり
  • Shift-Enterなどの、IMEをバイパスするキー入力があったとき

です。

この方法でテキストが変更された場合、Gtk.Editable (Gtk.Entryの親クラス)のchangedシグナルも発生します。
ただし、改行の入力のように、テキストが変更されない場合はchangedシグナルは発生しません。

「CTRL-C」や「CTRL-Z」のような、特にUnix系OSではOSのシグナルとして扱われるような入力も、特に設定することなくkey-press-eventとして届きます。
ただし、シグナルの引数は、通常のCTRLモディファイヤ付きのアスキー文字列ではなく(例えば、特にデフォルトのキーバインドがない、「CTRL-A」のようなキー入力は、CTRLモディファイヤ付きの文字「A」の入力となります)、特殊な文字が渡されます。

シグナルの引数の詳細については、Gtk.Widget - Classes - Gtk 3.0を参照してください。
通常のキーに関してはGdk.EventKeyのstringメンバ、Enterキーなどの特殊キーについてはkeyvalメンバを判定するのが有効です(SHIFTやCTRLのように、キーボードに複数あるキーは、それぞれ異なるkeyval値を持つことに注意してください)。


※厳密には、「key-release-event」でも同等のことはできますが、GTK+では、Entryへ入力が反映されるのは「キーが押された時」なので、key-press-eventの方を取り上げました。
※ちなみに、マウス入力に関しては、「button-release-event」が起こった時に反映されます。

IMEからの入力

この入力は、変更を直接キャッチするのは面倒です。
シグナルは「preedit-changed」です。
起こる状況は、

  • IMEがオンの状態で、
  • 文字が入力されたとき、または
  • 文字が変換されたとき、または
  • 文字が確定されたとき

です。
このシグナルから得られる文字列は、直接Entryに影響を及ぼすものではなく、また、最終的にどの文字列に確定されたかを知るのは少々面倒です。

最も確実な状況は、preedit-changedシグナルから得られる文字列が空のときで、これはIMEで編集中の文字列が確定されたことを示します。
そして、そのときにはすでにEntryのテキストは変更されてしまっているので、IMEから入力される前のテキストとの差分を得るには、IMEへの入力が行われる「前」にテキストをどこかへ覚えておかねばならず、面倒です。

できるならば、IMEの確定のタイミングで、Entryのテキスト全文に対して処理を行うように設計・実装するべきです。

この方法でテキストが変更された場合、Gtk.Editable (Gtk.Entryの親クラス)のchangedシグナルも発生します。

コンテキストメニューからの「貼り付け」による入力

シグナルは「paste-clipboard」です。
このシグナルに他の引数はなく、届いた時点でEntryのテキストは変更されてしまっています。
そのため、入力のバリデーションなどを行う場合は、シグナルが届いた時点で全文をチェックすることになります。
Gtk.Entryはデフォルトでコンテキストメニューが提供されるので、常にこの経路での適すと変更は起こると考えるべきです。

この方法でテキストが変更された場合、Gtk.Editable (Gtk.Entryの親クラス)のchangedシグナルも発生します。

Gtk.Entry.set_text()関数による変更

シグナルは「changed」です。
シグナルに他の引数はなく、届いた時点でEntryのテキストは変更されており、差分を取得する直接的なサービスはありません。

これまで説明した他の変更イベントでもこのシグナルは発生するため、このルート経由での変更を考慮するにはコードに工夫が必要です。
ただ、通常のGUI操作によって、他のシグナルを経由せず、このシグナルによってのみキャッチ可能な変更操作は起こりません。

よって、プログラム内部でEntryのテキストを変更する際には、バリデーションなどの付随する処理も明示的に呼び出してやるのがベストです。

確認用コード

以下に、上記シグナルの確認用コードを記しておきます。
3つのファイルを同じディレクトリに置き、ターミナルから実行してみてください。
print()で状態を出力しているので、IDEのコンソール出力か、ターミナルを見てください。

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.2 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="window">
    <property name="can_focus">False</property>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkEntry" id="entry">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="button">
            <property name="label" translatable="yes">set "ABC123" to Entry
</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <placeholder/>
    </child>
    <style>
      <class name="window"/>
    </style>
  </object>
</interface>
.window {
	font-family: "Meiryo", "Hiragino Kaku Gothic ProN", "MS PGothic", Osaka, sans-serif;
}
# -*- coding: utf-8 -*-

'''
Created on 2018/02/21

@author: Lil68060
'''

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk

def entry_key_press(entry, event):
    print("key-press: is_modifier:{0}, keyval={1}, length={2}, state={3}, string=\"{4}\"".format(
        event.is_modifier, #0
        event.keyval, #1
        event.length, #2
        decode_state(event.state), #3
        event.string, #4
        ))
    return False

def decode_state(state):
    flags = []
    if state & Gdk.ModifierType.SHIFT_MASK:
        flags.append("SHIFT")
    if state & Gdk.ModifierType.META_MASK:
        flags.append("META")
    if state & Gdk.ModifierType.CONTROL_MASK:
        flags.append("CTRL")
    if state & Gdk.ModifierType.SUPER_MASK:
        flags.append("SUPER")
    return flags 

def entry_preedit_changed(entry, text):
    print("preedit-change(IME): [{0}](len={1})".format(text, len(text)))
    return False

def entry_paste_clipboard(entry):
    print("paste-clipboard: \"{0}\"".format(entry.get_text()))
    return False

def entry_changed(entry):
    print("changed: \"{0}\"".format(entry.get_text()))
    return False
    
def button_clicked(button, entry):
    txt = "ABC123"
    entry.set_text(txt)
    return False


if __name__ == '__main__':
    # gladeファイルを読み込む
    builder = Gtk.Builder()
    builder.add_from_file("gui.glade")
    window = builder.get_object("window")
    window.connect("delete-event", Gtk.main_quit)

    # CSSを設定(デフォルトのフォントはあまり良くないので)
    css_provider = Gtk.CssProvider()
    css_provider.load_from_path('style.css')
    Gtk.StyleContext.add_provider_for_screen(
        window.get_screen(),
        css_provider,
        Gtk.STYLE_PROVIDER_PRIORITY_USER)
    
    
    entry = builder.get_object("entry")
    entry.connect("key-press-event", entry_key_press)
    entry.connect("preedit-changed", entry_preedit_changed)
    entry.connect("paste-clipboard", entry_paste_clipboard)
    entry.connect("changed", entry_changed)

    button = builder.get_object("button")
    # connectメソッドへ追加の引数を渡すことで、シグナルハンドラへその引数を渡すことができる
    button.connect("clicked", button_clicked, entry)

    window.show_all()
    Gtk.main()

f:id:Lil68060:20180222212045j:plain
f:id:Lil68060:20180222212102j:plain

最後に

GTK+、特にPython(pygobject)から使用する方法については、なかなか資料がありません。
The Python GTK+ 3 Tutorial — Python GTK+ 3 Tutorial 3.4 documentationや、Classes - Gtk 3.0は貴重な資料となります。
みなさんも、使ってみて何か気付いたことがあれば、ぜひどこかで公開してください。

FreeCADでねじを切る

2018/2/20 細かな語句の間違いを修正しました。
水平拘束と水平距離拘束の語句の使い分けなど適切でない部分がありました。申し訳ありませんでした。

f:id:Lil68060:20180219193422j:plain
f:id:Lil68060:20180219194556j:plain
f:id:Lil68060:20180219200240j:plain

なぜこのような記事を書くに至ったか

※この記事はFreeCAD version 0.16 revision 6712(Git) 2017/07/17版を基に書いています。
FreeCADは無償で使える素晴らしい3D CADなのですが、挙動に癖があり、一部の複雑な形状を作成することができなかったり、作成が難しかったりします。
ねじを切るには普通、ねじ山・谷部分の断面をスケッチで作成し、螺旋でスイープしてねじ部を生成し、円筒などと和集合(ブーリアン演算)を取って作成すると思います。
しかし、FreeCADでは螺旋に関して重要なルール(と言うかバグ?)があります。それは

  • 螺旋状にスイープして作成した形状は、他の形状と和集合(ブーリアン演算)を取ってはならない
  • 螺旋状にスイープして作成した形状は、和集合により合成した形状とブーリアン演算してはならない

というものです。
これを守らないとFreeCADが計算に失敗し、不正な形状が生成されてしまいます。
このため、

  • おねじを切るためには、おねじの山部分も含めた円筒を作成し、螺旋状にスイープした形状で減算(ブーリアン演算)しなければならない
  • めねじを切るためには、めねじの谷部分を残して、減算により円筒を取り除いた形状から、螺旋状にスイープした形状で減算しなければならない
  • ねじを含む形状は、すべて減算によって作成しなければならない。つまり、最初に取り得る最大の体積の立方体や円筒、スイープ形状などを作成し、そこから不要部分を切除して望みの形状を作り出さないといけない
  • ねじ断面に対するブーリアン演算は、出来るだけ単純な形状の内に行っておく

という制限があります。
また、段差などのある形状(ここではソース形状と呼びます)に対し、螺旋状にスイープした形状(ここではターゲット形状と呼びます)を減算する場合、以下の制限を満たす必要があります。

  • ソース形状の面とターゲット形状の面の内、ぴったり重なり合う面が存在してはならない

数値を指定して形状を作成する時、きっちり寸法通りに作成されることを意図した結果、上記のような状態になることはよくありますが、この状況になるとFreeCADは形状の計算に失敗します。
FreeCADの公式Wiki
Thread for Screw Tutorial - FreeCAD Documentation
にもある通り(Method 3のRule 2)、
”Remember that helix in FreeCAD is an imprecise thing.”
FreeCADでは螺旋は厳密な円形をしているという想定をしてはいけないことになります。
このため、円筒面などと螺旋面をぴったり接触させた場合、見た目にはぴったりと接しているように見えても、計算上はそうでないということが有り得ます。
また、コンピュータの数値計算上の細かい誤差のために、タイトな位置関係のオブジェクトが干渉して、正常な計算結果が得られないことも有り得ます。

この対策としては、

  • ちゃんと減算で削りきるために、外形はターゲット形状の方が外側にはみ出すように作る
  • 面が接触しないように、削り込む部分ではターゲット形状をわずかに大きく・または小さくする

ことが有効です。
寸法を厳密に指定しないのは気持ち悪く感じるかもしれませんが、ワークの寸法精度から無視できる程度に小さな差を付ける(例えば0.001mmなど)だけで十分です。

例1 寸切りを作成する

M6、長さ10mmの寸切り(おねじ)を作成してみることにします。
作業の流れとしては、

  1. Sketchモードでおねじのねじを切るための断面形状を作成する
  2. Partモードでおねじの軌道を螺旋で作成し、スイープユーティリティで切断部を作成する
  3. 円筒を作成し、切断部を減算(ブーリアン演算)しておねじを完成させる

ということになります。

f:id:Lil68060:20180219192625j:plain

ワークベンチを「Sketcher」に切り替えます。

f:id:Lil68060:20180219192634j:plain

新規スケッチを作成します。

f:id:Lil68060:20180219192643j:plain

XZ平面に対し描画します。

f:id:Lil68060:20180219192655j:plain

円弧を1つ、線分を3つ、適当に作成します(寸法は後で拘束によって決定します)。

f:id:Lil68060:20180219192705j:plain

右クリックでツールをキャンセルした後、円弧の端点と線分の端点を1つずつ選択します。

f:id:Lil68060:20180219192714j:plain

一致拘束を適用します。

f:id:Lil68060:20180219192722j:plain

何もない所で1度左クリックし、前述の2点の選択を解除します。
その後、ねじを切るための形状になるように一致拘束を繰り返します。

f:id:Lil68060:20180219192730j:plain

選択を解除します。
次に、原点からの相対位置を指定します。
円弧の中心を選択し、ロック拘束を行います。

f:id:Lil68060:20180219192741j:plain

左側の「Constraints」に「Constraint5」と「Constraint6」が追加されるはずです。
これらを編集します。
まず先に「Constraint5」をダブルクリックし、

f:id:Lil68060:20180219192752j:plain

「長さ」に2.495mmと入力します。

f:id:Lil68060:20180219192800j:plain

「Constraint6」も同様にダブルクリックし、「長さ」に-0.375mmを入力します。

f:id:Lil68060:20180219192809j:plain

円弧を選択し、「円または円弧の半径を固定」を行います。

f:id:Lil68060:20180219192817j:plain

半径を0.072mmに設定します。

f:id:Lil68060:20180219192826j:plain

円弧がかなり小さくなったので、マウスホイールで拡大しつつ、各点をドラッグして形を整えます。

f:id:Lil68060:20180219192835j:plain
f:id:Lil68060:20180219192843j:plain

寸法線は、文字の部分を選択して(緑色になります)ドラッグすれば、好きな位置に持ってくることができます。
上下の2つの線分を選択し、角度を拘束します。

f:id:Lil68060:20180219192852j:plain

JISねじの山の角度は60度です。

f:id:Lil68060:20180219192901j:plain

角度が拘束されました。

f:id:Lil68060:20180219192912j:plain

上下の線分と円弧の接続を滑らかにします。

円弧と上側の線分を選択し、「2つのエンティティ間に正接拘束を作成」をクリックします。

f:id:Lil68060:20180219192920j:plain

滑らかにつながりました。

f:id:Lil68060:20180219192927j:plain

円弧と下側の線に対しても、同様に垂直拘束します。

f:id:Lil68060:20180219192944j:plain
f:id:Lil68060:20180219192952j:plain

右側の縦の線分に対して拘束をかけます。
まずは線分に対し垂直拘束をかけます。
線分を選択し、「選択されているアイテムに対し垂直拘束を作成する」をクリックします。

f:id:Lil68060:20180219193003j:plain
f:id:Lil68060:20180219193011j:plain

円弧の中心と縦線の上側の点を選択し、水平距離を拘束します。

f:id:Lil68060:20180219193022j:plain

正確には0.541mmになるのですが、先述の理由により0.542mmと、少し大きめの値にします。

f:id:Lil68060:20180219193031j:plain

断面形状が完成しました。

f:id:Lil68060:20180219193042j:plain

ワークベンチを「Part」に切り替えます。
Sketcherの編集モードが残っているので、左側の「タスク」の「Close」を押し、スケッチを終了します。

f:id:Lil68060:20180219193053j:plain

パラメトリックな幾何プリミティブを作成」をクリックし、幾何プリミティブビューから「螺旋」を選択します。
その後、下にある「場所」をクリックし、ビューを可視化します。

f:id:Lil68060:20180219193103j:plain

「場所」ビューの「z」に-0.375mmを設定、「幾何プリミティブ」ビューの「ピッチ」を1mm、「高さ」を11mm、「半径」を2.495mmに設定します。
設定後「作成」をクリックします。

f:id:Lil68060:20180219193114j:plain

「作成」をクリックします。
先ほど作成した断面の内、円弧の中心を通る螺旋が作成されます。

f:id:Lil68060:20180219193129j:plain

ねじ山のカットする部分を作成します。
「幾何プリミティブ」をCloseし、「スイープユーティリティ」をクリックします。

f:id:Lil68060:20180219193141j:plain

「スイープ」ビューの左側、「Sketch」をクリックして選択し、「→」で右側へ移動します。
また、下の「ソリッド作成」と「フレネ」をチェックします。

f:id:Lil68060:20180219193154j:plain
f:id:Lil68060:20180219193204j:plain

「スイープ経路」をクリックします。

f:id:Lil68060:20180219193218j:plain

スイープする経路を選択するモードになったので、「モデル」タブをクリックして選択し、「ラベルと属性」内の「螺旋」を選択します。

f:id:Lil68060:20180219193231j:plain
f:id:Lil68060:20180219193241j:plain

「タスク」タブをクリックし、「終了」をクリックします。

f:id:Lil68060:20180219193251j:plain

「OK」をクリックします。

f:id:Lil68060:20180219193301j:plain

切断用の形状ができました。

f:id:Lil68060:20180219193312j:plain

次に、円柱を作成します。
パラメトリックな幾何オブジェクトを作成」→「円柱」を選択し、「場所」ビューの「z」を0mm、「幾何プリミティブ」ビューの「半径」を3mm、「高さ」を10mmに設定します。

f:id:Lil68060:20180219193323j:plain

「作成」をクリックします。
円柱が作成されます。

f:id:Lil68060:20180219193340j:plain

「Close」をクリックして、「パラメトリックな幾何プリミティブを作成」モードを終了します。

f:id:Lil68060:20180219193350j:plain

「モデル」ビューの「ラベルと属性」内を、「円柱」→「Sweep」の順に、2つを選択します(CTRLキーを忘れずに)。
「2つの図形から減算結果を作成」をクリックします。

f:id:Lil68060:20180219193402j:plain

ねじが切られました。

f:id:Lil68060:20180219193411j:plain

ちゃんと中身が詰まっています。

f:id:Lil68060:20180219193422j:plain

例2 高ナットを作成する

M6、長さ10mmの高ナットを作成してみます。

まずは、ねじの断面をスケッチします。
ワークベンチを「Sketcher」にし、xz断面で作成します。

f:id:Lil68060:20180219193503j:plain

「Part」ワークベンチに変更し、螺旋を作成します。

f:id:Lil68060:20180219193516j:plain
f:id:Lil68060:20180219193526j:plain

「スイープユーティリティ」で、ねじの断面を作成します。

f:id:Lil68060:20180219193541j:plain
f:id:Lil68060:20180219193549j:plain

円柱を作成します。これが高ナットの本体になります。
半径は5.774mm、高さは10mm、zは0mmとなります。

f:id:Lil68060:20180219193601j:plain
f:id:Lil68060:20180219193609j:plain

上下端を面取りします。
円柱の上面の端となる円を選択し、「選択した図形のエッジをフィレットします」をクリックします。

f:id:Lil68060:20180219193626j:plain

「エッジをフィレット化」が出てくるので、「半径」を0.774mmに設定し、「OK」をクリックします。

f:id:Lil68060:20180219193639j:plain

R面取りされました。

f:id:Lil68060:20180219193651j:plain

下端も同様に処理します。

f:id:Lil68060:20180219193700j:plain

「Sketcher」ワークベンチへ変更し、xy面で新規スケッチを作成します。

f:id:Lil68060:20180219193711j:plain

これまでに作成した形状が、見た目の邪魔になるので一旦不可視にします。
「モデル」ビューで「Sweep」を右クリックし、コンテキストメニューの「表示切り替え」を選択します。
同様に、「Fillet001」も不可視にします。

f:id:Lil68060:20180219193725j:plain

六角形を構成できるよう、6本線分を引きます。

f:id:Lil68060:20180219193734j:plain

「一致拘束」で線をつなぎます。

f:id:Lil68060:20180219193749j:plain

一番上の点に対し、ロック拘束をかけます。
縦の長さ( I のような形のConstraint)を5.774mm、横の長さ( H のような形のConstraint)を0mmに設定します。

f:id:Lil68060:20180219194105j:plain

縦線に対して、垂直拘束をかけます。
まず、左側の線を選択し、「選択されているアイテムに対し垂直拘束を作成する」をクリックします。

f:id:Lil68060:20180219194120j:plain

線が垂直になり、横に赤色の|マークが出ます。
右側の縦線も同様に垂直拘束をかけます(上の画像ではすでにかかっています)。
最初に大まかに線を引いた時に、偶然垂直になったときにすでに垂直拘束がかかっていることがあります。
線の横のマークで確認してください。

一番下の点に対し、ロック拘束をかけます。
縦の長さを-5.774mm、横の長さを0mmに設定します。

f:id:Lil68060:20180219194135j:plain

左上の点に対し、水平距離を拘束します。

f:id:Lil68060:20180219194145j:plain

長さは-5mmです。

f:id:Lil68060:20180219194201j:plain

右上の点も同様に、水平距離を拘束します。長さは5mmです。

f:id:Lil68060:20180219194212j:plain

上下の辺の角度を拘束します。
適当に正六角形に近くなるように点を移動して、上側の2辺を選択し、角度を120度に拘束します。

f:id:Lil68060:20180219194225j:plain

左上の辺と左の辺も120度に拘束します。

f:id:Lil68060:20180219194235j:plain

同様に、下の2辺、右下と右の2辺も120度に拘束します。

f:id:Lil68060:20180219194246j:plain

スケッチはできました。
「Part」ワークベンチへ移動します。
パラメトリックな幾何プリミティブを作成」で直線を作成します。
始点はx:0mm、y:0mm、z:0mm、終点はx:0mm、y:0mm、z:10mmです。

f:id:Lil68060:20180219194255j:plain

スイープユーティリティで六角柱を作成します。
「Sketch1」を選択し、「ソリッド」と「フレネ」を選択、「スイープ経路」に「直線」を指定します。

f:id:Lil68060:20180219194309j:plain
f:id:Lil68060:20180219194319j:plain

さらに、円柱を作成します。
半径は8mm、高さ10mmで作成します。

f:id:Lil68060:20180219194329j:plain

「円柱001」→「Sweep001」の順に選択し、減算します。

f:id:Lil68060:20180219194340j:plain
f:id:Lil68060:20180219194350j:plain

コンテキストメニューの「表示切り替え」で、「Fillet001」を表示します。

f:id:Lil68060:20180219194402j:plain

「Fillet001」→「Cut」の順に選択し、減算します。

f:id:Lil68060:20180219194413j:plain
f:id:Lil68060:20180219194421j:plain

さらに、中心を抜くために円柱を作成します。
半径は2.459mm、高さ10mmで作成します。

f:id:Lil68060:20180219194438j:plain
f:id:Lil68060:20180219194447j:plain

「Cut001」→「円柱002」の順に選択し、減算します。

f:id:Lil68060:20180219194500j:plain
f:id:Lil68060:20180219194508j:plain

コンテキストメニューの「表示切り替え」で、「Sweep」を表示します。

f:id:Lil68060:20180219194528j:plain

「Cut002」→「Sweep」の順に選択し、減算します。

f:id:Lil68060:20180219194542j:plain

完成です。

f:id:Lil68060:20180219194556j:plain

例3 六角ボルトを作成する

M6、長さ10mmの六角ボルトを作成してみます。
これまで簡単のため端折っていた部分を説明し、より効率的な方法で作成してみます。

始めに、ねじの断面を計算して作成します。
ねじの寸法やパラメータに関しては広く公開されています。
ここでは、https://www.iwata-fa.jp/html/technicaldata/tec_other_09.pdf
http://www.ikekin.co.jp/contents/catalog/standard/bolt/を参照しました。

まず、ワークベンチを「Sketcher」に切り替えます。

f:id:Lil68060:20180219194611j:plain

XZ平面にスケッチを作成します。

f:id:Lil68060:20180219194624j:plain

ねじの外形は6mm、即ち半径は3mmなので、原点から3mmの位置に縦線を引きます。
Z軸より右側に引いてください。
適当に垂直線っぽく引くと、垂直拘束が付いてきます(線の近くに赤い|が表示される)。
付いてこなかった場合は、線を選択して垂直拘束してください。

f:id:Lil68060:20180219194641j:plain

線の上端の点と原点を選択し、水平距離を拘束します。
長さは先述の通り3mmになります。

画面をマウスホイールで拡大します。

f:id:Lil68060:20180219194654j:plain

ねじの断面の最外部の線を引きます。
資料より、外形からの距離はH/8、H=0.866025なので、外形の線より右側に垂直線を引きます。

f:id:Lil68060:20180219194708j:plain

水平距離を0.866025/4 = 0.21650625mmに拘束します。
画面を拡大します。

f:id:Lil68060:20180219194721j:plain

ねじの最内部の線を引きます。最外部からの距離は、H=0.866025mmです。
縦線を左側に引いて、水平距離を0.866025mmに拘束します。

f:id:Lil68060:20180219194733j:plain

ねじの断面の外形線となる斜線を2本引きます。
左側の端点は、一致拘束をかけます。

f:id:Lil68060:20180219194747j:plain

最内部の線と斜線の左端を拘束します。
最内線の端点と、斜線の左端の点を選択し、水平距離を0mmに拘束します。

f:id:Lil68060:20180219194800j:plain

斜線の角度を拘束します。JISねじではねじ山の角度は60度です。
斜線をそれぞれ選択し、角度拘束で60度に設定します。

f:id:Lil68060:20180219194814j:plain

最内部の線と斜線の角度を拘束します。
それぞれの線を選択し、角度拘束で60度に設定します。

f:id:Lil68060:20180219194823j:plain

ねじ山内側のR面部分を作成します。
まず、R面の始まる位置を通る縦線を引きます。

f:id:Lil68060:20180219194838j:plain

ねじの外形からの距離H1=0.541266P、Pはねじのピッチで、M6では1mmですから、外形からの水平距離を0.541266mmに拘束します。

f:id:Lil68060:20180219194850j:plain

円弧ツールで近い位置に円弧を描きます。

f:id:Lil68060:20180219194900j:plain

円弧の上側の端点と、上側の斜線を正接拘束します。

f:id:Lil68060:20180219194913j:plain

円弧の下側の端点と、下側の斜線も正接拘束します。

f:id:Lil68060:20180219194925j:plain

円弧のどちらかの端点と、R面の始点を示す縦線(の両端のどちらかの点)の水平距離を拘束します。
「長さ」は0mmです。

f:id:Lil68060:20180219194940j:plain

円弧の中心と原点の垂直距離を拘束します。「長さ」は0mmです。

f:id:Lil68060:20180219194950j:plain

ねじを切り取る面の外側の線に余裕を持たせます。
3mmに設定されている水平距離拘束の、「3mm」の文字の部分をダブルクリックします。

f:id:Lil68060:20180219195004j:plain

3.001mmに設定し直します。

f:id:Lil68060:20180219195019j:plain

斜線とねじの外形線との間に水平距離拘束を設定します。
「長さ」は0mmです。

f:id:Lil68060:20180219195032j:plain
f:id:Lil68060:20180219195046j:plain
f:id:Lil68060:20180219195058j:plain

全ての線分を補助モードにします。
補助モードになった図形は、寸法等の参照用になり、描画対象にならなくなります。
まず、線分を全て選択します。
そして、「ツールバーまたは選択したジオメトリを補助モードと切り替えます」をクリックします。

f:id:Lil68060:20180219195111j:plain

線の色が変わり、補助線になります。

f:id:Lil68060:20180219195123j:plain

線分を新たに3本引きます。

f:id:Lil68060:20180219195138j:plain

ねじを切り取る面の各点とロック拘束します。

f:id:Lil68060:20180219195152j:plain

補助線と位置がかぶっているために表示されませんが、タスクをクローズすると、閉じた図形になっていることが確認できます。

f:id:Lil68060:20180219195351j:plain

スケッチモードでは、拘束に使用した図形は極力削除せず、補助モードに切り替えた方が無難です。
図形を削除すると意図しない拘束の解除が行われ、閉じた図形にはならなくなる場合があります。
こうなると、後でスケッチを使用してソリッドを生成したときに不正な形状になったり、一見正常に見えても後でブーリアン演算を行ったときに不具合を起こす可能性があります。
要注意です。

原点と円弧中心を選択し、「ツールバーまたは選択した拘束を参照モードと切り替える」をクリックします。

f:id:Lil68060:20180219195410j:plain

いくつかの拘束アイコンが青く変わり、参照モードになります。
この状態で水平距離拘束をかけます。

f:id:Lil68060:20180219195422j:plain

出てきた値、2.78449mmは、後で螺旋を生成する際の半径として使用するためのものです。
どこかにメモしておいてください。

適当な点を選択してもう一度参照モードと切り替え、元の状態に戻します
(そうしないと後で他のスケッチを行う時困ります)。

これで、1つ目のスケッチは終わりです。

f:id:Lil68060:20180219195434j:plain

次に、タスクをCloseし、「Part」ワークベンチに切り替えます。

f:id:Lil68060:20180219195447j:plain

さっき作成したスケッチに名前を付けておきます。
「モデル」タブを選択し、「Sketch1」を右クリック→コンテキストメニューから「名前の変更」です。
「ねじ切り面」としておきます。

パラメトリックな幾何プリミティブを作成」→「螺旋」、
ピッチ:1mm、高さ:16mm、半径:2.78449mm(さっき出した値です)、z:0mm→「作成」

f:id:Lil68060:20180219195459j:plain

タスクをCloseして、「スイープユーティリティ」。
「スイープ」に「ねじ切り面」、「ソリッド」と「フレネ」にチェック。
スイープ経路に「螺旋」を選択します。
後は「OK」。

f:id:Lil68060:20180219195513j:plain
f:id:Lil68060:20180219195523j:plain

この時、生成された形状をよく確認します。
断面などが生成されていない場合は、スケッチに戻り、閉じた図形になっているか再確認し、問題を修正する必要があります。
下図の丸で囲った部分などが透けている場合は、不正な形状が生成されています(この図は正常な場合です)。

f:id:Lil68060:20180219195538j:plain

次に、不要部分を除去します(ボルトの頭を削ってしまうので)。
パラメトリックな幾何プリミティブを作成」→「円柱」
半径:4mm、高さ:6.001mm、z:-2mmで作成します。
高さが半端なのは、切断面がボルトの頭の面と同一面にならないようにするためです。

f:id:Lil68060:20180219195548j:plain

Closeして、「Sweep」→「円柱」の順に選択し、減算します。

f:id:Lil68060:20180219195600j:plain

名前を「Cut」から、「ねじ除去部」へ変更しておきます。

f:id:Lil68060:20180219195613j:plain

次は、ボルトの頭の平面形状を作成します。
「Sketcher」ワークベンチへ移り、新規スケッチを作成、XY平面で作成します。

f:id:Lil68060:20180219195623j:plain

原点を中心に円を作成します。
円周を選択し、半径を5mmに拘束します。
M6六角ボルトは10mmのスパナが入るので、その半分の5mmです。
・・・が、しかし、ブーリアン演算で減算する際も、あまり面やエッジを共有すると動作がおかしくなる場合があるので、ここでは半径を4.999mmとします。

f:id:Lil68060:20180219195636j:plain

円を原点とロック拘束します。

f:id:Lil68060:20180219195647j:plain

上下に平行な線分を引きます。必要に応じて水平拘束してください。

f:id:Lil68060:20180219195701j:plain

円と正接拘束します。

f:id:Lil68060:20180219195712j:plain

六角形になるように、線分を4本追加します。
追加した後は、端点をロック拘束し結合します。

f:id:Lil68060:20180219195724j:plain

4本とも円と正接拘束します。
隣り合う線分を120度の角度拘束にかけます。

f:id:Lil68060:20180219195738j:plain

頭の形状が出来ました。
実は「スケッチャーで正多角形を作成」を使うとすぐに作成できるのですが、ボルトの頭のR面取りの寸法を取りたいため、手作業で作成しました。

さらに、円を作成します。半径は7mm、原点とロック拘束します。

f:id:Lil68060:20180219195755j:plain

円の右側に垂直な線分を書き、内側の円と正接拘束します。

f:id:Lil68060:20180219195806j:plain

垂直線の端点を選択し、「ツールバーまたは選択した拘束を参照モードに切り替える」で、参照モードにします。

f:id:Lil68060:20180219195819j:plain

垂直線の端点に加え、六角形の右端の点を選択して水平距離拘束をかけます。
そして、六角形の右端の点と原点を選択して水平距離拘束をかけます。

f:id:Lil68060:20180219195831j:plain

0.773348mm、これがR面の半径です。
そして、5.77235mm、これが、ボルトの頭の半径です。
R面取りが不要な場合は、これらの数値は不要です。

参照モードを終了し、垂直線と内側の円を選択し、補助線にします。

f:id:Lil68060:20180219195846j:plain
f:id:Lil68060:20180219195858j:plain

スケッチを終了し、名前を「ボルト頭」に変更しておきます。

f:id:Lil68060:20180219195911j:plain

次に、ねじ本体の断面をスケッチします。新規作成でXZ平面上にスケッチを作成します。
これまで作成した形状は邪魔になるので不可視にしておきます。

f:id:Lil68060:20180219195926j:plain

断面形状になるように線分を配置します。

f:id:Lil68060:20180219195937j:plain

垂直拘束と水平拘束、垂直距離拘束と水平距離拘束が図の通りになるようにしてください。
図のように、Z軸を通る縦線の左上に円弧形の拘束アイコンが出た場合は、Deleteで削除してください(後で冗長な拘束と怒られます)。

原点からの距離を拘束します。下図を参考にしてください。
さっき求めたボルトの頭の半径を使用しています。

f:id:Lil68060:20180219195950j:plain

角度拘束と垂直距離拘束が付いている部分は、ねじ部の端面のC面処理のためです。
0.501mmはM6ねじのピッチ1mmの半分(例によってねじ切除部との干渉防止で0.001mmずらしています)、角度は断面に対し30度です。

それぞれの線分を一致拘束します。

f:id:Lil68060:20180219200002j:plain

断面が出来ました。
ワークベンチを「Part」へ変更し、タスクをCloseします。
スケッチ名を「ねじ断面」に変更します。
ねじ本体の作成には、「選択した図形を回転」を使用します。

f:id:Lil68060:20180219200016j:plain
f:id:Lil68060:20180219200032j:plain

「シェイプ」を「ねじ断面」、「軸」を「Z」、「ソリッド」にチェックを入れます。
「OK」で作成されます。

f:id:Lil68060:20180219200042j:plain

ボルトの頭のR面取りをします。
ボルトの頭頂部の円を選択、「選択した図形のエッジをフィレットします」をクリック。

f:id:Lil68060:20180219200055j:plain

「半径」はさっき求めた0.773348mmにします。「OK」をクリックします。

f:id:Lil68060:20180219200112j:plain

R面取りができました。

f:id:Lil68060:20180219200126j:plain

ねじを切ります。
「ねじ除去部」を表示し、「Revolve」→「ねじ切除部」の順に選択し、減算します。

f:id:Lil68060:20180219200139j:plain
f:id:Lil68060:20180219200151j:plain

ボルトの頭を成型します。
「ボルト頭」を表示、「選択したスケッチを押し出し」をクリックします。

f:id:Lil68060:20180219200204j:plain

Z軸に沿って押し出すため、X:0、Y:0、Z:1、「長さ」=4mm、「ソリッド作成」にチェック、「シェイプ」は「ボルト頭」を選択し、「OK」をクリックします。

f:id:Lil68060:20180219200214j:plain

「Cut001」→「Extrude」の順にクリックし、減算します。

f:id:Lil68060:20180219200228j:plain

完成です。

f:id:Lil68060:20180219200240j:plain
f:id:Lil68060:20180219200249j:plain

終わりに

ねじを切る時のコツとしては、螺旋を対象の円柱より上下1周(1ピッチ)ずつ程度長く作成することです。
ボルトなど、切れすぎては困るときは、作成したSweepからはみ出た分を円柱などで減算することで、必要なサイズの螺旋を作るようにします。
また、このときに、減算して出来る面が他の面と同一平面上に来ないようにすることが非常に重要です(0.001mmか、0.00001mm程度ずらしてやるようにしましょう)。

とにかく、

  • ねじを含む形状を作成する時は、『ブーリアン演算は減算以外使わない』
  • ねじに関する面が、他の面と同一平面上に来ないようにする

を守ることが重要です。

Python3 + GTK+3をテストしてみる

概要

ちょっとしたGUIアプリケーションを作りたいと思い、MinGW上のPython3にpygobjectとGTK+3をインストールし、試しにコードを書いてみました。
GUIの部品配置はgladeで行い、CSSを適用しました。

ちなみに、MinGW上では、

pacman -S mingw-w64-x86_64-gtk3
pacman -S mingw-w64-x86_64-python3-gobject
pacman -S mingw-w64-x86_64-glade

にて必要なパッケージをインストールし、gladeはMSYS2のシェル上から起動します。

作ったものについて

とりあえず、CSSの適用と、ウィジェットの状態変化に応じて適用するCSSの変更を試してみたかったので、以下のようなものにしてみました。

  • テキストフィールド(Gtk.Entry)を3つ生成
  • 各テキストフィールドに対し、選択状態と非選択状態で異なるCSSを適用する

ソースコード

の3つです。

 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
 
 def entry_focus_in(entry, event):
     stylecontext = entry.get_style_context()
     stylecontext.add_class("borderentry")
     stylecontext.remove_class("noborderentry")
 
 def entry_focus_out(entry, event):
     stylecontext = entry.get_style_context()
     stylecontext.add_class("noborderentry")
     stylecontext.remove_class("borderentry")
 
 if __name__ == '__main__':
     builder = Gtk.Builder()
     builder.add_from_file("python_glade_test.glade")
     window = builder.get_object("mainframe")
     css_provider = Gtk.CssProvider()
     css_provider.load_from_path('style.css')
     Gtk.StyleContext.add_provider_for_screen(
         window.get_screen(),
         css_provider,
         Gtk.STYLE_PROVIDER_PRIORITY_USER)
 
     entry1 = builder.get_object("entry1")
     entry1.connect("focus-in-event", entry_focus_in)
     entry1.connect("focus-out-event", entry_focus_out)
     entry2 = builder.get_object("entry2")
     entry2.connect("focus-in-event", entry_focus_in)
     entry2.connect("focus-out-event", entry_focus_out)
     entry3 = builder.get_object("entry3")
     entry3.connect("focus-in-event", entry_focus_in)
     entry3.connect("focus-out-event", entry_focus_out)
     window.show_all()
     Gtk.main()
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Generated with glade 3.20.2 -->
 <interface>
   <requires lib="gtk+" version="3.20"/>
   <object class="GtkWindow" id="mainframe">
     <property name="can_focus">False</property>
     <child>
       <object class="GtkBox">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="GtkEntry" id="entry1">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <style>
               <class name="borderentry"/>
             </style>
           </object>
           <packing>
             <property name="expand">False</property>
             <property name="fill">True</property>
             <property name="position">0</property>
           </packing>
         </child>
         <child>
            <object class="GtkEntry" id="entry2">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <style>
               <class name="noborderentry"/>
             </style>
           </object>
           <packing>
             <property name="expand">False</property>
             <property name="fill">True</property>
             <property name="position">1</property>
           </packing>
         </child>
         <child>
           <object class="GtkEntry" id="entry3">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <style>
               <class name="noborderentry"/>
             </style>
           </object>
           <packing>
             <property name="expand">False</property>
             <property name="fill">True</property>
             <property name="position">2</property>
           </packing>
         </child>
       </object>
     </child>
     <child type="titlebar">
       <placeholder/>
     </child>
   </object>
 </interface>
 .noborderentry {
 	font-family: "Meiryo", "Hiragino Kaku Gothic ProN", "MS PGothic", Osaka, sans-serif;
 }
 
 .borderentry {
 	font-family: "Meiryo", "Hiragino Kaku Gothic ProN", "MS PGothic", Osaka, sans-serif;
 	border-width: 2px;
 	border-color: #FF0000;
 	margin: 2px 2px 2px 2px;
 }

ちなみに、デフォルトでは何かひどいフォントで描画されるため、メイリオなどのフォントを明示的に指定しました。
上記3ファイルを同じフォルダに置き、

python3 python_glade_test.py

で実行します。
下記のような画面になるはずです。

f:id:Lil68060:20180122173922p:plain

ここで、3つのテキストフィールドをマウスで選択すると、選択したテキストフィールドへフォーカスが移ると共に、赤い枠線とマージンが移動することを確認できると思います。

追記

pygobject(pygi)とGTK+3で使用可能なCSSについて調べる際は、以下のサイトが役立つと思われます(正直かなり探し回りました)。

MinGW64(MSYS2)上のPython3にPyOpenClをインストールする

MinGW64(MSYS2)上のPython3にmatplotlibをインストールする - 4クロックで支度しなの続きみたいなものです。
前回の流れのようにサクッとPyOpenClもインストールできるか?・・・と思ったらハマッたので。

前提条件として、OpenClのCライブラリがMinGWから参照できるように設定してあることを想定します。
例えばAMD APP SDKなら、include/CLフォルダとopencl.dll.aがMinGWGCCから参照可能であるようにしてください(わたしは単純にMinGWのincludeとlibフォルダにコピーしました。真似は勧めません)。

先に、PyOpenClが要求するモジュールであるMaKoをインストールしておきます。

$ pacman -S mingw-w64-x86_64-python3-mako

そして、PyOpenCl(pyopencl-2017.2.2.tar.gz)をhttps://pypi.python.org/pypi/pyopenclからダウンロードします。
作業フォルダに展開して移動し、

$ python3 configure.py --python-exe=python3.exe

これでMakefileが生成されるので、make、・・・と行きたい所ですが、2017年12月26日現在のMSYS2環境上(gcc-7.2.0-1、mingw-w64-x86_64-crt-git 5.0.0.5002.34a7c1c0-1、mingw-w64-x86_64-libwinpthread-git 5.0.0.4850.d1662dc7-1)でpyopencl-2017.2.2.tar.gzのファイルをmakeしようとすると、エラーで終了します。
原因はどうやらMinGW64の進歩とPyOpenClの対応の齟齬のようで、C++で書かれたいくつかのクラスが衝突します。
対処法としては、単純に、src/c_wrapper/mingw-std-threadsフォルダ内の

mingw.mutex.h
mingw.thread.h

を適当にリネームし(アンダースコアでも付けてください)、上記2つのファイル名で空のファイルを作成します。
その後は

$ make
$ make install

で問題なくmake・installが完了します(わたしの環境ではしました)。

MinGW64(MSYS2)上のPython3にmatplotlibをインストールする

Windows7 SP1(64bit)上で機械学習をしようと思い、python3.6と周辺のあれこれをインストールしようと思った次第。
せっかくなのでMinGW64環境下で使おうと思ったが、pacmanやらpipやら色々迷ったので、ここに最も簡単な手順を記しておくことにします。
条件は、

  • MSYS2をインストールしてあること

これは適当にググッて下さい。

$ pacman -S mingw-w64-x86_64-toolchain

しておくと、色々楽でしょう(python-2.7がインストールされてアレなのですが)。

後は、MSYS2のシェルを起動して、

$ pacman -S mingw-w64-x86_64-python3
$ pacman -S mingw-w64-x86_64-python3-matplotlib

これだけです。
NumPyやらPyQt5やらは勝手に入ってくれます(6GB以上ストレージを消費するので注意)。
重要なポイントは、
pythonのモジュールのインストールにpipを使わない事です。
必要なものはpacmanからインストール可能です。
pipで入れるとpacmanのモジュール管理と衝突して面倒な事になるので、くれぐれもご注意を。

Debianで入力メソッドの切り替えキーを変更する方法

前書き

ちょっとArmbianを近々使うかもという事になって、その予行演習にDebianVirtualBox上にインストールしたのですが、Emacsをインストール、~/emacs.d/init.elを編集しようという所で、何か頻繁に日本語入力に切り替わってとても苦痛。
わたしはどうもカッコの後とかのスペースを、シフトキーを押したまま入力してしまうようで、Shift-Spaceで入力メソッドが切り替わってしまうのです。
個人的にこれはつらすぎるので、入力メソッドの切り替えキーの変更方法を探してみました。
※ちなみにこれはわたしがインストール時にGNOMEを追加したせいかもしれません。

具体的な手順

まずメニューの「アクティビティ」→「アプリケーションを表示する」(一番下の白い点が9個ある奴です)でアイコンを表示。
ずらっと並んだ一番下(スクロールしてください)に「入力メソッド」が2つほどあるので、青い半円が2つ並んだ方の奴を選択します(Uim-pref-gtkというようです)。
f:id:Lil68060:20171119234246p:plain
ウィンドウを開いたら、「全体キー設定1」というタブに、「[全体] オン」「[全体] オフ」という項目があるので編集します。
f:id:Lil68060:20171119234259p:plain
以上の操作で、好きなように変更できるはずです。