Анализ Windows и очистка кешей, мусора и остатков разработки. Ядро: - каталог правил: 228 правил путей (496 шаблонов) + 43 правила артефактов проектов — Windows, браузеры, кеши сборки, IDE, приложения, пакетные менеджеры; - рекурсивный поиск node_modules, target, build, __pycache__, .venv и т.п. с проверкой маркеров проекта, чтобы не ловить ложные срабатывания; - поиск крупных файлов и дубликатов (хеш по началу/середине/концу и полная сверка), инвентаризация установленных программ через реестр; - ограничение области сканирования по дискам; - рекомендации и текстовый отчёт. Безопасность: - четыре уровня риска, автоматически отмечается только «Безопасно»; - список защищённых путей проверяется и при выборе, и перед удалением; - для каталогов вроде %TEMP% удаляется содержимое, а не сам каталог; - удаление в Корзину по умолчанию, пробный запуск, журнал очисток. Интерфейс на Dear PyGui: - JetBrains Mono Nerd Font: кириллица и иконки в одном шрифте; - тёмная и светлая темы, адаптивная раскладка; - заголовок окна ставится через SetWindowTextW — Dear PyGui отдаёт его в ANSI-функцию как UTF-8 и портит кириллицу; - запуск всегда с правами администратора: манифест UAC в exe и самоперезапуск через ShellExecuteW при работе из исходников. Сборка в один exe через PyInstaller со вшитым шрифтом и значком. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
112 lines
4.1 KiB
Python
112 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
||
"""Генерация значка SysDevCleaner.
|
||
|
||
Значок рисуется геометрией, а не глифом шрифта: тонкая «палочка» из Nerd Font
|
||
на 16×16 превращалась в неразборчивое пятно. Здесь метла нарисована жирными
|
||
примитивами и остаётся узнаваемой даже в самом мелком размере.
|
||
|
||
python make_icon.py
|
||
|
||
Результат — ``sysdevcleaner.ico`` (размеры 16…256) и ``sysdevcleaner.png``
|
||
256×256 для README.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
|
||
from PIL import Image, ImageDraw
|
||
|
||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||
ICO = os.path.join(ROOT, "sysdevcleaner.ico")
|
||
PNG = os.path.join(ROOT, "sysdevcleaner.png")
|
||
|
||
SIZES = (16, 24, 32, 48, 64, 128, 256)
|
||
R = 1024 # рисуем крупно, потом уменьшаем — края мягче
|
||
|
||
BG_TOP = (82, 154, 226) # accent из тёмной темы, чуть светлее
|
||
BG_BOTTOM = (42, 88, 150)
|
||
FG = 255 # метла белая, задаётся через маску
|
||
|
||
|
||
def gradient(size: int) -> Image.Image:
|
||
img = Image.new("RGB", (1, size))
|
||
d = ImageDraw.Draw(img)
|
||
for y in range(size):
|
||
t = y / max(1, size - 1)
|
||
d.point((0, y), tuple(int(a + (b - a) * t)
|
||
for a, b in zip(BG_TOP, BG_BOTTOM)))
|
||
return img.resize((size, size), Image.BICUBIC)
|
||
|
||
|
||
def rounded_mask(size: int, radius_ratio: float = 0.22) -> Image.Image:
|
||
mask = Image.new("L", (size, size), 0)
|
||
ImageDraw.Draw(mask).rounded_rectangle(
|
||
[0, 0, size - 1, size - 1], radius=int(size * radius_ratio), fill=255)
|
||
return mask
|
||
|
||
|
||
def broom_mask() -> Image.Image:
|
||
"""Маска метлы: рисуем вертикально, потом поворачиваем.
|
||
|
||
Вертикальная заготовка проще в координатах, а наклон даёт узнаваемый силуэт.
|
||
"""
|
||
pad = R // 2
|
||
m = Image.new("L", (R + pad, R + pad), 0)
|
||
d = ImageDraw.Draw(m)
|
||
cx = (R + pad) // 2
|
||
|
||
# черенок
|
||
d.rounded_rectangle([cx - 52, 150, cx + 52, 640], radius=52, fill=FG)
|
||
# обхват под черенком
|
||
d.rounded_rectangle([cx - 132, 610, cx + 132, 710], radius=40, fill=FG)
|
||
# ворс — расширяющаяся трапеция
|
||
d.polygon([(cx - 118, 700), (cx + 118, 700),
|
||
(cx + 250, 1030), (cx - 250, 1030)], fill=FG)
|
||
# прорези в ворсе: режем маску, а не рисуем поверх фона
|
||
for dx in (-125, 0, 125):
|
||
d.line([(cx + dx * 0.62, 790), (cx + dx, 1040)], fill=0, width=34)
|
||
# нижний край чуть скруглим, срезав уголки
|
||
d.polygon([(cx - 250, 1030), (cx - 250, 1100), (cx + 250, 1100),
|
||
(cx + 250, 1030)], fill=0)
|
||
|
||
m = m.rotate(-38, resample=Image.BICUBIC, expand=True,
|
||
center=(cx, (R + pad) // 2))
|
||
|
||
# вписываем в плитку: обрезаем по содержимому и масштабируем до 66%
|
||
bbox = m.getbbox()
|
||
m = m.crop(bbox)
|
||
target = int(R * 0.66)
|
||
scale = target / max(m.size)
|
||
m = m.resize((max(1, int(m.width * scale)), max(1, int(m.height * scale))),
|
||
Image.LANCZOS)
|
||
|
||
out = Image.new("L", (R, R), 0)
|
||
out.paste(m, ((R - m.width) // 2, (R - m.height) // 2))
|
||
return out
|
||
|
||
|
||
def build() -> Image.Image:
|
||
base = gradient(R).convert("RGBA")
|
||
base.putalpha(rounded_mask(R))
|
||
|
||
mask = broom_mask()
|
||
white = Image.new("RGBA", (R, R), (255, 255, 255, 0))
|
||
white.putalpha(mask)
|
||
return Image.alpha_composite(base, white)
|
||
|
||
|
||
def main() -> int:
|
||
icon = build()
|
||
icon.resize((256, 256), Image.LANCZOS).save(PNG)
|
||
print(f" {PNG}")
|
||
|
||
frames = [icon.resize((s, s), Image.LANCZOS) for s in SIZES]
|
||
frames[-1].save(ICO, format="ICO",
|
||
sizes=[(s, s) for s in SIZES], append_images=frames[:-1])
|
||
print(f" {ICO} (размеры: {', '.join(str(s) for s in SIZES)})")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|