--- Title: ベクタパス作成ツール 4 Author: yamasyuh68 Web: https://mimemo.io/m/OX6eWonrjPowPDQ --- 190719 # 書換えほぼ完了 https://live.staticflickr.com/65535/48326497707_9476e26daf_z.jpg - パス操作ベースにした、これが本来だと思う そのための派生クラスを作ってある程度の操作はクラス自身にやらせるようにした - ポイントの矩形の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 でベクターオブジェクト作成ツールを作る #](https://mimemo.io/m/3Rx1XoR367le95E)