docs: add FileOrigin API reference and Kindred addon test suite

- Add docs/src/reference/cpp-file-origin.md: full API reference for the
  FileOrigin abstract interface, OriginType/ConnectionState enums,
  LocalFileOrigin behavior, and ownership detection algorithm
- Add SUMMARY.md entry under new 'C++ API Reference' section
- Add tests/test_kindred_pure.py: 78 pure-logic unit tests covering
  update_checker, datum_commands, spreadsheet_commands, silo_commands,
  silo_start, and silo_origin (no FreeCAD binary required)
- Add tests/run_kindred_tests.py: two-tier test runner with CI exit codes
- Add pixi task 'test-kindred' for running addon tests
- Add CI/CD step in build.yml to run addon tests before build

Closes #130
This commit is contained in:
2026-02-10 07:54:26 -06:00
parent 5035cf7f93
commit bdbe1b163a
6 changed files with 871 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
# FileOrigin — Abstract Interface
> **Header:** `src/Gui/FileOrigin.h`
> **Implementation:** `src/Gui/FileOrigin.cpp`
> **Namespace:** `Gui`
`FileOrigin` is the abstract base class that all document storage backends
implement. It defines the contract for creating, opening, saving, and
tracking FreeCAD documents through a pluggable origin system.
## Key Design Principle
Origins do **not** change where files are stored — all documents are always
saved to the local filesystem. Origins change the **workflow** and
**identity model**:
| Origin | Identity | Tracking | Authentication |
|--------|----------|----------|----------------|
| Local | File path | None | None |
| PLM (Silo) | Database UUID | External DB + MinIO | Required |
| Cloud | URL / key | External service | Required |
## Enums
### `OriginType`
```cpp
enum class OriginType {
Local, // Local filesystem storage
PLM, // Product Lifecycle Management system (e.g., Silo)
Cloud, // Generic cloud storage
Custom // User-defined origin type
};
```
### `ConnectionState`
```cpp
enum class ConnectionState {
Disconnected, // Not connected
Connecting, // Connection in progress
Connected, // Successfully connected
Error // Connection error occurred
};
```
Used by the [OriginSelectorWidget](./cpp-origin-selector-widget.md) to
render status overlays on origin icons.
## Pure Virtual Methods (must override)
Every `FileOrigin` subclass must implement these methods.
### Identity
| Method | Return | Purpose |
|--------|--------|---------|
| `id()` | `std::string` | Unique origin ID (`"local"`, `"silo"`, …) |
| `name()` | `std::string` | Display name for menus (`"Local Files"`, `"Kindred Silo"`) |
| `nickname()` | `std::string` | Short label for toolbar (`"Local"`, `"Silo"`) |
| `icon()` | `QIcon` | Icon for UI representation |
| `type()` | `OriginType` | Classification enum |
### Workflow Characteristics
| Method | Return | Purpose |
|--------|--------|---------|
| `tracksExternally()` | `bool` | `true` if origin syncs to a remote system |
| `requiresAuthentication()` | `bool` | `true` if origin needs login |
### Document Identity
| Method | Return | Purpose |
|--------|--------|---------|
| `documentIdentity(doc)` | `std::string` | Immutable tracking key. File path for Local, UUID for PLM. |
| `documentDisplayId(doc)` | `std::string` | Human-readable ID. File path for Local, part number for PLM. |
| `ownsDocument(doc)` | `bool` | Whether this origin owns the document. |
**Ownership detection** is the mechanism that determines which origin
manages a given document. The
[OriginManager](./cpp-origin-manager.md) calls `ownsDocument()` on each
registered origin to resolve ownership.
- `LocalFileOrigin` owns documents that have **no** `SiloItemId` property
on any object.
- `SiloOrigin` (Python) owns documents where any object **has** a
`SiloItemId` property.
### Core Document Operations
| Method | Parameters | Return | Purpose |
|--------|-----------|--------|---------|
| `newDocument` | `name = ""` | `App::Document*` | Create a new document |
| `openDocument` | `identity` | `App::Document*` | Open by identity (non-interactive) |
| `openDocumentInteractive` | — | `App::Document*` | Open via dialog (file picker / search) |
| `saveDocument` | `doc` | `bool` | Save document |
| `saveDocumentAs` | `doc, newIdentity` | `bool` | Save with new identity |
| `saveDocumentAsInteractive` | `doc` | `bool` | Save via dialog |
Returns `nullptr` / `false` on failure or cancellation.
## Virtual Methods with Defaults (optional overrides)
### Capability Queries
These default to `false`. Override to advertise capabilities that the
[CommandOrigin](./cpp-command-origin.md) commands check before enabling
menu items.
| Method | Default | Enables |
|--------|---------|---------|
| `supportsRevisions()` | `false` | Commit / Pull / Push commands |
| `supportsBOM()` | `false` | BOM command |
| `supportsPartNumbers()` | `false` | Info command |
| `supportsAssemblies()` | `false` | (reserved for future use) |
### Connection State
| Method | Default | Purpose |
|--------|---------|---------|
| `connectionState()` | `Connected` | Current connection state |
| `connect()` | `return true` | Attempt to connect / authenticate |
| `disconnect()` | no-op | Disconnect from origin |
### Extended PLM Operations
These default to no-op / `false`. Override in PLM origins.
| Method | Default | Purpose |
|--------|---------|---------|
| `commitDocument(doc)` | `false` | Create a versioned snapshot |
| `pullDocument(doc)` | `false` | Fetch latest from remote |
| `pushDocument(doc)` | `false` | Upload changes to remote |
| `showInfo(doc)` | no-op | Show metadata dialog |
| `showBOM(doc)` | no-op | Show Bill of Materials dialog |
| `syncProperties(doc)` | `true` | Push property changes to backend |
## Signal
```cpp
fastsignals::signal<void(ConnectionState)> signalConnectionStateChanged;
```
Origins must emit this signal when their connection state changes. The
`OriginSelectorWidget` subscribes to it to update the toolbar icon
overlay (green = connected, red X = disconnected, warning = error).
## Construction and Lifetime
- `FileOrigin` is non-copyable (deleted copy constructor and assignment
operator).
- The protected default constructor prevents direct instantiation.
- Instances are owned by `OriginManager` via `std::unique_ptr`.
- Python origins are wrapped by `FileOriginPython` (see
[Python-C++ bridge](./cpp-file-origin-python.md)).
## LocalFileOrigin
`LocalFileOrigin` is the built-in concrete implementation that ships with
Kindred Create. It is always registered by `OriginManager` as the
`"local"` origin and serves as the universal fallback.
### Behavior Summary
| Method | Behavior |
|--------|----------|
| `ownsDocument` | Returns `true` if **no** object has a `SiloItemId` property |
| `documentIdentity` | Returns `doc->FileName` (full path) |
| `newDocument` | Delegates to `App::GetApplication().newDocument()` |
| `openDocument` | Delegates to `App::GetApplication().openDocument()` |
| `openDocumentInteractive` | Shows standard FreeCAD file-open dialog with format filters |
| `saveDocument` | Calls `doc->save()`, returns `false` if no filename set |
| `saveDocumentAs` | Calls `doc->saveAs()` |
| `saveDocumentAsInteractive` | Calls `Gui::Document::saveAs()` (shows save dialog) |
### Ownership Detection Algorithm
```
for each object in document:
if object has property "SiloItemId":
return false ← owned by PLM, not local
return true ← local owns this document
```
This negative-match approach means `LocalFileOrigin` is the **universal
fallback** — it owns any document that no other origin claims.
## See Also
- [OriginManager](./cpp-origin-manager.md) — singleton registry that
manages origin instances
- [FileOriginPython](./cpp-file-origin-python.md) — bridge for
implementing origins in Python
- [CommandOrigin](./cpp-command-origin.md) — File menu commands that
dispatch to origins
- [OriginSelectorWidget](./cpp-origin-selector-widget.md) — toolbar
dropdown for switching origins
- [Creating a Custom Origin (C++)](../guide/custom-origin-cpp.md)
- [Creating a Custom Origin (Python)](../guide/custom-origin-python.md)