feat(kc): foundation — Silo tree infrastructure, document observer, and base objects #37

Closed
opened 2026-02-18 21:14:58 +00:00 by forbes · 0 comments
Owner

Summary

Build the core infrastructure that creates Silo metadata tree nodes in the FreeCAD document tree when a .kc file is opened. This is Phase 1 of the Silo Metadata Viewport specification — no viewer widgets yet, just the tree structure and lifecycle management.

Background

When a .kc file is opened, the silo/ directory in the ZIP contains metadata files (manifest, metadata, history, etc.). These should appear as nodes in the document tree under a "Silo" group, below the standard geometry objects. Currently, kc_format.py preserves silo/ entries across saves but does not parse or expose them as document objects.

Scope

New files

File Purpose
src/Mod/Create/silo_objects.py SiloViewerObjectApp::FeaturePython proxy with transient properties (SiloPath, ContentType, RawContent). All properties use Prop_Transient (flag 2) so nothing is written to Document.xml. __getstate__ returns None.
src/Mod/Create/silo_viewproviders.py SiloViewerViewProvider — controls tree icon via getIcon(), context menu via setupContextMenu(), and doubleClicked() (stub returning False for now).
src/Mod/Create/silo_tree.py SiloTreeBuilder — reads silo/ entries from a ZIP file, creates App::DocumentObjectGroup "Silo" with conditional child objects based on which silo/ entries exist. Handles stale stub cleanup on load.
src/Mod/Create/silo_document.py SiloDocumentObserver — hooks slotCreatedDocument + QTimer.singleShot(0) to create tree after restore. Also hooks slotActivateDocument as fallback guard.

Modified files

File Change
src/Mod/Create/kc_format.py Add pre_reinject callback hook so the viewport module can update silo/ cache entries before they are written back to the ZIP on save.
src/Mod/Create/InitGui.py Register SiloDocumentObserver via deferred QTimer after kc_format registration (observer ordering matters for save-back).
src/Mod/Create/CMakeLists.txt Add the 4 new .py files to the install list.

Tree structure

Document
├── Body (FreeCAD standard)
│   ├── Sketch
│   ├── Pad
│   └── Fillet
└── Silo (App::DocumentObjectGroup)
    ├── Manifest (SiloViewerObject — always present)
    ├── Metadata (SiloViewerObject — if metadata.json exists)
    ├── History (SiloViewerObject — if history.json has revisions)
    ├── Approvals (SiloViewerObject — if approvals.json has eco field)
    ├── Dependencies (SiloViewerObject — if dependencies.json has links)
    ├── Jobs (App::DocumentObjectGroup — if jobs/ has YAML files)
    │   └── default.yaml (SiloViewerObject per file)
    └── Macros (App::DocumentObjectGroup — if macros/ has .py files)
        └── on_save.py (SiloViewerObject per file)

Key technical decisions

  1. Transient properties: All SiloViewerObject properties are created with Prop_Transient (value 2) via obj.addProperty("App::PropertyString", "SiloPath", "Silo", "doc", 2). This prevents property content from being written to Document.xml. Minimal stubs (name + type) still appear — these are harmless and cleaned on next load.

  2. Restore hook: Python observer API lacks slotFinishRestoreDocument. Use slotCreatedDocument(doc) — check doc.FileName ends with .kc, then QTimer.singleShot(0, lambda: _build_tree(doc)) to defer to next event loop tick (document fully loaded by then). slotActivateDocument serves as fallback for documents opened before observer registration.

  3. Save-back coordination: kc_format.py caches silo/ entries in slotStartSaveDocument and reinjects in slotFinishSaveDocument. The new pre_reinject hook is called between cache and reinject, allowing modified object content to overwrite cache entries before ZIP write.

  4. Stale stub cleanup: On restore, SiloTreeBuilder removes any existing "Silo" group and children before creating fresh objects from silo/ data. This handles the edge case of .kc files with leftover XML stubs.

Acceptance criteria

  • Opening a .kc with silo/manifest.json creates a "Silo" group with a "Manifest" child in the tree
  • Opening a .kc with multiple silo/ entries creates the correct conditional tree nodes
  • Opening a .kc with only silo/manifest.json creates only Silo > Manifest (no empty groups)
  • Saving and reopening the .kc recreates the tree identically
  • Document.xml in the saved ZIP contains only minimal stubs for Silo objects (no property content)
  • Opening an .fcstd produces no Silo tree objects
  • Double-clicking a Silo tree node is a no-op (returns False)
  • Opening the same .kc in vanilla FreeCAD produces no errors (stubs log harmless warnings)

Dependencies

  • None — builds on existing kc_format.py infrastructure

References

  • docs/KC_SPECIFICATION.md §3 (Document Tree Structure), §4 (Base Classes), §6.1 (Creation)
  • docs/SILO_VIEWPORT_PLAN.md Phase 1
## Summary Build the core infrastructure that creates Silo metadata tree nodes in the FreeCAD document tree when a `.kc` file is opened. This is Phase 1 of the Silo Metadata Viewport specification — no viewer widgets yet, just the tree structure and lifecycle management. ## Background When a `.kc` file is opened, the `silo/` directory in the ZIP contains metadata files (manifest, metadata, history, etc.). These should appear as nodes in the document tree under a "Silo" group, below the standard geometry objects. Currently, `kc_format.py` preserves `silo/` entries across saves but does not parse or expose them as document objects. ## Scope ### New files | File | Purpose | |------|---------| | `src/Mod/Create/silo_objects.py` | `SiloViewerObject` — `App::FeaturePython` proxy with transient properties (`SiloPath`, `ContentType`, `RawContent`). All properties use `Prop_Transient` (flag `2`) so nothing is written to `Document.xml`. `__getstate__` returns `None`. | | `src/Mod/Create/silo_viewproviders.py` | `SiloViewerViewProvider` — controls tree icon via `getIcon()`, context menu via `setupContextMenu()`, and `doubleClicked()` (stub returning `False` for now). | | `src/Mod/Create/silo_tree.py` | `SiloTreeBuilder` — reads `silo/` entries from a ZIP file, creates `App::DocumentObjectGroup` "Silo" with conditional child objects based on which `silo/` entries exist. Handles stale stub cleanup on load. | | `src/Mod/Create/silo_document.py` | `SiloDocumentObserver` — hooks `slotCreatedDocument` + `QTimer.singleShot(0)` to create tree after restore. Also hooks `slotActivateDocument` as fallback guard. | ### Modified files | File | Change | |------|--------| | `src/Mod/Create/kc_format.py` | Add `pre_reinject` callback hook so the viewport module can update `silo/` cache entries before they are written back to the ZIP on save. | | `src/Mod/Create/InitGui.py` | Register `SiloDocumentObserver` via deferred `QTimer` after `kc_format` registration (observer ordering matters for save-back). | | `src/Mod/Create/CMakeLists.txt` | Add the 4 new `.py` files to the install list. | ## Tree structure ``` Document ├── Body (FreeCAD standard) │ ├── Sketch │ ├── Pad │ └── Fillet └── Silo (App::DocumentObjectGroup) ├── Manifest (SiloViewerObject — always present) ├── Metadata (SiloViewerObject — if metadata.json exists) ├── History (SiloViewerObject — if history.json has revisions) ├── Approvals (SiloViewerObject — if approvals.json has eco field) ├── Dependencies (SiloViewerObject — if dependencies.json has links) ├── Jobs (App::DocumentObjectGroup — if jobs/ has YAML files) │ └── default.yaml (SiloViewerObject per file) └── Macros (App::DocumentObjectGroup — if macros/ has .py files) └── on_save.py (SiloViewerObject per file) ``` ## Key technical decisions 1. **Transient properties**: All `SiloViewerObject` properties are created with `Prop_Transient` (value `2`) via `obj.addProperty("App::PropertyString", "SiloPath", "Silo", "doc", 2)`. This prevents property content from being written to `Document.xml`. Minimal stubs (name + type) still appear — these are harmless and cleaned on next load. 2. **Restore hook**: Python observer API lacks `slotFinishRestoreDocument`. Use `slotCreatedDocument(doc)` — check `doc.FileName` ends with `.kc`, then `QTimer.singleShot(0, lambda: _build_tree(doc))` to defer to next event loop tick (document fully loaded by then). `slotActivateDocument` serves as fallback for documents opened before observer registration. 3. **Save-back coordination**: `kc_format.py` caches `silo/` entries in `slotStartSaveDocument` and reinjects in `slotFinishSaveDocument`. The new `pre_reinject` hook is called between cache and reinject, allowing modified object content to overwrite cache entries before ZIP write. 4. **Stale stub cleanup**: On restore, `SiloTreeBuilder` removes any existing "Silo" group and children before creating fresh objects from `silo/` data. This handles the edge case of `.kc` files with leftover XML stubs. ## Acceptance criteria - [ ] Opening a `.kc` with `silo/manifest.json` creates a "Silo" group with a "Manifest" child in the tree - [ ] Opening a `.kc` with multiple `silo/` entries creates the correct conditional tree nodes - [ ] Opening a `.kc` with only `silo/manifest.json` creates only Silo > Manifest (no empty groups) - [ ] Saving and reopening the `.kc` recreates the tree identically - [ ] `Document.xml` in the saved ZIP contains only minimal stubs for Silo objects (no property content) - [ ] Opening an `.fcstd` produces no Silo tree objects - [ ] Double-clicking a Silo tree node is a no-op (returns `False`) - [ ] Opening the same `.kc` in vanilla FreeCAD produces no errors (stubs log harmless warnings) ## Dependencies - None — builds on existing `kc_format.py` infrastructure ## References - `docs/KC_SPECIFICATION.md` §3 (Document Tree Structure), §4 (Base Classes), §6.1 (Creation) - `docs/SILO_VIEWPORT_PLAN.md` Phase 1
forbes added the enhancement label 2026-02-18 21:14:58 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kindred/silo-mod#37