import math from krita import Krita, ManagedColor from PyQt5.QtCore import QPointF from PyQt5.QtWidgets import ( QCheckBox, QComboBox, QDialog, QDoubleSpinBox, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, ) class GridDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Grid Generator") self.setup_ui() def setup_ui(self): layout = QVBoxLayout(self) # Thickness thickness_layout = QHBoxLayout() thickness_layout.addWidget(QLabel("Line Thickness (px):")) self.thickness_spin = QDoubleSpinBox() self.thickness_spin.setRange(0.1, 1000.0) self.thickness_spin.setValue(2.0) thickness_layout.addWidget(self.thickness_spin) layout.addLayout(thickness_layout) # Width width_layout = QHBoxLayout() width_layout.addWidget(QLabel("Grid Width (px):")) self.width_spin = QDoubleSpinBox() self.width_spin.setRange(1.0, 10000.0) self.width_spin.setValue(100.0) self.width_spin.valueChanged.connect(self.on_width_changed) width_layout.addWidget(self.width_spin) layout.addLayout(width_layout) # Height height_layout = QHBoxLayout() height_layout.addWidget(QLabel("Grid Height (px):")) self.height_spin = QDoubleSpinBox() self.height_spin.setRange(1.0, 10000.0) self.height_spin.setValue(100.0) self.height_spin.valueChanged.connect(self.on_height_changed) height_layout.addWidget(self.height_spin) layout.addLayout(height_layout) # Lock Width/Height self.lock_check = QCheckBox("Lock Aspect Ratio") self.lock_check.setChecked(True) layout.addWidget(self.lock_check) # Grid Type type_layout = QHBoxLayout() type_layout.addWidget(QLabel("Grid Type:")) self.type_combo = QComboBox() self.type_combo.addItems(["Square", "Hexagon"]) type_layout.addWidget(self.type_combo) layout.addLayout(type_layout) # Layer Option layer_layout = QHBoxLayout() layer_layout.addWidget(QLabel("Generate on:")) self.layer_combo = QComboBox() self.layer_combo.addItems(["New Layer", "Current Layer"]) layer_layout.addWidget(self.layer_combo) layout.addLayout(layer_layout) # Buttons button_layout = QHBoxLayout() self.ok_button = QPushButton("Generate") self.ok_button.clicked.connect(self.generate) self.cancel_button = QPushButton("Cancel") self.cancel_button.clicked.connect(self.reject) button_layout.addWidget(self.ok_button) button_layout.addWidget(self.cancel_button) layout.addLayout(button_layout) def on_width_changed(self, value): if self.lock_check.isChecked(): self.height_spin.blockSignals(True) self.height_spin.setValue(value) self.height_spin.blockSignals(False) def on_height_changed(self, value): if self.lock_check.isChecked(): self.width_spin.blockSignals(True) self.width_spin.setValue(value) self.width_spin.blockSignals(False) def generate(self): app = Krita.instance() doc = app.activeDocument() if not doc: return window = app.activeWindow() if not window: return thickness = self.thickness_spin.value() width = self.width_spin.value() height = self.height_spin.value() grid_type = self.type_combo.currentText() layer_option = self.layer_combo.currentText() # Determine target layer target_node = None if layer_option == "New Layer": target_node = doc.createNode("grid", "vectorlayer") doc.rootNode().addChildNode(target_node, None) else: target_node = doc.activeNode() # If current layer is not a vector layer, we should probably create one above it # because SVG drawing only works on vector layers in Krita's API. if target_node and target_node.type() != "vectorlayer": parent = target_node.parentNode() new_vector = doc.createNode("grid", "vectorlayer") parent.addChildNode(new_vector, target_node) target_node = new_vector if not target_node: target_node = doc.createNode("grid", "vectorlayer") doc.rootNode().addChildNode(target_node, None) self.draw_grid(target_node, doc, thickness, width, height, grid_type) doc.refreshProjection() self.accept() def draw_grid(self, node, doc, thickness, width, height, grid_type): if grid_type == "Square": self.draw_square_grid(node, doc, thickness, width, height) else: self.draw_hexagon_grid(node, doc, thickness, width, height) def draw_square_grid(self, node, doc, thickness, width, height): svg = f'' # Vertical lines x = 0 while x <= doc.width(): svg += f'' x += width # Horizontal lines y = 0 while y <= doc.height(): svg += f'' y += height svg += "" node.addShapesFromSvg(svg) def draw_hexagon_grid(self, node, doc, thickness, width, height): # Pointy topped hex grid # Horizontal spacing = width # Vertical spacing = height * 0.75 svg = f'' v_spacing = height * 0.75 h_spacing = width rows = int(doc.height() / v_spacing) + 2 cols = int(doc.width() / h_spacing) + 2 for r in range(rows): offset_x = (width / 2) if (r % 2 == 1) else 0 for c in range(cols): cx = c * h_spacing + offset_x cy = r * v_spacing # Vertices for pointy-topped hex v = [ (cx, cy - height / 2), (cx + width / 2, cy - height / 4), (cx + width / 2, cy + height / 4), (cx, cy + height / 2), (cx - width / 2, cy + height / 4), (cx - width / 2, cy - height / 4), ] points = " ".join([f"{px},{py}" for px, py in v]) svg += f'' svg += "" node.addShapesFromSvg(svg)