|
前頁では、幾何要素/表記要素フィーチャの折線について見ましたので次は円について、という前に、メインのプログラム 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; |
これを点マーカ・線分・折線のデータ項目の追加登録を行う関数で利用するようにしておき、全データ描画を行う手続きでこれを利用するようにします。幾何要素データ=複合図形ツリー内データは表記要素データ=用紙上データの後に描画するようにします。
それでは、円について見ていきます。
幾何要素/表記要素|円 |
|
パラメータ | 型 | 説明 | 範囲 |
Layer | Int | レイヤコード |
|
Color | Int | 色コード |
|
Type | Int | 線種コード |
|
line_width | Int | 線幅コード |
|
Center_x | double | 中心X座標 |
double(64bits)の
範囲(有効桁15桁) |
Center_y | double | 中心Y座標 |
double(64bits)の
範囲(有効桁15桁) |
Radius | double | 半径 | 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
|