208 lines · 6.2 KB
1
"""
2
SaveUtils.py  -  core logic for the SaveUtils FreeCAD addon.
3
"""
4
5
import re
6
import os
7
import datetime
8
import FreeCAD
9
from PySide import QtCore
10
11
if FreeCAD.GuiUp:
12
    import FreeCADGui
13
    from PySide import QtGui
14
15
QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP
16
17
FreeCAD.Console.PrintLog("=== SaveUtils: SaveUtils.py loading ===\n")
18
19
# ---------------------------------------------------------------------------
20
# Regex patterns
21
# ---------------------------------------------------------------------------
22
_TS_PATTERN = re.compile(r"-\d{8}-\d{6}\.FCStd$", re.IGNORECASE)
23
_INC_PATTERN = re.compile(r"-(\d{1,2})\.FCStd$", re.IGNORECASE)
24
_EXT_PATTERN = re.compile(r"\.FCStd$", re.IGNORECASE)
25
26
27
def _strip_fcstd(path):
28
    return _EXT_PATTERN.sub("", path)
29
30
31
def _get_base_path():
32
    doc = FreeCAD.ActiveDocument
33
    if doc is None:
34
        QtGui.QMessageBox.warning(
35
            FreeCADGui.getMainWindow(), "SaveUtils", "No active document."
36
        )
37
        return None
38
    return doc.FileName or None
39
40
41
def _ask_base_filename(title):
42
    path, _ = QtGui.QFileDialog.getSaveFileName(
43
        FreeCADGui.getMainWindow(),
44
        title,
45
        os.path.expanduser("~/untitled"),
46
        "FreeCAD files (*.FCStd)",
47
    )
48
    if not path:
49
        return None
50
    return _strip_fcstd(path)
51
52
53
def _save_as(new_path):
54
    doc = FreeCAD.ActiveDocument
55
    try:
56
        doc.saveAs(new_path)
57
        FreeCAD.Console.PrintMessage(f"SaveUtils: saved as '{new_path}'\n")
58
        return True
59
    except Exception as exc:
60
        QtGui.QMessageBox.critical(
61
            FreeCADGui.getMainWindow(),
62
            "SaveUtils error",
63
            f"Could not save file:\n{exc}",
64
        )
65
        return False
66
67
68
# ---------------------------------------------------------------------------
69
# Commands
70
# ---------------------------------------------------------------------------
71
72
73
class CmdSaveTimestamp:
74
    def GetResources(self):
75
        return {
76
            "MenuText": "Save As with Timestamp",
77
            "ToolTip": "Save a copy appending -YYYYMMDD-HHMMSS to the filename",
78
            "Pixmap": "",
79
        }
80
81
    def IsActive(self):
82
        return FreeCAD.ActiveDocument is not None
83
84
    def Activated(self):
85
        current = _get_base_path()
86
        if current:
87
            base = (
88
                _TS_PATTERN.sub("", current)
89
                if _TS_PATTERN.search(current)
90
                else _strip_fcstd(current)
91
            )
92
        else:
93
            base = _ask_base_filename("Save As with Timestamp – choose base filename")
94
            if base is None:
95
                return
96
        stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
97
        _save_as(f"{base}-{stamp}.FCStd")
98
99
100
class CmdSaveIncrement:
101
    def GetResources(self):
102
        return {
103
            "MenuText": "Save As Increment",
104
            "ToolTip": "Save a copy incrementing the trailing -NN counter",
105
            "Pixmap": "",
106
        }
107
108
    def IsActive(self):
109
        return FreeCAD.ActiveDocument is not None
110
111
    def Activated(self):
112
        current = _get_base_path()
113
        if current:
114
            m = _INC_PATTERN.search(current)
115
            if m:
116
                new_num = int(m.group(1)) + 1
117
                base = current[: m.start()]
118
                _save_as(f"{base}-{new_num:02d}.FCStd")
119
            else:
120
                _save_as(f"{_strip_fcstd(current)}-01.FCStd")
121
        else:
122
            base = _ask_base_filename("Save As Increment – choose base filename")
123
            if base is None:
124
                return
125
            _save_as(f"{base}-01.FCStd")
126
127
128
# ---------------------------------------------------------------------------
129
# Menu injection
130
# ---------------------------------------------------------------------------
131
132
133
def install():
134
    FreeCAD.Console.PrintLog("=== SaveUtils: install() called ===\n")
135
136
    # Register commands
137
    FreeCADGui.addCommand("SaveUtils_Timestamp", CmdSaveTimestamp())
138
    FreeCADGui.addCommand("SaveUtils_Increment", CmdSaveIncrement())
139
140
    _inject_menu()
141
142
143
def _inject_menu():
144
    mw = FreeCADGui.getMainWindow()
145
    if mw is None:
146
        FreeCAD.Console.PrintError("=== SaveUtils: main window not found ===\n")
147
        return
148
149
    menubar = mw.menuBar()
150
    file_menu = None
151
152
    # Search by objectName first (most reliable), then fall back to display text
153
    for action in menubar.actions():
154
        menu = action.menu()
155
        if menu is None:
156
            continue
157
        obj_name = menu.objectName().replace("&", "").strip().lower()
158
        disp_name = action.text().replace("&", "").strip().lower()
159
        if obj_name in ("file", "&file") or disp_name == "file":
160
            file_menu = menu
161
            break
162
163
    if file_menu is None:
164
        FreeCAD.Console.PrintError("=== SaveUtils: File menu not found ===\n")
165
        FreeCAD.Console.PrintMessage(
166
            "=== SaveUtils: menus found: "
167
            + str(
168
                [
169
                    (a.text(), a.menu().objectName() if a.menu() else "")
170
                    for a in menubar.actions()
171
                ]
172
            )
173
            + " ===\n"
174
        )
175
        return
176
177
    # Avoid duplicates
178
    for act in file_menu.actions():
179
        if act.text() == "Save As with Timestamp":
180
            FreeCAD.Console.PrintMessage(
181
                "=== SaveUtils: already installed, skipping ===\n"
182
            )
183
            return
184
185
    # Build plain QActions
186
    ts_action = QtGui.QAction("Save As with Timestamp", file_menu)
187
    ts_action.setToolTip("Save a copy appending -YYYYMMDD-HHMMSS to the filename")
188
    ts_action.triggered.connect(lambda: FreeCADGui.runCommand("SaveUtils_Timestamp"))
189
190
    inc_action = QtGui.QAction("Save As Increment", file_menu)
191
    inc_action.setToolTip("Save a copy incrementing the trailing -NN counter")
192
    inc_action.triggered.connect(lambda: FreeCADGui.runCommand("SaveUtils_Increment"))
193
194
    # Insert before the first separator
195
    actions = file_menu.actions()
196
    insert_before = next((a for a in actions if a.isSeparator()), None)
197
198
    if insert_before:
199
        file_menu.insertAction(insert_before, ts_action)
200
        file_menu.insertAction(insert_before, inc_action)
201
        file_menu.insertSeparator(insert_before)
202
    else:
203
        file_menu.addSeparator()
204
        file_menu.addAction(ts_action)
205
        file_menu.addAction(inc_action)
206
207
    FreeCAD.Console.PrintLog("=== SaveUtils: menu items injected ===\n")
208