AFsoft WebSite(エーエフソフト・ウェブサイト)
 

オペレーティング・システムについて

プログラミングについて
ホームページについて
キャドについて
電子カタログについて
書籍・雑誌
イベント
リンク集
CAD作ろ! 端点を拾う(スナップ)
スナップについては既にざっと記述しましたし、プログラム内でも取りあえず出来るようにしていますが、ここではもう少し詳しく書いていきます。
まず、マウス「近傍」をどれくらいにするか?についてですが、本プログラムでは、スナップを行う関数「TFmMain.MouseSnap(〜)」内で
const
 AREA = 5; // マウス指定箇所からの認識範囲(dot)
と指定しています。この数値を他の関数でも使うのであれば、グローバルな定数として、このユニットの冒頭部や「UnitGVar.pas」で定義しておいても良いですし、この数値を「設定画面」で変更出来るようにさせるのならば、「UnitGVar.pas」で外部変数として定義するのも良いでしょう。
拾いたい点にマウスを近づけてクリックすると、その点を拾うには、その点の座標とマウスの座標が同じであればOK、という事は直ぐに分かると思います。マウスの座標はドット値なのでmmに変換します。
マウスのX座標→「X」(dot)
マウスのY座標→「Y」(dot)
  ↓ dot座標→mm座標へ変換
マウスのX座標:「tx」(mm)
マウスのY座標:「ty」(mm)
mmとドットの座標変換より
tx=WD_x1+X/MM_dot
ty=WD_y2−Y/MM_dot
単に同じ座標かどうか?を判定するには、
// xx:調べたい点のX座標
// yy:調べたい点のY座標
if (tx = xx)and(ty = yy) then begin
 ・・・(座標が同じである時の処理)
end;
となりますが、画面の解像度が細かいと、マウスで小さな点に合わすには至難の業となります。そこで、マウスがその点の近くであればヒットさせるようにします。範囲を持たせる訳です。

点を拾いにくい
 
範囲内にあればいいので拾い易い

認識範囲の定数「AREA」をdotからmmに変換した値を変数「a」に入れます。
a := AREA/MM_dot;
マウス座標が点の範囲内にあるか?の判定は、
xx-a ≦ tx ≦ xx+a
yy-a ≦ ty ≦ yy+a
if文で表すと
if (xx - a <= tx)and(tx <= xx + a)
and(yy - a <= ty)and(ty <= yy + a) then begin
 ・・・(座標が同じである時の処理)
end;
となります。これでも構いませんが、多少、判定式が長いかなという感じがしますので、私は以下のようにしています。
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 ・・・(座標が同じである時の処理)
end;
Abs関数は、絶対値を得る関数です。速度的にどちらが速いのかは細かく調べていませんので不明ですが、見易さを優先しています。
 
 
スナップを行う関数「function TFmMain.MouseSnap(var x,y:double):Boolean;」は、端点を拾えたか拾えなかったか?という情報と、拾えた場合の座標値を返しています。しかしDelphiの関数(function)は、1つの値しか返せません。その場合には、煩雑になるのを覚悟してグローバル変数に入れるか、ここのように引数に「var」を指定し関数内でx,yの値を変更したら関数を呼び出した側へ値を戻せるように、します。
 
まずは、原点です。原点の座標は(0,0)です。つまり、上記の(xx,yy)が(0,0)になりますので、
if (Abs(tx) <= a)and(Abs(ty) <= a) then begin
 (原点を拾いました)
end;
です。
次に、用紙枠4隅の点です。用紙サイズは、横:PA_x、縦:PA_y、で管理しています。中央が(0,0)ですから用紙枠は、
−PA_x/2 〜 PA_x/2 、 −PA_y/2 〜 PA_y/2
の範囲となりますので、その4隅を順次、認識チェックします。
xx := -PA_x/2.0 ;
yy := -PA_y/2.0 ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 ・・・(右下点を拾いました)
end;
xx := PA_x/2.0 ;
yy := -PA_y/2.0 ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 ・・・(左下点を拾いました)
end;
xx := -PA_x/2.0 ;
yy := PA_y/2.0 ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 ・・・(左上点を拾いました)
end;
xx := PA_x/2.0 ;
yy := PA_y/2.0 ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 ・・・(右上点を拾いました)
end;
 
それでは、要素の端点を拾う事を考えてみます。
本プログラムでは現在、
@要素データが無ければ終了
Aレイヤ内要素が無ければ次のレイヤへ
B非Activeのレイヤであれば次のレイヤへ
C全要素のループをして、該当レイヤならば次へ
Dレイヤ内要素のチェック
という事をしていますが、実際のCADソフトでは更に、画面に見えていない要素はチェック除外、という事もしますし、画面内にあるデータでも、画面上では数ドットという小さい状態のものは端点なんて拾えませんので、チェック除外という事をするでしょう。
要素が増えれば増える程、スナップの認識時間が掛かる(認識速度が遅くなる)ので、なるべく速くするための手法をいろいろ考えなければなりません。
 
@線分要素の端点を拾う
 線分要素の開始点(x1,y1)、終了点(x2,y2)、をチェックします。もし中点も拾いたいのであれば、中点((x1+x2)/2,(y1+y2)/2)のチェックも行います。チェックする項目が増えれば増える程、遅くなりますので注意して下さい。上記と同じ要領で、
xx := (開始点X座標) ;
yy := (開始点Y座標) ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 (開始点を拾いました)
end;
xx := (終了点X座標) ;
yy := (終了点Y座標) ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 (終了点を拾いました)
end;
xx := ((開始点X座標)+(終了点X座標))/2 ;
yy := ((開始点Y座標)+(終了点Y座標))/2 ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 (中点を拾いました)
end;
という感じになります。データ定義は以下のようになっていますが、
TDataEntities = record  // データ定義
 typ : Integer ; // 要素の種類(1:直線 2:円 3:円弧 4:点 5:文字)
 lay : Integer ; // レイヤ番号
 col : Integer ; // 色番号
 lin : Integer ; // 線種番号
 wid : double ;  // 線幅
 p1,p2,p3,p4,p5,p6 : Double; // 第1〜第6データ[mm][°]
 font,moji : String ; // フォント名、文字内容
end;
・・・
Dat : array of TDataEntities; // データ
Dat_N : Integer ; // データ量
線分の開始点は(p1,p2)・終了点は(p3,p4)ですので、
xx := Dat[i].p1;
yy := Dat[i].p2;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 (開始点を拾いました)
end;
xx := Dat[i].p3;
yy := Dat[i].p4;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 (終了点を拾いました)
end;
xx := (Dat[i].p1+Dat[i].p3)/2.0 ;
yy := (Dat[i].p2+Dat[i].p4)/2.0 ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 (中点を拾いました)
end;
という具合になりますが、レイヤ尺度(LayTbl[〜].sc)を考慮に入れる必要がありますので、
xx := Dat[i].p1 *LayTbl[k].sc ;
yy := Dat[i].p2 *LayTbl[k].sc ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 (開始点を拾いました)
end;
xx := Dat[i].p3 *LayTbl[k].sc ;
yy := Dat[i].p4 *LayTbl[k].sc ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 (終了点を拾いました)
end;
xx := (Dat[i].p1+Dat[i].p3)/2.0 *LayTbl[k].sc ;
yy := (Dat[i].p2+Dat[i].p4)/2.0 *LayTbl[k].sc ;
if (Abs(tx - xx) <= a)and(Abs(ty - yy) <= a) then begin
 (中点を拾いました)
end;
という具合になります。端点を拾った時は、処理を速く終わらせるようにするため、ループ処理を「break」で抜け出します。
 
A円要素の端点を拾う
 円要素の場合には、1/4点、つまり、0°・90°・180°・270°の点をチェックします。もし1/8点を得たいのであれば、45°・135°・225°・315°の点もチェックします。円のデータは、中心(p1,p2)、半径 p3 ですので、
// 0°
xx := p1+p3;
yy := p2 ;
(チェック文)
// 90°
xx := p1 ;
yy := p2+p3 ;
(チェック文)
// 180°
xx := p1−p3;
yy := p2 ;
(チェック文)
// 270°
xx := p1 ;
yy := p2−p3 ;
(チェック文)
 
のようになります。もし、1/8点も対応するのなら、
45°点:(p1+D,p2+D)  但し、D=p3×√2/2
135°点:(p1−D,p2+D)
225°点:(p1−D,p2−D)
315°点:(p1+D,p2−D)
の分のチェックも行う必要があります。
また、円の中心点を拾いたい場合、1/4点や1/8点以外の、円上の点をクリックした時に拾う、という事にしたい場合には、マウス点・中心点間の距離が半径±認識範囲であればOK、という具合にします。
// マウス点・中心点間の距離
d := Sqrt((xx - p1)*(xx - p1)+(yy - p2)*(yy - p2));
// チェック
if (Abs(d - p3) <= a) then begin
 (中心点を拾いました)
end;
 
B円弧要素の端点を拾う
円弧要素の場合には、開始角度(p4)・終了角度(p5)の指定があり、円弧の開始点・終了点を拾えるようにします。
// 開始点(開始角度の点)
xx := p1+p3×Cos(p4) ;
yy := p2+p3×Sin(p4) ;
(チェック文)
// 終了点(終了角度の点)
xx := p1+p3×Cos(p5) ;
yy := p2+p3×Sin(p5) ;
(チェック文)
 
p4,p5の値は、°単位ですが、Sin関数やCos関数は、rad単位ですので変換する必要があります(〜*Pi/180)。
また、円弧の場合にも、1/4点を拾いたい場合ですが、円弧の指定方法によっては、その点が画面上に描画されていない場合もありますので注意をしないといけません。単純に、開始角度<90°<終了角度であれば、90°点は描画されていると判定出来るのか?といえばそうでもなく、開始角度>終了角度となる場合もありますので注意をしないといけません。開始角度>終了角度の場合には、開始角度>90°>終了角度という条件になる訳です。

つまり
if (開始角度<終了角度)and(開始角度<90)and(90<終了角度)
or (開始角度>終了角度)and(開始角度>90)and(90>終了角度) then begin
 xx := p1 ;
 yy := p2+p3 ;
 (チェック文)
end;
のようになります。0°点、180°点、270°点、も同様です。が、if文が多少煩雑に思えます。考え方を少し変えます。開始角度>終了角度となってしまう状態を考えたくないため、開始角度を0°に持って行きます。そして、終了角度の値から開始角度分を引き算します。引き算の結果がマイナス値になれば、360°を足します。判断したい0°、90°、180°、270°も、開始角度分を引き算し、マイナス値になれば360°を足します。そう考えると、角度範囲の判定は簡単になります。

開始角度と終了角度はどちらが大きいのか?等を考える必要はなく、0〜終了角度分の範囲内にあるかどうか?で済みます。
 
C点要素を拾う
点要素の場合には、単純に、その配置点(p1,p2)を拾えるようにします。
xx := p1 ;
yy := p2 ;
(チェック文)
 
D文字要素の端点を拾う
文字要素の場合には、まず、その配置点(p1,p2)を拾えるようにします。
xx := p1 ;
yy := p2 ;
(チェック文)
次に、文字の基点と同じく、その文字の他の8点を拾えるようにしたい場合には、それらを計算せねばなりません。少なくとも、左下点と右下点だけでも拾いたい場合には、それらを計算せねばなりません。一々算出するのが面倒であれば、データ定義に、左下点と右下点も入れられるようにしておき(p7〜p10を追加)、データ登録時にその点を保管しておくようにすれば、スナップ時に計算する時間を省略する事が出来ます。勿論、その分のメモリ容量は消費されてしまいます。

また、左下点を(p7,p8)として保管するようにしておき、各点は随時するようにしておく、という手法もあります。将来、文字を2点指定させて作図させたいのであれば、どのみち、あと2つ変数を追加する事にはなるでしょう。この場合、配置点(p1,p2)は右下点座標を格納する事となるでしょう。AutoCAD(DXF)で左右合わせ又はフィットで受け渡した文字をオブジェクトプロパティで基点変更すると左下点が基準となるのではなく、右下点が基準となってしまうのは、同様の理由であろうと推測されます。左下点を元に、右下点を計算するのは簡単です。
右下点X=左下点X+L×Cos(A)
右下点Y=左下点Y+L×Sin(A)
右上点・左上点・中央点等が通常、端点のスナップで必要かどうか、という事もありますし、仮に、必要であるとしても、計算によって得る事は出来ます。現在登録されている基点が何で、判定文を入れて、プログラムも煩雑になってしまうのであれば、左下点(p7,p8)のデータも保持するようにしておくのがベターと判断出来るかもしれません。
なお、文字の各点は、実際には画面には見えません。ですのでスナップするのも難しいかもしれません。それではどうするか?例えば、文字枠の長方形(傾き有り)を表示させる方法、文字内部をクリックしたら端点を算出させる方法、等があるでしょう。いろいろ考えてみて下さい。
 
 
文字の基点を算出するコードが何度もあるとプログラムも見難くややこしくなりますので、まず、UnitFunc.pasに、
procedure GetTextPos(x,y,h,w,d,a:double;s:string;z1,z2:integer;var rx,ry:double) ;
という手続きを作成しておきます。これは、ある文字要素の任意の基点座標を計算するものです。指定した文字要素の左上点を算出し、指定した基点座標を算出しています。
 
文字要素登録後、及び、レイヤ設定後の図形範囲再計算で、以前は簡単に書いていた文字要素位置の図形範囲への適用部分を、このGetTextPosを使って書き換えます。これにより、図形範囲表示(マウス左下ドラッグ)をした時に、文字要素がちゃんと画面内に納まるように表示されます。
 
次に、文字の基点がどこになるのかが分かりにくいので、テストとして、文字描画(UnitGraph.pas内 手続きGL_CHAR)で、文字背景をグレー表示するようにしておきます。通常は、透明(Brush.StyleをbsClear)にするか、白色(背景色)で塗り潰し(Brush.ColorをclWhite、Brush.StyleをbsSold)にします。
 
それでは、スナップ用関数 MouseSnap() を見てみます。
// 原点(0,0)サーチ
if (Abs(x) <= a)and(Abs(y) <= a) then begin
 x := 0.0;
 y := 0.0;
 Result := True ;
 exit;  // 直ぐに終了
end;
というように1つずつ認識範囲内かどうかを確認していきます。
@原点(0,0)
A用紙枠点
B要素端点
 B-1 線分(Dat[i].typ = 1)
 B-2 円 (Dat[i].typ = 2)
 B-3 円弧(Dat[i].typ = 3)
 B-4 点 (Dat[i].typ = 4)
 B-5 文字(Dat[i].typ = 5)
・以前は、線分の中点を拾っていませんでしたので、中点を拾うように追加します。
・円上の点をクリックした時、円の中心点を拾うように追加します。1/8点を拾うようにすると判定文を4つも追加する必要がありますので見送ります。
・円弧の場合にも、0°点、90°点、180°点、270°点、を拾えるようにします。円と同様に、円弧上でクリックした時、円弧の中心点を拾うように追加します。
 但し、UnitFunc.pas内の関数 GetAngleがマイナス値を返して円弧の開始角度・終了角度にマイナス値が入ってしまうと正常に判定出来ませんので、GetAngleはマイナス値を返さないように修正します。
・文字は、左下点・右下点も拾えるように追加します。但し、文字を表示する文字高さは最大500ドットとしていますので、表示している文字の基点とズレが出てしまう場合があります。
 
ここまでのサンプルプログラムです。
サンプルプログラムのソース
 
 
CAD装置(1)
CAD装置(2)
メディア
AutoCADの
DIESELマクロ
CSV
DXF
PCES
IGES
STEP
数学とCAD
CAD作ろ!
 ▲PREV
 ▼NEXT
 
お問い合わせ 
本サイトはリンクフリーです
リンクバナー
(C)Copyright 1999-2015 By AFsoft All Rights Reserved.