Merge pull request 'feat(create): silo tree foundation for .kc files' (#268) from feat/silo-tree-foundation into main
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #268
This commit was merged in pull request #268.
This commit is contained in:
@@ -22,6 +22,10 @@ install(
|
|||||||
InitGui.py
|
InitGui.py
|
||||||
addon_loader.py
|
addon_loader.py
|
||||||
kc_format.py
|
kc_format.py
|
||||||
|
silo_document.py
|
||||||
|
silo_objects.py
|
||||||
|
silo_tree.py
|
||||||
|
silo_viewproviders.py
|
||||||
update_checker.py
|
update_checker.py
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/version.py
|
${CMAKE_CURRENT_BINARY_DIR}/version.py
|
||||||
DESTINATION
|
DESTINATION
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ def _register_kc_format():
|
|||||||
FreeCAD.Console.PrintLog(f"Create: kc_format registration skipped: {e}\n")
|
FreeCAD.Console.PrintLog(f"Create: kc_format registration skipped: {e}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _register_silo_document_observer():
|
||||||
|
"""Register the Silo document observer for .kc tree building."""
|
||||||
|
try:
|
||||||
|
import silo_document
|
||||||
|
|
||||||
|
silo_document.register()
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintLog(f"Create: silo_document registration skipped: {e}\n")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Silo integration enhancements
|
# Silo integration enhancements
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -122,6 +132,7 @@ try:
|
|||||||
from PySide.QtCore import QTimer
|
from PySide.QtCore import QTimer
|
||||||
|
|
||||||
QTimer.singleShot(500, _register_kc_format)
|
QTimer.singleShot(500, _register_kc_format)
|
||||||
|
QTimer.singleShot(600, _register_silo_document_observer)
|
||||||
QTimer.singleShot(1500, _register_silo_origin)
|
QTimer.singleShot(1500, _register_silo_origin)
|
||||||
QTimer.singleShot(2000, _setup_silo_auth_panel)
|
QTimer.singleShot(2000, _setup_silo_auth_panel)
|
||||||
QTimer.singleShot(3000, _check_silo_first_start)
|
QTimer.singleShot(3000, _check_silo_first_start)
|
||||||
|
|||||||
@@ -18,6 +18,19 @@ import FreeCAD
|
|||||||
# Cache: filepath -> {entry_name: bytes}
|
# Cache: filepath -> {entry_name: bytes}
|
||||||
_silo_cache = {}
|
_silo_cache = {}
|
||||||
|
|
||||||
|
# Pre-reinject hooks: called with (doc, filename, entries) before ZIP write.
|
||||||
|
_pre_reinject_hooks = []
|
||||||
|
|
||||||
|
|
||||||
|
def register_pre_reinject(callback):
|
||||||
|
"""Register a callback invoked before silo/ entries are written to ZIP.
|
||||||
|
|
||||||
|
Signature: callback(doc, filename, entries) -> None
|
||||||
|
``entries`` is a dict {entry_name: bytes} or None. Mutate in place.
|
||||||
|
"""
|
||||||
|
_pre_reinject_hooks.append(callback)
|
||||||
|
|
||||||
|
|
||||||
KC_VERSION = "1.0"
|
KC_VERSION = "1.0"
|
||||||
|
|
||||||
|
|
||||||
@@ -62,6 +75,13 @@ class _KcFormatObserver:
|
|||||||
_silo_cache.pop(filename, None)
|
_silo_cache.pop(filename, None)
|
||||||
return
|
return
|
||||||
entries = _silo_cache.pop(filename, None)
|
entries = _silo_cache.pop(filename, None)
|
||||||
|
for _hook in _pre_reinject_hooks:
|
||||||
|
try:
|
||||||
|
_hook(doc, filename, entries)
|
||||||
|
except Exception as exc:
|
||||||
|
FreeCAD.Console.PrintWarning(
|
||||||
|
f"kc_format: pre_reinject hook failed: {exc}\n"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
with zipfile.ZipFile(filename, "a") as zf:
|
with zipfile.ZipFile(filename, "a") as zf:
|
||||||
existing = set(zf.namelist())
|
existing = set(zf.namelist())
|
||||||
|
|||||||
85
src/Mod/Create/silo_document.py
Normal file
85
src/Mod/Create/silo_document.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
"""
|
||||||
|
silo_document.py - Document observer that builds the Silo metadata tree
|
||||||
|
when a .kc file is opened in FreeCAD.
|
||||||
|
|
||||||
|
Hooks slotCreatedDocument (primary) and slotActivateDocument (fallback)
|
||||||
|
to detect .kc opens, then defers tree building to the next event loop
|
||||||
|
tick via QTimer.singleShot(0, ...) so the document is fully loaded.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD
|
||||||
|
|
||||||
|
_observer = None
|
||||||
|
|
||||||
|
|
||||||
|
def _on_kc_restored(doc):
|
||||||
|
"""Deferred callback: build the Silo tree after document is fully loaded."""
|
||||||
|
try:
|
||||||
|
filename = doc.FileName
|
||||||
|
if not filename:
|
||||||
|
return
|
||||||
|
from silo_tree import SiloTreeBuilder
|
||||||
|
|
||||||
|
contents = SiloTreeBuilder.read_silo_directory(filename)
|
||||||
|
if contents:
|
||||||
|
SiloTreeBuilder.build_tree(doc, contents)
|
||||||
|
except Exception as exc:
|
||||||
|
FreeCAD.Console.PrintWarning(
|
||||||
|
f"silo_document: failed to build silo tree for {doc.Name!r}: {exc}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SiloDocumentObserver:
|
||||||
|
"""Singleton observer that triggers Silo tree creation for .kc files."""
|
||||||
|
|
||||||
|
def slotCreatedDocument(self, doc):
|
||||||
|
"""Called when a document is created or opened."""
|
||||||
|
try:
|
||||||
|
filename = doc.FileName
|
||||||
|
if not filename or not filename.lower().endswith(".kc"):
|
||||||
|
return
|
||||||
|
from PySide.QtCore import QTimer
|
||||||
|
|
||||||
|
QTimer.singleShot(0, lambda: _on_kc_restored(doc))
|
||||||
|
except Exception as exc:
|
||||||
|
FreeCAD.Console.PrintWarning(
|
||||||
|
f"silo_document: slotCreatedDocument error: {exc}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
def slotActivateDocument(self, doc):
|
||||||
|
"""Fallback for documents opened before observer registration."""
|
||||||
|
try:
|
||||||
|
filename = doc.FileName
|
||||||
|
if not filename or not filename.lower().endswith(".kc"):
|
||||||
|
return
|
||||||
|
if doc.getObject("Silo") is not None:
|
||||||
|
return
|
||||||
|
from PySide.QtCore import QTimer
|
||||||
|
|
||||||
|
QTimer.singleShot(0, lambda: _on_kc_restored(doc))
|
||||||
|
except Exception as exc:
|
||||||
|
FreeCAD.Console.PrintWarning(
|
||||||
|
f"silo_document: slotActivateDocument error: {exc}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
def slotDeletedDocument(self, doc):
|
||||||
|
"""Placeholder for future cleanup."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
"""Register the singleton observer. Safe to call multiple times."""
|
||||||
|
global _observer
|
||||||
|
if _observer is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
_observer = SiloDocumentObserver()
|
||||||
|
FreeCAD.addDocumentObserver(_observer)
|
||||||
|
FreeCAD.Console.PrintLog("silo_document: observer registered\n")
|
||||||
|
|
||||||
|
# Bootstrap: handle documents already open before registration.
|
||||||
|
try:
|
||||||
|
for doc in FreeCAD.listDocuments().values():
|
||||||
|
_observer.slotActivateDocument(doc)
|
||||||
|
except Exception as exc:
|
||||||
|
FreeCAD.Console.PrintWarning(f"silo_document: bootstrap scan failed: {exc}\n")
|
||||||
58
src/Mod/Create/silo_objects.py
Normal file
58
src/Mod/Create/silo_objects.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
silo_objects.py - FreeCAD FeaturePython proxy for Silo tree leaf nodes.
|
||||||
|
|
||||||
|
Each silo/ ZIP entry in a .kc file gets one SiloViewerObject in the
|
||||||
|
FreeCAD document tree. All properties are Transient so they are never
|
||||||
|
persisted in Document.xml.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD
|
||||||
|
|
||||||
|
|
||||||
|
class SiloViewerObject:
|
||||||
|
"""Proxy for App::FeaturePython silo viewer nodes.
|
||||||
|
|
||||||
|
Properties (all Transient):
|
||||||
|
SiloPath - ZIP entry path, e.g. "silo/manifest.json"
|
||||||
|
ContentType - "json", "yaml", or "py"
|
||||||
|
RawContent - decoded UTF-8 content of the entry
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
obj.Proxy = self
|
||||||
|
|
||||||
|
obj.addProperty(
|
||||||
|
"App::PropertyString",
|
||||||
|
"SiloPath",
|
||||||
|
"Silo",
|
||||||
|
"ZIP entry path of this silo item",
|
||||||
|
)
|
||||||
|
obj.addProperty(
|
||||||
|
"App::PropertyString",
|
||||||
|
"ContentType",
|
||||||
|
"Silo",
|
||||||
|
"Content type of this silo item",
|
||||||
|
)
|
||||||
|
obj.addProperty(
|
||||||
|
"App::PropertyString",
|
||||||
|
"RawContent",
|
||||||
|
"Silo",
|
||||||
|
"Raw text content of this silo entry",
|
||||||
|
)
|
||||||
|
|
||||||
|
obj.setPropertyStatus("SiloPath", "Transient")
|
||||||
|
obj.setPropertyStatus("ContentType", "Transient")
|
||||||
|
obj.setPropertyStatus("RawContent", "Transient")
|
||||||
|
|
||||||
|
obj.setEditorMode("SiloPath", 1) # read-only in property panel
|
||||||
|
obj.setEditorMode("ContentType", 1)
|
||||||
|
obj.setEditorMode("RawContent", 2) # hidden in property panel
|
||||||
|
|
||||||
|
def execute(self, obj):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
pass
|
||||||
229
src/Mod/Create/silo_tree.py
Normal file
229
src/Mod/Create/silo_tree.py
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
"""
|
||||||
|
silo_tree.py - Builds the Silo metadata tree in the FreeCAD document.
|
||||||
|
|
||||||
|
Reads silo/ entries from a .kc ZIP and creates a conditional hierarchy
|
||||||
|
of App::FeaturePython and App::DocumentObjectGroup objects in the
|
||||||
|
document tree.
|
||||||
|
|
||||||
|
Tree structure:
|
||||||
|
Silo (App::DocumentObjectGroup)
|
||||||
|
+-- Manifest (always present)
|
||||||
|
+-- Metadata (if metadata.json is non-empty)
|
||||||
|
+-- History (if history.json has revisions)
|
||||||
|
+-- Approvals (if approvals.json has eco field)
|
||||||
|
+-- Dependencies (if dependencies.json has links)
|
||||||
|
+-- Jobs (group, if silo/jobs/ has YAML files)
|
||||||
|
| +-- default.yaml
|
||||||
|
+-- Macros (group, if silo/macros/ has .py files)
|
||||||
|
+-- on_save
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
import FreeCAD
|
||||||
|
|
||||||
|
_SILO_GROUP_NAME = "Silo"
|
||||||
|
|
||||||
|
# Top-level silo/ entries with their object names, labels, and
|
||||||
|
# optional JSON field checks for conditional creation.
|
||||||
|
_KNOWN_ENTRIES = [
|
||||||
|
# (zip_name, object_name, label, json_check)
|
||||||
|
# json_check is None (always create) or (field_name, check_fn)
|
||||||
|
("silo/manifest.json", "SiloManifest", "Manifest", None),
|
||||||
|
("silo/metadata.json", "SiloMetadata", "Metadata", None),
|
||||||
|
(
|
||||||
|
"silo/history.json",
|
||||||
|
"SiloHistory",
|
||||||
|
"History",
|
||||||
|
("revisions", lambda v: isinstance(v, list) and len(v) > 0),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"silo/approvals.json",
|
||||||
|
"SiloApprovals",
|
||||||
|
"Approvals",
|
||||||
|
("eco", lambda v: v is not None),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"silo/dependencies.json",
|
||||||
|
"SiloDependencies",
|
||||||
|
"Dependencies",
|
||||||
|
("links", lambda v: isinstance(v, list) and len(v) > 0),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _content_type(entry_name):
|
||||||
|
"""Determine content type from ZIP entry name."""
|
||||||
|
if entry_name.endswith(".json"):
|
||||||
|
return "json"
|
||||||
|
if entry_name.endswith((".yaml", ".yml")):
|
||||||
|
return "yaml"
|
||||||
|
if entry_name.endswith(".py"):
|
||||||
|
return "py"
|
||||||
|
return "text"
|
||||||
|
|
||||||
|
|
||||||
|
def _decode(data):
|
||||||
|
"""Decode bytes to UTF-8 string, returning '' on failure."""
|
||||||
|
try:
|
||||||
|
return data.decode("utf-8")
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _should_create(data, json_check):
|
||||||
|
"""Check whether a conditional node should be created."""
|
||||||
|
if json_check is None:
|
||||||
|
return True
|
||||||
|
field_name, check_fn = json_check
|
||||||
|
try:
|
||||||
|
parsed = json.loads(data)
|
||||||
|
return check_fn(parsed.get(field_name))
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _create_leaf(doc, parent, entry_name, data, obj_name, label):
|
||||||
|
"""Create one App::FeaturePython leaf and add it to parent group."""
|
||||||
|
from silo_objects import SiloViewerObject
|
||||||
|
from silo_viewproviders import SiloViewerViewProvider
|
||||||
|
|
||||||
|
obj = doc.addObject("App::FeaturePython", obj_name)
|
||||||
|
SiloViewerObject(obj)
|
||||||
|
|
||||||
|
obj.SiloPath = entry_name
|
||||||
|
obj.ContentType = _content_type(entry_name)
|
||||||
|
obj.RawContent = _decode(data)
|
||||||
|
obj.Label = label
|
||||||
|
|
||||||
|
try:
|
||||||
|
import FreeCADGui # noqa: F401
|
||||||
|
|
||||||
|
if obj.ViewObject is not None:
|
||||||
|
SiloViewerViewProvider(obj.ViewObject)
|
||||||
|
except ImportError:
|
||||||
|
pass # headless mode
|
||||||
|
|
||||||
|
parent.addObject(obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class SiloTreeBuilder:
|
||||||
|
"""Reads silo/ from a .kc ZIP and builds the document tree."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_silo_directory(filename):
|
||||||
|
"""Read silo/ entries from a .kc ZIP.
|
||||||
|
|
||||||
|
Returns dict {entry_name: bytes}, e.g. {"silo/manifest.json": b"..."}.
|
||||||
|
Returns {} on failure.
|
||||||
|
"""
|
||||||
|
entries = {}
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(filename, "r") as zf:
|
||||||
|
for name in zf.namelist():
|
||||||
|
if name.startswith("silo/") and not name.endswith("/"):
|
||||||
|
entries[name] = zf.read(name)
|
||||||
|
except Exception as exc:
|
||||||
|
FreeCAD.Console.PrintWarning(
|
||||||
|
f"silo_tree: could not read silo/ from {filename!r}: {exc}\n"
|
||||||
|
)
|
||||||
|
return entries
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_tree(doc, silo_contents):
|
||||||
|
"""Create the Silo group hierarchy in doc from silo/ entries."""
|
||||||
|
if not silo_contents:
|
||||||
|
return
|
||||||
|
|
||||||
|
SiloTreeBuilder.remove_silo_tree(doc)
|
||||||
|
|
||||||
|
root = doc.addObject("App::DocumentObjectGroup", _SILO_GROUP_NAME)
|
||||||
|
root.Label = "Silo"
|
||||||
|
|
||||||
|
# Top-level known entries (conditional creation)
|
||||||
|
for zip_name, obj_name, label, json_check in _KNOWN_ENTRIES:
|
||||||
|
if zip_name not in silo_contents:
|
||||||
|
continue
|
||||||
|
data = silo_contents[zip_name]
|
||||||
|
if not _should_create(data, json_check):
|
||||||
|
continue
|
||||||
|
_create_leaf(doc, root, zip_name, data, obj_name, label)
|
||||||
|
|
||||||
|
# Jobs subgroup
|
||||||
|
job_entries = {
|
||||||
|
k: v
|
||||||
|
for k, v in silo_contents.items()
|
||||||
|
if k.startswith("silo/jobs/") and not k.endswith("/")
|
||||||
|
}
|
||||||
|
if job_entries:
|
||||||
|
jobs_group = doc.addObject("App::DocumentObjectGroup", "SiloJobs")
|
||||||
|
jobs_group.Label = "Jobs"
|
||||||
|
root.addObject(jobs_group)
|
||||||
|
for entry_name in sorted(job_entries):
|
||||||
|
basename = entry_name.split("/")[-1]
|
||||||
|
safe_name = "SiloJob_" + basename.replace(".", "_").replace("-", "_")
|
||||||
|
_create_leaf(
|
||||||
|
doc,
|
||||||
|
jobs_group,
|
||||||
|
entry_name,
|
||||||
|
job_entries[entry_name],
|
||||||
|
safe_name,
|
||||||
|
basename,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Macros subgroup
|
||||||
|
macro_entries = {
|
||||||
|
k: v
|
||||||
|
for k, v in silo_contents.items()
|
||||||
|
if k.startswith("silo/macros/") and not k.endswith("/")
|
||||||
|
}
|
||||||
|
if macro_entries:
|
||||||
|
macros_group = doc.addObject("App::DocumentObjectGroup", "SiloMacros")
|
||||||
|
macros_group.Label = "Macros"
|
||||||
|
root.addObject(macros_group)
|
||||||
|
for entry_name in sorted(macro_entries):
|
||||||
|
basename = entry_name.split("/")[-1]
|
||||||
|
label = basename[:-3] if basename.endswith(".py") else basename
|
||||||
|
safe_name = "SiloMacro_" + basename.replace(".", "_").replace("-", "_")
|
||||||
|
_create_leaf(
|
||||||
|
doc,
|
||||||
|
macros_group,
|
||||||
|
entry_name,
|
||||||
|
macro_entries[entry_name],
|
||||||
|
safe_name,
|
||||||
|
label,
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.recompute()
|
||||||
|
FreeCAD.Console.PrintLog(
|
||||||
|
f"silo_tree: built tree with {len(silo_contents)} entries in {doc.Name!r}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove_silo_tree(doc):
|
||||||
|
"""Remove the Silo group and all descendants. Safe if absent."""
|
||||||
|
root = doc.getObject(_SILO_GROUP_NAME)
|
||||||
|
if root is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
names = []
|
||||||
|
|
||||||
|
def _collect(obj):
|
||||||
|
if obj.Name in names:
|
||||||
|
return
|
||||||
|
names.append(obj.Name)
|
||||||
|
if hasattr(obj, "OutList"):
|
||||||
|
for child in obj.OutList:
|
||||||
|
_collect(child)
|
||||||
|
|
||||||
|
_collect(root)
|
||||||
|
|
||||||
|
for name in reversed(names):
|
||||||
|
try:
|
||||||
|
doc.removeObject(name)
|
||||||
|
except Exception as exc:
|
||||||
|
FreeCAD.Console.PrintWarning(
|
||||||
|
f"silo_tree: could not remove {name!r}: {exc}\n"
|
||||||
|
)
|
||||||
77
src/Mod/Create/silo_viewproviders.py
Normal file
77
src/Mod/Create/silo_viewproviders.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
silo_viewproviders.py - ViewProvider proxy for Silo tree leaf nodes.
|
||||||
|
|
||||||
|
Controls tree icon, double-click behavior, and context menu for
|
||||||
|
SiloViewerObject nodes in the document tree.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Icon directory — Phase 6 will add SVGs here.
|
||||||
|
_ICON_DIR = os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"resources",
|
||||||
|
"icons",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Map silo/ paths to icon basenames (without .svg extension).
|
||||||
|
_SILO_PATH_ICONS = {
|
||||||
|
"silo/manifest.json": "silo-manifest",
|
||||||
|
"silo/metadata.json": "silo-metadata",
|
||||||
|
"silo/history.json": "silo-history",
|
||||||
|
"silo/approvals.json": "silo-approvals",
|
||||||
|
"silo/dependencies.json": "silo-dependencies",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prefix-based fallbacks for subdirectory entries.
|
||||||
|
_SILO_PREFIX_ICONS = {
|
||||||
|
"silo/jobs/": "silo-job",
|
||||||
|
"silo/macros/": "silo-macro",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _icon_for_path(silo_path):
|
||||||
|
"""Return absolute icon path for a silo/ entry, or '' if not found."""
|
||||||
|
name = _SILO_PATH_ICONS.get(silo_path)
|
||||||
|
if name is None:
|
||||||
|
for prefix, icon_name in _SILO_PREFIX_ICONS.items():
|
||||||
|
if silo_path.startswith(prefix):
|
||||||
|
name = icon_name
|
||||||
|
break
|
||||||
|
if name is None:
|
||||||
|
return ""
|
||||||
|
path = os.path.join(_ICON_DIR, f"{name}.svg")
|
||||||
|
return path if os.path.exists(path) else ""
|
||||||
|
|
||||||
|
|
||||||
|
class SiloViewerViewProvider:
|
||||||
|
"""ViewProvider proxy for SiloViewerObject leaf nodes."""
|
||||||
|
|
||||||
|
def __init__(self, vobj):
|
||||||
|
vobj.Proxy = self
|
||||||
|
self.Object = vobj.Object
|
||||||
|
|
||||||
|
def attach(self, vobj):
|
||||||
|
"""Store back-reference; called on document restore."""
|
||||||
|
self.Object = vobj.Object
|
||||||
|
|
||||||
|
def getIcon(self):
|
||||||
|
"""Return icon path based on SiloPath; '' uses FreeCAD default."""
|
||||||
|
try:
|
||||||
|
return _icon_for_path(self.Object.SiloPath)
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def doubleClicked(self, vobj):
|
||||||
|
"""Phase 1: no action on double-click."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
def setupContextMenu(self, vobj, menu):
|
||||||
|
"""Phase 1: no context menu items."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user