update
This commit is contained in:
Binary file not shown.
112
tools/generowanie/_explore_buteleczki_klaudia.py
Normal file
112
tools/generowanie/_explore_buteleczki_klaudia.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""Inspekcja PSD: wejscie do kazdego SO i wypisanie jego warstw."""
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import photoshop.api as ps
|
||||
|
||||
PSD = r"d:\pomysloweprezenty.pl\projekty\buteleczki - indywidualny wzór\Klaudia Buczma.psd"
|
||||
|
||||
PHOTOSHOP_EXE_CANDIDATES = [
|
||||
r"C:\Program Files\Adobe\Adobe Photoshop 2024\Photoshop.exe",
|
||||
r"C:\Program Files\Adobe\Adobe Photoshop 2023\Photoshop.exe",
|
||||
r"C:\Program Files\Adobe\Adobe Photoshop 2025\Photoshop.exe",
|
||||
r"C:\Program Files\Adobe\Adobe Photoshop 2026\Photoshop.exe",
|
||||
]
|
||||
|
||||
|
||||
def ensure_photoshop():
|
||||
try:
|
||||
return ps.Application()
|
||||
except Exception:
|
||||
for exe in PHOTOSHOP_EXE_CANDIDATES:
|
||||
if os.path.exists(exe):
|
||||
subprocess.Popen([exe])
|
||||
break
|
||||
for _ in range(30):
|
||||
time.sleep(2)
|
||||
try:
|
||||
return ps.Application()
|
||||
except Exception:
|
||||
continue
|
||||
raise RuntimeError("brak PS")
|
||||
|
||||
|
||||
def open_smart_object(app):
|
||||
desc = ps.ActionDescriptor()
|
||||
ref = ps.ActionReference()
|
||||
ref.putEnumerated(
|
||||
app.stringIDToTypeID("layer"),
|
||||
app.stringIDToTypeID("ordinal"),
|
||||
app.stringIDToTypeID("targetEnum"),
|
||||
)
|
||||
desc.putReference(app.stringIDToTypeID("null"), ref)
|
||||
app.executeAction(app.stringIDToTypeID("placedLayerEditContents"), desc)
|
||||
|
||||
|
||||
def walk(layer, prefix=""):
|
||||
try:
|
||||
name = layer.name
|
||||
except Exception:
|
||||
name = "?"
|
||||
kind = type(layer).__name__
|
||||
extra = ""
|
||||
try:
|
||||
if hasattr(layer, "kind"):
|
||||
extra = f" kind={layer.kind}"
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
bounds = [float(b) for b in layer.bounds]
|
||||
extra += f" bounds={bounds}"
|
||||
except Exception:
|
||||
pass
|
||||
content = ""
|
||||
try:
|
||||
if hasattr(layer, "textItem"):
|
||||
content = f" text=\"{layer.textItem.contents}\""
|
||||
except Exception:
|
||||
pass
|
||||
print(f"{prefix}- {name} [{kind}]{extra}{content}")
|
||||
for attr in ("layerSets", "artLayers"):
|
||||
try:
|
||||
for c in getattr(layer, attr):
|
||||
walk(c, prefix + " ")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def dump_doc(doc, label):
|
||||
print(f"=== {label}: {doc.name} ===")
|
||||
for ls in doc.layerSets:
|
||||
walk(ls, "")
|
||||
for al in doc.artLayers:
|
||||
walk(al, "")
|
||||
|
||||
|
||||
def main():
|
||||
app = ensure_photoshop()
|
||||
doc = app.open(PSD)
|
||||
dump_doc(doc, "ROOT")
|
||||
|
||||
so_names = ["Warstwa 2", "Warstwa 2 kopia", "Warstwa 2 kopia 2", "Warstwa 2 kopia 3"]
|
||||
for so_name in so_names:
|
||||
try:
|
||||
target = doc.artLayers[so_name]
|
||||
except Exception:
|
||||
print(f"!! brak warstwy {so_name}")
|
||||
continue
|
||||
doc.activeLayer = target
|
||||
try:
|
||||
open_smart_object(app)
|
||||
except Exception as e:
|
||||
print(f"!! nie udalo sie wejsc do {so_name}: {e}")
|
||||
continue
|
||||
so_doc = app.activeDocument
|
||||
dump_doc(so_doc, f"SO[{so_name}]")
|
||||
so_doc.close(ps.SaveOptions.DoNotSaveChanges)
|
||||
|
||||
doc.close(ps.SaveOptions.DoNotSaveChanges)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
3
tools/generowanie/_mark_zarnowska.sql
Normal file
3
tools/generowanie/_mark_zarnowska.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
SET NAMES utf8mb4;
|
||||
UPDATE order_items SET project_generated = 1, project_generated_at = NOW() WHERE id IN (5911, 5912);
|
||||
SELECT id, project_generated, project_generated_at FROM order_items WHERE id IN (5911, 5912);
|
||||
5
tools/generowanie/_query_1009.sql
Normal file
5
tools/generowanie/_query_1009.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
SET NAMES utf8mb4;
|
||||
SELECT oi.id, oi.original_name, oi.quantity, oi.project_generated
|
||||
FROM order_items oi
|
||||
WHERE oi.order_id = 1009
|
||||
ORDER BY oi.id;
|
||||
8
tools/generowanie/_query_1009_full.sql
Normal file
8
tools/generowanie/_query_1009_full.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
SET NAMES utf8mb4;
|
||||
SHOW COLUMNS FROM orders;
|
||||
SELECT '---';
|
||||
SHOW COLUMNS FROM order_items;
|
||||
SELECT '---';
|
||||
SELECT * FROM order_items WHERE id = 5898\G
|
||||
SELECT '---';
|
||||
SELECT * FROM orders WHERE id = 1009\G
|
||||
27
tools/generowanie/_query_zarnowska.sql
Normal file
27
tools/generowanie/_query_zarnowska.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
SET NAMES utf8mb4;
|
||||
SELECT
|
||||
oi.id AS item_id,
|
||||
oi.original_name AS product_name,
|
||||
oi.personalization,
|
||||
oi.quantity,
|
||||
o.id AS order_id,
|
||||
o.internal_order_number,
|
||||
o.status_code AS status_code,
|
||||
oa.name AS buyer_name,
|
||||
oa.email AS buyer_email,
|
||||
pm.script_name,
|
||||
pm.output_dir,
|
||||
pm.requires_photo,
|
||||
(SELECT GROUP_CONCAT(n.comment SEPARATOR '\n---\n') FROM order_notes n WHERE n.order_id = o.id AND n.note_type = 'message') AS customer_message
|
||||
FROM order_items oi
|
||||
JOIN orders o ON oi.order_id = o.id
|
||||
JOIN order_statuses os ON o.status_code = os.code
|
||||
JOIN order_status_groups osg ON os.group_id = osg.id
|
||||
JOIN project_mappings pm ON pm.is_active = 1 AND oi.original_name = pm.product_name_pattern
|
||||
LEFT JOIN order_addresses oa ON o.id = oa.order_id AND oa.address_type = 'customer'
|
||||
WHERE osg.id = 2
|
||||
AND oi.project_generated = 0
|
||||
AND oi.personalization IS NOT NULL
|
||||
AND oi.personalization <> ''
|
||||
AND oa.name LIKE '%Żarnowska%'
|
||||
ORDER BY o.id;
|
||||
3
tools/generowanie/_update_1009.sql
Normal file
3
tools/generowanie/_update_1009.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
SET NAMES utf8mb4;
|
||||
UPDATE order_items SET quantity = 1 WHERE id = 5898;
|
||||
SELECT id, original_name, quantity FROM order_items WHERE id = 5898;
|
||||
3
tools/generowanie/_update_1009_total.sql
Normal file
3
tools/generowanie/_update_1009_total.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
SET NAMES utf8mb4;
|
||||
UPDATE orders SET total_with_tax = 89.00, total_paid = 89.00 WHERE id = 1009;
|
||||
SELECT id, internal_order_number, total_with_tax, total_paid FROM orders WHERE id = 1009;
|
||||
275
tools/generowanie/buteleczki_klaudia_buczma_batch.py
Normal file
275
tools/generowanie/buteleczki_klaudia_buczma_batch.py
Normal file
@@ -0,0 +1,275 @@
|
||||
"""
|
||||
Generator wsadowy PSD: buteleczki "Klaudia Buczma" (4 gosci per PSD).
|
||||
|
||||
Otwiera szablon `Klaudia Buczma.psd`, dla kazdej z 4 warstw Smart Object
|
||||
(Warstwa 2, Warstwa 2 kopia, Warstwa 2 kopia 2, Warstwa 2 kopia 3) wchodzi
|
||||
do srodka i podmienia warstwe tekstowa `gosc` (sciezka: Warstwa 4 -> gosc).
|
||||
Zapisuje jako `Klaudia Buczma NN.psd` w `_gotowe/`.
|
||||
|
||||
Lista gosci pobierana z .docx (komorki tabeli).
|
||||
Grupy < 4 osob -> brakujace SO sa ukrywane (visible=False).
|
||||
|
||||
Uzycie:
|
||||
python buteleczki_klaudia_buczma_batch.py # wszystkie grupy
|
||||
python buteleczki_klaudia_buczma_batch.py --only 1 # tylko pierwsza grupa (test)
|
||||
python buteleczki_klaudia_buczma_batch.py --from 5 --to 7 # zakres grup
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import photoshop.api as ps
|
||||
from docx import Document
|
||||
|
||||
|
||||
PROJEKT_DIR = r"d:\pomysloweprezenty.pl\projekty\buteleczki - indywidualny wzór"
|
||||
SZABLON_PATH = os.path.join(PROJEKT_DIR, "Klaudia Buczma.psd")
|
||||
GOTOWE_DIR = os.path.join(PROJEKT_DIR, "_gotowe_edytowalne")
|
||||
DOCX_PATH = r"d:\temp\pomysloweprezenty.pl\buteleczki.docx"
|
||||
|
||||
SO_LAYER_NAMES = [
|
||||
"Warstwa 2 kopia 2", # lewy gora
|
||||
"Warstwa 2 kopia 3", # prawy gora
|
||||
"Warstwa 2 kopia", # lewy dol
|
||||
"Warstwa 2", # prawy dol
|
||||
]
|
||||
|
||||
# Sciezka do warstwy `gosc` wewnatrz SO
|
||||
SO_GROUP_NAME = "Warstwa 4"
|
||||
GOSC_LAYER_NAME = "gosc"
|
||||
|
||||
PHOTOSHOP_EXE_CANDIDATES = [
|
||||
r"C:\Program Files\Adobe\Adobe Photoshop 2024\Photoshop.exe",
|
||||
r"C:\Program Files\Adobe\Adobe Photoshop 2023\Photoshop.exe",
|
||||
r"C:\Program Files\Adobe\Adobe Photoshop 2025\Photoshop.exe",
|
||||
r"C:\Program Files\Adobe\Adobe Photoshop 2026\Photoshop.exe",
|
||||
]
|
||||
|
||||
|
||||
def ensure_photoshop():
|
||||
try:
|
||||
return ps.Application()
|
||||
except Exception:
|
||||
for exe in PHOTOSHOP_EXE_CANDIDATES:
|
||||
if os.path.exists(exe):
|
||||
subprocess.Popen([exe])
|
||||
break
|
||||
for _ in range(30):
|
||||
time.sleep(2)
|
||||
try:
|
||||
return ps.Application()
|
||||
except Exception:
|
||||
continue
|
||||
raise RuntimeError("Nie udalo sie uruchomic Photoshopa")
|
||||
|
||||
|
||||
def open_smart_object(app):
|
||||
"""Otwiera zawartosc aktywnej warstwy Smart Object do edycji."""
|
||||
desc = ps.ActionDescriptor()
|
||||
ref = ps.ActionReference()
|
||||
ref.putEnumerated(
|
||||
app.stringIDToTypeID("layer"),
|
||||
app.stringIDToTypeID("ordinal"),
|
||||
app.stringIDToTypeID("targetEnum"),
|
||||
)
|
||||
desc.putReference(app.stringIDToTypeID("null"), ref)
|
||||
app.executeAction(app.stringIDToTypeID("placedLayerEditContents"), desc)
|
||||
|
||||
|
||||
def delete_active_layer(app):
|
||||
"""Usuwa aktywna warstwe (przez action manager - dziala na surowych COM)."""
|
||||
desc = ps.ActionDescriptor()
|
||||
ref = ps.ActionReference()
|
||||
ref.putEnumerated(
|
||||
app.stringIDToTypeID("layer"),
|
||||
app.stringIDToTypeID("ordinal"),
|
||||
app.stringIDToTypeID("targetEnum"),
|
||||
)
|
||||
desc.putReference(app.stringIDToTypeID("null"), ref)
|
||||
app.executeAction(app.stringIDToTypeID("delete"), desc)
|
||||
|
||||
|
||||
def smart_object_make_copy(app):
|
||||
"""Layer > Smart Objects > New Smart Object Via Copy.
|
||||
Odlacza aktywny SO od pozostalych zlinkowanych instancji."""
|
||||
app.executeAction(
|
||||
app.stringIDToTypeID("placedLayerMakeCopy"), ps.ActionDescriptor()
|
||||
)
|
||||
|
||||
|
||||
def set_text(layer, new_text):
|
||||
"""Zmienia tekst zachowujac srodek bounding boxa."""
|
||||
b = [float(x) for x in layer.bounds]
|
||||
cx_before = (b[0] + b[2]) / 2
|
||||
cy_before = (b[1] + b[3]) / 2
|
||||
layer.textItem.contents = new_text
|
||||
b2 = [float(x) for x in layer.bounds]
|
||||
cx_after = (b2[0] + b2[2]) / 2
|
||||
cy_after = (b2[1] + b2[3]) / 2
|
||||
dx = cx_before - cx_after
|
||||
dy = cy_before - cy_after
|
||||
if dx or dy:
|
||||
layer.translate(dx, dy)
|
||||
|
||||
|
||||
def split_two_lines(name):
|
||||
"""Lamie nazwe na 2 linie - przy spacji najblizszej srodkowi.
|
||||
Dla 1-slowowych zwraca bez zmian."""
|
||||
parts = name.split(" ")
|
||||
if len(parts) < 2:
|
||||
return name
|
||||
mid = len(name) / 2
|
||||
best_i = None
|
||||
best_dist = None
|
||||
pos = 0
|
||||
for i, part in enumerate(parts[:-1]):
|
||||
pos += len(part)
|
||||
dist = abs(pos - mid)
|
||||
if best_dist is None or dist < best_dist:
|
||||
best_dist = dist
|
||||
best_i = i
|
||||
pos += 1 # spacja
|
||||
left = " ".join(parts[: best_i + 1])
|
||||
right = " ".join(parts[best_i + 1 :])
|
||||
return f"{left}\r{right}"
|
||||
|
||||
|
||||
def read_names(docx_path):
|
||||
doc = Document(docx_path)
|
||||
names = []
|
||||
for t in doc.tables:
|
||||
for row in t.rows:
|
||||
for cell in row.cells:
|
||||
txt = cell.text.strip()
|
||||
if txt:
|
||||
names.append(txt)
|
||||
return names
|
||||
|
||||
|
||||
def chunk4(items):
|
||||
for i in range(0, len(items), 4):
|
||||
yield items[i:i + 4]
|
||||
|
||||
|
||||
def find_layer_by_name(container, name):
|
||||
"""Rekurencyjnie szuka warstwy po nazwie w doc/layerSet."""
|
||||
for ls in container.layerSets:
|
||||
if ls.name == name:
|
||||
return ls
|
||||
found = find_layer_by_name(ls, name)
|
||||
if found is not None:
|
||||
return found
|
||||
for al in container.artLayers:
|
||||
if al.name == name:
|
||||
return al
|
||||
return None
|
||||
|
||||
|
||||
def edit_so_gosc(app, doc, so_layer_name, gosc_text):
|
||||
"""Wchodzi do SO, podmienia gosc, zapisuje i wraca."""
|
||||
target = find_layer_by_name(doc, so_layer_name)
|
||||
if target is None:
|
||||
raise RuntimeError(f"Nie znaleziono warstwy {so_layer_name}")
|
||||
doc.activeLayer = target
|
||||
# 4 SO w szablonie sa zlinkowane (wspolny kontener). "New Smart Object Via
|
||||
# Copy" tworzy niezalezna kopie obok zrodla. Kasujemy oryginal i
|
||||
# przemianowujemy kopie - efekt: ta sama warstwa w tym samym miejscu,
|
||||
# ale juz odpieta od pozostalych instancji.
|
||||
smart_object_make_copy(app)
|
||||
independent = doc.activeLayer
|
||||
independent_name_tmp = independent.name
|
||||
# Skasuj oryginal (target) - aktywuj go i usun przez action manager
|
||||
doc.activeLayer = target
|
||||
delete_active_layer(app)
|
||||
# Wroc do niezaleznej kopii i nadaj jej nazwe oryginalu
|
||||
doc.activeLayer = independent
|
||||
independent.name = so_layer_name
|
||||
open_smart_object(app)
|
||||
so_doc = app.activeDocument
|
||||
gosc_layer = find_layer_by_name(so_doc, GOSC_LAYER_NAME)
|
||||
if gosc_layer is None:
|
||||
raise RuntimeError(f"Nie znaleziono warstwy '{GOSC_LAYER_NAME}' w SO {so_layer_name}")
|
||||
old = gosc_layer.textItem.contents
|
||||
set_text(gosc_layer, gosc_text)
|
||||
so_doc.save()
|
||||
so_doc.close()
|
||||
print(f" {so_layer_name}: '{old.strip()}' -> '{gosc_text}'")
|
||||
|
||||
|
||||
def generate_group(app, names_group, index):
|
||||
"""Generuje jeden PSD dla grupy do 4 nazwisk."""
|
||||
os.makedirs(GOTOWE_DIR, exist_ok=True)
|
||||
out_name = f"Klaudia Buczma {index:02d}.psd"
|
||||
out_path = os.path.join(GOTOWE_DIR, out_name)
|
||||
|
||||
doc = app.open(SZABLON_PATH)
|
||||
print(f"[{index:02d}] otwarto szablon ({len(names_group)} osob)")
|
||||
|
||||
for slot_idx, so_name in enumerate(SO_LAYER_NAMES):
|
||||
if slot_idx < len(names_group):
|
||||
edit_so_gosc(app, doc, so_name, split_two_lines(names_group[slot_idx]))
|
||||
else:
|
||||
doc.artLayers[so_name].visible = False
|
||||
print(f" {so_name}: UKRYTE (brak osoby)")
|
||||
|
||||
psd_opts = ps.PhotoshopSaveOptions()
|
||||
doc.saveAs(out_path, psd_opts, True)
|
||||
print(f"[{index:02d}] zapisano: {out_path}")
|
||||
doc.close(ps.SaveOptions.DoNotSaveChanges)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--only", type=int, help="Tylko jedna grupa (1-based)")
|
||||
parser.add_argument("--from", dest="from_", type=int, default=1, help="Od grupy N")
|
||||
parser.add_argument("--to", type=int, help="Do grupy N (wlacznie)")
|
||||
parser.add_argument(
|
||||
"--names",
|
||||
help="Lista nazwisk oddzielona ';' (1-4 sztuk). Generuje jeden PSD.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--index",
|
||||
type=int,
|
||||
help="Numer wynikowego PSD przy uzyciu --names (np. 24 -> 'Klaudia Buczma 24.psd')",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
app = ensure_photoshop()
|
||||
|
||||
while app.documents.length > 0:
|
||||
app.activeDocument.close(ps.SaveOptions.DoNotSaveChanges)
|
||||
|
||||
if args.names:
|
||||
if args.index is None:
|
||||
raise SystemExit("--names wymaga --index N")
|
||||
custom = [n.strip() for n in args.names.split(";") if n.strip()]
|
||||
if not (1 <= len(custom) <= 4):
|
||||
raise SystemExit("--names: 1-4 nazwisk oddzielonych ';'")
|
||||
generate_group(app, custom, args.index)
|
||||
print("Gotowe!")
|
||||
return
|
||||
|
||||
names = read_names(DOCX_PATH)
|
||||
groups = list(chunk4(names))
|
||||
print(f"Wczytano {len(names)} osob -> {len(groups)} grup po 4")
|
||||
|
||||
if args.only:
|
||||
indices = [args.only]
|
||||
else:
|
||||
end = args.to or len(groups)
|
||||
indices = list(range(args.from_, end + 1))
|
||||
|
||||
for idx in indices:
|
||||
if not (1 <= idx <= len(groups)):
|
||||
print(f"Pominieto grupe {idx} (poza zakresem)")
|
||||
continue
|
||||
generate_group(app, groups[idx - 1], idx)
|
||||
|
||||
print("Gotowe!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user