2019年7月26日金曜日

Qt のツールがうまく動かないとき

Qt のツールがうまく動かないとき、Qt のディレクトリ一式をある場所から別の場所に移動したときに問題が発生したときは、qt.conf ファイルを見直しましょう。

qt.conf ファイルはテキストファイルで、qmake.exe と同じフォルダにあります。
qt.conf ファイルがないときは、作ります。
[Paths]
Prefix = c:/Qt/5.12.3/msvc2015
 PrefixはQtのベースフォルダを指定します。上記の設定の場合、qmake.exe や qt.conf ファイルの場所は C:/Qt/5.12.3/msvc2015/bin になり、Qt5Core.lib や Qt5XmlD.lib ファイルの場所は C:/Qt/5.12.3/msvc2015/lib になります。
lib だの bin だのと言ったフォルダ名も変更されている場合は、さらに設定が必要です。

2019年7月8日月曜日

PowerShell スクリプトで関数を作り再利用する

TeighaShell で図面の作成や変更ができることを確認できましたでしょうか?
共通する処理はシェルスクリプトのライブラリファイルを作って、それを再利用することも可能です。

TeighaShellプロジェクト
TeighaShell.ps1 ライブラリスクリプト

注意

TeighaShell プロジェクトの OdActivationInfo.cs ファイルは仮のファイルになっています。OpenDesignAllianceのメンバーになって、OdActivattionInfo.cs の正式のファイルを入手してTeighaShellをビルドしないと、TeighaShell は動作しません。

書き込みサンプル

# TsLib.ps1 の関数や変数を取り込み
. C:\Repos\CADKhanRepos\TeighaShell\TeighaShell.ps1
# 既存の図面を開く
$db = Open-Drawing c:\temp\test.dwg
# トランザクションを作成(すると以降の図形は1つのトランザクションにまとめられる)
$tr = $db.TransactionManager.StartTransaction()
# 保存先としてのモデルブロックのオブジェクトIDを取得
$model = Get-ModelBlockId $db
# 線分の始点と終点を作成
$p0 = Create-Point3d 0,0,0
$p1 = Create-Point3d 10,10,0
# 連続して5つの線分を作成
Add-Line $model $p0 $p1
Add-Line $model $p0 $p1
Add-Line $model $p0 $p1
Add-Line $model $p0 $p1
Add-Line $model $p0 $p1
# トランザクションの確定と破棄
$tr.Commit()
$tr.Dispose()
# 図面の上書き保存と破棄
Save-Drawing -Database $db
$db.dispose()
スクリプトについて解説すると#から始まる行はコメント行である。
「.(ドット) ファイル名」は、ファイル名で指定したスクリプトをライブラリとして読み込むという意味である。スクリプトの中で使用すると、そのスクリプトの実行中のみ有効なライブラリスクリプトになり、TeighaShellのプロンプトで使用するとTeighaShellのセッションで有効なライブラリスクリプトになる。
Open-DrawingとSave-DrawingはTeighaShell.exeに実装されたコマンドレット。
Get-ModelBlockId, Create-Point3d, Add-Line はライブラリスクリプトに実装された関数。
他はそれぞれのインスタンスのメソッドやプロパティとなっている。

ライブラリスクリプト

Create-Point3d 関数は、x,y,z 座標値から Point3d インスタンスを作る簡単な関数。
function Create-Point3d
{
  param($pts)
  return New-Object -TypeName Teigha.Geometry.Point3d -ArgumentList $pts
}
 Add-Line 関数は、指定したブロックテーブルレコードに、始点と終点を指定して線分を作成する関数。画層、線色、線種、太さといったプロパティは、CLAYER、CECOLOR、CELTYPE、CELWEIGHT システム変数から設定する。
function Add-Line
{
param([Teigha.DatabaseServices.ObjectId]$owner,        [Teigha.Geometry.Point3d]$start,[Teigha.Geometry.Point3d]$end)
$db = $owner.Database
$tr = $db.TransactionManager.topTransaction
$ttr = $false
if( !$tr )
{
$tr = $db.TransactionManager.startTransaction()
$ttr = $true
}
$rec = $tr.GetObject($owner,$openmode::ForWrite)
$ent = New-Object -TypeName Teigha.DatabaseServices.Line
$ent.SetDatabaseDefaults($db)
$ent.StartPoint = $start
$ent.EndPoint = $end
$id = $rec.AppendEntity($ent)
$tr.AddNewlyCreatedDBObject($ent,$true)
if( $ttr )
{
$tr.Commit()
$tr.Dispose()
}
return $id
}
トランザクションが実行中の場合、線分は実行中のトランザクションに登録する。実行中のトランザクションがない場合、関数内でトランザクションを作成し 線分を図面に登録する。

読み込みサンプル

# TeighaShell.ps1 の関数や変数を取り込み
. C:\Repos\CADKhanRepos\TeighaShell\TeigaShell.ps1
# 既存の図面を開く
$db = Open-Drawing c:\temp\test.dwg
# トランザクションを作成(すると以降の図形は1つのトランザクションにまとめられる)
$tr = $db.transactionmanager.starttransaction()
# 読み込み元としてのモデルブロックのオブジェクトIDを取得
$model = Get-ModelBlockId $db
# モデルブロックレコードオブジェクトを取得
$rec = $tr.GetObject($model,$openmode::forread)
# オブジェクトIDが順に取得できる
foreach($id in $rec)
{
  $ent = $tr.GetObject($id, $openmode::forRead)
  Write-Output $ent,$ent.Handle
}
# トランザクションの確定と破棄
$tr.Commit()
$tr.Dispose()
# WorkingDatabase を上書き保存と破棄
$db.dispose()
読み込みサンプルのキモは、ブロックテーブルレコードに登録されている図形のオブジェクトIDがforeach 構文で順に取得できることだ。
オブジェクトIDを図形にするには、トランザクションのGetObject()メソッドが必要。変数$entの型は実行中に変わるので、図形種類によって処理を分岐し、存在しないメソッドやプロパティをアクセスしないようにしてください。

 画層テーブルから画層レコードを取得するのも、foreach構文で順に取得することも可能ですし、$ltbl[画層名]のように配列操作でも画層レコードを取得できます。どちらもオブジェクトIDが入手できるので、トランザクションのGetObject()メソッドでオブジェクトに変換します。

まとめ

ライブラリ関数を用意することで、ユーザーはAutoCADコマンドスクリプト並みの簡単なスクリプトで作図できるようになる。
PowerShell は、テキストファイル、CSVやXMLなど構造を持つファイル、SQLデータベースに容易にアクセスできるので、それらを使った自動作図も気軽にできる。

C# でコマンドレットを作成する

TeighaShell プログラムには、C#で定義したコマンドレットがいくつかある。DrawingOperation.cs ファイルに定義されている。

多くのコマンドレットはパイプラインで処理を行う。ProcessRecord() メソッドでパイプラインから渡された複数のオブジェクト(データ)を1つずつ処理するコードを書くのが普通だ。
DrawingOperation.cs ファイルに定義したコマンドレットは1個の Database オブジェクトを処理するように設計しているので、EndProcessing() メソッドでパラメータとして受け取ったオブジェクト(データ)を処理するようにしている。
WriteObject()メソッドが重要で、オブジェクト(データ)を次のパイプラインや出力結果に流す働きをしている。

Open-Drawing コマンドレットは、空のデータベースオブジェクトを作成し、ReadDwgFile() メソッドでDWG/DXF/DXBファイルをデータベースに読み込んで、WriteObject() メソッドでデータベースオブジェクトをパイプラインに渡す。

Save-Drawing コマンドレットは、ファイル名、データベース、保存形式、バージョン、DXFファイルの精度をパラメータとして、図面をファイルに保存するためのもの。
上書き保存と、名前を付けて保存で呼び出すメソッドが異なるので、その辺をユーザーに意識させないようにコマンドレットで処理を行うようにした。

参考
Windows PowerShell コマンドレットの記述



TeighaShell プログラムの骨格を説明

 Teigha は、C++ のネイティブ API と、オートメーション APIと、Classic と Swig の2種類の .Net API が提供されている。
 このうち、オートメーション API と、Teigha Classic .Net API が AutoCAD 準拠になっている。Swig API は、SWIG (Simplified Wrapper and Interface Generator) によってネイティブ API を .Net Framework に提供したものなので、Teigha 独自の仕様になっている。

2019/7/24
Teigha Classic .Net API のホストサービスを持つクラスライブラリとしてマネージドライブラリを作成しました。
これを、PowerShell コンソールや PowerShell ISE にロードさせることで、Teigha Classic .Net API の機能を、PowerShell コンソールや PowerShell ISE に「公開」することができたので、下記の記述は古いです。
下記の方法で得られた知見もあったので、取り消し線を引いて残しておきます。


最初は PowerShell に、 system.reflection.assembly クラスの LoadFrom(dllファイル名) 手続きで、 Teigha Classic .Net API を提供する TD_Mgd_20.5_15.dll ファイルをロードすればいけるのかと思ったのですが、それだけではうまくいきませんでした。

Teigha のホストアプリケーションを作成して、そこから PowerShell を起動する必要がありました。TeighaShell プログラムは、Teigha .Net Classic API の最低限のホストアプリケーションが、PowerShell を起動しているという骨格になっています。

Teigha .Net Classic ホストアプリケーションの骨格

メインプログラム (Program.cs) からTeigha に関係する部分のみ抜き出すと次のようになります。
[STAThread]
static void Main(string[] args)
{
    // アクティベーションを割り当て
    Services.odActivate(ActivationData.userInfo,
    ActivationData.userSignature);
    using (Services svcs = new Services())
    {
        // アプリケーションサービスを割り当て
        HostApplicationServices.Current = new HostAppServ();
               // 起動時メッセージを表示
        Console.WriteLine("\nTeighaShell developed using {0} ver {1}",
           HostApplicationServices.Current.Product,
           HostApplicationServices.Current.VersionString);
               (略 PowerShell の実行部)
    }//TeighaServiceの終了
}//メインプログラムの終了

最低限のホストアプリケーションサービス(HostAppServ.cs)は、Teighaのサンプルそのままを使用しています。
Teigha.DatabaseServices.HostApplicationService クラスは、FindFile メソッドが抽象メソッド abstract になっているので、これを派生クラス HostAppServ で実装する必要があります。HostAppServクラスのFindFileメソッドの実装は、AutoCADがインストールされたときに存在するレジストリや、ユーザーデータフォルダを調べて、サポートファイルのパス名を解決しているので、自社の環境に合わせて修正するのが望ましい。

PowerShellの起動と実行

メインプログラム (Program.cs) からPowerShell に関係する最低限の部分を抜き出すと次のようになります
[STAThread]
static void Main(string[] args)
{
(略)
// Runspace 実行空間インスタンスを生成
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
  runspace.Open();
  // PowerShell インスタンスを生成
  using (var powershell = PowerShell.Create())
  {
    powershell.Runspace = runspace;
    // メインループ
    while (true)
    {
      // スクリプトの1行入力待ち
      string st = Console.ReadLine();
      // ユーザーが入力した内容をスクリプトとする
      powershell.AddScript(st);
      // コマンドを実行
      Collection<PSObject> psOutput = powershell.Invoke();
      // 実行結果を文字列化
      foreach (PSObject outputItem in psOutput)
      {
      strbuilder.AppendLine(outputItem.ToString());
      }
// 結果をコンソールに出力
Console.Write(strbuilder.ToString());
// コマンドとエラーをクリア
powershell.Commands.Clear();
powershell.Streams.Error.Clear();
}
  }// PowerShellの終了
}// Runspaceの終了
(略)
}//メインプログラムの終了
実行空間を作って、PowerShellエンジンを作って実行空間に割り当てる。
入力した内容を、スクリプトとしてエンジンに渡してから処理を行う。
結果はPSObjectのコレクションに返されるので、それを文字列化して画面に表示する。
という動きになる。

PowerShell で実行した結果エラーになったら、エラーメッセージを表示したいとか、この状態では PowerShell スクリプトが実行できないので、実行できるようにするとか、コンソールの1行入力は、CTRL+C の押下でコンソールアプリケーションが突然死するので、それをやめたいとか、骨格に追加した部分についてはソースプログラムとそのコメントで確認してください。



TeighaShell を作ってみた

TeighaShell サンプルプログラム

AutoCADには、AcCoreConsole.exe というグラフィックス画面を持たない図面編集プログラムがある。
コマンドプロンプトに LINE [Enter] と入力すれば線分の始点と終点を入力でき、実際に絵を描くことができる。PLOT[Enter]と入力すればコマンドユーザーインタフェースバージョンの印刷コマンドを起動して、図面をプリンタに出力したり、PDFなどのファイル出力が可能である。
画面がないということは、基本的に処理が速いということにつながる。

コマンドユーザーインターフェースからグラフィック操作なしで、DWGファイルを扱いたいというニーズは、たぶんどこにもあって、AutoCAD ベースだといかんせんお高いので、Teigha ベースでできないか、というのを考えたのがこちらのプログラムになる。

仕組み

・C# で作ったコンソールアプリケーション
・PowerShell を組み込んでいる
・Teigha .Net Classic API が利用できる

以上

動作の様子

TeighaShell.exe を起動する。

正しく起動できれば、Teigha のバージョンを表示した後に、PowerShell のプロンプトを表示する。
プロンプトに、下記のスクリプトの完全パスを指定して[Enter]を押す。
#
# DrawArc.ps1
# TeighaShell 上で動作する
#
# 新しい図面を作成する
$db = New-Drawing
# 変数は$から始まる英数字、大文字小文字は区別しない
# New-Drawing は TeighaShell.exeが提供するコマンドレット
#
# SYNOPSIS
#  New-Drawing [-UseCtb ($true|$false)]
# RETURN
#  Teigha.DatabaseServices.Database
# トランザクションを開始する
$tr = $db.TransactionManager.startTransaction()
# インスタンスのプロパティは、インスタンス.プロパティ名でアクセスできる
# インスタンスのメソッドは、インスタンス.メソッド名(引数)で実行できる
# 円弧を作成する
$arc = New-Object -TypeName Teigha.DatabaseServices.Arc
# C# で New で作成するものは、PowerShell では New-Object で作成する
# 現在の画層名、線種、図形の色を円弧にまとめて設定
$arc.SetDatabaseDefaults($db)
# 半径を指定する
$arc.radius = 100
# 開始角は 0°のまま
# 終了角 60°を指定する
$arc.EndAngle = 60.0 * 180.0 / [Math]::PI
# 円周率は System.Mathクラスに 定数 PI として定義されている
# ロード済みの System 名前空間は省略できる
# [名前空間.型名] でクラス名や列挙型などの型名を変数に設定できる
$BTR = [Teigha.DatabaseServices.BlockTableRecord]
$OpenMode = [Teigha.DatabaseServices.OpenMode]
# ブロックテーブルを読み込みオープンする
$tbl = $tr.GetObject($db.BlockTableId,$OpenMode::ForRead)
# モデルレイアウトブロックを書き込みオープンする
$rec = $tr.GetObject($tbl[$BTR::ModelSpace],$OpenMode::ForWrite)
# ブロックテーブルからブロックレコードを取得するには、テーブル名の配列を使用する。
# 配列の要素にアクセスするには [] を使う。
# 戻り値はオブジェクトID
# ブロックレコードクラスの定数 ModelSpace PaperSpace を使ってモデルレイアウト名(*MODEL_SPACE)が取得
# シンボルユーティリティサービスクラスを使って、
# モデルレイアウトブロックのオブジェクトIDを取得する方法もある
#$usrv = [Teigha.DatabaseServices.SymbolUtilityServices]
#$rec = $tr.GetObject($usrv::GetBlockModelSpaceId($db),$OpenMode::ForWrite)
# 円弧をモデルレイアウトに追加
$rec.AppendEntity($arc)
# トランザクションに円弧を追加
$tr.AddNewlyCreatedDBObject($arc,$true)
# トランザクションをコミット
$tr.Commit()
# トランザクションを廃棄
$tr.Dispose()
# 図面を名前を付けて保存
Write-Drawing c:\temp\arc.dwg $db
# 図面を破棄 (Write-DrawingにDispose()を含めたので呼ぶ必要なし)
#$db.Dispose()
上記のスクリプトを実行すれば、C:\temp\arc.dwg ファイルに円弧を1個書いた図面を保存してくれる。


異尺度注釈図形にアタッチされている尺度を知る

 異尺度注釈図形が持っている尺度(文字列)を表示する ;;; ;;; LISTANNOSCALE.LSP ;;; (defun C:LISTANNOSCALE ( / el id f)   (setq f T)   ;異尺度注釈図形を選択   (if (setq el (entg...