Files
create/docs/src/reference/cpp-local-file-origin.md
forbes-0023 8fd664d509 docs(c++): LocalFileOrigin implementation guide
Covers ownership-by-exclusion algorithm, document identity, open/save
workflows, capability flags, connection state, OriginManager integration,
and unregister protection.

Closes #131
2026-02-10 08:14:29 -06:00

8.4 KiB

LocalFileOrigin

LocalFileOrigin is the built-in, default FileOrigin implementation. It handles documents stored on the local filesystem without external tracking or synchronisation.

  • Header: src/Gui/FileOrigin.h
  • Source: src/Gui/FileOrigin.cpp
  • Origin ID: "local"
  • Display name: "Local Files" (nickname "Local")
  • Type: OriginType::Local

Design principles

  1. Identity is the file path. A document's identity under LocalFileOrigin is its FileName property — the absolute path on disk.
  2. Ownership by exclusion. LocalFileOrigin claims every document that is not claimed by a PLM origin. It detects this by scanning for the SiloItemId property; if no object in the document has it, the document is local.
  3. Always available. LocalFileOrigin is created by OriginManager at startup and cannot be unregistered. It is the mandatory fallback origin.
  4. Thin delegation. Most operations delegate directly to App::GetApplication() or App::Document methods. LocalFileOrigin adds no persistence layer of its own.

Identity and capabilities

std::string id()       const override;  // "local"
std::string name()     const override;  // "Local Files"
std::string nickname() const override;  // "Local"
QIcon       icon()     const override;  // theme icon "document-new"
OriginType  type()     const override;  // OriginType::Local

Capability flags

All capability queries return false:

Method Returns Reason
tracksExternally() false No external database or server
requiresAuthentication() false No login required
supportsRevisions() false No revision history
supportsBOM() false No Bill of Materials
supportsPartNumbers() false No part number system
supportsAssemblies() false No native assembly tracking

These flags control which toolbar buttons and menu items are enabled. With all flags false, the origin toolbar commands (Commit, Pull, Push, Info, BOM) are disabled for local documents.

Connection state

LocalFileOrigin uses the base-class defaults — it is always ConnectionState::Connected:

Method Behaviour
connectionState() Returns ConnectionState::Connected
connect() Returns true immediately
disconnect() No-op
signalConnectionStateChanged Never emitted

Ownership detection

bool ownsDocument(App::Document* doc) const override;

Algorithm:

  1. Return false if doc is null.
  2. Iterate every object in the document (doc->getObjects()).
  3. For each object, check obj->getPropertyByName("SiloItemId").
  4. If any object has the property, return false — the document belongs to a PLM origin.
  5. If no object has it, return true.

The constant SiloItemId is defined as a static const char* in FileOrigin.cpp.

Edge cases:

  • An empty document (no objects) is owned by LocalFileOrigin.
  • A document where only some objects have SiloItemId is still considered PLM-owned.
  • Performance is O(n) where n is the number of objects in the document.

Ownership resolution order

OriginManager::findOwningOrigin() checks non-local origins first, then falls back to LocalFileOrigin. This ensures PLM origins with specific ownership criteria take priority:

for each registered origin (excluding "local"):
    if origin.ownsDocument(doc) → return that origin

if localOrigin.ownsDocument(doc) → return localOrigin

return nullptr

Results are cached in OriginManager::_documentOrigins for O(1) subsequent lookups.

Document identity

std::string documentIdentity(App::Document* doc) const override;
std::string documentDisplayId(App::Document* doc) const override;

Both return the same value: the document's FileName property (its absolute filesystem path). Returns an empty string if the document is null, not owned, or has never been saved.

For PLM origins these would differ — documentIdentity might return a UUID while documentDisplayId returns a part number. For local files, the path serves both purposes.

Document operations

Creating documents

App::Document* newDocument(const std::string& name = "") override;

Delegates to App::GetApplication().newDocument(). If name is empty, defaults to "Unnamed". The returned document has no FileName set until saved.

Opening documents

App::Document* openDocument(const std::string& identity) override;
App::Document* openDocumentInteractive() override;

openDocument — Non-interactive. Takes a file path, delegates to App::GetApplication().openDocument(). Returns nullptr if the path is empty or the file cannot be loaded.

openDocumentInteractive — Shows a file dialog with the following behaviour:

  1. Builds a format filter list from FreeCAD's registered import types, with .FCStd first.
  2. Shows FileDialog::getOpenFileNames() (multi-file selection).
  3. For each selected file, uses SelectModule::importHandler() to determine the loader module.
  4. Calls Application::Instance->open() with the UserInitiatedOpenDocument flag set.
  5. Runs checkPartialRestore() and checkRestoreError() for validation.
  6. Returns the last loaded document, or nullptr if the dialog was cancelled.

Saving documents

bool saveDocument(App::Document* doc) override;
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
bool saveDocumentAsInteractive(App::Document* doc) override;

saveDocument — Requires that the document already has a FileName set. If the path is empty (never-saved document), returns false. Otherwise calls doc->save().

saveDocumentAs — Non-interactive. Calls doc->saveAs(newIdentity) with the given path. Updates the document's FileName property as a side effect.

saveDocumentAsInteractive — Gets the Gui::Document wrapper and delegates to guiDoc->saveAs(), which shows a save dialog. Returns false if the GUI document cannot be found or the user cancels.

Save workflow pattern:

First save:   saveDocument() → false (no path)
              → command layer calls saveDocumentAsInteractive()
              → user picks path → saveAs()

Later saves:  saveDocument() → true (path exists)
              → doc->save()

PLM operations (not implemented)

These inherited base-class methods are no-ops for LocalFileOrigin:

Method Returns Behaviour
commitDocument(doc) false No external commits
pullDocument(doc) false No remote to pull from
pushDocument(doc) false No remote to push to
showInfo(doc) void No-op
showBOM(doc) void No-op

OriginManager integration

Lifecycle

LocalFileOrigin is created in the OriginManager constructor via ensureLocalOrigin():

auto localOrigin = std::make_unique<LocalFileOrigin>();
_origins[LOCAL_ORIGIN_ID] = std::move(localOrigin);
_currentOriginId = LOCAL_ORIGIN_ID;

It is set as the default current origin. It is the only origin that exists before any Python workbenches load.

Unregister protection

OriginManager::unregisterOrigin() explicitly blocks removal of the local origin:

if (id == LOCAL_ORIGIN_ID) {
    Base::Console().warning(
        "OriginManager: Cannot unregister built-in local origin\n");
    return false;
}

Preference persistence

The current origin ID is saved to User parameter:BaseApp/Preferences/General/Origin under the key CurrentOriginId. On restart, if the saved ID is not "local" but the corresponding origin hasn't been registered yet, LocalFileOrigin remains the active origin until a Python workbench registers the saved origin.

Constants

Defined in FileOrigin.cpp:

static const char* SILO_ITEM_ID_PROP = "SiloItemId";

Defined in OriginManager.cpp:

static const char* LOCAL_ORIGIN_ID = "local";
static const char* PREF_PATH = "User parameter:BaseApp/Preferences/General/Origin";
static const char* PREF_CURRENT_ORIGIN = "CurrentOriginId";

See also