Skip to content

Commit 95ccd08

Browse files
committed
feat: add asset exporter, reference updater, and smartprop comment property editor
1 parent 6a3e337 commit 95ccd08

9 files changed

Lines changed: 97 additions & 20 deletions

File tree

src/editors/asset_exporter/dependency_resolver.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def _walk(self, path: str):
1919
return
2020
self._visited.add(path)
2121
ext = os.path.splitext(path)[1].lower()
22-
if ext in ('.vmdl', '.vsmart', '.vmat', '.vpcf', '.vsndevts', '.vtex', '.vsnd'):
22+
if ext in ('.vmdl', '.vsmart', '.vmat', '.vpcf', '.vsndevts', '.vtex', '.vsnd', '.vmap', '.vpost', '.vanim', '.vseq', '.vphys'):
2323
self._parse_kv3_deps(path)
2424

2525
def _parse_kv3_deps(self, path: str):
@@ -28,7 +28,8 @@ def _parse_kv3_deps(self, path: str):
2828
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
2929
content = f.read()
3030

31-
pattern = r'"([^"]+\.(?:vmdl|vsmart|vmat|vpcf|vsndevts|vtex|vsnd|txt|kv3|vmap|vpost)(?:_c)?)"'
31+
# Broaden the regex to capture more file extensions used in Source 2, including raw source files like .tga
32+
pattern = r'"(?:resource:)?([^"]+\.(?:vmdl|vsmart|vmat|vpcf|vsndevts|vtex|vsnd|txt|kv3|vmap|vpost|tga|png|jpg|jpeg|psd|wav|mp3|fbx|obj|vfx|vcs|vjs|vcss|vanim|vseq|vphys)(?:_c)?)"'
3233
matches = re.findall(pattern, content, re.IGNORECASE)
3334
for m in matches:
3435
self._add_dep(m)
@@ -39,6 +40,10 @@ def _add_dep(self, rel_path: str):
3940
if not rel_path:
4041
return
4142

43+
# Strip resource prefixes common in KV3 files
44+
for prefix in ["resource:", "panorama:", "file:"]:
45+
if rel_path.lower().startswith(prefix):
46+
rel_path = rel_path[len(prefix):]
4247
# Handle compiled asset path mapping
4348
if rel_path.endswith('_c'):
4449
rel_path = rel_path[:-2]

src/editors/asset_exporter/exporter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def __init__(self, files, addon_content_path, dest_root, layout, addon_name, ass
2121
def run(self):
2222
import tempfile
2323
import zipfile
24-
SCANNABLE_EXTS = {'.vmdl', '.vsmart', '.vmat', '.vpcf', '.vsndevts', '.vsnd'}
24+
SCANNABLE_EXTS = {'.vmdl', '.vsmart', '.vmat', '.vpcf', '.vsndevts', '.vsnd', '.vtex', '.vmap', '.vpost', '.vanim', '.vseq', '.vphys'}
2525

2626
path_mapping = {}
2727
for src in self.files:

src/editors/asset_exporter/main.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ def __init__(self, parent=None):
5252
self.ui.radio_preserve.toggled.connect(self.toggle_thirdparty_fields)
5353

5454
self.toggle_thirdparty_fields()
55+
56+
desktop_dir = os.path.join(os.path.expanduser("~"), "Desktop")
57+
self.ui.edit_output_dir.setText(os.path.normpath(desktop_dir))
5558

5659
from PySide6.QtWidgets import QCheckBox
5760
self.ui.checkbox_zip = QCheckBox("Export to ZIP Archive (name based on selected asset)")
@@ -107,7 +110,8 @@ def resolve_dependencies(self):
107110
)
108111

109112
def browse_output_dir(self):
110-
d = QFileDialog.getExistingDirectory(self, "Select Output Directory")
113+
start_dir = self.ui.edit_output_dir.text() or os.path.join(os.path.expanduser("~"), "Desktop")
114+
d = QFileDialog.getExistingDirectory(self, "Select Output Directory", start_dir)
111115
if d:
112116
self.ui.edit_output_dir.setText(os.path.normpath(d))
113117

src/editors/asset_manager/reference_updater.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22

33
class ReferenceUpdater:
4-
SCANNABLE_EXTS = {'.vmdl', '.vsmart', '.vmat', '.vpcf', '.vsndevts', '.vsnd', '.vmap'}
4+
SCANNABLE_EXTS = {'.vmdl', '.vsmart', '.vmat', '.vpcf', '.vsndevts', '.vsnd', '.vmap', '.vpost', '.vanim', '.vseq', '.vphys'}
55

66
def __init__(self, addon_content_path: str):
77
self.addon_content_path = addon_content_path

src/editors/smartprop_editor/commands.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,3 +521,26 @@ def undo(self):
521521
self.document._restore_choices(self.old_state)
522522
except Exception as e:
523523
print(f"[SPE][Choices] undo: ERROR — {e}")
524+
525+
526+
class HierarchyItemRenameCommand(QUndoCommand):
527+
"""Undo/redo support for renaming hierarchy tree items (m_sLabel)."""
528+
def __init__(self, item, old_label, new_label):
529+
super().__init__("Rename")
530+
self.item = item
531+
self.old_label = old_label
532+
self.new_label = new_label
533+
534+
def redo(self):
535+
if self.item is not None:
536+
self.item.setText(0, self.new_label)
537+
# Update the underlying data if it exists
538+
if hasattr(self.item, 'data') and isinstance(self.item.data, dict):
539+
self.item.data['m_sLabel'] = self.new_label
540+
541+
def undo(self):
542+
if self.item is not None:
543+
self.item.setText(0, self.old_label)
544+
# Update the underlying data if it exists
545+
if hasattr(self.item, 'data') and isinstance(self.item.data, dict):
546+
self.item.data['m_sLabel'] = self.old_label

src/editors/smartprop_editor/document.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ def __init__(self, parent=None, update_title=None):
148148
# Choices rename undo state: captured on itemDoubleClicked, consumed by itemChanged.
149149
self._choices_rename_old_state = None
150150

151+
# Hierarchy item rename undo state: captured on itemDoubleClicked, consumed by itemChanged.
152+
self._hierarchy_rename_old_label = None
153+
self._hierarchy_rename_item = None
154+
151155
# Choices widget-edit debounce (ComboboxTreeChild, VariableWidget, etc.)
152156
self._choices_widget_old_state = None
153157
self._choices_widget_debounce_desc = "Edit Choices"
@@ -187,6 +191,8 @@ def __init__(self, parent=None, update_title=None):
187191
self.ui.tree_hierarchy_widget.setAcceptDrops(True)
188192
self.ui.tree_hierarchy_widget.setDropIndicatorShown(True)
189193
self.ui.tree_hierarchy_widget.setDragDropMode(QTreeWidget.InternalMove)
194+
self.ui.tree_hierarchy_widget.itemDoubleClicked.connect(self._on_hierarchy_item_about_to_edit)
195+
self.ui.tree_hierarchy_widget.itemChanged.connect(self._on_hierarchy_item_changed)
190196

191197
# Content version
192198
self.content_version_spinbox = QSpinBox()
@@ -2321,6 +2327,28 @@ def _flush_choices_widget_if_pending(self):
23212327
self._choices_widget_debounce.stop()
23222328
self._push_choices_widget_edit()
23232329

2330+
def _on_hierarchy_item_about_to_edit(self, item, column):
2331+
"""Capture the 'before' label when the user starts an inline rename in the hierarchy tree."""
2332+
if column == 0:
2333+
self._hierarchy_rename_old_label = item.text(0)
2334+
self._hierarchy_rename_item = item
2335+
2336+
def _on_hierarchy_item_changed(self, item, column):
2337+
"""Push rename undo command once the inline edit is committed in the hierarchy tree."""
2338+
if (
2339+
column == 0
2340+
and self._hierarchy_rename_item is item
2341+
and self._hierarchy_rename_old_label is not None
2342+
):
2343+
new_label = item.text(0)
2344+
if new_label != self._hierarchy_rename_old_label:
2345+
from src.editors.smartprop_editor.commands import HierarchyItemRenameCommand
2346+
self.undo_stack.push(
2347+
HierarchyItemRenameCommand(item, self._hierarchy_rename_old_label, new_label)
2348+
)
2349+
self._hierarchy_rename_old_label = None
2350+
self._hierarchy_rename_item = None
2351+
23242352
def _setup_history_dock(self):
23252353
self._history_dock = QDockWidget("History", self)
23262354
self._history_dock.setObjectName("SPE_history_dock")

src/editors/smartprop_editor/property/comment.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from src.editors.smartprop_editor.property.ui_comment import Ui_Widget
22
from PySide6.QtWidgets import QWidget
3-
from PySide6.QtCore import Signal
3+
from PySide6.QtCore import Signal, QTimer, Qt
44

55

66
class PropertyComment(QWidget):
@@ -17,12 +17,43 @@ def __init__(self, value_class, value):
1717
self.ui.text_field.setPlainText(value)
1818
self.ui.text_field.textChanged.connect(self.on_changed)
1919

20+
# Timer for debouncing resize operations
21+
self._resize_timer = QTimer()
22+
self._resize_timer.setSingleShot(True)
23+
self._resize_timer.timeout.connect(self._do_auto_resize)
24+
self._resize_timer.setInterval(100)
2025

2126
self.change_value()
27+
# Initial resize after content is set
28+
self._do_auto_resize()
2229

2330
def on_changed(self):
2431
self.change_value()
2532
self.edited.emit()
33+
# Debounce the resize to avoid excessive updates during rapid typing
34+
self._resize_timer.start()
35+
36+
def _do_auto_resize(self):
37+
doc = self.ui.text_field.document()
38+
doc_height = doc.size().height()
39+
40+
padding = 16
41+
new_height = max(128, int(doc_height) + padding)
42+
43+
self.setFixedHeight(new_height)
44+
self.updateGeometry()
45+
46+
from src.editors.smartprop_editor.property_frame import PropertyFrame
47+
parent = self.parentWidget()
48+
while parent is not None:
49+
parent.updateGeometry()
50+
parent_layout = QWidget.layout(parent)
51+
if parent_layout is not None:
52+
parent_layout.activate()
53+
parent.adjustSize()
54+
if isinstance(parent, PropertyFrame):
55+
break
56+
parent = parent.parentWidget()
2657

2758
def change_value(self):
2859
value = self.ui.text_field.toPlainText()

src/editors/smartprop_editor/property/comment.ui

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,6 @@
1010
<height>157</height>
1111
</rect>
1212
</property>
13-
<property name="minimumSize">
14-
<size>
15-
<width>0</width>
16-
<height>128</height>
17-
</size>
18-
</property>
19-
<property name="maximumSize">
20-
<size>
21-
<width>16777215</width>
22-
<height>256</height>
23-
</size>
24-
</property>
2513
<property name="windowTitle">
2614
<string>Form</string>
2715
</property>

src/editors/smartprop_editor/property/ui_comment.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ def setupUi(self, Widget):
2323
if not Widget.objectName():
2424
Widget.setObjectName(u"Widget")
2525
Widget.resize(830, 157)
26-
Widget.setMinimumSize(QSize(0, 128))
27-
Widget.setMaximumSize(QSize(16777215, 256))
2826
Widget.setStyleSheet(u".QWidget {\n"
2927
" font: 580 10pt \"Segoe UI\";\n"
3028
" border: 2px solid black;\n"

0 commit comments

Comments
 (0)