Edward
Edward

Reputation: 21

With QPainter, I created a path, duplicated it and connected their corresponding points with straight lines, how do I fill the new shapes?

So I'm trying to create a fake 3D / isometric extrusion effect by creating a QPainterPath, duplicating and offsetting it, converting both to polygons, extracting their points and finally connecting these points with straight lines. This is the result:

I can't figure out a way to fill the inner lines with color, any help?

Here's the full code to run the app from the .gif posted above:

from PyQt6.QtGui import QPainter, QPainterPath, QFont, QPen, QBrush, QColor
from PyQt6.QtCore import QPointF, Qt
from PyQt6.QtWidgets import QApplication, QWidget, QSlider, QVBoxLayout
import sys
import math

class TextPathPoints(QWidget):
    def __init__(self):
        super().__init__()

        self.resize(800, 300)

        # Create a QPainterPath with text
        self.font = QFont("Super Dessert", 120)  # Use a valid font
        self.path = QPainterPath()
        self.path.addText(100, 200, self.font, "HELLO!")

        # Control variables for extrusion
        self.extrusion_length = 25  # Length of extrusion
        self.extrusion_angle = 45  # Angle in degrees (measured counterclockwise from the positive x-axis)

        layout = QVBoxLayout()

        # Create slider for extrusion length (range 0-100, step 1)
        self.length_slider = QSlider()
        self.length_slider.setRange(0, 100)
        self.length_slider.setValue(self.extrusion_length)
        self.length_slider.setTickInterval(1)
        self.length_slider.valueChanged.connect(self.update_extrusion_length)
        layout.addWidget(self.length_slider)

        # Create slider for extrusion angle (range 0-360, step 1)
        self.angle_slider = QSlider()
        self.angle_slider.setRange(0, 360)
        self.angle_slider.setValue(self.extrusion_angle)
        self.angle_slider.setTickInterval(1)
        self.angle_slider.valueChanged.connect(self.update_extrusion_angle)
        layout.addWidget(self.angle_slider)

        self.setLayout(layout)

    def update_extrusion_length(self, value):
        self.extrusion_length = value
        self.update()  # Trigger repaint to update the path

    def update_extrusion_angle(self, value):
        self.extrusion_angle = value
        self.update()  # Trigger repaint to update the path

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)

        painter.setRenderHint(QPainter.RenderHint.Antialiasing)

        # Convert angle to radians
        angle_rad = math.radians(self.extrusion_angle)

        # Calculate x and y offsets based on extrusion length and angle
        self.offset_x = self.extrusion_length * math.cos(angle_rad)
        self.offset_y = self.extrusion_length * math.sin(angle_rad)

        # Duplicate the path
        self.duplicated_path = QPainterPath(self.path)  # Duplicate the original path
        self.duplicated_path.translate(self.offset_x, self.offset_y)  # Offset using calculated values

        # Convert paths to polygons
        original_polygon = self.path.toFillPolygon()
        duplicated_polygon = self.duplicated_path.toFillPolygon()

        # Extract points from polygons
        self.original_points = [(p.x(), p.y()) for p in original_polygon]
        self.duplicated_points = [(p.x(), p.y()) for p in duplicated_polygon]

        # Set brush for filling the path
        brush = QBrush(QColor("#ebd086"))  # Front and back fill
        painter.setBrush(brush)

        # Fill the original path
        painter.fillPath(self.path, brush)

        # Set pen for drawing lines between points
        pen = QPen()
        pen.setColor(QColor("black"))  # Color of the lines
        pen.setWidthF(1.4)
        painter.setPen(pen)
        pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
        pen.setCapStyle(Qt.PenCapStyle.RoundCap)

        # Draw duplicated path
        painter.drawPath(self.duplicated_path)

        # Connect corresponding points between the original and duplicated paths
        num_points = min(len(self.original_points), len(self.duplicated_points))
        for i in range(num_points):
            original_x, original_y = self.original_points[i]
            duplicated_x, duplicated_y = self.duplicated_points[i]
            painter.drawLine(QPointF(original_x, original_y), QPointF(duplicated_x, duplicated_y))

        # Draw the original path
        painter.drawPath(self.path)


app = QApplication(sys.argv)
window = TextPathPoints()
window.show()
sys.exit(app.exec())


*Updated to fill the sides, there's still problems with see through side walls:

from PyQt6.QtGui import QPainter, QPainterPath, QPen, QBrush, QColor
from PyQt6.QtCore import QPointF, Qt
from PyQt6.QtWidgets import QApplication, QWidget, QSlider, QVBoxLayout
import sys
import math


class ExtrudeWidget(QWidget):
    def __init__(self):
        super().__init__()

        self.resize(800, 300)

        # Create a QPainterPath with rect
        self.path = QPainterPath()
        self.path.addRect(200, 100, 200, 50)

        # Control variables for extrusion
        self.extrusion_length = 15  # Length of extrusion
        self.extrusion_angle = 135  # Angle in degrees (measured counterclockwise from the positive x-axis)

        layout = QVBoxLayout()

        # Create slider for extrusion length (range 0-100, step 1)
        self.length_slider = QSlider()
        self.length_slider.setRange(0, 100)
        self.length_slider.setValue(self.extrusion_length)
        self.length_slider.setTickInterval(1)
        self.length_slider.valueChanged.connect(self.update_extrusion_length)
        layout.addWidget(self.length_slider)

        # Create slider for extrusion angle (range 0-360, step 1)
        self.angle_slider = QSlider()
        self.angle_slider.setRange(0, 360)
        self.angle_slider.setValue(self.extrusion_angle)
        self.angle_slider.setTickInterval(1)
        self.angle_slider.valueChanged.connect(self.update_extrusion_angle)
        layout.addWidget(self.angle_slider)

        self.setLayout(layout)

    def update_extrusion_length(self, value):
        self.extrusion_length = value
        self.update()  # Trigger repaint to update the path

    def update_extrusion_angle(self, value):
        self.extrusion_angle = value
        self.update()  # Trigger repaint to update the path

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)

        # Convert angle to radians
        angle_rad = math.radians(self.extrusion_angle)

        # Calculate x and y offsets based on extrusion length and angle
        offset_x = self.extrusion_length * math.cos(angle_rad)
        offset_y = self.extrusion_length * math.sin(angle_rad)

        # Duplicate the path
        duplicated_path = QPainterPath(self.path)  # Duplicate the original path
        duplicated_path.translate(offset_x, offset_y)  # Offset using calculated values

        # Convert paths to polygons
        original_polygon = self.path.toFillPolygon()
        duplicated_polygon = duplicated_path.toFillPolygon()

        # Extract points from polygons
        original_points = [(p.x(), p.y()) for p in original_polygon]
        duplicated_points = [(p.x(), p.y()) for p in duplicated_polygon]

        # Create brushes
        main_brush = QBrush(QColor("#ebd086"))
        side_brush = QBrush(QColor("#c4a56a"))
        # Define a list of colors to cycle through for debugging
        debug_colors = [
            QColor("#ff0000"),  # Red
            QColor("#00ff00"),  # Green
            QColor("#0000ff"),  # Blue
            QColor("#ffff00"),  # Yellow
            QColor("#ff00ff"),  # Magenta
            QColor("#00ffff"),  # Cyan
            QColor("#ff7f00")  # Orange
        ]

        # Fill the original path
        painter.fillPath(self.path, main_brush)

        # Set pen for drawing lines
        pen = QPen()
        pen.setColor(QColor("black"))  # Color of the lines
        pen.setWidthF(1)
        painter.setPen(pen)
        pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
        pen.setCapStyle(Qt.PenCapStyle.RoundCap)

        # Draw duplicated path
        painter.drawPath(duplicated_path)

        # Create and connect side polygons
        num_points = min(len(original_points), len(duplicated_points))
        for i in range(num_points - 1):  # Iterate over pairs of points
            orig_p1 = QPointF(*original_points[i])
            orig_p2 = QPointF(*original_points[i + 1])
            dup_p1 = QPointF(*duplicated_points[i])
            dup_p2 = QPointF(*duplicated_points[i + 1])

            # Create a side polygon
            side_face = QPainterPath()
            side_face.moveTo(orig_p1)
            side_face.lineTo(orig_p2)
            side_face.lineTo(dup_p2)
            side_face.lineTo(dup_p1)
            side_face.closeSubpath()

            # Choose a color from the debug_colors list based on the index
            side_color = debug_colors[i % len(debug_colors)]  # Cycle through the colors
            side_brush = QBrush(side_color)  # Use the color for filling

            # Fill the side with the chosen color
            painter.fillPath(side_face, side_brush)

            # Draw the outlines
            painter.drawPath(side_face)

        # Draw the original path
        painter.setBrush(main_brush)
        painter.drawPath(self.path)


app = QApplication(sys.argv)
window = ExtrudeWidget()
window.show()
sys.exit(app.exec())

Upvotes: 1

Views: 81

Answers (0)

Related Questions