Skip to content

Commit 09a408e

Browse files
committed
feat: initialize project structure with main UI, asset editors, and utility modules
1 parent 0d8fd25 commit 09a408e

14 files changed

Lines changed: 957 additions & 710 deletions

File tree

src/editors/asset_exporter/dependency_resolver.py

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
import os
2-
from src.common import Kv3ToJson
32

43
class DependencyResolver:
5-
ASSET_DEPENDENCY_KEYS = [
6-
# vmdl / vsmart
7-
'm_refMeshes', 'm_refPhysicsData', 'model',
8-
# vmat
9-
'TextureColor', 'TextureNormal', 'TextureRoughness',
10-
'TextureMetalness', 'g_tColor', 'g_tNormal',
11-
# generic
12-
'material', 'mesh', 'texture', 'm_strPsd'
13-
]
144

155
def __init__(self, addon_content_path: str):
166
self.addon_content_path = addon_content_path
@@ -33,28 +23,18 @@ def _walk(self, path: str):
3323
self._parse_kv3_deps(path)
3424

3525
def _parse_kv3_deps(self, path: str):
26+
import re
3627
try:
3728
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
3829
content = f.read()
39-
# If it's pure binary or random data, kv3 parsing will fail, which is fine
40-
# We catch exceptions
41-
data = Kv3ToJson(content)
42-
if data:
43-
self._find_deps_in_dict(data)
30+
31+
pattern = r'"([^"]+\.(?:vmdl|vsmart|vmat|vpcf|vsndevts|vtex|vsnd|txt|kv3|vmap|vpost)(?:_c)?)"'
32+
matches = re.findall(pattern, content, re.IGNORECASE)
33+
for m in matches:
34+
self._add_dep(m)
4435
except Exception as e:
4536
print(f"DependencyResolver: Error parsing {path}: {e}")
4637

47-
def _find_deps_in_dict(self, data):
48-
if isinstance(data, dict):
49-
for k, v in data.items():
50-
if k in self.ASSET_DEPENDENCY_KEYS and isinstance(v, str):
51-
self._add_dep(v)
52-
else:
53-
self._find_deps_in_dict(v)
54-
elif isinstance(data, list):
55-
for item in data:
56-
self._find_deps_in_dict(item)
57-
5838
def _add_dep(self, rel_path: str):
5939
if not rel_path:
6040
return

src/editors/asset_exporter/exporter.py

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,78 @@ class ExportWorker(QThread):
77
file_copied = Signal(str)
88
finished_export = Signal(str)
99

10-
def __init__(self, files, addon_content_path, dest_root, layout, addon_name, asset_stem):
10+
def __init__(self, files, addon_content_path, dest_root, layout, addon_name, asset_stem, export_to_zip=False, zip_name="Export"):
1111
super().__init__()
1212
self.files = files
1313
self.addon_content_path = addon_content_path
1414
self.dest_root = dest_root
1515
self.layout = layout
1616
self.addon_name = addon_name
1717
self.asset_stem = asset_stem
18+
self.export_to_zip = export_to_zip
19+
self.zip_name = zip_name
1820

1921
def run(self):
22+
import tempfile
23+
import zipfile
24+
SCANNABLE_EXTS = {'.vmdl', '.vsmart', '.vmat', '.vpcf', '.vsndevts', '.vsnd'}
25+
26+
path_mapping = {}
27+
for src in self.files:
28+
old_rel = os.path.relpath(src, self.addon_content_path).replace('\\', '/')
29+
new_rel = self._compute_dest_rel(src)
30+
path_mapping[old_rel] = new_rel
31+
32+
if self.export_to_zip:
33+
temp_dir = tempfile.mkdtemp()
34+
base_dir = temp_dir
35+
else:
36+
base_dir = self.dest_root
37+
2038
for i, src in enumerate(self.files):
21-
dest = self._compute_dest(src)
39+
dest = self._compute_dest(src, base_dir)
2240
os.makedirs(os.path.dirname(dest), exist_ok=True)
2341
try:
24-
shutil.copy2(src, dest)
42+
ext = os.path.splitext(src)[1].lower()
43+
if ext in SCANNABLE_EXTS:
44+
with open(src, 'r', encoding='utf-8', errors='ignore') as f:
45+
content = f.read()
46+
47+
for old_rel in sorted(path_mapping.keys(), key=len, reverse=True):
48+
new_rel = path_mapping[old_rel]
49+
if old_rel != new_rel and old_rel in content:
50+
content = content.replace(old_rel, new_rel)
51+
52+
with open(dest, 'w', encoding='utf-8') as f:
53+
f.write(content)
54+
else:
55+
shutil.copy2(src, dest)
2556
self.file_copied.emit(src)
2657
except Exception as e:
2758
print(f"Error copying {src} to {dest}: {e}")
2859
self.progress.emit(int((i + 1) / len(self.files) * 100))
60+
61+
if self.export_to_zip:
62+
zip_path = os.path.join(self.dest_root, self.zip_name)
63+
shutil.make_archive(zip_path, 'zip', temp_dir)
64+
shutil.rmtree(temp_dir)
65+
2966
self.finished_export.emit(self.dest_root)
3067

31-
def _compute_dest(self, src):
32-
rel = os.path.relpath(src, self.addon_content_path)
68+
def _compute_dest_rel(self, src):
69+
rel = os.path.relpath(src, self.addon_content_path).replace('\\', '/')
3370
if self.layout == 'thirdparty':
34-
return os.path.join(self.dest_root,
35-
'folder_thirdparty', self.addon_name,
36-
self.asset_stem, rel)
37-
return os.path.join(self.dest_root, rel)
71+
parts = rel.split("/", 1)
72+
if len(parts) == 2:
73+
root_dir, rest = parts[0], parts[1]
74+
path_suffix = "thirdparty"
75+
if self.addon_name:
76+
path_suffix = f"{path_suffix}/{self.addon_name}"
77+
if self.asset_stem:
78+
path_suffix = f"{path_suffix}/{self.asset_stem}"
79+
return f"{root_dir}/{path_suffix}/{rest}"
80+
return rel
81+
82+
def _compute_dest(self, src, base_dir):
83+
rel = self._compute_dest_rel(src)
84+
return os.path.normpath(os.path.join(base_dir, rel))

src/editors/asset_exporter/main.py

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from .dependency_resolver import DependencyResolver
77
from .exporter import ExportWorker
88
from src.settings.main import get_addon_name, get_cs2_path
9-
from PySide6.QtWidgets import QLineEdit
109
from src.common import enable_dark_title_bar
1110
from src.styles.common import apply_stylesheets, qt_stylesheet_widgetlist2
1211

@@ -19,28 +18,6 @@ def __init__(self, parent=None):
1918
enable_dark_title_bar(self)
2019
apply_stylesheets(self)
2120

22-
qt_stylesheet_lineedit = """
23-
QLineEdit {
24-
font: 580 10pt "Segoe UI";
25-
border: 2px solid black;
26-
border-radius: 2px;
27-
border-color: rgba(80, 80, 80, 255);
28-
height:22px;
29-
padding-top: 2px;
30-
padding-bottom:2px;
31-
padding-left: 4px;
32-
padding-right: 4px;
33-
color: #E3E3E3;
34-
background-color: #1C1C1C;
35-
}
36-
QLineEdit:hover {
37-
background-color: #414956;
38-
color: white;
39-
}
40-
"""
41-
for line_edit in self.findChildren(QLineEdit):
42-
line_edit.setStyleSheet(qt_stylesheet_lineedit)
43-
4421
self.ui.deps_list.setStyleSheet(qt_stylesheet_widgetlist2)
4522

4623
self.cs2_path = get_cs2_path()
@@ -76,6 +53,11 @@ def __init__(self, parent=None):
7653

7754
self.toggle_thirdparty_fields()
7855

56+
from PySide6.QtWidgets import QCheckBox
57+
self.ui.checkbox_zip = QCheckBox("Export to ZIP Archive (name based on selected asset)")
58+
self.ui.checkbox_zip.setChecked(True)
59+
self.ui.verticalLayout.insertWidget(6, self.ui.checkbox_zip)
60+
7961
self.deps_model = QStandardItemModel()
8062
self.ui.deps_list.setModel(self.deps_model)
8163

@@ -155,13 +137,21 @@ def start_export(self):
155137

156138
layout = 'thirdparty' if is_thirdparty else 'preserve'
157139

140+
export_to_zip = self.ui.checkbox_zip.isChecked()
141+
zip_name = "Export"
142+
if self.sources_to_export:
143+
first_src = os.path.basename(self.sources_to_export[0])
144+
zip_name = os.path.splitext(first_src)[0]
145+
158146
self.worker = ExportWorker(
159147
files_to_export,
160148
self.addon_content_path,
161149
output_dir,
162150
layout,
163151
addon_name,
164-
asset_stem
152+
asset_stem,
153+
export_to_zip,
154+
zip_name
165155
)
166156
self.worker.progress.connect(self.ui.progress_bar.setValue)
167157
self.worker.finished_export.connect(self.on_export_finished)

src/editors/asset_exporter/ui_main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def retranslateUi(self, AssetExporterWidget):
111111
self.btn_resolve.setText(QCoreApplication.translate("AssetExporterWidget", u"Resolve Dependencies", None))
112112
self.groupBox.setTitle(QCoreApplication.translate("AssetExporterWidget", u"Export Options", None))
113113
self.radio_preserve.setText(QCoreApplication.translate("AssetExporterWidget", u"Preserve Addon Structure", None))
114-
self.radio_thirdparty.setText(QCoreApplication.translate("AssetExporterWidget", u"Third-Party Package Layout (folder_thirdparty/...)", None))
114+
self.radio_thirdparty.setText(QCoreApplication.translate("AssetExporterWidget", u"Third-Party Package Layout (models/thirdparty/...)", None))
115115
self.edit_addon_name.setPlaceholderText(QCoreApplication.translate("AssetExporterWidget", u"Addon Name", None))
116116
self.edit_asset_stem.setPlaceholderText(QCoreApplication.translate("AssetExporterWidget", u"Asset Stem", None))
117117
self.edit_output_dir.setPlaceholderText(QCoreApplication.translate("AssetExporterWidget", u"Output Directory", None))

src/editors/asset_manager/main.py

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from .ui_main import Ui_AssetManagerWidget
66
from .move_worker import MoveWorker
77
from src.settings.main import get_addon_name, get_cs2_path, app_dir
8-
from PySide6.QtWidgets import QLineEdit
98
from src.common import enable_dark_title_bar
109
from src.styles.common import apply_stylesheets
1110

@@ -18,28 +17,6 @@ def __init__(self, parent=None):
1817
enable_dark_title_bar(self)
1918
apply_stylesheets(self)
2019

21-
qt_stylesheet_lineedit = """
22-
QLineEdit {
23-
font: 580 10pt "Segoe UI";
24-
border: 2px solid black;
25-
border-radius: 2px;
26-
border-color: rgba(80, 80, 80, 255);
27-
height:22px;
28-
padding-top: 2px;
29-
padding-bottom:2px;
30-
padding-left: 4px;
31-
padding-right: 4px;
32-
color: #E3E3E3;
33-
background-color: #1C1C1C;
34-
}
35-
QLineEdit:hover {
36-
background-color: #414956;
37-
color: white;
38-
}
39-
"""
40-
for line_edit in self.findChildren(QLineEdit):
41-
line_edit.setStyleSheet(qt_stylesheet_lineedit)
42-
4320
self.cs2_path = get_cs2_path()
4421
if not self.cs2_path:
4522
return
@@ -48,7 +25,7 @@ def __init__(self, parent=None):
4825
self.addon_content_path = os.path.join(self.cs2_path, 'content', 'csgo_addons', self.addon_name)
4926

5027
self.setWindowFlags(Qt.Window)
51-
self.setWindowTitle("Asset Manager")
28+
self.setWindowTitle("Move Assets")
5229
self.ui.source_tree.hide()
5330
self.sources_to_move = []
5431

0 commit comments

Comments
 (0)