めもがき

曲線の始点終点どっちが近いのかな?判断

曲線の始点終点どっちが近いのかな?判断 - C#ATIA

関連のなにか。

実環境が無いのでスペルミス上等ということで。

Option Explicit

Private Sub Sample(crv As INFITF.Reference, pln As INFITF.Reference)
    
    Dim measureCrv As SPATypeLib.Measurable
    Set measureCrv = GetMeasurable(crv)
    Select Case measureCrv.GeometryName
        Case CatMeasurableCurve, CatMeasurableCircle, CatMeasurableLine 'OK
        Case Else: Err.Raise 13
    End Select
    
    Dim A As Double, B As Double, C As Double, D As Double
    ComputePlaneEquationABCD pln, A, B, C, D
    
    Dim onCrvPointsCoordinates(0 To 8) As Variant
    Call asDisp(measureCrv).GetPointsOnCurve(onCrvPointsCoordinates)
    
    'Start point coordinates
    Dim ptX As Double, ptY As Double, ptZ As Double
    ptX = onCrvPointsCoordinates(0)
    ptY = onCrvPointsCoordinates(1)
    ptZ = onCrvPointsCoordinates(2)
    
    '面の方程式と、始点から伸びる直線上の点の座標の方程式を解く
    
    'Origin point coordinates as ptX, ptY, ptZ
    'Plane projection point coordinates as prjX, prjY , prjZ
    'Distance of origin to projection as L
    
    'A * prjX + B * prjY + C * prjZ = D
    'prjX = ptX + A * L
    'prjY = ptY + B * L
    'prjZ = ptZ + C * L
    
    Dim L As Double
    L = (D + A * ptX + B * ptY + C * ptZ) / _
        (A ^ 2 + B ^ 2 + C ^ 2)
    Dim prjX As Double, prjY As Double, prjZ As Double
    prjX = ptX + A * L
    prjY = ptY + B * L
    prjZ = ptZ + C * L
    
    Debug.Print ComputeScalar(ptX - prjX, ptY - prjY, ptZ - prjZ)
    
End Sub

'Plane Equation
'Ax + By + Cz = D
Private Sub ComputePlaneEquationABCD( _
              iPlane As INFITF.Reference, _
        ByRef oA As Double, _
        ByRef oB As Double, _
        ByRef oC As Double, _
        ByRef oD As Double _
    )
    
    Dim measurePln As SPATypeLib.Measurable
    Set measurePln = GetMeasurable(iPlane)
    Select Case measureCrv.GeometryName
        Case CatMeasurablePlane 'OK
        Case Else: Err.Raise 13
    End Select
    
    Dim planeComponents(0 To 8) As Variant
    Call asDisp(measurePln).GetPlane(planeComponents)
    
    
    Dim x1st As Double, y1st As Double, z1st As Double
    x1st = planeComponents(3)
    y1st = planeComponents(4)
    z1st = planeComponents(5)
    
    Dim x2nd As Double, y2nd As Double, z2nd As Double
    x2nd = planeComponents(6)
    y2nd = planeComponents(7)
    z2nd = planeComponents(8)
    
    Dim planeNomalDirection() As Double
    planeNomalDirection = CrossProduct( _
        x1st, y1st, z1st, _
        x2nd, y2nd, z2nd _
    )
    
    Let oA = planeNomalDirection(0)
    Let oB = planeNomalDirection(1)
    Let oC = planeNomalDirection(2)
    
    Dim plnOriginX As Double, plnOriginY As Double, plnOriginZ As Double
    plnOriginX = planeComponents(0)
    plnOriginY = planeComponents(1)
    plnOriginZ = planeComponents(2)
    
    Let oD = ComputeScalar(plnOriginX, plnOriginY, plnOriginZ)
End Sub

'ベクトルの外積
Public Function CrossProduct( _
        iX1 As Double, iY1 As Double, iZ1 As Double, _
        iX2 As Double, iY2 As Double, iZ2 As Double _
    ) As Double() 'Double(0 To 2)
    
    Const X = 0, Y = 1, Z = 2
    Dim resultVector(0 To 2) As Double
    resultVector(X) = iY1 * iZ2 - iZ1 * iY2
    resultVector(Y) = iZ1 * iX2 - iX1 * iZ2
    resultVector(Z) = iX1 * iY2 - iY1 * iX2
    
    Let CrossProduct = resultVector
End Function

'ベクトルから大きさを求める
Public Function ComputeScalar( _
                 iX As Double, _
                 iY As Double, _
        Optional iZ As Double = 0# _
    ) As Double
    
    Let ComputeScalar = VBA.Math.Sqr(iX ^ 2 + iY ^ 2 + iZ ^ 2)
    
End Function

'てきとう
Public Function GetMeasurable(iRef As INFITF.Reference) As SPATypeLib.Measurable
    Dim doc As INFITF.Document
    Set doc = GetModelElement(iRef).Document
    Dim spaWb As SPATypeLib.SPAWorkbench
    Set spaWb = doc.GetWorkbench("SPAWorkbench")
    Set GetMeasurable = spaWb.GetMeasurable(iRef)
End Function

'[選択要素からドキュメントを取得する - C#ATIA](http://kantoku.hatenablog.com/entry/2016/04/07/183709 "選択要素からドキュメントを取得する - C#ATIA")
Public Function GetModelElement(iAnyObject As INFITF.AnyObject) As INFITF.ModelElement
    Set GetModelElement = iAnyObject.GetItem("ModelElement")
End Function

'disable VBE static syntax check.
Private Function asDisp(o As INFITF.CATBaseDispatch) As INFITF.CATBaseDispatch
    Set asDisp = o
End Function

参考

選択要素からドキュメントを取得する - C#ATIA
GetDirectionが上手く行かない2 - C#ATIA

何度でもよみがえるメモ帳(ネタ)

とあるソフトを間違えて閉じてしまうことが頻発したため、終了してもゾンビのごとく蘇るようにしてみた。

もっと良い方法がありそう……。

# メモ帳を起動してイベントを購読する処理
[scriptblock]$startNotepad = {
    # メモ帳を起動
    [Diagnostics.Process]$notepadProc = 
        Start-Process -FilePath notepad -PassThru
    # 起動を待機
    $notepadProc.WaitForInputIdle() > $null
    # イベントを通知させる
    $notepadProc.EnableRaisingEvents = $true

    # Exited(終了時)のイベントを購読開始
    Register-ObjectEvent -InputObject $notepadProc -EventName Exited
}

# 無限ループ
while ($true) {
    # メモ帳を起動
    $startNotepad.Invoke()
    
    # 何かしらイベントが起きるまで待つ
    Wait-Event

    # 発生したイベント情報を取得して破棄(破棄しないと`Wait-Event`で待機しない)
    Get-Event | Remove-Event
}

PowerShellなり、Windows PowerShell ISEなりに貼り付けて実行すると、何回閉じても復活するメモ帳が起動する。

終了したい場合は、PowerShellのウィンドウでCtrl+Cを押すか、PowerShellそのものを終了する。


190111追記

そもそもイベントにする必要が無かった。

# 無限ループ
while ($true) {
    # メモ帳を起動
    [Diagnostics.Process]$notepadProc = Start-Process -FilePath notepad -PassThru
    # 終了を待機
    $notepadProc.WaitForExit()
}

【VBA実験】何回NotしてもTrueになるTrueを作る

あけましておめでとうございます。 今年もよろしくお願いいたします。

前書き

私が職場で使っているVBAのライブラリの中には、「何回NotしてもTrueになるTrue」を返すAPIを持つものがあります(一般には使われていないライブラリ)。

この「何回NotしてもTrueになるTrue」をVBAだけで作成する方法の記事となります。
そのため、実際のコードに役立つことはほぼ無いでしょう。

VBAのBoolean周りの動作

本来、VBAのTrueは、16bitの符号付き整数で表すと-1になります(VBAの16進数表現で&HFFFF)。
これを、Not演算子でビット単位で否定すると0&H0000)、すなわちFalseになります。

対して、問題のTrueは16bitの符号付き整数で表すと1&H0001)になっており、これに対してNot演算子を使用すると-2&HFFFE)、0ではないためTrueになります。

コード

以下のコードにあるGetTrueNotTrue()関数の返り値が「何回NotしてもTrueになるTrue」になります。

Private Type intType
    Value As Integer
End Type

Private Type boolType
    Value As Boolean
End Type

Function GetTrueNotTrue() As Boolean
    Dim i As intType
    i.Value = 1
    
    Dim b As boolType
    LSet b = i
    
    Let GetTrueNotTrue = b.Value
    
End Function

コードの解説

普通に数値をBoolean型の変数に代入しても、自動型変換が行われ、VBA本来のTrue・Falseとして代入されてしまいます。
今回は数値のバイナリ表現を保ったまま、Boolean型の変数に代入したいため、別の方法をとる必要がありました。

上記のコードでは、ユーザー定義型のバイナリコピーができるLSetステートメントを使用し、Integer型の1のバイナリ表現をBoolean型にコピーしています。

Private Type intType
    Value As Integer
End Type

Private Type boolType
    Value As Boolean
End Type

で入れ物となるユーザー定義型を定義します (VBAではIntegerもBooleanも2バイトの領域を必要とします)。

    Dim i As intType
    i.Value = 1

でInteger型の1を設定、

    Dim b As boolType
    LSet b = i

で別のユーザー定義型にバイナリコピーしています。

動作の確認

Private Sub Sample()
    Debug.Print GetTrueNotTrue()        '-> True
    Debug.Print Not GetTrueNotTrue()    '-> True
    Debug.Print CInt(GetTrueNotTrue())  '-> 1
End Sub

無事、「何回NotしてもTrueになるTrue」を作成できました。

タスクバーに通知を表示するPowerShellスクリプト

小ネタ

以下のようなメッセージを簡単に表示できるPowerShellスクリプト
表示はそれぞれWIn 8.1 Win10

f:id:imihito:20180822223221p:plain

f:id:imihito:20180822223007p:plain

PowerShellスクリプト

本体。適当な場所に「○○.ps1」として保存する。

param(
    [string]$Prompt = 'メッセージ',
    [string]$Title  = '通知',
    $CallBack = ''
)

Add-Type -AssemblyName System.Windows.Forms, System.Drawing

function Show-NotifyIcon {
    param(
        [string]$Prompt = 'メッセージ',
        [string]$Title  = '通知',
        [scriptblock]$CallBack = {}
    )
    
    [Windows.Forms.NotifyIcon]$notifyIcon =
        New-Object -TypeName Windows.Forms.NotifyIcon -Property @{
            BalloonTipIcon  = [Windows.Forms.ToolTipIcon]::Info
            BalloonTipText  = $Prompt
            BalloonTipTitle = $Title
            Icon    = [Drawing.SystemIcons]::Information
            Text    = $Title
            Visible = $true
        }
    
    # イベント定義
    $notifyIcon.add_BalloonTipClicked( $CallBack )
    
    [int]$timeout = 3 # sec

    [DateTimeOffset]$finishTime = 
        [DateTimeOffset]::UtcNow.AddSeconds( $timeout )

    $notifyIcon.ShowBalloonTip( $timeout )
    
    # そのままだとイベントが走らない&すぐに消えてしまうので適当wait
    while ( [DateTimeOffset]::UtcNow -lt $finishTime ) {
        Start-Sleep -Milliseconds 1
    }
    $notifyIcon.Dispose()
}

$parameters = $MyInvocation.BoundParameters
$parameters.CallBack = [scriptblock]::Create( $CallBack )
Show-NotifyIcon @parameters

使い方

引数1:表示するメッセージ
引数2:タイトル
引数3:クリックされたときのコールバック処理
を文字列で渡す。

バッチ

powershell.exe -Sta -NoProfile -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File 上記のps1ファイル メッセージ タイトル コールバック処理

VBA

VBAならWindows API使えば?という話はさておく。

Sub ShowNotifySample()
    Const NotifyScriptPath = "保存したスクリプト(ps1ファイル)の保存場所"
    Const PsCommandLineBase = "powershell.exe -Sta -NoProfile -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File """
    Const WQuoteSpaceWQuote = """ """
    
    '表示するメッセージ及びタイトル
    Dim notifyMessage As String
    notifyMessage = "めっせーじ"
    Dim notifyTitle As String
    notifyTitle = "たいとる"
    
    'クリックされた時の処理(PowerShellスクリプト)
    'エクスプローラーでエクセルの場所を開く
    Dim callBackPsScript As String
    callBackPsScript = "explorer.exe " & Excel.Application.Path
    
    Dim execCmd As String
    execCmd = PsCommandLineBase & NotifyScriptPath & WQuoteSpaceWQuote & _
              notifyMessage & WQuoteSpaceWQuote & _
              notifyTitle & WQuoteSpaceWQuote & _
              callBackPsScript & """"
    
    Call VBA.Shell(execCmd, vbHide)
    
End Sub

im@sparqlを触ってみる その3 - クエリの内容を理解する

imihito.hatenablog.jp

の続き。

実際にちょこちょこ触って、クエリの雰囲気を掴めてきたので、自分なりの理解の仕方を書いておく(一部間違えているかも)。

用語

Resource Description Framework - Wikipedia

略称RDF。 データベース(im@sparql)内のデータの構成を示すもの。

主語(subject)・述語(predicate)・目的語(object)の3要素(トリプル)で関係情報を示す。 (リレーショナルデータベースでは無い=NoSQLと考えて良いのだろうか?)

無理矢理オブジェクトに当てはめると、「主語」はオブジェクト本体、「述語」はプロパティ名及び型、「目的語」はプロパティの値、のようなものと解釈した。

主語・述語にはURI、目的語には値やURIなどが指定される(目的語がURIの場合はオブジェクト型のプロパティのような形で階層構造になる)。

SPARQL - Wikipedia

RDFに対するクエリ言語。 SQL風の文法らしい(私自身はSQLを碌に触ったことないので不明)。

今回の記事で扱うクエリはこの言語で書かれたものになる。

どことなくXPathに近い雰囲気も感じる。

クエリ

今回は以下のクエリの内容を理解する。

PREFIX

PREFIX schema: <http://schema.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/URIs/imas-schema.ttl#>

C#で言う名前空間エイリアス

主語・述語・目的語の一部はURIで示されるが、いちいち書くのは大変なのでエイリアスを設定するのが一般的。

例えば

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

http://www.w3.org/1999/02/22-rdf-syntax-ns#rdf:で省略できるようになり、 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type>rdf:type と書けるようになる。

SELECT

SELECT *

SELECT ?変数名とすることで、結果として出力する値(変数)を指定できる。 今回は*が指定されているため、次のWHERE内で使用された変数全てが結果として出力される。

WHERE-1

WHERE内は文が多いので、前後に分けて考えてみる。

WHERE {
  ?s rdf:type imas:ScriptText;
     imas:Source ?source;
     schema:text ?text.

WHERE内には主語 述語 目的語と書き、書かれた条件に当てはまるものが探索される。

まず?s rdf:type imas:ScriptTextとなっているため、主語(?s)は任意で、述語がrdf:type、目的語がimas:ScriptTextとなるものが探索され、結果が?s変数に入る。

3行目には2個しか要素が書かれていないが、前の行が;で終わっているため、主語は前の行と同じもの指定する、となる(4行目も同じ)。

3行目、4行目では2行目で見つかった主語(?s)についてimas:Sourceschema:textの情報を?source変数、?text変数に格納している。

WHERE-2

  ?source schema:name ?name;
     filter(regex(str(?name),"千早"))
}

1行目は先ほどと同じで、?sourceについてschema:nameの情報を?name変数に格納している。

2行目ではフィルター、特定の要素のみの抽出が行われている。

filterはかっこ内の評価結果がTrueとなるものだけを結果として出力する。

中の式は、?name変数をstr()関数で文字列化し、regex正規表現マッチ)で"千早"と一致するものだけがTrueとなる。

WHEREまとめ

出力される変数(結果)

  • rdf:typeimas:ScriptTextとなる要素?s
  • ?simas:Sourceを示す?source
  • ?sschema:textを示す?text
  • ?sourceschema:nameを示す?name

ただし、?name"千早"に一致するもののみ。

Order By

order by ?text

?text変数の値でソート。

自分なりの解釈まとめ

はてなブログだとシンタックスハイライトが効かないので読みにくいけれど……。

# 使用する名前空間の宣言
PREFIX schema: <http://schema.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/URIs/imas-schema.ttl#>

# WHERE内の全変数出力
SELECT *
WHERE {
  # rdf:type が imas:ScriptText のものを ?s に入れて
  ?s rdf:type imas:ScriptText;
     # $s の imas:Source を ?source に
     imas:Source ?source;
     # $s の schema:text を ?text に入れる
     schema:text ?text.
  # ?source の schema:name を ?name に入れて
  ?source schema:name ?name;
     # ?name が "千早" と一致するものだけを抽出
     filter(regex(str(?name),"千早"))
}order by ?text # ?text の値でソート

参考サイト

sparql.crssnky.xyz

im@sparql.doc

SPARQL 1.1クエリ言語

im@sparqlを触ってみる その2 - PowerShellのデータ取得を関数化する

imihito.hatenablog.jp

上記の記事でPowerShellから情報を取得することには成功したが、そのままでは使いにくいため、関数に切り出してみた。

動作環境

Windows10 Pro 64bit上のWindows PowerShellのみで確認。

> $PSVersionTable

Name                           Value                                                  
----                           -----                                                  
PSVersion                      5.1.17134.165
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.17134.165
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

コード(psm1)

毎回クエリを投げるのは負荷的に避けたかったため、簡単なキャッシュ機構を付けてみた。

ただ、関数だけでは状態を保持しにくいため、モジュール(psm1)として、モジュール内の変数にキャッシュを保持させる構成としている。

# 結果のキャッシュ
[Collections.Generic.Dictionary[string,psobject[]]]$jsonCache =
    New-Object -TypeName 'Collections.Generic.Dictionary[string,psobject[]]'

function Request-Imasparql {
    [CmdletBinding()]Param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$QueryString
    )
    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'

    # 改行及びインデント削除
    [string]$trimQuery = 
        [regex]::Replace( $QueryString , ' *[\r\n]{1,2} *' , [string]::Empty )

    # キャッシュにあれば情報取得&リターン
    [psobject[]]$jsons = @()
    if ( $jsonCache.TryGetValue( $trimQuery , [ref]$jsons ) ) {
        Write-Information 'use cache'
        Write-Information ('query = ' + $trimQuery)
        return $jsons
    }
    
    
    Write-Information 'access'
    [Net.WebClient]$wc = 
        New-Object -TypeName Net.WebClient -Property @{
            BaseAddress = 'https://sparql.crssnky.xyz/spql/imas/query'
            Encoding    = [Text.UTF8Encoding]$false
        }

    # クエリの設定
    $wc.QueryString.Set( 'output', 'json' ) # 出力形式
    [string]$escapedQuery = [uri]::EscapeDataString( $trimQuery )
    $wc.QueryString.Set( 'query', $escapedQuery )
    Write-Information ('escaped query = ' + $escapedQuery)

    [string]$jsonTxt = $wc.DownloadString( [string]::Empty )

    [psobject]$data = ConvertFrom-Json -InputObject $jsonTxt
    $jsons = $data.results.bindings
    $jsonCache.Add( $trimQuery , $jsons )
    
    return $jsons
}

Export-ModuleMember -Function Request-Imasparql

サンプル

上記モジュールを使って前回の記事と同じ処理を書くと以下のようになる。

Import-Module -Name '上記コードを○○.psm1として保存して、保存したファイルのフルパス'

# https://sparql.crssnky.xyz/imas/ 内の「千早のセリフテキストを取得」のクエリ
[string]$query = @'
PREFIX schema: <http://schema.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/URIs/imas-schema.ttl#>

SELECT *
WHERE {
  ?s rdf:type imas:ScriptText;
     imas:Source ?source;
     schema:text ?text.
  ?source schema:name ?name;
     filter(regex(str(?name),"千早"))
}order by ?text
'@

# 情報取得
[psobject[]]$resultJson = 
    Request-Imasparql -QueryString $query -InformationAction Continue

# 必要な情報をコンソール出力
$resultJson | 
    select -Property @(
        @{N="Name";E={$_.name.value}}, 
        @{N="Text";E={$_.text.value}}
    ) |
    Write-Output

im@sparqlを触ってみる その1 - PowerShellでデータを取得する

その2

はじめに

自分のプログラミング以外の趣味関係のファンサイトに、「im@sparql」というサイトがある。

こちらはWEB上にデータベースがあり、クエリ文字列を付加してGETすれば情報が取れるというものらしい。 (自分も良く分かっていないので詳細は以下なども参照)

京都肉上げ croMisa

自分自身、各種基本知識・理解が不足しているため、「とりあえず動かしてみる」を目標にPowerShellから情報を取得するコードを作ってみた。

取得する情報

今回はim@sparqlの「千早のセリフテキストを取得」のクエリをそのまま使用して、情報を取得する。

コード

# ベースのURL
[string]$baseUrl = 'https://sparql.crssnky.xyz/spql/imas/query?query='

# https://sparql.crssnky.xyz/imas/ 内の「千早のセリフテキストを取得」のクエリ
# 一行に納めるため、SplitしてからJoinしている
[string]$query = [string]::Join( '' , @'
PREFIX schema: <http://schema.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/URIs/imas-schema.ttl#>

SELECT *
WHERE {
  ?s rdf:type imas:ScriptText;
     imas:Source ?source;
     schema:text ?text.
  ?source schema:name ?name;
     filter(regex(str(?name),"千早"))
}order by ?text
'@.Split("`r`n") )

# クエリ文字列をエスケープ
# また、そのままだとXMLで結果が返されるため '&output=json' を付与する
[uri]$openUri = [uri]($baseUrl + [uri]::EscapeDataString( $query ) + '&output=json')

# 情報取得
$result = Invoke-WebRequest -Method Get -Uri $openUri

if ( $result.StatusCode -ne 200 ) { throw }

# $result.Content はByte配列なので、文字列(JSON)に変換し
# JSON文字列をPSObjectへ変換
$json = 
    [Text.Encoding]::UTF8.GetString( $result.Content ) |
    ConvertFrom-Json

# 必要な情報を出力
$json.results.bindings |
    select -Property @(
        @{N="Name";E={$_.name.value}}, 
        @{N="Text";E={$_.text.value}}
    )

コンソール出力

Name Text                                                       
---- ----                                                       
如月千早 …!                                                         
如月千早 …………。                                                      
如月千早 …いえ、もうすぐ本番ですし、そこで体力を使い果たしてしまったら問題ですよ。                      
(中略)         
如月千早 静香…あなたならいつか、できる時がくるわ。アイドルを続けていれば、必ず。…必ずよ。 

ハマったところ

結果がXML

JavaScriptXMLHttpRequestやCOMのMSXML2.XMLHTTPを使用した場合、responseTextとしてJSON文字列が取得できた。

しかし、.NET Framework系のメソッドを使用した場合はXMLで結果が返却された。

詳しい人からGET時のURLにパラメータを追加することで結果を制御できるとの話があり、試すとうまくいった('&output=json'の付与)。

文字化け

XMLで結果が返却されたときの話だが、エンコードが違うのか文字化けをしてしまった。

エンコードをUTF8に設定することで文字化けせずにXMLを取得することができた。