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

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

プログラミングについて
ホームページについて
キャドについて
電子カタログについて
書籍・雑誌
イベント
リンク集
CADを考える:円
前頁では、幾何要素/表記要素フィーチャの折線について見ましたので次は円について、という前に、メインのプログラム Unit1.pas が煩雑になってきましたので、登録したデータの描画を行う部分を別のユニット UnitDataGraph.pas としてまとめることにします。
これを期に、少し変数名を変更しておきます。取り合えずクラス化はしていません。
UnitDataGraph.pas
var
 ScrX, ScrY : integer ;   // 画面サイズ(ドット)
 CenX, CenY : integer ;   // 画面中央点(ドット)
 mm_dot : double ;     // mm→ドット座標変換係数
 WndX1, WndY1 : double ;  // 画面左下点(mm)
 WndX2, WndY2 : double ;  // 画面右上点(mm)
 CData : TDataClass ;
 gp : TGraphClass ;
 
procedure ZInit ;
procedure Z2D(var ix,iy:integer;x,y:double);
procedure DisplayLineSub1(px1,py1,px2,py2:double);
procedure DisplayLineSub2(l:integer;var i:integer;var d1:double;px1,py1,px2,py2:double);
procedure DisplayTenMarker(lay,col,mc:integer;px,py,an,sc:double);
procedure DisplayLine(lay,col,ltp,wid:integer;px1,py1,px2,py2:double);
procedure DisplayLines(lay,col,ltp,wid,num:integer;vx,vy:array of double);
procedure DisplayAllData ;
用紙mm座標→画面ドット座標 の変換をする手続きZ2D()では、今後、画面を拡大・縮小・移動をしたときにもそのまま利用出来るように、画面左下点・右上点の座標から算出するようにしています。
また、線分描画は他の関数・手続きからも再利用可能なため、実線描画用の DisplayLineSub1() と 破線等描画用のDisplayLineSub2() を用意し、点マーカ、線分、折線での線分描画がそれを利用するようにしています。
 
また、色・線種・線幅の部分も手続き化してまとめます。
// 線分の表示
procedure DisplayLine(lay,col,ltp,wid:integer;px1,py1,px2,py2:double);
var
 c,cc,l,w,ww, i : integer ;
 d1 : double ;
begin
 c := col ;
 if (c = 0) then c := CData.zLay[lay-1].Color ;
 c := RGB(CData.zCol[c-1].Red,CData.zCol[c-1].Green,CData.zCol[c-1].Blue);
 gp.G_SetPen(c);
 l := ltp ;
 if (l = 0) then l := CData.zLay[lay-1].Ltype ;
 w := wid ;
 if (w = 0) then w := CData.zLay[lay-1].Width ;
 ww := Round(CData.zWid[w-1].Width * mm_dot) ;
 if (ww < 1) then ww := 1 ;
 gp.G_SetWidth(ww);
 //
 if (CData.zLtp[l-1].Segment = 0)
  or(CData.zLtp[l-1].SpaceMax * mm_dot < 2.0)
  or(CData.zLtp[l-1].SpaceMax < CData.zWid[w-1].Width) then begin
  DisplayLineSub1(px1,py1,px2,py2);
 end
 else begin
  i := 0 ;
  d1:= 0.0;
  DisplayLineSub2(l,i,d1, px1,py1,px2,py2);
 end;
end;
          ↓
// 線分の表示
procedure DisplayLine(lay,col,ltp,wid:integer;px1,py1,px2,py2:double);
var
 c,cc,l,w, i : integer ;
 ww, d1 : double ;
begin
 DisplayLayCol2(lay,col,ltp,wid, c,cc,l,w,ww); // 色・線種・線幅
 //
 if (CData.zLtp[l-1].Segment = 0)
  or(CData.zLtp[l-1].SpaceMax * mm_dot < 2.0)
  or(CData.zLtp[l-1].SpaceMax * mm_dot < ww ) then begin
  DisplayLineSub1(px1,py1,px2,py2);
 end
 else begin
  i := 0 ;
  d1:= 0.0;
  DisplayLineSub2(l,i,d1, px1,py1,px2,py2);
 end;
end;
 
ここまでで、点マーカ、線分、折線の描画を出来るようにしましたが、全データ表示では、点マーカデータを全部描画→線分データを全部描画→折線データを全部描画、となっていますが、実際のCADでの作図のことを考えると、作図順の通りに描画、をしたくなる場合があります。例えば、塗り潰しを重ねる場合などは、作図・変更した順序が重要になる場合がありますが、それをユーザーがコントロール出来ない状況というのは余り好ましくありません。線分の場合も、線幅がある場合には、重なり具合によって見え方が変わってきます。
という訳で、作図順序で描画させるためには、作図順序を記録しておくための変数が必要になりますので、これを用意する事にします。
 
UnitData.pas
type
 ・・・
 TDataOrder = record    // データ作図順
  DataType : Integer ;  // データタイプ (1:点マーカ 2:線分~
  DataNo : Integer ;   // データ要素No (0-)
 end;
 
 TFukugoZukeiDef = record  // 構造化要素|複合図形定義
  exf : Boolean ;     // 存在フラグ(True:有り False:無し)
  name : string ;     // 複合図形名
  Flag : Integer ;     // 種別フラグ
  cnt : Integer ;     // 配置数
  ・・・
  mOrd : array of TDataOrder;    // 作図順
  mOrdN : Integer ;         // 数
 end;
 
 TDataClass = class
  public
  { Public 宣言 }
  ・・・
  dOrd : array of TDataOrder;    // 作図順
  dOrdN : Integer ;         // 数
// データ作図順の登録
function TDataClass.AddDataOrder(m,tp,no:integer) : Boolean;
begin
 Result := False;
 if (m < 0) then begin
  try
   Inc(dOrdN);
   if ((dOrdN mod 200) = 1) then SetLength(dOrd, dOrdN+199);
   with dOrd[dOrdN-1] do begin
    DataType := tp ;
    DataNo := no ;
   end;
   Result := True ;
  except
   Dec(dOrdN);
  end;
 end
 else if (m > 0) then begin
  with fDef[m-1] do begin
   try
    Inc(mOrdN);
    if ((mOrdN mod 200) = 1) then SetLength(mOrd, mOrdN+199);
    with mOrd[mOrdN-1] do begin
     DataType := tp ;
     DataNo := no ;
    end;
    Result := True ;
   except
    Dec(mOrdN);
   end;
  end;
 end;
end;
これを点マーカ・線分・折線のデータ項目の追加登録を行う関数で利用するようにしておき、全データ描画を行う手続きでこれを利用するようにします。幾何要素データ=複合図形ツリー内データは表記要素データ=用紙上データの後に描画するようにします。
 
 
それでは、円について見ていきます。
 
幾何要素/表記要素|円
パラメータ説明範囲
LayerIntレイヤコード
ColorInt色コード
TypeInt線種コード
line_widthInt線幅コード
Center_xdouble中心X座標 double(64bits)の
範囲(有効桁15桁)
Center_ydouble中心Y座標 double(64bits)の
範囲(有効桁15桁)
Radiusdouble半径0<半径<1.0×1015
備考
・となり合わせた点に同一の点は指定できない。
SXF Ver.3.1実装規約では、線分・折線同様、円について何も無さそうです。
 
それでは円のデータ構造を決めます。
UnitData.pas
type
 ・・・
 TDataCircle = record  // 幾何要素/表記要素|円
  exf : Boolean ;    // 存在フラグ(True:有り False:無し)
  Layer : Integer ;   // レイヤ(1〜256)
  Color : Integer ;   // 色 (0:レイヤ色  1〜256)
  Ltype : Integer ;   // 線種 (0:レイヤ線種 1〜32)
  line_width: Integer ; // 線幅 (0:レイヤ線幅 1〜16)
  Center_x : double ;  // 中心点X座標
  Center_y : double ;  // 中心点Y座標
  Radius : double ;   // 半径
 end;
 
 TFukugoZukeiDef = record  // 構造化要素|複合図形定義
  exf : Boolean ;     // 存在フラグ(True:有り False:無し)
  name : string ;     // 複合図形名
  Flag : Integer ;     // 種別フラグ
  cnt : Integer ;     // 配置数
  ・・・
  mCir : array of TDataCircle;   // 幾何要素|円
  mCirN : Integer ;         // 数
  mFzk : array of TDataFukugoZukei; // 構造化要素|複合図形配置
  mFzkN : Integer ;         // 数
 end;
 
 TDataClass = class
  public
  { Public 宣言 }
  ・・・
  dCir : array of TDataCircle;   // 表記要素|円
  dCirN: Integer ;         // 数
  ・・・
 
データ追加用の関数は
function AddDataCircle(s:string;lay,col,ltp,wid:integer;cx,cy,cr:double) : Boolean;
のようにします。線分等と同様、最初に「データ追加先の複合図形名(幾何要素) null:用紙へ追加(表記要素)」を指定するようにしています。
 
UnitData.pas
// 円 データ項目の追加登録
function TDataClass.AddDataCircle(s:string;lay,col,ltp,wid:integer;cx,cy,cr:double) : Boolean;
var
 m : integer ;
begin
 Result := False;
 if (lay < 1) or (lay > zLayN) then lay := 1 ;
 if (col < 0) or (col > zColN) then col := 0 ;
 if (ltp < 0) or (ltp > zLtpN) then ltp := 0 ;
 if (wid < 0) or (wid > zWidN) then wid := 0 ;
 
 if (s = '') then begin
  // 用紙へ追加
  if (AddDataOrder(-1,4,dCirN)) then begin
   try
    Inc(dCirN);
    if ((dCirN mod 200) = 1) then SetLength(dCir, dCirN+199);
    with dCir[dCirN-1] do begin
     exf := True ;
     Layer := lay ;
     Color := col ;
     Ltype := ltp ;
     line_width := wid ;
     Center_x := cx ;
     Center_y := cy ;
     Radius := cr ;
    end;
    Result := True ;
    AddLayerCnt(lay,col,ltp,wid,-1);
   except
    Dec(dCirN);
    Dec(dOrdN);
   end;
  end;
 end
 else begin
  m := FZukeiNameCheck(0,s);
  if (m = 0) then exit ;
  with fDef[m-1] do begin
   if (AddDataOrder(m,4,mCirN)) then begin
    try
     Inc(mCirN);
     if ((mCirN mod 200) = 1) then SetLength(mCir, mCirN+199);
     with mCir[mCirN-1] do begin
      exf := True ;
      Layer := lay ;
      Color := col ;
      Ltype := ltp ;
      line_width := wid ;
      Center_x := cx ;
      Center_y := cy ;
      Radius := cr ;
     end;
     Result := True ;
     AddLayerFCnt(lay,col,ltp,wid,-1);
    except
     Dec(mCirN);
     Dec(mOrdN);
    end;
   end;
  end;
 end;
end;
 
さて、これで円データの登録は出来るようになりました。
 
次に、円の描画についてですが、GDI/GDI+/Direct2Dでの円の描画については既に UnitGraph.pas に「G_Circle()」「G_CircleF()」として実装済みですが、ドット単位なので mmからドットに変換をしてこれを利用すればいい、という場合もありえるのですが、
・部分図・作図部品によって変形された状態の円になる場合がある
・クリッピング処理を付けた時、円用のクリッピング処理が必要である
・破線等に対応出来ない(実線描画は可)
既に「円のクリッピング」でも描きましたが、変形されて楕円状態になったものには対応していません。ですのでここでは、円を内接する正多角形状態として作図させるようにします。
 
そうした場合、何角形にすればいいのか?(何分割すればいいのか?)という問題があります。円を綺麗に描きたい場合には、多くの線を描けば良いですが、多くの線を描かせると描画する時間が掛かります。速く描かせたいとなると円が粗くなってしまいます。私自身はこれまで、円を72分割して描画するという場合が多かったです。しかし、小さい円を描かせるのに72本の線分を描かせる必要があるのか?逆に、かなり大きな円の場合に72分割でもいいのか?
角度刻みで指定する方法(例えば5°ずつ描画、等)は、固定の角数(分割数;補間数)で行う方法と余り大差はありません。360÷5=72 で、72本の線分での描画と同じだからです。円弧の描画の際には変わってくるでしょうけれども。あとは、補間する線分の長さを指定する方法、弧・弦の高さを指定する方法、等があります。
 
線分の長さを指定する=円弧長と考えて指定する方法は、円周=2πr、円弧長=円弧角×半径、ですから、円弧角=指定長さ÷半径 → 分割数が分かるので、最低分割数・最大分割数を決めておけば、それなりの角数での円描画が出来るであろう、という事です。
 
弧・弦の高さ(h)を指定する方法では、

θ=Acos((r-h)/r) ですから、与える h が小さければ小さい程、綺麗な円に近づき、h は半径 r 以上の値を指定出来ない、という事で出来るでしょう。例えば、半径100の円に対して、補間高さ 1 と指定した場合は、ACos((100-1)/100)=約8.1°ということになります。
 
ここでは、線分の長さを指定する手法で試しにやってみます。
UnitDataGraph.pas
// 円の表示
procedure DisplayCircle(lay,col,ltp,wid:integer;px,py,pr:double);
const
 ARC_LENGTH = 10.0 ;   // 補間線分長さ[mm値]
 ARC_MIN = 8 ;
 ARC_MAX = 360 ;
var
 c,cc,l,w, i,j : integer ;
 ww, d1, a,aa,n, x1,y1,x2,y2 : double ;
 fl : Boolean ;
begin
 a := ARC_LENGTH / pr ;
 n := Int(2.0*Pi/a) ;
 if (n < ARC_MIN) then n := ARC_MIN ;
 if (n > ARC_MAX) then n := ARC_MAX ;
 a := 2.0*Pi / n ;
 
 DisplayLayCol2(lay,col,ltp,wid, c,cc,l,w,ww); // 色・線種・線幅
 //
 fl := False ;
 if (CData.zLtp[l-1].Segment = 0)
  or(CData.zLtp[l-1].SpaceMax * mm_dot < 2.0)
  or(CData.zLtp[l-1].SpaceMax * mm_dot < ww ) then
  fl := True ;
 
 i := 0 ;
 d1 := 0.0 ;
 aa := 0.0 ;
 x1 := px + pr*Cos(aa);
 y1 := py + pr*Sin(aa);
 for j:=1 to Round(n) do begin
  aa := aa + a;
  x2 := px + pr*Cos(aa);
  y2 := py + pr*Sin(aa);
  if (fl) then
   DisplayLineSub1(x1,y1,x2,y2)
  else
   DisplayLineSub2(l,i,d1, x1,y1,x2,y2) ;
  x1 := x2 ;
  y1 := y2 ;
 end;
end;
これで実行すると、画面が小さい場合に円が少し粗いな、と感じた場合、画面を大きくしても、同じ分割数で表示されるため、円の粗さが余計に目立ってしまうことになります。画面を大きくしたら、それなりに綺麗に表示して欲しいという期待は出てくるでしょう。すると、補間線分長さは mm値での指定ではなく、ドット値での指定になってくると思われます。
UnitDataGraph.pas
// 円の表示
procedure DisplayCircle(lay,col,ltp,wid:integer;px,py,pr:double);
const
 ARC_LENGTH = 15.0 ;   // 補間線分長さ[dot相当値]
 ARC_MIN = 8 ;
 ARC_MAX = 360 ;
var
 c,cc,l,w, i,j : integer ;
 ww, d1, a,aa,n, x1,y1,x2,y2 : double ;
 fl : Boolean ;
begin
 a := ARC_LENGTH / ( pr * mm_dot) ;
 n := Int(2.0*Pi/a) ;
 if (n < ARC_MIN) then n := ARC_MIN ;
 if (n > ARC_MAX) then n := ARC_MAX ;
 a := 2.0*Pi / n ;
 
 ・・・
次に、この手続きの呼び出し側で幾何要素(部分図以下)の変換を掛ける場合、円は楕円になったりしますので、この手続きとは別に、それ用の処理が出来る手続きが必要となってしまいます。
そうなるとこの手続きの需要は半分になってしまい、呼び出し側の方も煩雑になってしまうので、この手続き内で幾何要素(部分図以下)の作図も出来るよう、引数に変換行列を指定出来るようにします。
UnitDataGraph.pas
// 円の表示
procedure DisplayCircle(lay,col,ltp,wid:integer;px,py,pr:double;t:TMatrix);
const
 ARC_LENGTH = 15.0 ;   // 補間線分長さ[dot相当値]
 ARC_MIN = 8 ;
 ARC_MAX = 360 ;
var
 c,cc,l,w, i,j : integer ;
 ww, d1, a,aa,n, x1,y1,x2,y2 : double ;
 fl : Boolean ;
begin
 a := ARC_LENGTH / ( pr * mm_dot) ;
 n := Int(2.0*Pi/a) ;
 if (n < ARC_MIN) then n := ARC_MIN ;
 if (n > ARC_MAX) then n := ARC_MAX ;
 a := 2.0*Pi / n ;
 
 DisplayLayCol2(lay,col,ltp,wid, c,cc,l,w,ww); // 色・線種・線幅
 //
 fl := False ;
 if (CData.zLtp[l-1].Segment = 0)
  or(CData.zLtp[l-1].SpaceMax * mm_dot < 2.0)
  or(CData.zLtp[l-1].SpaceMax * mm_dot < ww ) then
  fl := True ;
 
 i := 0 ;
 d1 := 0.0 ;
 aa := 0.0 ;
 x1 := px + pr*Cos(aa);
 y1 := py + pr*Sin(aa);
 MtxXY(x1,y1, x1,y1, t);
 for j:=1 to Round(n) do begin
  aa := aa + a;
  x2 := px + pr*Cos(aa);
  y2 := py + pr*Sin(aa);
  MtxXY(x2,y2, x2,y2, t);
  if (fl) then
   DisplayLineSub1(x1,y1,x2,y2)
  else
   DisplayLineSub2(l,i,d1, x1,y1,x2,y2) ;
  x1 := x2 ;
  y1 := y2 ;
 end;
end;
幾何要素(部分図以下)で倍率が掛けられた場合に、円データの半径値そのままだと円の描画に反映しません。例えば、下図の斜めになっている部分図A配置の倍率は、X尺度 1.5、Y尺度 1.2 ですが、

これを、X尺度 5、Y尺度 3 にした場合

というように粗い状態のまま倍率されてしまいます。そのため、変換行列によって変換された円(楕円)の半径相当値を考慮した分割数算出を行う必要があります。
UnitDataGraph.pas
// 円の表示
procedure DisplayCircle(lay,col,ltp,wid:integer;px,py,pr:double;t:TMatrix);
 ・・・
begin
 x1 := 0.0 ;
 y1 := 0.0 ;
 x2 := pr*Cos(0.25*Pi) ;
 y2 := pr*Sin(0.25*Pi) ;
 MtxXY(x1,y1, x1,y1, t);
 MtxXY(x2,y2, x2,y2, t);
 d1 := Dist(x1,y1,x2,y2);
 a := ARC_LENGTH / (d1 * mm_dot) ;
 // a := ARC_LENGTH / (pr * mm_dot) ;
 n := Int(2.0*Pi/a) ;
 if (n < ARC_MIN) then n := ARC_MIN ;
 if (n > ARC_MAX) then n := ARC_MAX ;
 a := 2.0*Pi / n ;
 
 DisplayLayCol2(lay,col,ltp,wid, c,cc,l,w,ww); // 色・線種・線幅
 ・・・
これでコンパイル(再構築)・実行をすると、下図のようになります。

内側の円(楕円)のカクカクさが気になるようでしたら、現在は最小分割数(ARC_MIN)を「8」にしていますが、これを「16」等にしたり、補間線分長さ(ARC_LENGTH)の値を小さくすれば良いでしょう。この辺りのバランス調整は自由に変えてみて下さい。現在は手続き内での定数宣言をしていますが、後に外部変数化する可能性はあります。
また、点マーカの円も、この円の表示手続きを利用するよう変更しておきます。点マーカの点の大きさは固定ですし塗り潰しも必要なので、この手続きは利用しません。
 
実行すれば分かりますが、描画には結構な時間が掛かる、という事がすぐに分かると思います。特に、GDI+の遅さは際立っています。画面を大きくし、用紙サイズを小さくすると、たった20個の円の描画で、こんなに時間が掛かってしまうのか、という事に少しびっくりです。例えば、円が100分割されていれば、20×100=2000本の線の描画になりますし、破線状態で1本の線が5本の短い線分で描画されている場合には、10,000本の線の描画となってきます。
こうなってくると、少なくとも GDI+は別処理で、とか、考えがちですが、そういう事をすると、煩雑化し、バグの原因になる可能性もあります。それよりは、例えば画面外の線は描画させない=クリッピング処理をする、という風に考えて、GDI・GDI+・Direct2Dの違いとは無関係なように、もっと上のレベルでの対処を考えた方がいいと思われます。
 
 
それでは、ここまでのテストプログラムです。実行ファイル、gdiplus.dll、gdipフォルダは入っていません。ソースのみです。
 
CAD装置(1)
CAD装置(2)
メディア
AutoCADの
DIESELマクロ
CSV
DXF
PCES
IGES
STEP
数学とCAD
CAD作ろ!
CADを考える
 ▲PREV
 ▼NEXT
M7
Jw_cad
 
お問い合わせ 
本サイトはリンクフリーです
リンクバナー
(C)Copyright 1999-2015 By AFsoft All Rights Reserved.