diff --git a/grid_generator.zip b/grid_generator.zip new file mode 100644 index 0000000..36d27df Binary files /dev/null and b/grid_generator.zip differ diff --git a/pykrita/grid_generator.desktop b/pykrita/grid_generator.desktop index 1f34df0..2c128d6 100644 --- a/pykrita/grid_generator.desktop +++ b/pykrita/grid_generator.desktop @@ -1,7 +1,8 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin -X-Krita-Manual=Manual.html +X-KDE-Library=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 index 5745c59..539bd70 100644 --- a/pykrita/grid_generator/__init__.py +++ b/pykrita/grid_generator/__init__.py @@ -1,6 +1 @@ -from krita import Krita - -from .grid_generator import GridGenerator - -# Create the plugin instance and add it to Krita -Krita.instance().addExtension(GridGenerator(Krita.instance())) +from .grid_docker import * diff --git a/pykrita/grid_generator/__pycache__/__init__.cpython-313.pyc b/pykrita/grid_generator/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..e0c74ed Binary files /dev/null and b/pykrita/grid_generator/__pycache__/__init__.cpython-313.pyc differ diff --git a/pykrita/grid_generator/__pycache__/grid_dialog.cpython-313.pyc b/pykrita/grid_generator/__pycache__/grid_dialog.cpython-313.pyc new file mode 100644 index 0000000..a2a0e06 Binary files /dev/null and b/pykrita/grid_generator/__pycache__/grid_dialog.cpython-313.pyc differ diff --git a/pykrita/grid_generator/__pycache__/grid_docker.cpython-313.pyc b/pykrita/grid_generator/__pycache__/grid_docker.cpython-313.pyc new file mode 100644 index 0000000..a45c599 Binary files /dev/null and b/pykrita/grid_generator/__pycache__/grid_docker.cpython-313.pyc differ diff --git a/pykrita/grid_generator/__pycache__/grid_generator.cpython-313.pyc b/pykrita/grid_generator/__pycache__/grid_generator.cpython-313.pyc new file mode 100644 index 0000000..018f33e Binary files /dev/null and b/pykrita/grid_generator/__pycache__/grid_generator.cpython-313.pyc differ diff --git a/pykrita/grid_generator/grid_dialog.py b/pykrita/grid_generator/grid_docker.py similarity index 64% rename from pykrita/grid_generator/grid_dialog.py rename to pykrita/grid_generator/grid_docker.py index 0bef4cd..53a2f66 100644 --- a/pykrita/grid_generator/grid_dialog.py +++ b/pykrita/grid_generator/grid_docker.py @@ -1,31 +1,34 @@ import math -from krita import Krita, ManagedColor +from krita import DockWidget, DockWidgetFactory, DockWidgetFactoryBase, Krita from PyQt5.QtCore import QPointF from PyQt5.QtWidgets import ( QCheckBox, QComboBox, - QDialog, QDoubleSpinBox, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, + QWidget, ) +DOCKER_ID = "grid_generator_docker" -class GridDialog(QDialog): - def __init__(self, parent=None): - super().__init__(parent) + +class GridDocker(DockWidget): + def __init__(self): + super().__init__() self.setWindowTitle("Grid Generator") self.setup_ui() def setup_ui(self): - layout = QVBoxLayout(self) + main_widget = QWidget() + layout = QVBoxLayout(main_widget) # Thickness thickness_layout = QHBoxLayout() - thickness_layout.addWidget(QLabel("Line Thickness (px):")) + thickness_layout.addWidget(QLabel("Thickness (px):")) self.thickness_spin = QDoubleSpinBox() self.thickness_spin.setRange(0.1, 1000.0) self.thickness_spin.setValue(2.0) @@ -34,7 +37,7 @@ class GridDialog(QDialog): # Width width_layout = QHBoxLayout() - width_layout.addWidget(QLabel("Grid Width (px):")) + width_layout.addWidget(QLabel("Width (px):")) self.width_spin = QDoubleSpinBox() self.width_spin.setRange(1.0, 10000.0) self.width_spin.setValue(100.0) @@ -44,7 +47,7 @@ class GridDialog(QDialog): # Height height_layout = QHBoxLayout() - height_layout.addWidget(QLabel("Grid Height (px):")) + height_layout.addWidget(QLabel("Height (px):")) self.height_spin = QDoubleSpinBox() self.height_spin.setRange(1.0, 10000.0) self.height_spin.setValue(100.0) @@ -59,7 +62,7 @@ class GridDialog(QDialog): # Grid Type type_layout = QHBoxLayout() - type_layout.addWidget(QLabel("Grid Type:")) + type_layout.addWidget(QLabel("Type:")) self.type_combo = QComboBox() self.type_combo.addItems(["Square", "Hexagon"]) type_layout.addWidget(self.type_combo) @@ -67,21 +70,27 @@ class GridDialog(QDialog): # Layer Option layer_layout = QHBoxLayout() - layer_layout.addWidget(QLabel("Generate on:")) + 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 - 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) + layout.addWidget(self.ok_button) + + layout.addStretch() + self.setWidget(main_widget) def on_width_changed(self, value): if self.lock_check.isChecked(): @@ -95,89 +104,109 @@ class GridDialog(QDialog): 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 - 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() + color_hex = self.get_color_hex() # Determine target layer target_node = None if layer_option == "New Layer": - target_node = doc.createNode("grid", "vectorlayer") + target_node = doc.createVectorLayer("grid") 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") + new_vector = doc.createVectorLayer("grid") parent.addChildNode(new_vector, target_node) target_node = new_vector if not target_node: - target_node = doc.createNode("grid", "vectorlayer") + target_node = doc.createVectorLayer("grid") 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) + if hasattr(target_node, "addShapesFromSvg"): + self.draw_grid( + target_node, doc, thickness, width, height, grid_type, color_hex + ) else: - self.draw_hexagon_grid(node, doc, thickness, width, height) + print( + "Error: Target node does not support SVG shapes. Please ensure you are using Krita 5.0+ and a Vector Layer." + ) - def draw_square_grid(self, node, doc, thickness, width, height): - svg = f'' + doc.refreshProjection() - # Vertical lines + 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 - - # 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'' - + 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 - - # Vertices for pointy-topped hex v = [ (cx, cy - height / 2), (cx + width / 2, cy - height / 4), @@ -186,9 +215,13 @@ class GridDialog(QDialog): (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) +) diff --git a/pykrita/grid_generator/grid_generator.py b/pykrita/grid_generator/grid_generator.py deleted file mode 100644 index 4e59182..0000000 --- a/pykrita/grid_generator/grid_generator.py +++ /dev/null @@ -1,21 +0,0 @@ -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_()