2011/01/25

非平面を平面に2

前回の続き。
以前のものは選択したポリゴン面の法泉を基準(これは少し怪しい)に平面化してましたが
今回はスクリプトを実行するとポイントをピックさせられます。
3点ピックするとそれを元に平面化します。
相変わらず4つ以上の面に接したポイントがあるとダメです。
いやぁ使えない使えない。。。

from win32com.client import *
xsi = Application

def GetReferencePoints():
    ''' リファレンスポイントを取得(最低3点) '''
    xsi.ActivateVertexSelTool()
    lPnts = []
    while len(lPnts) < 3:
        rtn = xsi.PickElement(constants.siPointFilter)
        oPick = rtn[2]
        if not oPick:  #ピックが足りない場合処理中止
            return
        for oCmp in oPick.SubComponent.ComponentCollection:
            lPnts.append(oCmp)
    return lPnts

def GetReferenceTransform(lPnts):
    ''' リファレンストランスフォームを取得 '''
    #外積を取得
    v1 = XSIMath.CreateVector3()
    v2 = XSIMath.CreateVector3()
    vCrs = XSIMath.CreateVector3()
    v1.Sub(lPnts[1].Position, lPnts[0].Position)
    v2.Sub(lPnts[2].Position, lPnts[0].Position)
    vCrs.Cross(v1, v2)
    #トランスフォームに変換(後でレイキャストさせるために位置をどのポイントとも違う位置(ポイントの中点)に)
    vPos = XSIMath.CreateVector3()
    for i in range(3):
        vPos.AddInPlace(lPnts[i].Position)
    vPos.ScaleInPlace(1.0/len(lPnts))
    vYAxis = XSIMath.CreateVector3(0,1,0)
    oTrans = XSIMath.CreateTransform()
    oTrans.Translation = vPos
    nAgl   = vYAxis.Angle(vCrs)
    vCrs.Cross(vCrs, vYAxis)
    vCrs.NegateInPlace()
    oTrans.SetRotationFromAxisAngle(vCrs, nAgl)
    return oTrans

def MovePointToReferenceTransform(oFace, oItemTrans, oRefTrans, oAllPnts):
    ''' ポイントをリファレンストランスフォーム上のY=0に移動 '''
    lPosArr = [list(t) for t in oAllPnts.PositionArray]
    for oPnt in oFace.Points:
        vPosOnRef = XSIMath.MapObjectPositionToObjectSpace(oItemTrans, oRefTrans, oPnt.Position)
        vPosOnRef.Y = 0
        vPosOnRef.ScaleInPlace(10)
        vPos = XSIMath.MapObjectPositionToObjectSpace(oRefTrans, oItemTrans, vPosOnRef)
        lPosArr[0][oPnt.Index] = vPos.X
        lPosArr[1][oPnt.Index] = vPos.Y
        lPosArr[2][oPnt.Index] = vPos.Z
    oAllPnts.PositionArray = lPosArr
        
def GetEdgeInfo(oFace):
    ''' エッジ情報を取得 '''
    
    def GetInfo(oEdge, lPntIndxsOnFace):
        oPnts = oEdge.Points
        if oPnts[0].Index in lPntIndxsOnFace:
            oStartPnt = oPnts[1]
            oEndPnt   = oPnts[0]
        else:
            oStartPnt = oPnts[0]
            oEndPnt   = oPnts[1]
        vStart = oStartPnt.Position
        vEnd   = oEndPnt.Position
        vEnd.SubInPlace(vStart)
        vStart.SubInPlace(vEnd)
        return vStart, vEnd, oEndPnt.Index
        
    oNgbrEdges = oFace.Edges.NeighborEdges()
    lPntIndxsOnFace = [oPnt.Index for oPnt in oFace.Points]
    lPos    = [] #開始位置リスト
    lAxis   = [] #方向リスト
    lPntIdx = [] #対象ポイントインデックスリスト
    for oEdge in oNgbrEdges:
        lInfo = GetInfo(oEdge, lPntIndxsOnFace)
        lPos.extend(lInfo[0].Get2())
        lAxis.extend(lInfo[1].Get2())
        lPntIdx.append(lInfo[2])
    return lPos, lAxis, lPntIdx
    
def MovePointToRaycastPosition(oGeom, oFace, lEdgeInfo):
    ''' ポイントをレイキャストした先に移動 '''
    oGeom.SetupPointLocatorQueries(3, None, [oFace.Index], -1)
    oPntLocations = oGeom.GetRaycastIntersections(lEdgeInfo[0], lEdgeInfo[1], 0)
    lPos = oGeom.EvaluatePositions(oPntLocations)
    oPnts = oGeom.Points
    lPosArr = [list(t) for t in oPnts.PositionArray]
    for idx, allidx in enumerate(lEdgeInfo[2]):
        lPosArr[0][allidx] = lPos[0][idx]
        lPosArr[1][allidx] = lPos[1][idx]
        lPosArr[2][allidx] = lPos[2][idx]
    oPnts.PositionArray = lPosArr
    
def main():
    oSel = xsi.Selection(0)
    if oSel.Type != 'polySubComponent': #ポリゴンが選択されてない場合処理中止
        return
    #選択ポリゴンのサブコンポーネントからいろいろ取得
    oSubCmp    = oSel.SubComponent
    oFace      = oSubCmp.ComponentCollection[0]
    oPoly      = oFace.Nodes[0]
    oItem      = oSubCmp.Parent3DObject
    xsi.FreezeModeling(oItem)                              #フリーズしとかないとポイントの移動ができない
    oItemTrans = oItem.Kinematics.Global.Transform
    oGeom      = oItem.ActivePrimitive.Geometry
    oAllPnts   = oGeom.Points
    lPosArr    = [list(t) for t in oAllPnts.PositionArray]
    oAllPnts.PositionArray = lPosArr                       #一旦ポイントをいじっとくことで初回実行からちゃんと動く(なぞ)
    lEdgeInfo  = GetEdgeInfo(oFace)                        #エッジ情報を保存
    lPnts      = GetReferencePoints()                      #参照トランスフォーム作成用ポイントをピックさせる(最低3点)
    if not lPnts:
        return
    oRefTrans  = GetReferenceTransform(lPnts)              #参照トランスフォームを作成
    MovePointToReferenceTransform(oFace, oItemTrans, oRefTrans, oAllPnts) #とりあえず平面化
    MovePointToRaycastPosition(oGeom, oFace, lEdgeInfo)                   #できた面と保存しておいてエッジをレイキャストして補正
    xsi.ActivateRaycastPolySelTool()
    
main()

2011/01/20

非平面を平面に

ツイッター上で話題になっていた非平面ポリゴンを平面化する、というスクリプトです。
Softimage2011で確認。
単純に平面化するとほかのポリゴン面の形を変えてしまってせっかく平面だったものも非平面にしてしまうので
いろいろこねくり回して回避してます。
汚いコードですがとりあえずそれっぽく動くようにはなってます。
いくつか問題はありますが。。。

使い方は、平面化したいポリゴンを選択後実行。です。
使用前


使用後



おおまかな処理内容としては
・平面化したいポリゴン面に隣接するエッジ情報をとっておく
・平面化
・平面化したポリゴンととっておいたエッジの交差点にポイントを移動
てな感じです。

上にも書きましたが問題がありまして、
・ポリゴンの法泉がうまく取得できなくて失敗することがある。
PolygonNode.Normalから取得してるんですが値の意味を勘違いしてるのか使い方が悪いのか
けっこうな頻度でうまくいかない。
再現性があるのでなんかしらの理由があるのだろうけどわかりません。
情報求む。

ここに関しては横着せずに他の人がツイッターに書いていたように各ポイント法線から算出すれば解決するかもしれません。

・対象ポリゴンを構成するポイントが4つ以上のポリゴンに属しているとうまくいかない
最初にとっておいてるエッジ情報のインデックスと最後に移動させるポイントのインデックスが合わないのが一つ。
これはもうちょい手を入れればなんとかなりそう。
もうひとつがオレの陳腐な脳みそでは可決不能。
4つ以上の面に接したポイントをどう動かせばすべての面を平面に保てるかわからない。
誰か頭のいい人教えてください。

・なぜか1回目の実行は処理がされない
なんで???

とまぁこんな感じです。
そのうち直せたら直したい。

以下コード。

from win32com.client import *

xsi = Application

#サブコンポーネントからいろいろ取得
oSubCmp = xsi.Selection(0).SubComponent
oFace   = oSubCmp.ComponentCollection[0]
oPoly   = oFace.Nodes[0]
oParent = oSubCmp.Parent3DObject
oGeom   = oParent.ActivePrimitive.Geometry
xsi.FreezeModeling(oParent)

#隣接エッジのベクトルを保存しておく
#(レイキャスト時にちゃんとヒットするように拡大)
lPntIdxs = [oPnt.Index for oPnt in oFace.Points]
lEdgePos  = []
lEdgeAxis = []
lEdgePntIdx  = []
for oEdge in oFace.Edges.NeighborEdges():
    oEdgePnts = oEdge.Points
    if oEdgePnts[0].Index in lPntIdxs:
        oStartPnt = oEdgePnts[1]
        oEndPnt   = oEdgePnts[0]
    else:
        oStartPnt = oEdgePnts[0]
        oEndPnt   = oEdgePnts[1]
    vEdgeAxis = XSIMath.CreateVector3()
    vEdgeAxis.Sub(oEndPnt.Position, oStartPnt.Position)
    vEdgeAxis.ScaleInPlace(100)        #拡大
    lEdgeAxis.extend(vEdgeAxis.Get2())
    vPos = oStartPnt.Position
    vEdgeAxis.ScaleInPlace(0.5)        #逆側もレイキャストできるように後ろにずらす
    vPos.Sub(vPos, vEdgeAxis)
    lEdgePos.extend(vPos.Get2())
    lEdgePntIdx.append(oEndPnt.Index)
  

#ポリゴン面にそったトランスフォームを作成
vFacePos = XSIMath.CreateVector3()
oPnts = oFace.Points
for oPnt in oPnts:
    vFacePos.Add(vFacePos, oPnt.Position)
vFacePos.Scale(1.0/oPnts.Count, vFacePos)
 
oTrans = XSIMath.CreateTransform()
oTrans.Translation = vFacePos
vYAxis = XSIMath.CreateVector3(0, 1, 0)
vNrml = XSIMath.CreateVector3()
nAngle = vYAxis.Angle(oPoly.Normal) #radians
vCross = XSIMath.CreateVector3()
vCross.Cross(vYAxis, oPoly.Normal)
oTrans.SetRotationFromAxisAngle(vCross, nAngle)

#平面化
#(さっき作ったのトランスフォーム上でのY=0に移動)
#(レイキャスト時にちゃんとヒットするように拡大)
dPos    = {}
for oPnt in oPnts:
    vPntOnFace = XSIMath.MapWorldPositionToObjectSpace(oTrans, oPnt.Position)
    vPntOnFace.ScaleInPlace(5)
    vPntOnFace.Y = 0
    vPntOnFace = XSIMath.MapObjectPositionToWorldSpace(oTrans, vPntOnFace)
    dPos[oPnt.Index] = vPntOnFace
oPnts = oGeom.Points
lPosArr = [list(l) for l in oPnts.PositionArray]
for idx, v in dPos.iteritems():
    lPosArr[0][idx] = v.X
    lPosArr[1][idx] = v.Y
    lPosArr[2][idx] = v.Z
oPnts.PositionArray = lPosArr

#エッジベクトルから平面にレイキャスト
oGeom.SetupPointLocatorQueries(3, None, [oFace.Index], -1)
oPntLocations = oGeom.GetRaycastIntersections(lEdgePos, lEdgeAxis, 1)
lPos = oGeom.EvaluatePositions(oPntLocations)
for idx, allidx in enumerate(lEdgePntIdx):
    lPosArr[0][allidx] = lPos[0][idx]
    lPosArr[1][allidx] = lPos[1][idx]
    lPosArr[2][allidx] = lPos[2][idx]
oPnts.PositionArray = lPosArr