Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs-static-site/src/content/docs/user-guide/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Understanding the concepts laid out here will help you understand how to use the

Note: if you are already knowledgeable in git, just review git term next to each heading to familiarize yourself with the terminology mapping, and you'll be ready to rock and roll.

Prefer the git terms everywhere? Enable **Edit → Preferences → History → Terminology → "Use git terminology"** to relabel the interface (Iteration → Commit, Project → Repository, Reviewed → Staged, and so on). Restart FreeCAD for the change to apply to every label.

## FreeCAD Terminology

FreeCAD stores files in the `.FCStd` format, and represents a single **Document**. From the workbench's perspective, a FreeCAD "Document" and "file" mean the same thing.
Expand Down
1 change: 1 addition & 0 deletions freecad/history_wb/application/actions/result_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def has_any(self) -> bool:
"""Return True when any issue exists on either side or general bucket."""
return self.old_snapshot is not None or self.new_snapshot is not None or bool(self.general)


@dataclass(frozen=True)
class DocumentDiffResult:
"""Application-level diff result for one FCStd document."""
Expand Down
13 changes: 13 additions & 0 deletions freecad/history_wb/domain/settings/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ def get_settings(self) -> Settings:
"""
...

def git_terminology_enabled(self) -> bool:
"""Return whether the git-native terminology display toggle is on.

Controls whether the UI relabels CAD-friendly vocabulary (Iteration,
Project, Reviewed, ...) with the underlying git terms (Commit,
Repository, Staged, ...). Independent of diff computation.
"""
...

def set_git_terminology_enabled(self, enabled: bool) -> None:
"""Persist the git-native terminology display toggle."""
...


class SettingsPersistenceRepository(Protocol):
"""Interface for raw diff settings persistence state access."""
Expand Down
122 changes: 93 additions & 29 deletions freecad/history_wb/entrypoints/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from ..qt import QtCore
from ..resources import ICONPATH
from ..utils import Log, translate
from ..utils import Log, term, translate


if TYPE_CHECKING:
Expand Down Expand Up @@ -92,8 +92,14 @@ class _CommitCommand:
def GetResources(self) -> CommandResources:
"""Return FreeCAD command metadata for UI integration."""
return {
"MenuText": QtCore.QT_TRANSLATE_NOOP("HistoryCommit", "Save Iteration"),
"ToolTip": QtCore.QT_TRANSLATE_NOOP("HistoryCommit", "Save reviewed changes as an iteration"),
"MenuText": term(
QtCore.QT_TRANSLATE_NOOP("HistoryCommit", "Save Iteration"),
QtCore.QT_TRANSLATE_NOOP("HistoryCommit", "Commit"),
),
"ToolTip": term(
QtCore.QT_TRANSLATE_NOOP("HistoryCommit", "Save reviewed changes as an iteration"),
QtCore.QT_TRANSLATE_NOOP("HistoryCommit", "Commit the staged changes"),
),
"Pixmap": os.path.join(ICONPATH, "Commit.svg"),
}

Expand All @@ -117,14 +123,27 @@ class _RefreshRepositoryCommand:
def GetResources(self) -> CommandResources:
"""Return FreeCAD command metadata for UI integration."""
return {
"MenuText": QtCore.QT_TRANSLATE_NOOP("HistoryRefreshRepository", "Refresh Project"),
"ToolTip": QtCore.QT_TRANSLATE_NOOP(
"HistoryRefreshRepository",
"Refresh the detected project and reload iterations.\n"
"Open at least one FreeCAD document "
"located within a project before running this command.\n"
"How it works: open FreeCAD "
"documents are checked one by one until one is found to be located within a project.",
"MenuText": term(
QtCore.QT_TRANSLATE_NOOP("HistoryRefreshRepository", "Refresh Project"),
QtCore.QT_TRANSLATE_NOOP("HistoryRefreshRepository", "Refresh Repository"),
),
"ToolTip": term(
QtCore.QT_TRANSLATE_NOOP(
"HistoryRefreshRepository",
"Refresh the detected project and reload iterations.\n"
"Open at least one FreeCAD document "
"located within a project before running this command.\n"
"How it works: open FreeCAD "
"documents are checked one by one until one is found to be located within a project.",
),
QtCore.QT_TRANSLATE_NOOP(
"HistoryRefreshRepository",
"Refresh the detected repository and reload commits.\n"
"Open at least one FreeCAD document "
"located within a repository before running this command.\n"
"How it works: open FreeCAD "
"documents are checked one by one until one is found to be located within a repository.",
),
),
"Pixmap": os.path.join(ICONPATH, "RefreshRepository.svg"),
}
Expand All @@ -146,10 +165,19 @@ class _InitializeGitRepositoryCommand:
def GetResources(self) -> CommandResources:
"""Return FreeCAD command metadata for UI integration."""
return {
"MenuText": QtCore.QT_TRANSLATE_NOOP("HistoryInitializeGitRepository", "Initialize Project"),
"ToolTip": QtCore.QT_TRANSLATE_NOOP(
"HistoryInitializeGitRepository",
"Initialize a new project in the selected directory",
"MenuText": term(
QtCore.QT_TRANSLATE_NOOP("HistoryInitializeGitRepository", "Initialize Project"),
QtCore.QT_TRANSLATE_NOOP("HistoryInitializeGitRepository", "Initialize Repository"),
),
"ToolTip": term(
QtCore.QT_TRANSLATE_NOOP(
"HistoryInitializeGitRepository",
"Initialize a new project in the selected directory",
),
QtCore.QT_TRANSLATE_NOOP(
"HistoryInitializeGitRepository",
"Initialize a new repository in the selected directory",
),
),
"Pixmap": os.path.join(ICONPATH, "CreateGitRepository.svg"),
}
Expand All @@ -173,13 +201,25 @@ class _OpenAllDocumentsInRepositoryCommand:
def GetResources(self) -> CommandResources:
"""Return FreeCAD command metadata for UI integration."""
return {
"MenuText": QtCore.QT_TRANSLATE_NOOP(
"HistoryOpenAllDocumentsInRepository",
"Open All Documents in Project",
"MenuText": term(
QtCore.QT_TRANSLATE_NOOP(
"HistoryOpenAllDocumentsInRepository",
"Open All Documents in Project",
),
QtCore.QT_TRANSLATE_NOOP(
"HistoryOpenAllDocumentsInRepository",
"Open All Documents in Repository",
),
),
"ToolTip": QtCore.QT_TRANSLATE_NOOP(
"HistoryOpenAllDocumentsInRepository",
"Open every .FCStd file found in the project. Useful for generating en masse.",
"ToolTip": term(
QtCore.QT_TRANSLATE_NOOP(
"HistoryOpenAllDocumentsInRepository",
"Open every .FCStd file found in the project. Useful for generating en masse.",
),
QtCore.QT_TRANSLATE_NOOP(
"HistoryOpenAllDocumentsInRepository",
"Open every .FCStd file found in the repository. Useful for generating en masse.",
),
),
"Pixmap": os.path.join(ICONPATH, "OpenAllDocuments.svg"),
}
Expand All @@ -202,8 +242,14 @@ def Activated(self) -> None:
repo = ui_registry.application_state.git_repository
if repo is None:
dialog_view.show_warning_message(
translate("History", "No Project"),
translate("History", "No project detected. Open a FreeCAD document in a project first."),
term(
translate("History", "No Project"),
translate("History", "No Repository"),
),
term(
translate("History", "No project detected. Open a FreeCAD document in a project first."),
translate("History", "No repository detected. Open a FreeCAD document in a repository first."),
),
)
return

Expand All @@ -216,10 +262,19 @@ class _UpdateGitIgnoreCommand:
def GetResources(self) -> CommandResources:
"""Return FreeCAD command metadata for UI integration."""
return {
"MenuText": QtCore.QT_TRANSLATE_NOOP("HistoryUpdateGitIgnore", "Edit Ignored Files"),
"ToolTip": QtCore.QT_TRANSLATE_NOOP(
"HistoryUpdateGitIgnore",
"Edit project ignored files list (.gitignore)",
"MenuText": term(
QtCore.QT_TRANSLATE_NOOP("HistoryUpdateGitIgnore", "Edit Ignored Files"),
QtCore.QT_TRANSLATE_NOOP("HistoryUpdateGitIgnore", "Edit .gitignore"),
),
"ToolTip": term(
QtCore.QT_TRANSLATE_NOOP(
"HistoryUpdateGitIgnore",
"Edit project ignored files list (.gitignore)",
),
QtCore.QT_TRANSLATE_NOOP(
"HistoryUpdateGitIgnore",
"Edit repository ignored files list (.gitignore)",
),
),
"Pixmap": os.path.join(ICONPATH, "GitIgnore.svg"),
}
Expand Down Expand Up @@ -314,7 +369,10 @@ class _CloseDiffWindowsCommand:
def GetResources(self) -> CommandResources:
"""Return FreeCAD command metadata for UI integration."""
return {
"MenuText": QtCore.QT_TRANSLATE_NOOP("HistoryCloseDiffWindows", "Close Comparison Windows"),
"MenuText": term(
QtCore.QT_TRANSLATE_NOOP("HistoryCloseDiffWindows", "Close Comparison Windows"),
QtCore.QT_TRANSLATE_NOOP("HistoryCloseDiffWindows", "Close Diff Windows"),
),
"ToolTip": QtCore.QT_TRANSLATE_NOOP(
"HistoryCloseDiffWindows",
"Close every document starting with 'Diff_' without saving",
Expand All @@ -339,7 +397,13 @@ def Activated(self) -> None:


def register_commands() -> None:
"""Register the Diff Workbench commands with FreeCAD."""
"""Register the Diff Workbench commands with FreeCAD.

Command labels resolve the git-terminology toggle inside GetResources via
term(), so no wrapper is needed. FreeCAD queries GetResources at Initialize
(before the container exists); term() reads the toggle directly from
preferences in that case.
"""
import FreeCADGui as Gui # pylint: disable=import-error

Gui.addCommand("HistoryConfigureAuthorCommand", _ConfigureAuthorCommand())
Expand Down
11 changes: 9 additions & 2 deletions freecad/history_wb/entrypoints/workbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from ..qt import QtCore, QtGui, QtWidgets
from ..resources import ICONPATH
from ..utils import Log, set_logger, translate
from ..utils import Log, set_logger, term, translate


_PREFERENCES_REGISTRY_ATTR = "_history_wb_preference_pages"
Expand Down Expand Up @@ -57,7 +57,13 @@ class HistoryWorkbench(Gui.Workbench):
def __init__(self):
super().__init__()
self.MenuText = cast(str, QtCore.QT_TRANSLATE_NOOP("Workbench", "History"))
self.ToolTip = cast(str, QtCore.QT_TRANSLATE_NOOP("Workbench", "Track project iterations and history"))
self.ToolTip = cast(
str,
term(
QtCore.QT_TRANSLATE_NOOP("Workbench", "Track project iterations and history"),
QtCore.QT_TRANSLATE_NOOP("Workbench", "Track repository commits and history"),
),
)
self._subwindow = None # Store reference to MDI subwindow

def GetClassName(self) -> str:
Expand Down Expand Up @@ -232,6 +238,7 @@ def _on_subwindow_closed(self) -> None:

# Clear panel-scoped presenters; application state survives for command access
from ..ui.registry import ui_registry

ui_registry.clear_presenters()

def _focus_diff_panel_deferred(self) -> None:
Expand Down
34 changes: 33 additions & 1 deletion freecad/history_wb/infrastructure/freecad/settings_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,25 @@ def SetInt(self, key: str, value: int) -> None: ...
MIN_FLOAT_PRECISION = 0
MAX_FLOAT_PRECISION = 12

# Single source of truth for where the History workbench preferences live.
PARAM_GROUP_PATH = "User parameter:BaseApp/Preferences/Mod/History"
KEY_GIT_TERMINOLOGY = "GitTerminology"


def read_git_terminology_enabled() -> bool:
"""Read the git-terminology toggle directly from FreeCAD preferences.

Used during command registration, which happens at workbench Initialize
before the application container (and its cached settings repository)
exists. Returns False when FreeCAD is unavailable (e.g. unit tests).
"""
try:
import FreeCAD as App # pylint: disable=import-error

return bool(App.ParamGet(PARAM_GROUP_PATH).GetBool(KEY_GIT_TERMINOLOGY, False))
except (ImportError, AttributeError):
return False


class FreeCADSettingsRepository:
"""Settings repository implementation using FreeCAD's Parameter system.
Expand All @@ -67,8 +86,9 @@ class FreeCADSettingsRepository:

def __init__(self, ctx: FreeCadContext) -> None:
self._ctx = ctx
self._group_path = "User parameter:BaseApp/Preferences/Mod/History"
self._group_path = PARAM_GROUP_PATH
self._cached_settings: Settings | None = None
self._cached_git_terminology: bool | None = None

def _get_group(self) -> _ParamGroup:
return cast(_ParamGroup, self._ctx.app.ParamGet(self._group_path))
Expand Down Expand Up @@ -232,3 +252,15 @@ def get_settings(self) -> Settings:
self._cached_settings = self._build_settings_from_state(self.get_persistence_state())

return self._cached_settings

def git_terminology_enabled(self) -> bool:
"""Return the git-terminology display toggle, caching the lookup."""
if self._cached_git_terminology is None:
self._cached_git_terminology = self._get_group().GetBool(KEY_GIT_TERMINOLOGY, False)

return self._cached_git_terminology

def set_git_terminology_enabled(self, enabled: bool) -> None:
"""Persist the git-terminology display toggle and refresh the cache."""
self._get_group().SetBool(KEY_GIT_TERMINOLOGY, enabled)
self._cached_git_terminology = enabled
Loading