commit d72ad18b23ef27470bb313de35041112c7a717cb Author: grimsace Date: Tue May 19 10:21:13 2026 -0500 initial commit diff --git a/pykrita/grid_generator.desktop b/pykrita/grid_generator.desktop new file mode 100644 index 0000000..330f5cc --- /dev/null +++ b/pykrita/grid_generator.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-Krita-Manual=Manual.html +Component=grid_generator +X-Python-2-Compatible=false +X-Krita-Python-Extension=grid_generator +Name=Grid Generator +Comment=Generates square and hexagonal grids. diff --git a/pykrita/grid_generator/__init__.py b/pykrita/grid_generator/__init__.py new file mode 100644 index 0000000..d7a1774 --- /dev/null +++ b/pykrita/grid_generator/__init__.py @@ -0,0 +1,8 @@ +from krita import Krita + +from .grid_generator import GridGenerator + +# Create the plugin instance and add it to Krita +instance = Krita.instance() +extension = GridGenerator(instance) +instance.addExtension(extension) diff --git a/pykrita/grid_generator/grid_dialog.py b/pykrita/grid_generator/grid_dialog.py new file mode 100644 index 0000000..0bef4cd --- /dev/null +++ b/pykrita/grid_generator/grid_dialog.py @@ -0,0 +1,194 @@ +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) diff --git a/pykrita/grid_generator/grid_generator.py b/pykrita/grid_generator/grid_generator.py new file mode 100644 index 0000000..4e59182 --- /dev/null +++ b/pykrita/grid_generator/grid_generator.py @@ -0,0 +1,21 @@ +from krita import Extension + +from .grid_dialog import GridDialog + + +class GridGenerator(Extension): + def __init__(self, parent): + super().__init__(parent) + + def setup(self): + pass + + def createActions(self, window): + action = window.createAction( + "grid_generator", "Grid Generator...", "tools/scripts" + ) + action.triggered.connect(self.show_dialog) + + def show_dialog(self): + dialog = GridDialog() + dialog.exec_()