-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
202 lines (170 loc) · 7.26 KB
/
main.py
File metadata and controls
202 lines (170 loc) · 7.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
"""EfficientAssetRipper — Unpack UE4/5 game files and export to Blender."""
import sys
import logging
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import Qt, QPointF, QRect, QRectF, QTimer
from PySide6.QtGui import (
QIcon, QPixmap, QPainter, QColor, QBrush, QPen,
QLinearGradient, QPolygonF,
)
from gui.main_window import MainWindow
from gui.splash import SplashScreen
import gui.theme as theme
import config
# ── Toggle the startup animation on/off ──────────────────────────────────────
SHOW_SPLASH = True
def _make_icon() -> QIcon:
"""Gem/crystal icon rendered at multiple sizes for crisp display."""
c = theme.current_scheme()
icon = QIcon()
for size in (16, 32, 48, 64, 256):
px = QPixmap(size, size)
px.fill(Qt.GlobalColor.transparent)
p = QPainter(px)
p.setRenderHint(QPainter.RenderHint.Antialiasing)
s = float(size)
cx, cy = s / 2, s / 2
pad = s * 0.05
radius = s * 0.18
# --- dark rounded background ---
bg = QLinearGradient(0.0, 0.0, s, s)
bg.setColorAt(0.0, QColor(c["bg_darkest"]))
bg.setColorAt(1.0, QColor(c["bg_dark"]))
p.setBrush(QBrush(bg))
p.setPen(Qt.PenStyle.NoPen)
p.drawRoundedRect(QRectF(pad, pad, s - 2 * pad, s - 2 * pad), radius, radius)
# --- gem diamond shape ---
dh = s * 0.36 # half-height
dw = s * 0.27 # half-width
gem = QPolygonF([
QPointF(cx, cy - dh), # top
QPointF(cx + dw, cy), # right
QPointF(cx, cy + dh), # bottom
QPointF(cx - dw, cy), # left
])
gem_grad = QLinearGradient(cx - dw, cy - dh, cx + dw, cy + dh)
gem_grad.setColorAt(0.0, QColor(c["accent"]))
gem_grad.setColorAt(0.45, QColor(c["accent_hover"]))
gem_grad.setColorAt(1.0, QColor(c["accent_muted"]))
p.setBrush(QBrush(gem_grad))
p.setPen(Qt.PenStyle.NoPen)
p.drawPolygon(gem)
# --- inner facet lines (only worth drawing at >=32 px) ---
if size >= 32:
lw = max(1.0, s * 0.025)
p.setPen(QPen(QColor(255, 255, 255, 190), lw))
p.drawLine(QPointF(cx, cy - dh), QPointF(cx + dw, cy))
p.drawLine(QPointF(cx, cy - dh), QPointF(cx - dw * 0.55, cy - dh * 0.25))
p.setPen(QPen(QColor(255, 255, 255, 80), lw * 0.7))
p.drawLine(QPointF(cx - dw, cy), QPointF(cx + dw, cy))
# --- outer glow ring ---
if size >= 32:
gw = max(1.5, s * 0.055)
glow_pen = QPen(QColor(c["accent"]).lighter(120), gw)
glow_pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
p.setPen(glow_pen)
p.setBrush(Qt.BrushStyle.NoBrush)
scale = 1.22
p.drawPolygon(QPolygonF([
QPointF(cx, cy - dh * scale),
QPointF(cx + dw * scale, cy),
QPointF(cx, cy + dh * scale),
QPointF(cx - dw * scale, cy),
]))
# --- tiny accent dots at cardinal corners (>=48 px) ---
if size >= 48:
dot_r = s * 0.035
p.setPen(Qt.PenStyle.NoPen)
p.setBrush(QBrush(QColor(c["accent"]).lighter(140)))
for dx, dy in ((0, -dh * 1.32), (dw * 1.32, 0),
(0, dh * 1.32), (-dw * 1.32, 0)):
p.drawEllipse(QRectF(cx + dx - dot_r, cy + dy - dot_r,
dot_r * 2, dot_r * 2))
p.end()
icon.addPixmap(px)
return icon
def _load_saved_custom_schemes():
"""Restore user-defined colour schemes from QSettings/config."""
import json
from gui.color_schemes import register_custom_scheme
raw = config.get("custom_schemes")
if not raw or raw == "{}":
return
try:
custom = json.loads(raw)
for name, colors in custom.items():
register_custom_scheme(name, colors)
except (json.JSONDecodeError, TypeError) as e:
logging.getLogger(__name__).warning(
"Failed to parse custom_schemes from config: %s", e
)
def main():
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
# Strip AES keys / hex blobs from any log record before it's emitted.
from core.log_redaction import install_global_redactor
install_global_redactor()
# Catch otherwise-uncaught exceptions and Qt fatal messages, write a
# report under logs/, and offer the user a one-click bug filing path.
# Must come AFTER install_global_redactor so report contents share the
# same redaction filter as ordinary log lines.
from core import crash_reporter
crash_reporter.install(
active_profile_provider=lambda: config.get("active_profile") or "",
)
# Windows: give this process its own identity so the taskbar shows
# our icon instead of the generic Python interpreter icon.
if sys.platform == "win32":
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
"EfficientAssetRipper.App.1"
)
app = QApplication(sys.argv)
app.setApplicationName("EfficientAssetRipper")
app.setOrganizationName("EfficientAssetRipper")
# Apply centralised theme (palette + stylesheet)
_load_saved_custom_schemes()
saved_scheme = config.get("color_scheme") or None
theme.apply(app, saved_scheme)
icon = _make_icon()
app.setWindowIcon(icon)
# Build the main window (hidden) so it loads during the splash. Geometry
# restoration runs inside MainWindow.__init__ — it falls back to a centred
# 1600×950 default when no saved layout exists or the schema mismatches.
window = MainWindow()
window.setWindowIcon(icon)
if SHOW_SPLASH:
# Show the main window first so Windows treats it as the active app
# window from frame 1 — the splash will cover it visually but won't
# steal focus (WA_ShowWithoutActivating). When the splash closes,
# focus naturally stays on the window instead of getting handed off
# to whatever app was previously active.
window.show()
def _on_splash_done():
# Belt-and-braces: ensure the window is on top + has focus once
# the splash has finished animating away.
window.raise_()
window.activateWindow()
# MainWindow has already realised its geometry (saved layout or
# default centring) inside __init__, so read the live frame here.
_wg = window.frameGeometry()
splash = SplashScreen(
finish_callback=_on_splash_done,
target_rect=QRect(_wg.x(), _wg.y(), _wg.width(), _wg.height()),
)
splash.start()
else:
window.show()
try:
rc = app.exec()
except Exception:
# The crash reporter's excepthook already wrote a report and showed
# the dialog before this re-raise reaches us. We just have to make
# sure we exit non-zero so launchers / CI know something went wrong.
logging.getLogger(__name__).exception("Fatal error in main event loop")
rc = 1
sys.exit(rc)
if __name__ == "__main__":
main()