added ability to set forground of background color for the grid
This commit is contained in:
Binary file not shown.
@@ -1,7 +1,8 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Service
|
Type=Service
|
||||||
ServiceTypes=Krita/PythonPlugin
|
ServiceTypes=Krita/PythonPlugin
|
||||||
X-Krita-Manual=Manual.html
|
X-KDE-Library=grid_generator
|
||||||
|
X-Python-2-Compatible=false
|
||||||
X-Krita-Python-Extension=grid_generator
|
X-Krita-Python-Extension=grid_generator
|
||||||
Name=Grid Generator
|
Name=Grid Generator
|
||||||
Comment=Generates square and hexagonal grids.
|
Comment=Generates square and hexagonal grids.
|
||||||
|
|||||||
@@ -1,6 +1 @@
|
|||||||
from krita import Krita
|
from .grid_docker import *
|
||||||
|
|
||||||
from .grid_generator import GridGenerator
|
|
||||||
|
|
||||||
# Create the plugin instance and add it to Krita
|
|
||||||
Krita.instance().addExtension(GridGenerator(Krita.instance()))
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,31 +1,34 @@
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
from krita import Krita, ManagedColor
|
from krita import DockWidget, DockWidgetFactory, DockWidgetFactoryBase, Krita
|
||||||
from PyQt5.QtCore import QPointF
|
from PyQt5.QtCore import QPointF
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QCheckBox,
|
QCheckBox,
|
||||||
QComboBox,
|
QComboBox,
|
||||||
QDialog,
|
|
||||||
QDoubleSpinBox,
|
QDoubleSpinBox,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DOCKER_ID = "grid_generator_docker"
|
||||||
|
|
||||||
class GridDialog(QDialog):
|
|
||||||
def __init__(self, parent=None):
|
class GridDocker(DockWidget):
|
||||||
super().__init__(parent)
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
self.setWindowTitle("Grid Generator")
|
self.setWindowTitle("Grid Generator")
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
layout = QVBoxLayout(self)
|
main_widget = QWidget()
|
||||||
|
layout = QVBoxLayout(main_widget)
|
||||||
|
|
||||||
# Thickness
|
# Thickness
|
||||||
thickness_layout = QHBoxLayout()
|
thickness_layout = QHBoxLayout()
|
||||||
thickness_layout.addWidget(QLabel("Line Thickness (px):"))
|
thickness_layout.addWidget(QLabel("Thickness (px):"))
|
||||||
self.thickness_spin = QDoubleSpinBox()
|
self.thickness_spin = QDoubleSpinBox()
|
||||||
self.thickness_spin.setRange(0.1, 1000.0)
|
self.thickness_spin.setRange(0.1, 1000.0)
|
||||||
self.thickness_spin.setValue(2.0)
|
self.thickness_spin.setValue(2.0)
|
||||||
@@ -34,7 +37,7 @@ class GridDialog(QDialog):
|
|||||||
|
|
||||||
# Width
|
# Width
|
||||||
width_layout = QHBoxLayout()
|
width_layout = QHBoxLayout()
|
||||||
width_layout.addWidget(QLabel("Grid Width (px):"))
|
width_layout.addWidget(QLabel("Width (px):"))
|
||||||
self.width_spin = QDoubleSpinBox()
|
self.width_spin = QDoubleSpinBox()
|
||||||
self.width_spin.setRange(1.0, 10000.0)
|
self.width_spin.setRange(1.0, 10000.0)
|
||||||
self.width_spin.setValue(100.0)
|
self.width_spin.setValue(100.0)
|
||||||
@@ -44,7 +47,7 @@ class GridDialog(QDialog):
|
|||||||
|
|
||||||
# Height
|
# Height
|
||||||
height_layout = QHBoxLayout()
|
height_layout = QHBoxLayout()
|
||||||
height_layout.addWidget(QLabel("Grid Height (px):"))
|
height_layout.addWidget(QLabel("Height (px):"))
|
||||||
self.height_spin = QDoubleSpinBox()
|
self.height_spin = QDoubleSpinBox()
|
||||||
self.height_spin.setRange(1.0, 10000.0)
|
self.height_spin.setRange(1.0, 10000.0)
|
||||||
self.height_spin.setValue(100.0)
|
self.height_spin.setValue(100.0)
|
||||||
@@ -59,7 +62,7 @@ class GridDialog(QDialog):
|
|||||||
|
|
||||||
# Grid Type
|
# Grid Type
|
||||||
type_layout = QHBoxLayout()
|
type_layout = QHBoxLayout()
|
||||||
type_layout.addWidget(QLabel("Grid Type:"))
|
type_layout.addWidget(QLabel("Type:"))
|
||||||
self.type_combo = QComboBox()
|
self.type_combo = QComboBox()
|
||||||
self.type_combo.addItems(["Square", "Hexagon"])
|
self.type_combo.addItems(["Square", "Hexagon"])
|
||||||
type_layout.addWidget(self.type_combo)
|
type_layout.addWidget(self.type_combo)
|
||||||
@@ -67,21 +70,27 @@ class GridDialog(QDialog):
|
|||||||
|
|
||||||
# Layer Option
|
# Layer Option
|
||||||
layer_layout = QHBoxLayout()
|
layer_layout = QHBoxLayout()
|
||||||
layer_layout.addWidget(QLabel("Generate on:"))
|
layer_layout.addWidget(QLabel("On:"))
|
||||||
self.layer_combo = QComboBox()
|
self.layer_combo = QComboBox()
|
||||||
self.layer_combo.addItems(["New Layer", "Current Layer"])
|
self.layer_combo.addItems(["New Layer", "Current Layer"])
|
||||||
layer_layout.addWidget(self.layer_combo)
|
layer_layout.addWidget(self.layer_combo)
|
||||||
layout.addLayout(layer_layout)
|
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
|
# Buttons
|
||||||
button_layout = QHBoxLayout()
|
|
||||||
self.ok_button = QPushButton("Generate")
|
self.ok_button = QPushButton("Generate")
|
||||||
self.ok_button.clicked.connect(self.generate)
|
self.ok_button.clicked.connect(self.generate)
|
||||||
self.cancel_button = QPushButton("Cancel")
|
layout.addWidget(self.ok_button)
|
||||||
self.cancel_button.clicked.connect(self.reject)
|
|
||||||
button_layout.addWidget(self.ok_button)
|
layout.addStretch()
|
||||||
button_layout.addWidget(self.cancel_button)
|
self.setWidget(main_widget)
|
||||||
layout.addLayout(button_layout)
|
|
||||||
|
|
||||||
def on_width_changed(self, value):
|
def on_width_changed(self, value):
|
||||||
if self.lock_check.isChecked():
|
if self.lock_check.isChecked():
|
||||||
@@ -95,89 +104,109 @@ class GridDialog(QDialog):
|
|||||||
self.width_spin.setValue(value)
|
self.width_spin.setValue(value)
|
||||||
self.width_spin.blockSignals(False)
|
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):
|
def generate(self):
|
||||||
app = Krita.instance()
|
app = Krita.instance()
|
||||||
doc = app.activeDocument()
|
doc = app.activeDocument()
|
||||||
if not doc:
|
if not doc:
|
||||||
return
|
return
|
||||||
|
|
||||||
window = app.activeWindow()
|
|
||||||
if not window:
|
|
||||||
return
|
|
||||||
|
|
||||||
thickness = self.thickness_spin.value()
|
thickness = self.thickness_spin.value()
|
||||||
width = self.width_spin.value()
|
width = self.width_spin.value()
|
||||||
height = self.height_spin.value()
|
height = self.height_spin.value()
|
||||||
grid_type = self.type_combo.currentText()
|
grid_type = self.type_combo.currentText()
|
||||||
layer_option = self.layer_combo.currentText()
|
layer_option = self.layer_combo.currentText()
|
||||||
|
color_hex = self.get_color_hex()
|
||||||
|
|
||||||
# Determine target layer
|
# Determine target layer
|
||||||
target_node = None
|
target_node = None
|
||||||
if layer_option == "New Layer":
|
if layer_option == "New Layer":
|
||||||
target_node = doc.createNode("grid", "vectorlayer")
|
target_node = doc.createVectorLayer("grid")
|
||||||
doc.rootNode().addChildNode(target_node, None)
|
doc.rootNode().addChildNode(target_node, None)
|
||||||
else:
|
else:
|
||||||
target_node = doc.activeNode()
|
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":
|
if target_node and target_node.type() != "vectorlayer":
|
||||||
parent = target_node.parentNode()
|
parent = target_node.parentNode()
|
||||||
new_vector = doc.createNode("grid", "vectorlayer")
|
new_vector = doc.createVectorLayer("grid")
|
||||||
parent.addChildNode(new_vector, target_node)
|
parent.addChildNode(new_vector, target_node)
|
||||||
target_node = new_vector
|
target_node = new_vector
|
||||||
|
|
||||||
if not target_node:
|
if not target_node:
|
||||||
target_node = doc.createNode("grid", "vectorlayer")
|
target_node = doc.createVectorLayer("grid")
|
||||||
doc.rootNode().addChildNode(target_node, None)
|
doc.rootNode().addChildNode(target_node, None)
|
||||||
|
|
||||||
self.draw_grid(target_node, doc, thickness, width, height, grid_type)
|
if hasattr(target_node, "addShapesFromSvg"):
|
||||||
doc.refreshProjection()
|
self.draw_grid(
|
||||||
self.accept()
|
target_node, doc, thickness, width, height, grid_type, color_hex
|
||||||
|
)
|
||||||
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:
|
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):
|
doc.refreshProjection()
|
||||||
svg = f'<svg><g fill="none" stroke="black" stroke-width="{thickness}">'
|
|
||||||
|
|
||||||
# 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'<svg><g fill="none" stroke="{color_hex}" stroke-width="{thickness}">'
|
||||||
x = 0
|
x = 0
|
||||||
while x <= doc.width():
|
while x <= doc.width():
|
||||||
svg += f'<line x1="{x}" y1="0" x2="{x}" y2="{doc.height()}" />'
|
svg += f'<line x1="{x}" y1="0" x2="{x}" y2="{doc.height()}" />'
|
||||||
x += width
|
x += width
|
||||||
|
|
||||||
# Horizontal lines
|
|
||||||
y = 0
|
y = 0
|
||||||
while y <= doc.height():
|
while y <= doc.height():
|
||||||
svg += f'<line x1="0" y1="{y}" x2="{doc.width()}" y2="{y}" />'
|
svg += f'<line x1="0" y1="{y}" x2="{doc.width()}" y2="{y}" />'
|
||||||
y += height
|
y += height
|
||||||
|
|
||||||
svg += "</g></svg>"
|
svg += "</g></svg>"
|
||||||
node.addShapesFromSvg(svg)
|
node.addShapesFromSvg(svg)
|
||||||
|
|
||||||
def draw_hexagon_grid(self, node, doc, thickness, width, height):
|
def draw_hexagon_grid(self, node, doc, thickness, width, height, color_hex):
|
||||||
# Pointy topped hex grid
|
svg = f'<svg><g fill="none" stroke="{color_hex}" stroke-width="{thickness}">'
|
||||||
# Horizontal spacing = width
|
|
||||||
# Vertical spacing = height * 0.75
|
|
||||||
|
|
||||||
svg = f'<svg><g fill="none" stroke="black" stroke-width="{thickness}">'
|
|
||||||
|
|
||||||
v_spacing = height * 0.75
|
v_spacing = height * 0.75
|
||||||
h_spacing = width
|
h_spacing = width
|
||||||
|
|
||||||
rows = int(doc.height() / v_spacing) + 2
|
rows = int(doc.height() / v_spacing) + 2
|
||||||
cols = int(doc.width() / h_spacing) + 2
|
cols = int(doc.width() / h_spacing) + 2
|
||||||
|
|
||||||
for r in range(rows):
|
for r in range(rows):
|
||||||
offset_x = (width / 2) if (r % 2 == 1) else 0
|
offset_x = (width / 2) if (r % 2 == 1) else 0
|
||||||
for c in range(cols):
|
for c in range(cols):
|
||||||
cx = c * h_spacing + offset_x
|
cx = c * h_spacing + offset_x
|
||||||
cy = r * v_spacing
|
cy = r * v_spacing
|
||||||
|
|
||||||
# Vertices for pointy-topped hex
|
|
||||||
v = [
|
v = [
|
||||||
(cx, cy - height / 2),
|
(cx, cy - height / 2),
|
||||||
(cx + width / 2, cy - height / 4),
|
(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),
|
||||||
(cx - width / 2, cy - height / 4),
|
(cx - width / 2, cy - height / 4),
|
||||||
]
|
]
|
||||||
|
|
||||||
points = " ".join([f"{px},{py}" for px, py in v])
|
points = " ".join([f"{px},{py}" for px, py in v])
|
||||||
svg += f'<polygon points="{points}" />'
|
svg += f'<polygon points="{points}" />'
|
||||||
|
|
||||||
svg += "</g></svg>"
|
svg += "</g></svg>"
|
||||||
node.addShapesFromSvg(svg)
|
node.addShapesFromSvg(svg)
|
||||||
|
|
||||||
|
|
||||||
|
# Register the docker
|
||||||
|
Krita.instance().addDockWidgetFactory(
|
||||||
|
DockWidgetFactory(DOCKER_ID, DockWidgetFactoryBase.DockRight, GridDocker)
|
||||||
|
)
|
||||||
@@ -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_()
|
|
||||||
Reference in New Issue
Block a user