ベクタパス作成ツール 4

190719

書換えほぼ完了

  • パス操作ベースにした、これが本来だと思う
    そのための派生クラスを作ってある程度の操作はクラス自身にやらせるようにした
  • ポイントの矩形のrect作成、ポイントのヒット判定、ポイント移動時にコントロールポイントも平行やシンメトリに移動する機能など
import sys , os , pickle
from PyQt5.QtWidgets import QApplication, QWidget,QMessageBox,QMenu,QAction
from PyQt5.QtCore import QPointF ,QRectF ,Qt,QDataStream,QIODevice,QDataStream,QFile
from PyQt5.QtGui import QPainterPath ,QPainter ,QPen,QCursor


class mypath(QPainterPath):
    def __init__(self,p):
        super().__init__(p)
        self.pointrect=[]
        self.a = QPointF(4,4) # Tolerance in detecting point
        self.closed=0

    def makepointrect(self): # ポイントの矩形を作る、描画と判定に使う
        self.pointrect.clear()
        for i in range(self.elementCount() ): # 0か2の前の3  or 0か二つ目の3
            self.pointrect.append( [ i , QRectF( QPointF( self.elementAt(i).x,self.elementAt(i).y) -self.a  ,
                QPointF( self.elementAt(i).x,self.elementAt(i).y) + self.a)  ] )

    def setclosed(self,flag):
        self.closed=flag
    def index(self,p):# 終点と始点の操作用、前のポイントや次のポイントを参照したいときに呼ぶ
        max=self.elementCount()
        if self.closed : 
            if p==-1:
                return max-2
            elif p==max:
                return max-1
            else :
                return p
        else :
            if p < 0 :
                return 0
            elif p > max -1 :
                return max -1
            else :
                return p

    def movepoint(self, p , pos , flag ): # flag : true-->sync
        if flag :
            lam = lambda q: QPointF(self.elementAt(q).x , self.elementAt(q).y )
            type1,type2 = self.elementAt(p).type ,self.elementAt(self.index(p-1)).type 
            if type1==2 :                  # cp symmetry first
                self.movepoint( self.index(p-2) , lam(self.index(p-1)) + lam(self.index(p-1)) - pos ,0) # (p-1) + pos->(p-1)
            elif type1==3 and type2==2 :   # cp symmetry second
                self.movepoint( self.index(p+2) , lam(self.index(p+1)) + lam(self.index(p+1)) - pos ,0) # (p+1) + pos->(p+1)
            else :                                     # point parerell
                self.movepoint( self.index(p+1) , lam(self.index(p+1)) - lam(p) + pos , 0) # (p+1) + p->pos
                self.movepoint( self.index(p-1) , lam(self.index(p-1)) - lam(p) + pos , 0) # (p-1) + p->pos
        if self.closed:
            if p==0:
                self.setElementPositionAt( self.elementCount()-1 , pos.x() , pos.y() )
        self.setElementPositionAt( p , pos.x() , pos.y() )
        #####ここにrect計算を書く
        self.makepointrect()

    def contains_m(self,  p ) : # QPointF p
        for l in self.pointrect :
            if l[1].contains(p) :
                return l[0] #        hit point
        if self.contains(p):
            return -1 #               contain but point
        else : 
            return -2 #                not contain


class makepath(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('hogetta')
        self.setGeometry( 500, 50 , 500 , 500 )
        self.setMouseTracking (True)  # <----------------*****
        self.plist=[]
        self.plist.append( QPointF(10,250) )  # start point
        self.plist.append( QPointF(100,100) )  #
        self.plist.append( QPointF(300,200) )  #
        self.plist.append( QPointF(490,250) )  #

        # self.path = QPainterPath( self.plist[0] )
        self.path = mypath(QPointF(10,250)  )
        for i in range (1, len(self.plist) ,3):
            self.path.cubicTo( self.plist[i] , self.plist[i+1] , self.plist[i+2] )

        self.a = QPointF(4,4) # Tolerance in detecting point
        self.pselected = -1             # selected point
        self.pmoving = 0                # move index
        # self.drawp=0                    # selected start point
        # self.isclosed=0                 # closed flag

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect( self.custommenu )

        self.show()
    def custommenu(self):
        menu=QMenu(self)
        action = QAction('Add Point', self)
        action.triggered.connect(self.addpoint)
        menu.addAction(action)

        action = QAction('Close Path', self)
        action.triggered.connect(self.pathclose)
        menu.addAction(action)
        action = QAction('Open Path', self)
        action.triggered.connect(self.pathopen)
        menu.addAction(action)

        menu.addSeparator()

        action = QAction('Point Info', self)
        action.triggered.connect(self.showelem)
        menu.addAction(action)

        action = QAction('Save Path', self)
        action.triggered.connect(self.pathsave)
        menu.addAction(action)
        action = QAction('Load Path', self)
        action.triggered.connect(self.pathload)
        menu.addAction(action)

        menu.exec_(QCursor.pos ())   

    def pathsave(self):
        fname=os.path.join(os.path.dirname(__file__) , 'qtsave')
        f = QFile(fname)
        f.open( QIODevice.WriteOnly )
        ds = QDataStream(f)
        ds.__lshift__(self.path)
        f.close() 
    def pathload(self):
        self.path=mypath(QPointF(0,0))
        fname=os.path.join(os.path.dirname(__file__) , 'qtsave')
        f = QFile(fname)
        f.open( QIODevice.ReadOnly)
        ds = QDataStream(f)
        ds.__rshift__(self.path)
        f.close() 
        self.path.makepointrect()
        self.update()
        # self.showelem()

    def pathclose(self):
        self.path.setclosed(1)
        self.path.movepoint( 0 ,QPointF( self.path.elementAt(0).x,self.path.elementAt(0).y ),0)
        self.update()
    def pathopen(self):
        self.path.setclosed(0)

    def showelem(self):# パスの中身を確認するため
        print('show element')
        for i in range(self.path.elementCount() ):
            print('format: {} x: {} y: {}'.format( self.path.elementAt(i).type ,
                            self.path.elementAt(i).x , self.path.elementAt(i).y ) )

    def addpoint(self): # 終点にベジェを追加
        e=self.mapFromGlobal( QCursor.pos() )
        p=QPointF( self.path.elementAt(self.path.elementCount()-1).x,
            self.path.elementAt(self.path.elementCount()-1).y )
        q = QPointF( (e.x() - p.x()) /3 , (e.y() - p.y())/3 )
        self.path.cubicTo(p+q,p+q+q,p+q+q+q)
        self.path.makepointrect()
        self.update()

    def mouseMoveEvent(self ,e):# ここがメインのルーチン
        if self.pmoving == -1 : # not Dragging
            l = self.path.contains_m( e.pos() )  # <------------判定メソッド
            if self.pselected >= 0 : # point selecting
                if l >= self.pselected-1 and l <= self.pselected+1 :
                    return             # same point -- ignore
                elif l< 0 :
                    return
                else :
                    type1=self.path.elementAt(l).type
                    type2=self.path.elementAt(l-1).type
                    if type1==0 or type1==1 or (type1==3 and type2==3):
                        self.pselected = l # reselect point
                        self.setWindowTitle('selected point {}'.format(l))
            else:
                if self.pselected == l :  # case of -1 -2 , same--ignore
                    return
                else :
                     self.pselected = l
        else : # dragging 
            self.path.movepoint( self.pmoving ,e.pos(), e.modifiers().__eq__(Qt.ShiftModifier))
        self.update()

    def mousePressEvent(self ,e):
        if self.pselected <= -1 :
            return
        l = self.path.contains_m( e.pos() )  # <------------判定メソッド
        if l >= 0 :
            self.pmoving = l
            self.setWindowTitle('moving point {}'.format(l))

    def mouseReleaseEvent(self ,e):
        self.pmoving = -1
        if self.path.contains_m( e.pos() ) == -2 : 
            self.pselected = -2   # release selected mode
            self.update()

    def paintEvent (self,e): # もっとすっきり書きたいけどなあ
       
        painter= QPainter()
        painter.begin(self) 
        if self.pselected==-2:
            painter.setPen( QPen( Qt.black , 2, style=Qt.SolidLine ) )
            painter.drawPath(self.path)
        elif self.pselected >=-1:
            painter.setPen( QPen( Qt.blue , 2, style=Qt.SolidLine ) )
            painter.drawPath(self.path)
            for i,l in enumerate(self.path.pointrect) :
                type1=self.path.elementAt(i).type
                type2=self.path.elementAt(i-1).type
                if type1==0 or type1==1 or (type1==3 and type2==3):
                    painter.drawRect(l[1])
        if self.pselected >=0:
            painter.setPen( QPen( Qt.red , 2, style=Qt.SolidLine ) )
            p = QPointF( self.path.elementAt(self.pselected).x , self.path.elementAt(self.pselected).y)

            p2 = QPointF( self.path.elementAt(1).x , self.path.elementAt(1).y) \
                if self.pselected==self.path.elementCount()-1 else \
                QPointF( self.path.elementAt(self.pselected+1).x , self.path.elementAt(self.pselected+1).y)

            p3 = QPointF( self.path.elementAt(self.path.elementCount()-2).x , 
                self.path.elementAt(self.path.elementCount()-2).y) if self.pselected==0 else \
                QPointF( self.path.elementAt(self.pselected-1).x , self.path.elementAt(self.pselected-1).y)
 
            painter.drawRect( QRectF( p-self.a , p+self.a)) 
            if self.pselected!=self.path.elementCount()-1 :
                painter.drawLine( p , p2 ) 
                painter.drawRect( QRectF( p2-self.a , p2+self.a)) 
            if self.pselected!=0  or self.path.closed :
                painter.drawLine( p , p3 ) 
                painter.drawRect( QRectF( p3-self.a , p3+self.a)) 
 
        painter.end() 

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex = makepath()
    sys.exit(app.exec_())

しかし

  • パスオブジェクトにポイントの追加はできるけど、挿入のメソッドは見当たらないし削除も無い
    これではオーサリングは出来ん!!!!(¯―¯٥)
  • リストベースのほうが自由度高いな
    当たり前か
    戻そうかな??
  • 派生クラスに実装しようかな??
  • クラスの中で一旦リストに書き出してポイントを追加・削除して最初からパスを作りなおせば良い( ´∀`)
  • 今のコードでほぼ本体は出来たからそこには手を付けないで、派生クラスだけ書き直せば安全だ
  • うむ

--> PyQt でベクターオブジェクト作成ツールを作る #

END

Close