import math from krita import DockWidget, DockWidgetFactory, DockWidgetFactoryBase, Krita from PyQt5.QtCore import QPointF from PyQt5.QtWidgets import ( QCheckBox, QComboBox, QDoubleSpinBox, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget, ) DOCKER_ID = "grid_generator_docker" class GridDocker(DockWidget): def __init__(self): super().__init__() self.setWindowTitle("Grid Generator") self.setup_ui() def setup_ui(self): main_widget = QWidget() layout = QVBoxLayout(main_widget) # Thickness thickness_layout = QHBoxLayout() thickness_layout.addWidget(QLabel("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("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("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("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("On:")) self.layer_combo = QComboBox() self.layer_combo.addItems(["New Layer", "Current Layer"]) layer_layout.addWidget(self.layer_combo) layout.addLayout(layer_layout) # Color Option color_layout = QHBoxLayout() color_layout.addWidget(QLabel("Color:")) self.color_combo = QComboBox() self.color_combo.addItems(["Foreground", "Background"]) color_layout.addWidget(self.color_combo) layout.addLayout(color_layout) # Buttons self.ok_button = QPushButton("Generate") self.ok_button.clicked.connect(self.generate) layout.addWidget(self.ok_button) layout.addStretch() self.setWidget(main_widget) 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 canvasChanged(self, canvas): pass def get_color_hex(self): app = Krita.instance() window = app.activeWindow() if not window: return "#000000" view = window.activeView() if not view: return "#000000" color = None if self.color_combo.currentText() == "Foreground": color = view.foregroundColor() else: color = view.backgroundColor() # Ensure the color is in RGBA U8 sRGB for easy extraction # setColorSpace signature: (colorModelId, bitDepthId, profileName) color.setColorSpace("RGBA", "U8", "sRGB built-in") # componentsOrdered returns normalized floats [R, G, B, A] comps = color.componentsOrdered() r = int(max(0, min(1, comps[0])) * 255) g = int(max(0, min(1, comps[1])) * 255) b = int(max(0, min(1, comps[2])) * 255) return f"#{r:02x}{g:02x}{b:02x}" def generate(self): app = Krita.instance() doc = app.activeDocument() if not doc: 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() color_hex = self.get_color_hex() # Determine target layer target_node = None if layer_option == "New Layer": target_node = doc.createVectorLayer("grid") doc.rootNode().addChildNode(target_node, None) else: target_node = doc.activeNode() if target_node and target_node.type() != "vectorlayer": parent = target_node.parentNode() new_vector = doc.createVectorLayer("grid") parent.addChildNode(new_vector, target_node) target_node = new_vector if not target_node: target_node = doc.createVectorLayer("grid") doc.rootNode().addChildNode(target_node, None) if hasattr(target_node, "addShapesFromSvg"): self.draw_grid( target_node, doc, thickness, width, height, grid_type, color_hex ) else: print( "Error: Target node does not support SVG shapes. Please ensure you are using Krita 5.0+ and a Vector Layer." ) doc.refreshProjection() def draw_grid(self, node, doc, thickness, width, height, grid_type, color_hex): if grid_type == "Square": self.draw_square_grid(node, doc, thickness, width, height, color_hex) else: self.draw_hexagon_grid(node, doc, thickness, width, height, color_hex) def draw_square_grid(self, node, doc, thickness, width, height, color_hex): svg = f'' x = 0 while x <= doc.width(): svg += f'' x += width y = 0 while y <= doc.height(): svg += f'' y += height svg += "" node.addShapesFromSvg(svg) def draw_hexagon_grid(self, node, doc, thickness, width, height, color_hex): 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 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) # Register the docker Krita.instance().addDockWidgetFactory( DockWidgetFactory(DOCKER_ID, DockWidgetFactoryBase.DockRight, GridDocker) )