興味があろうがなかろうが。

なるべく役に立つ、とがった内容を記していきたいと思います。

【FreeCADプログラミング】要素同士の最近距離取得

本日のお題は『要素同士の最近距離取得』です。

ここ最近はかなり特殊なAPIの説明が多くなってきましたが、今回も相当なものです。

1. 環境

  • FreeCAD 0.18

2. 要素同士の最近距離を取得する場面

まさにタイトル通りですが、とある要素同士の最近距離(最短距離)がどの程度か確認する際に使用します。

3. 使用するAPI

Toposhape APIのdistToShapeメソッドを使用します。

4. どうやって使うのか?

以前使用したisSameメソッドと同様、Toposhape APIは継承関係的には、EdgeやFaceの親に当たるもののようなので、『Edge.distToShape(最近距離を測りたい要素)』、『Face.distToShape(最近距離を測りたい要素)』のように書くことで使用することができます。

5. プログラムを書いてみよう

今回は以前使用したGetSelectionExメソッドを使用して2つの要素を選択し、その要素間の最近距離を確認していきます。

# -*- coding: utf-8 -*-

import ptvsd
print("Waiting for debugger attach")
# 5678 is the default attach port in the VS Code debug configurations
ptvsd.enable_attach(address=('localhost', 5678), redirect_output=True)
ptvsd.wait_for_attach()

import FreeCAD
import FreeCADGui

sel = FreeCADGui.Selection.getSelectionEx()

if(len(sel) == 2):
	firstObj = sel[0].SubObjects[0]
	secondObj = sel[1].SubObjects[0]

	result = firstObj.distToShape(secondObj)
	FreeCAD.Console.PrintMessage("①最近距離: " + str(result[0]) + "\n")
	FreeCAD.Console.PrintMessage("②最近距離の計測元位置: " + str(result[1]) + "\n")
	FreeCAD.Console.PrintMessage("③最近距離の計測元のSubShapeとパラメータ: " + str(result[2]) + "\n")

www.interested-or-not.com

6. 実行結果

今回はFreeCADのサンプルデータとして用意されているPartDesignExample.FCStdに少し加工を加えてテストしてみました。

以下のように元々配置されているソリッドの真横に球体を1つ配置しました。

f:id:appli-get:20211209215251p:plain

テストモデルのイメージ

このモデルの『左側の円筒フェース』と『球体のフェース』を両方選択(※)して、マクロを実行します。

※ フェースを複数選択するときはCtrlキーを押しながら選択してください。

マクロの実行結果は以下の通りです。

①最近距離: 219.06293149491046
②最近距離の計測元位置: [(Vector (81.14455690073774, 13.185535227237938, 49.99999999203486), Vector (300.099024931802, 6.292866340840797, 50.000001216277155))]
③最近距離の計測元のSubShapeとパラメータ: [(b'Face', 0, (6.251715785889117, -49.99999999203486), b'Face', 0, (3.1101231279347448, 6.081385769568932e-09))]

②の結果に最近の位置を表す座標が表記されているので、モデル上に点を置いて確認してみましょう。以下のようになりました。

すいません。相当小さくて見づらいですが、円筒面の中心位置と球体の左側に黄色い点があるのが見えますか?

どうやら成功のようです。普通に考えるとここがお互いから見た最近の位置ですね。

そして、この点間の距離が『219.06293149491046mm』となるわけです。

f:id:appli-get:20211209215236p:plain

最近位置として出力された座標に点を置いてみた結果

7. 複数の要素が最近位置になる場合は、結果も複数になる

偶然分かりましたが、APIを実行した際に複数の要素が最近位置になる場合は、②・③の結果が複数になります。

例えば以下のようなモデルを用意します。

f:id:appli-get:20211209221126p:plain

複数の最近位置が取得される例

これで処理を実行すると、以下のような結果が出力されます。

①最近距離: 219.06293149491046
②最近距離の計測元位置: [(Vector (300.0990249318874, 6.292866343553993, 0.0), Vector (81.14455689632862, 13.18553508717689, 0.0)), (Vector (300.09902493361, 6.292866398274115, -5.343411234701294e-07), Vector (81.14455689381991, 13.185535007484166, 1.4070081688653918e-07))]
③最近距離の計測元のSubShapeとパラメータ: [(b'Face', 0, (3.110123127921172, 0.0), b'Edge', 2, 6.251715781510965), (b'Face', 0, (3.110123127647436, -2.671705617350647e-09), b'Face', 0, (6.25171577901986, -1.4070081688653918e-07))]

あえて色を変えましたが、赤色・黄色の2つの結果が出力されているのが分かります。

なぜ2つ出力されたかですが、下記2つの最近距離が同一になるからだと考えています。

  1. 球体フェースと円筒フェースの端部
  2. 球体フェースと円筒フェースの端部のエッジ

8. 3つ目のパラメータは何に使用するか不明

要素同士の最近距離を取得するという命題は解決したわけですが、本APIの3つ目のパラメータ(上の実行結果の③が該当)が何なのか、まだ分かっていません。

公式のAPIリファレンスには以下のように書かれていますが、『list<nearest subshapes & parameters』の部分が現状どうもよく分かりません。

(以下『TopoShape API - FreeCAD Documentation』より引用。

Description: Calculates the minimum distance between this and a given TopoShape.
Returns: float<minimum distance>,list<nearest points>,list<nearest subshapes & parameters>

こちらについては、分かり次第追記します。

おそらく、以前フェースの法線ベクトルを取得する際にUVパラメータを取得しましたが、そのパラメータが入っているのではないかと睨んでいます。ただ、b'Face'の次の0はなんだろう・・・。

www.interested-or-not.com

9. まとめ

  • 要素同士の最近距離を取得するには、distToShapeメソッドを使う。
  • distToShapeは最近距離以外にも、最近の位置と判断された座標も取得できる。
  • distToShapeで出力される最近の位置は複数になることがある。

10. 最後に

いかがでしたか。

最近距離が必要になる場面なんてなかなか想像がつかないかもしれませんが、結構役に立つことがありますので、頭の片隅に入れておいてください。

今度distToShapeを使う演習課題を作ってみたいと思います。お楽しみに。

更なる情報をお探しの方は!

下記のリンクから、FreeCADプログラミングのトップページに飛びます。

よろしければご参照くださいませ。

www.interested-or-not.com

【FreeCADプログラミング】オブジェクトの同一判定

本日のお題は『オブジェクトの比較』です。

CADのマクロ開発を行っていると、ある要素とある要素が同一か判定したくなることがよくあります。

今回は形状要素が同一かどうかを判定する方法を学んでいきたいと思います。

1. 環境

  • FreeCAD 0.18

2. オブジェクトの同一判定を行う場面

以前、選択したエッジと平行な方向を向いているエッジをカウントするプログラムを作りましたが、例えばこういったときに使用します。

上記のプログラムを作成した時は、オブジェクトの同一判定を行う方法を知らなかったため、比較元の自分自身もカウントしてしまう不具合が残っていました。

後日修正したいと思います。

www.interested-or-not.com

3. 使用するAPI

Toposhape APIのisSameメソッドを使用します。

4. どうやって使うのか?

Toposhape APIは継承関係的には、EdgeやFaceの親に当たるもののようなので、『Edge.isSame(比較したい要素)』、『Face.isSame(比較したい要素)』のように書くことで使用することができます。

ちなみに、isSameメソッドは同じ要素であればTrue、違う要素であればFalseを返してくれるようです。

5. プログラムを書いてみよう

今回は選択した2つの要素が同一か確認していきます。

なお実験的に、同じ要素をisSameメソッドの引数に渡したとき、Trueが返ってくるかも確認しています。

プログラムを動作させる際は、エッジもしくはフェースを2つ選択してから起動してください。

# -*- coding: utf-8 -*-

import FreeCAD
import FreeCADGui

sel = FreeCADGui.Selection.getSelectionEx()

if(len(sel) == 1):
	if(len(sel[0].SubObjects) == 2):
		firstObj = sel[0].SubObjects[0]
		secondObj = sel[0].SubObjects[1]

		FreeCAD.Console.PrintMessage("異なるオブジェクトで判定した結果: " + str(firstObj.isSame(secondObj)) + "\n")
		FreeCAD.Console.PrintMessage("同一のオブジェクトで判定した結果: " + str(firstObj.isSame(firstObj)) + "\n")

6. 実行結果

異なるオブジェクトで判定した結果: False
同一のオブジェクトで判定した結果: True

7. まとめ

  • オブジェクトの同一判定をする際は、TopoShape APIのisSameメソッドを使用する

8. 最後に

いかがでしたか。

あまりパッとした内容ではないですが、処理を完全自動化する際には必ずと言っていいほど必要になるAPIです。

こちらのAPIはいつでも使えるようにしておきましょう。

更なる情報をお探しの方は!

下記のリンクから、FreeCADプログラミングのトップページに飛びます。

よろしければご参照くださいませ。

www.interested-or-not.com

【FreeCADプログラミング】ある位置が要素の内側に位置しているか判定

本日のお題は『ある位置が要素の内側に位置しているか判定』です。

えらい頭でっかちなお題ですが、そのまんまの話題です。

1. 環境

  • FreeCAD 0.18

2. 使用する場面

あるとすれば、何かしらの方法で取得された座標位置にソリッドが存在しているかをチェックするときですかね。

個人的には、あまり使用場面は多くない気がします。

3. 使用するAPI

Toposhape APIのisInsideメソッドを使用します。

4. どうやって使うのか?

Toposhape APIは継承関係的には、EdgeやFaceの親に当たるもののようなので、『Edge.isInside()』、『Face.isInside()』のように書くことで使用することができます。

ちなみに、isInsideメソッドは指定した座標がソリッド内部に位置していればTrue、そうでなければFalseを返してくれるようです。

TopoShape API - FreeCAD Documentation

なお、指定する引数が3つ用意されており、それぞれ下記のような意味を持っているようです。

  • 第一引数: 検証する座標位置
  • 第二引数: 許容値
  • 第三引数: 第一引数で指定した座標位置がFace上に位置している場合、内部に位置していると判定するかを決めることができます。Trueを指定すると内部に位置していると判定します。

5. プログラムを書いてみよう

ひとまず、特定の座標位置が選択した要素の内部に位置しているか確認するプログラムを書いてみました。

# -*- coding: utf-8 -*-

import FreeCAD
import FreeCADGui

sel = FreeCADGui.Selection.getSelection()

if(len(sel) != 0):
	#選択した要素の取得
	bodyShape = sel[0].Shape
	#内側に位置されているか確認するための座標
	verifyPoint1 = FreeCAD.Vector(20, 20, 20)		#ボディ内部
	verifyPoint2 = FreeCAD.Vector(-10, -10, -10) 	  #ボディ外部
	verifyPoint3 = FreeCAD.Vector(0, 10, 10)		#フェース上

	#点がボディの内側にあるか判定
	isInside1 = bodyShape.isInside(verifyPoint1, 0.001, True)
	isInside2 = bodyShape.isInside(verifyPoint2, 0.001, True)
	isInside3 = bodyShape.isInside(verifyPoint3, 0.001, True)
	isInside4 = bodyShape.isInside(verifyPoint3, 0.001, False)
	
	FreeCAD.Console.PrintMessage(str(isInside1) + "\n")
	FreeCAD.Console.PrintMessage(str(isInside2) + "\n")
	FreeCAD.Console.PrintMessage(str(isInside3) + "\n")
	FreeCAD.Console.PrintMessage(str(isInside4) + "\n")

※ isInside3, isInside4は第三引数の動作の違いを確認するために、同じ座標を指定しています。

6. 実行結果

今回は高さ,幅,奥行100mmの立方体を選択し、上記のプログラムを実行した結果を見てみます。

ちなみに、立方体の位置は0,0,0から移動していません。

f:id:appli-get:20200306150300p:plain

高さ,幅,奥行100mmの立方体
True
False
True
False

3つ目と4つ目の結果に注目です。

第三引数のオプション設定の効果が出ていますね。

7. まとめ

  • ある位置が要素の内側に位置しているか判定したい場合はTopoShapeのisInsideメソッドを使用する
  • isInsideメソッドの第三引数で指定するパラメータによって、Face上の位置が指定された場合の計算結果が変わる

8. 最後に

この手のチェックは自前のAPIを作るのが困難なため、覚えておいて損は無いと思います。

ひょんなことで役に立つこともありますので、頭の片隅に入れておいてください。

更なる情報をお探しの方は!

下記のリンクから、FreeCADプログラミングのトップページに飛びます。

よろしければご参照くださいませ。

www.interested-or-not.com

【FreeCADプログラミング】選択した要素を取得する ~getSelectionEx編~

本日のお題は『選択した要素を取得する ~getSelectionEx編~』です。

以前『getSelection編』を紹介していましたが、実は選択用のAPIはもう一つあります。

それが『getSelectionEx』です。

実はこのAPIはすでに、他の記事でちょこちょこ使用していたのにお気づきでしょうか。

後出しになりましたが、私自身の整理も兼ねて一応記事にします。

www.interested-or-not.com

1. 環境

  • FreeCAD 0.18

2. getSelectionExを使用する場面

getSelectionExはAPIドキュメントを見る限りでは、選択したEdgeやFaceといった構成要素(SubObject)を取得するときに使用するようです。

Selection API - FreeCAD Documentation

確かに以前使用した『getSelection』メソッドは、Documentオブジェクトのようなツリービューにあるオブジェクトは取れるものの、EdgeやFaceを直接取得できませんでした。

3. どうやって使うのか?

使用方法は以前紹介した『getSelection』と同様です。

要素を選択した状態でマクロを起動すると、本メソッドの戻り値に選択した要素が返ってきます。

4. プログラムを書いてみよう

一応、違いを明確にするため、getSelectionとgetSelectionExで取得された要素のTypeIdを確認してみます。

TypeIdにはそのオブジェクトのクラス名らしきものが表記されるので、何が入っているのかなんとなくわかります。

# -*- coding: utf-8 -*-

import FreeCAD
import FreeCADGui

sel = FreeCADGui.Selection.getSelectionEx()
sel2 = FreeCADGui.Selection.getSelection()

if(len(sel) != 0):
	FreeCAD.Console.PrintMessage("getSelectionEx⇒ " + str(sel[0].TypeId) + "\n")
	FreeCAD.Console.PrintMessage("getSelection⇒ " + str(sel2[0].TypeId) + "\n")

5. 実行結果

今回はFreeCADのサンプルデータとして用意されているPartDesignExample.FCStdを使ってみましょう。

例えば、このデータのとあるフェースを選択してみます。

f:id:appli-get:20210803080652p:plain

フェースを選択した状態

その後、上に記載したプログラムを実行してみると、

以下のようになります。

getSelectionEx⇒ Gui::SelectionObject
getSelection⇒ PartDesign::Pocket

getSelectionExは選択された要素そのもの、getSelectionは選択した要素に関連するツリー上の履歴が取得されているようですね。

6. 要素はどうやって取得する?

選択された要素は、戻り値で取得した配列の各要素内にあるSubObjectsから取得します。

ソースコードは以下のようになります。

# -*- coding: utf-8 -*-

import FreeCAD
import FreeCADGui

sel = FreeCADGui.Selection.getSelectionEx()

if(len(sel) != 0):
	# 選択したフェース要素
	selectedFace = sel[0].SubObjects[0]

7. まとめ

  • FaceやEdgeといった構成要素を取得する際は、getSelectionExを使用する。
  • getSelectionExから選択要素を取得する場合は、SubObjectsを参照する。
  • getSelection, getSelectionExは、欲しい要素によって使い分けが必要。

8. 最後に

いかがでしたか。

ちょっとコアな話題でしたが、GUI絡みのアプリケーションを作る際には重要な内容です。

まずは選択要素を取得する際は、getSelectionとgetSelectionExというメソッドがあることだけでも認識しておいて頂ければと思います。

更なる情報をお探しの方は!

下記のリンクから、FreeCADプログラミングのトップページに飛びます。

よろしければご参照くださいませ。

www.interested-or-not.com

【FreeCADプログラミング】UNDOポイントを実装する

 

本日のお題は『UNDOを実装する』です。

前回の記事でUNDO, REDOを実行する方法を紹介しました。

www.interested-or-not.com

今回はUNDOを実行した際に『戻る位置(UNDOポイント)』を、プログラムから定義する方法を紹介したいと思います。

1. 環境

FreeCAD 0.18

2. 使用するAPI

下記2つのメソッドを使用します。

  1. App.ActiveDocument.openTransaction("文字列")
  2. App.ActiveDocument.commitTransaction()

1つ目のメソッドでUNDOポイントの定義を開始し、その後、2つ目のメソッドを実行することでUNDOポイントを作成(確定)します。

3. 何のために使うのか

自作のプログラムを作っている方であれば、お気づきかもしれませんが、プログラムを実行した後、実行前の状態に戻す操作ができず、モデルを開き直したりしていませんか。

例えば下図のような時系列を想像してください。

f:id:appli-get:20210811103601p:plain

自作プログラムにUNDOポイントを定義しなかった場合の時系列

 

この場合、図のUNDOポイント③の地点からUNDOを実行した場合、②の地点まで戻ってしまいます。

普通に考えれば、点線で書かれている自作プログラムを実行した地点まで戻りたいと思いますよね?

f:id:appli-get:20210811103902p:plain

UNDOポイント③からUNDOを実行した場合の時系列

上記より、UNDOを的確な場所に配置するのはマクロプログラム開発において、とても重要なことなのです。

4. プログラムを書いてみよう

どうせなので今回は、以前紹介した『ベクトル確認用矢印ソリッド』のプログラムに対して、UNDOポイントを作成する処理を追加してみましょう。 

www.interested-or-not.com 

# -*- coding: utf-8 -*-

import FreeCAD
import FreeCADGui
import Part
import JoinFeatures
import BOPTools.JoinFeatures
import math
import sys

import ptvsd
print("Waiting for debugger attach")
# 5678 is the default attach port in the VS Code debug configurations
ptvsd.enable_attach(address=('localhost', 5678), redirect_output=True)
ptvsd.wait_for_attach()

# ビュー上の選択されたオブジェクトを取得
# (フェースを選択を前提にしている。他のタイプが選択されたときの例外処理は入れていない)
sel = FreeCADGui.Selection.getSelectionEx()

if(len(sel) != 0):
	# UNDOポイントの定義開始
	App.ActiveDocument.openTransaction("CreateVectorSolid")

	# ピック位置と、ピック位置の法線ベクトルを取得
	pickCoord = sel[0].PickedPoints[0]
	selectedFace = sel[0].SubObjects[0]
	faceSurface = selectedFace.Surface
	uvCoord = faceSurface.parameter(pickCoord)
	normalVec = selectedFace.normalAt(uvCoord[0], uvCoord[1])
	print(str(normalVec))
	
	# 円錐の作成(デフォルトはheightが10mmになる)
	App.ActiveDocument.addObject("Part::Cone","Cone")
	App.ActiveDocument.ActiveObject.Label = "Cone"
	App.ActiveDocument.recompute()
	
	cone = App.ActiveDocument.getObject("Cone")
	App.ActiveDocument.getObject("Cone").Radius1 = '0 mm'
	App.ActiveDocument.getObject("Cone").Placement = App.Placement(App.Vector(0,0,0),App.Rotation(App.Vector(0,1,0),180))
	
	# 円柱の作成(デフォルトはheightが10mmになる)
	App.ActiveDocument.addObject("Part::Cylinder","Cylinder")
	App.ActiveDocument.ActiveObject.Label = "Cylinder"
	App.ActiveDocument.recompute()
	
	# 円錐と円柱がスレスレの位置にあると、和が取れない可能性があるので、0.1mm重なるように設定
	cylinder = App.ActiveDocument.getObject("Cylinder")
	App.ActiveDocument.getObject("Cone").Placement = App.Placement(App.Vector(0,0,19.99),App.Rotation(App.Vector(0,1,0),180))
	
	# 円錐と円柱の和を取る
	App.activeDocument().addObject("Part::MultiFuse","Fusion")
	App.activeDocument().Fusion.Shapes = [App.activeDocument().Cylinder,App.activeDocument().Cone,]
	App.ActiveDocument.recompute()
	
	# 絶対座標系
	xAxis = FreeCAD.Vector(1,0,0)
	yAxis = FreeCAD.Vector(0,1,0)
	zAxis = FreeCAD.Vector(0,0,1)
	
	# 法線ベクトルを単位化(おそらくデフォルトで単位化されているはずだが念のため)
	normalVec.normalize()
	
	# Z軸との外積を取得する(Z軸を軸に外積を取るのがミソ)
	rotationAxis = zAxis.cross(normalVec)
	
	# 回転のAPIの入力が度数を欲しているため変換
	angle = zAxis.getAngle(normalVec) * 180 / math.pi
	
	# ソリッドの移動と回転  
	App.activeDocument().getObject("Fusion").Placement = App.Placement(App.Vector(pickCoord.x,pickCoord.y,pickCoord.z),App.Rotation(rotationAxis, angle))

	#UNDOポイントの定義終了
	App.ActiveDocument.commitTransaction()

フェースを選択してから本マクロを実行すると、その選択した地点に矢印ソリッドが作成されます。

その後、キーボードで『Ctrl + Z』もしくは、FreeCADのメニューから『編集 - 元に戻す』を実行してみましょう。マクロの実行前まで戻りましたね。

ちなみに、以前の記事で紹介した矢印ソリッドのプログラムを実行すると、マクロのプログラム実行より一つ前のUNDOポイントまで戻ってしまっていました。

(上の時系列の図でいうと、③から②の地点に戻っていました。)

5. UNDOの多用はできるだけ控える

なにかと便利で強力なUNDOですが、多用するのはオススメしません

その理由は、一般的にUNDOポイントの作成は多くのメモリを消費するためです。

考えてみてください。UNDOポイントを置くということは、現在の状態を事細かに記録しておくことになります。

記録したり、戻すだけで重い処理になることは必然ですよね?

以上の理由から、絶対に必要な時以外はできるだけ使わないようにすることをオススメします。

6. まとめ

  • UNDOを作るときはApp.ActiveDocument.openTransaction()と、

    App.ActiveDocument.commitTransaction()を使用する。

  • UNDOポイントの作成は多くのメモリを消費する可能性が高いことが多いため、必要最小限で利用する。

7. 最後に

UNDOポイントの実装は自作プログラムを作成する際、必ず必要になります。

プログラムのテンプレートを作っている方は、あらかじめコードを組み込んでおいても良いかもしれませんね。

更なる情報をお探しの方は!

下記のリンクから、FreeCADプログラミングのトップページに飛びます。

よろしければご参照くださいませ。

www.interested-or-not.com