2

I have from previous codelines developed and added some special behaviour. The idea is to draw small circle shapes on a certain path inside rectangular shape and circular shape and place them according to the distance given in advance. I want to draw automatically and programmatically based on given distance by user. ie. distance and number will vary.

I have looked myself at this exercise, and reached to solve this with very complicated for-loops and long codelines. I am afraid of repeating same procedure here.

What I want to achieve:

enter image description here

enter image description here

In case of rectangular shape, size of circle is given by:

self._rebar = QtCore.QSizeF(yb, yb)

and vertical distance of each circle measured from top to circle center is given by:

cb, ct, ys, yb, yt = 25, 25, 8, 12, 12
di, dis = h-cb-ys-yb/2, ct+ys+yt/2
self.db, self.dt = [di, di, di, di-25, di-25, di-25], [dis, dis, dis, dis+25, dis+25]

Think that next input would increase or decrease number of rebars reasoning less or more distance values. That I would be able to plot and draw them orderly and regularly.

Input to circular shapes.

cb, ys, yb = 25, 8, 12
self.As = [yb, yb, yb, yb, yb, yb, yb]
self._rebar = QtCore.QSizeF(yb, yb)

In case of circle is there a way to draw and follow a circular path and place circles with arc angles? What I mean by that, I think the easist way to measure first number of rebars and have a distance then with a arc angle and length would be able to place and organise them in order, no matter what the number of circles items would become!

I appreciate any easy and smart way to come around that and to avoid long coding.

Explaining rectangular shape:

enter image description here

To your information, it is that so, list self.db = [di, di, di, di+25, di+25, di+25] would pass to function coming from another class, whic already has calculated how many circles would be in one layer, ie. for exampel, self.db = [200, 200, 180, 180] means that there would only be 2 circles at one layer and 2 in other layer. So beff would only contain 2 circles in first layer. in that case needs to find out how to place them in order and regularly.

I corrected this line of code self.db, self.dt = [di, di, di, di-25, di-25, di-25], [dis, dis, dis, dis+25, dis+25]

enter image description here

Layers

enter image description here

The code:

from PyQt5 import QtCore, QtGui, QtWidgets

class Foo(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Foo, self).__init__(parent)
        self.setGeometry(QtCore.QRect(200, 100, 800, 800))

        self.button = Button()
        self.paint = Createpaintwidget()
        self.button.valuesChanged.connect(self.paint.set_size_squares)
        self.button.valueChanged.connect(self.paint.set_size_round)

        self.lay = QtWidgets.QVBoxLayout(self)
        self.lay.addWidget(self.paint)
        self.lay.addWidget(self.button)

class Createpaintwidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.sizeHint()        
        self.setBackgroundRole(QtGui.QPalette.Base)     
        self.setAutoFillBackground(True)

        self._size = QtCore.QSizeF()
        self._path = QtGui.QPainterPath()
        self._rect = QtCore.QRectF()
        self._type = QtGui.QRegion.Rectangle
        self._factor = 1.0

        self._sizeouter = QtCore.QSizeF()
        self._rectouter = QtCore.QRectF()
        self._sizeinner = QtCore.QSizeF()
        self._rectinner = QtCore.QRectF()
        self._rebar = QtCore.QSizeF()
        self._rectrebar = QtCore.QRectF()

        self._pos = QtCore.QPointF()
        self._initial_flag = False
        fnt = self.font() 
        fnt.setPointSize(20) 
        self.setFont(fnt) 

    def showEvent(self, event):
        if not self._initial_flag:
            self._pos = self.rect().center()
            self._initial_flag = True

    @QtCore.pyqtSlot(int, int)
    def set_size_squares(self, w, h):
        cb, ct, ys, yb, yt = 25, 25, 8, 12, 12
        di, dis, ccb, cct = h-cb-ys-yb/2, ct+ys+yt/2, cb+ys+0.5*yb, ct+ys+0.5*yt
        self.db, self.dt = [di, di, di, di-25, di-25, di-25], [dis, dis, dis, dis+25, dis+25]
        beff_b, beff_t = w - 2*ccb, w - 2*cct
        db_d = dict(set((x,self.db.count(x)) for x in filter(lambda rec : self.db.count(rec)>1,self.db)))
        db_l = [k for k in dict.values(db_d)]
        dt_d = dict(set((x,self.dt.count(x)) for x in filter(lambda rec : self.dt.count(rec)>1,self.dt)))
        dt_l = [k for k in dict.values(dt_d)]
        xb, xt = [beff_b/(k-1) for k in db_l], [beff_t/(k-1) for k in dt_l]
        self.dbx, self.dtx = [], []
        start = 0 
        for k, b in enumerate(db_l): 
            sub_A = self.db[start:start+b] 
            start = start+b 
            for i, _ in enumerate(sub_A): 
                self.dbx.append(ccb+i*xb[k])
        start = 0 
        for k, b in enumerate(dt_l): 
            sub_A = self.dt[start:start+b] 
            start = start+b 
            for i, _ in enumerate(sub_A): 
                self.dtx.append(cct+i*xt[k])


        self._path = QtGui.QPainterPath()
        self._size = QtCore.QSizeF(w, h)
        self._sizeouter = QtCore.QSizeF(w-cb, h-ct)
        self._sizeinner = QtCore.QSizeF(w-cb-ys, h-ct-ys)
        self._rebar = QtCore.QSizeF(yb, yb)
        self._type = QtGui.QRegion.Rectangle
        self.updatePath()


    @QtCore.pyqtSlot(int)
    def set_size_round(self, v):
        cb, ys, yb = 25, 8, 12
        self.As = [yb, yb, yb, yb, yb, yb, yb]
        self._path = QtGui.QPainterPath()
        self._size = QtCore.QSizeF(v, v)
        self._sizeouter = QtCore.QSizeF(v-cb, v-cb)
        self._sizeinner = QtCore.QSizeF(v-cb-ys, v-cb-ys)
        self._rebar = QtCore.QSizeF(yb, yb)
        self._type = QtGui.QRegion.Ellipse
        self.updatePath()

    def paintEvent(self, event):
        pen = QtGui.QPen()
        brush = QtGui.QBrush(QtCore.Qt.black)
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        painter.setPen(pen)
        painter.setBrush(brush)

        painter.translate(self.rect().center())
        painter.scale(self._factor, self._factor)
        painter.translate(-self.rect().center())

        painter.translate(self._pos)
        painter.drawPath(self._path)



        S = (self._rectouter.size() + self._rectinner.size())/2
        s = (self._rectouter.size() - self._rectinner.size())/2
        r = QtCore.QRectF(QtCore.QPointF(), S)
        r.moveCenter(self._rectouter.center())
        path = QtGui.QPainterPath()
        painter.setBrush(QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))

        if self._type == QtGui.QRegion.Rectangle:
            painter.drawRect(self._rect)
            path.addRoundedRect(r, 10, 10)

            painter.setBrush(QtGui.QBrush(QtCore.Qt.cyan, QtCore.Qt.SolidPattern))
            painter.drawEllipse(self._rectrebar)

        elif self._type == QtGui.QRegion.Ellipse:
            painter.drawEllipse(self._rect)
            path.addEllipse(r)

            painter.setBrush(QtGui.QBrush(QtCore.Qt.cyan, QtCore.Qt.SolidPattern))
            painter.drawEllipse(self._rectrebar)

        stroker = QtGui.QPainterPathStroker()
        stroker.setWidth(s.width())
        stroke_path = stroker.createStroke(path)
        painter.setBrush(QtGui.QBrush(QtCore.Qt.cyan, QtCore.Qt.SolidPattern))
        painter.drawPath(stroke_path)

    def mousePressEvent(self, event):
        QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
        self._initial_pos = event.pos()
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        delta = event.pos() - self._initial_pos
        self._path.translate(delta)
        self._rect.translate(delta)
        self._rectinner.translate(delta)
        self._rectouter.translate(delta)
        self._rectrebar.translate(delta)
        self.update()
        self._initial_pos = event.pos()
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        QtWidgets.QApplication.restoreOverrideCursor()
        super().mouseReleaseEvent(event)

    def updatePath(self):
        r = QtCore.QRectF(QtCore.QPointF(), self._size)
        ro = QtCore.QRectF(QtCore.QPointF(), self._sizeouter)
        ri = QtCore.QRectF(QtCore.QPointF(), self._sizeinner)
        rr = QtCore.QRectF(QtCore.QPointF(), self._rebar)
        r.moveCenter(QtCore.QPointF())
        ro.moveCenter(QtCore.QPointF())
        ri.moveCenter(QtCore.QPointF())
        rr.moveCenter(QtCore.QPointF())

        self._rectouter = QtCore.QRectF(ro)
        self._rectinner = QtCore.QRectF(ri)
        self._rect = QtCore.QRectF(r)
        self._rectrebar = QtCore.QRectF(rr)     
        self.update()

    def wheelEvent(self, event):
        self._factor *= 1.01**(event.angleDelta().y()/15.0)
        self.update()
        super().wheelEvent(event)

class Button(QtWidgets.QWidget):
    valueChanged = QtCore.pyqtSignal(int)
    valuesChanged = QtCore.pyqtSignal(int,int)
    def __init__(self, parent=None):
        super(Button, self).__init__(parent)
        roundbutton = QtWidgets.QPushButton('Round')
        squarebutton = QtWidgets.QPushButton('Square')
        Alay = QtWidgets.QVBoxLayout(self)
        Alay.addWidget(roundbutton)
        Alay.addWidget(squarebutton)
        self.value = QtWidgets.QLabel()
        roundbutton.clicked.connect(self.getbuttonfunc)
        squarebutton.clicked.connect(self.sqaurebuttonfunc)

    @QtCore.pyqtSlot()
    def getbuttonfunc(self):
        number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
                                         self.tr("Input:"), 1, 1)
        if ok:
            self.valueChanged.emit(number)

    @QtCore.pyqtSlot()
    def sqaurebuttonfunc(self):
        number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
                                         self.tr("Input:"), 1, 1)
        if ok:
            self.valuesChanged.emit(number, number)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Foo()
    w.show()
    sys.exit(app.exec_())

Update code:

@QtCore.pyqtSlot(int, int)
def set_size_squares(self, w, h):
    cb, ct, ys, yb, yt = 25, 25, 8, 12, 12
    di, dis, ccb, cct = h-cb-ys-yb/2, ct+ys+yt/2, cb+ys+0.5*yb, ct+ys+0.5*yt
    self.db, self.dt = [di, di, di, di-25, di-25, di-25], [dis, dis, dis, dis+25, dis+25]
    beff_b, beff_t = w - 2*ccb, w - 2*cct
    db_d = dict(set((x,self.db.count(x)) for x in filter(lambda rec : self.db.count(rec)>1,self.db)))
    db_l = [k for k in dict.values(db_d)]
    dt_d = dict(set((x,self.dt.count(x)) for x in filter(lambda rec : self.dt.count(rec)>1,self.dt)))
    dt_l = [k for k in dict.values(dt_d)]
    xb, xt = [beff_b/(k-1) for k in db_l], [beff_t/(k-1) for k in dt_l]
    self.dbx, self.dtx = [], []
    start = 0 
    for k, b in enumerate(db_l): 
        sub_A = self.db[start:start+b] 
        start = start+b 
        for i, _ in enumerate(sub_A): 
            self.dbx.append(ccb+i*xb[k])
    start = 0 
    for k, b in enumerate(dt_l): 
        sub_A = self.dt[start:start+b] 
        start = start+b 
        for i, _ in enumerate(sub_A): 
            self.dtx.append(cct+i*xt[k])

                  ----------

The y coordinates for bottom circles and corresponding x coordinates given in list:

self.dbx = [x2,x3,x1,x5,x6,x4]
self.db = [y2, y3, y1, y5, y6, y4]

The y coordinates for top circles and corresponding x coordinates given in list:

self.dtx = [x2,x3,x1,x5,x4]
self.dt = [y2, y3, y1, y5, y4]
Pavel.D
  • 561
  • 1
  • 15
  • 41
  • Yes exactly. Regarding those inside rectangular shape, I would explain much more and update my code, I would notify when I do update that, – Pavel.D Dec 14 '18 at 20:18
  • See my update, hope that helps understanding, I know it is complicated. – Pavel.D Dec 14 '18 at 21:22
  • See my update image regarding layers. could you tell me how can I calculate length of similar values in list `self.db = [200, 200, 180, 180]`, not meaing len(self.db), then I give you calculation of x1, x2 and x3. – Pavel.D Dec 14 '18 at 21:50
  • I still do not understand you, in the image that you point out for that case you can not see the second layer: https://i.stack.imgur.com/ElXbR.png, what is the distance between the layers ?, I think to understand all the logic of the positions of the points would imply reading a complete manual which would not do it for free – eyllanesc Dec 14 '18 at 21:54
  • I understand and do not read any manual for free do not make any calculation, I would calculate all (x,y) positions and update my code, I notify when they are ready.. – Pavel.D Dec 14 '18 at 22:10
  • Could you be nice and look at my update, I have given x and y coordinates. – Pavel.D Dec 15 '18 at 22:20
  • Are these values about what coordinate systems? – eyllanesc Dec 15 '18 at 22:34
  • About x and y coordinates as shown on image, these global x and y coordinate is placed at topleft of square shape. – Pavel.D Dec 15 '18 at 22:43
  • see my answer.. – eyllanesc Dec 15 '18 at 22:43
  • some feedback?? – eyllanesc Dec 15 '18 at 23:01
  • I just right now came to my laptop. get back with feedback – Pavel.D Dec 15 '18 at 23:04

1 Answers1

1

Considering the lists of positions that you have provided and that are regarding the topleft of the rectangle the result is the following:

import math
from PyQt5 import QtCore, QtGui, QtWidgets

class Foo(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Foo, self).__init__(parent)
        self.setGeometry(QtCore.QRect(200, 100, 800, 800))

        self.button = Button()
        self.paint = Createpaintwidget()
        self.button.valuesChanged.connect(self.paint.set_size_squares)
        self.button.valueChanged.connect(self.paint.set_size_round)

        self.lay = QtWidgets.QVBoxLayout(self)
        self.lay.addWidget(self.paint)
        self.lay.addWidget(self.button)

class Createpaintwidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.sizeHint()        
        self.setBackgroundRole(QtGui.QPalette.Base)     
        self.setAutoFillBackground(True)

        self._size = QtCore.QSizeF()
        self._path = QtGui.QPainterPath()
        self._rect = QtCore.QRectF()
        self._type  = None
        self._factor = 1.0

        self._sizeouter = QtCore.QSizeF()
        self._rectouter = QtCore.QRectF()
        self._sizeinner = QtCore.QSizeF()
        self._rectinner = QtCore.QRectF()
        self._rebar = QtCore.QSizeF()
        self._rectrebar = QtCore.QRectF()

        self._pos = QtCore.QPointF()
        self._initial_flag = False
        fnt = self.font() 
        fnt.setPointSize(20) 
        self.setFont(fnt) 

    def showEvent(self, event):
        if not self._initial_flag:
            self._pos = self.rect().center()
            self._initial_flag = True

    @QtCore.pyqtSlot(int, int)
    def set_size_squares(self, w, h):
        cb, ct, ys, yb, yt = 25, 25, 8, 12, 12
        di, dis, ccb, cct = h-cb-ys-yb/2, ct+ys+yt/2, cb+ys+0.5*yb, ct+ys+0.5*yt
        self.db, self.dt = [di, di, di, di-25, di-25, di-25], [dis, dis, dis, dis+25, dis+25]
        beff_b, beff_t = w - 2*ccb, w - 2*cct
        db_d = dict(set((x,self.db.count(x)) for x in filter(lambda rec : self.db.count(rec)>1,self.db)))
        db_l = [k for k in dict.values(db_d)]
        dt_d = dict(set((x,self.dt.count(x)) for x in filter(lambda rec : self.dt.count(rec)>1,self.dt)))
        dt_l = [k for k in dict.values(dt_d)]
        xb, xt = [beff_b/(k-1) for k in db_l], [beff_t/(k-1) for k in dt_l]
        self.dbx, self.dtx = [], []
        start = 0 
        for k, b in enumerate(db_l): 
            sub_A = self.db[start:start+b] 
            start = start+b 
            for i, _ in enumerate(sub_A): 
                self.dbx.append(ccb+i*xb[k])
        start = 0 
        for k, b in enumerate(dt_l): 
            sub_A = self.dt[start:start+b] 
            start = start+b 
            for i, _ in enumerate(sub_A): 
                self.dtx.append(cct+i*xt[k])

        self._path = QtGui.QPainterPath()
        self._size = QtCore.QSizeF(w, h)
        self._sizeouter = QtCore.QSizeF(w-cb, h-ct)
        self._sizeinner = QtCore.QSizeF(w-cb-ys, h-ct-ys)
        self._rebar = QtCore.QSizeF(yb, yb)
        self._type = QtGui.QRegion.Rectangle
        self.updatePath()


    @QtCore.pyqtSlot(int)
    def set_size_round(self, v):
        cb, ys, yb = 25, 8, 12
        self.As = [yb, yb, yb, yb, yb, yb, yb]
        self._path = QtGui.QPainterPath()
        self._size = QtCore.QSizeF(v, v)
        self._sizeouter = QtCore.QSizeF(v-cb, v-cb)
        self._sizeinner = QtCore.QSizeF(v-cb-ys, v-cb-ys)
        self._rebar = QtCore.QSizeF(yb, yb)
        self._type = QtGui.QRegion.Ellipse
        self.updatePath()

    def paintEvent(self, event):
        pen = QtGui.QPen()
        brush = QtGui.QBrush(QtCore.Qt.black)
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        painter.setPen(pen)
        painter.setBrush(brush)

        painter.translate(self.rect().center())
        painter.scale(self._factor, self._factor)
        painter.translate(-self.rect().center())

        painter.translate(self._pos)
        painter.drawPath(self._path)



        S = (self._rectouter.size() + self._rectinner.size())/2
        s = (self._rectouter.size() - self._rectinner.size())/2
        r = QtCore.QRectF(QtCore.QPointF(), S)
        r.moveCenter(self._rectouter.center())
        path = QtGui.QPainterPath()
        painter.setBrush(QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))

        if self._type == QtGui.QRegion.Rectangle:
            painter.drawRect(self._rect)
            path.addRoundedRect(r, 10, 10)
            _r = QtCore.QRectF(self._rectrebar)
            for x, y in zip(self.dtx + self.dbx , self.dt + self.db):
                _r.moveCenter(QtCore.QPointF(x, y) + self._rect.topLeft())
                painter.setBrush(QtGui.QBrush(QtCore.Qt.cyan, QtCore.Qt.SolidPattern))
                painter.drawEllipse(_r)

        elif self._type == QtGui.QRegion.Ellipse:
            painter.drawEllipse(self._rect)
            path.addEllipse(r)
            painter.setBrush(QtGui.QBrush(QtCore.Qt.cyan, QtCore.Qt.SolidPattern))
            n = 7
            for i, _ in enumerate(self.As):
                angle = (i*2.0/n + 1)*math.pi
                radius = .5 *(r.width() - s.width() - self._rectrebar.width())
                dx = radius*QtCore.QPointF(math.cos(angle), math.sin(angle))
                r_ = QtCore.QRectF(self._rectrebar)
                r_.translate(dx)
                painter.drawEllipse(r_)

        stroker = QtGui.QPainterPathStroker()
        stroker.setWidth(s.width())
        stroke_path = stroker.createStroke(path)
        painter.setBrush(QtGui.QBrush(QtCore.Qt.cyan, QtCore.Qt.SolidPattern))
        painter.drawPath(stroke_path)

    def mousePressEvent(self, event):
        QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
        self._initial_pos = event.pos()
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        delta = event.pos() - self._initial_pos
        self._path.translate(delta)
        self._rect.translate(delta)
        self._rectinner.translate(delta)
        self._rectouter.translate(delta)
        self._rectrebar.translate(delta)
        self.update()
        self._initial_pos = event.pos()
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        QtWidgets.QApplication.restoreOverrideCursor()
        super().mouseReleaseEvent(event)

    def updatePath(self):
        r = QtCore.QRectF(QtCore.QPointF(), self._size)
        ro = QtCore.QRectF(QtCore.QPointF(), self._sizeouter)
        ri = QtCore.QRectF(QtCore.QPointF(), self._sizeinner)
        rr = QtCore.QRectF(QtCore.QPointF(), self._rebar)
        r.moveCenter(QtCore.QPointF())
        ro.moveCenter(QtCore.QPointF())
        ri.moveCenter(QtCore.QPointF())
        rr.moveCenter(QtCore.QPointF())

        self._rectouter = QtCore.QRectF(ro)
        self._rectinner = QtCore.QRectF(ri)
        self._rect = QtCore.QRectF(r)
        self._rectrebar = QtCore.QRectF(rr)     
        self.update()

    def wheelEvent(self, event):
        self._factor *= 1.01**(event.angleDelta().y()/15.0)
        self.update()
        super().wheelEvent(event)

class Button(QtWidgets.QWidget):
    valueChanged = QtCore.pyqtSignal(int)
    valuesChanged = QtCore.pyqtSignal(int,int)
    def __init__(self, parent=None):
        super(Button, self).__init__(parent)
        roundbutton = QtWidgets.QPushButton('Round')
        squarebutton = QtWidgets.QPushButton('Square')
        Alay = QtWidgets.QVBoxLayout(self)
        Alay.addWidget(roundbutton)
        Alay.addWidget(squarebutton)
        self.value = QtWidgets.QLabel()
        roundbutton.clicked.connect(self.getbuttonfunc)
        squarebutton.clicked.connect(self.sqaurebuttonfunc)

    @QtCore.pyqtSlot()
    def getbuttonfunc(self):
        number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
                                         self.tr("Input:"), 1, 1)
        if ok:
            self.valueChanged.emit(number)

    @QtCore.pyqtSlot()
    def sqaurebuttonfunc(self):
        number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
                                         self.tr("Input:"), 1, 1)
        if ok:
            self.valuesChanged.emit(number, number)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Foo()
    w.show()
    sys.exit(app.exec_())

enter image description here

enter image description here

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Perfect, your solution is excellent, especially for circle shape, I remember I do it with python and tkinter, it was a long code, but you solve with few lines. It seems circles are slightly placed inner for rectangular shape, but maybe my coordinates should adjust. Thanks very much, That helps me a lot. – Pavel.D Dec 15 '18 at 23:25
  • @ZarKha Remember that in your task there are 2 processes: calculate the positions and paint the circles given those positions. the task of PyQt is only to paint, the part of the calculation of positions has nothing to do with PyQt. – eyllanesc Dec 15 '18 at 23:28
  • You are right, and you just have responsibility of PyQt part. I am aware of that, and feel my exercises getting heavy... – Pavel.D Dec 15 '18 at 23:33