feat: .kc Layer 1 — manifest auto-creation and platform file associations
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Some checks failed
Build and Test / build (pull_request) Has been cancelled
kc_format.py: - Auto-create silo/manifest.json with default fields (UUID, timestamps, username) when saving a .kc file that lacks one - Update modified_at timestamp on each save - KC_VERSION = 1.0 Platform integration: - kindred-create.desktop: add application/x-kindred-create MIME type - kindred-create.xml: register .kc glob patterns with dedicated MIME type (application/x-kindred-create), separate from .fcstd type
This commit is contained in:
@@ -7,7 +7,7 @@ Icon=kindred-create
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Graphics;Science;Engineering;
|
||||
MimeType=application/x-extension-fcstd;x-scheme-handler/kindred;
|
||||
MimeType=application/x-extension-fcstd;application/x-kindred-create;x-scheme-handler/kindred;
|
||||
Keywords=CAD;3D;modeling;engineering;design;parametric;
|
||||
StartupNotify=true
|
||||
StartupWMClass=KindredCreate
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type type="application/x-extension-fcstd">
|
||||
<comment>Kindred Create Document</comment>
|
||||
<comment xml:lang="en">Kindred Create Document</comment>
|
||||
<comment>FreeCAD Document</comment>
|
||||
<comment xml:lang="en">FreeCAD Document</comment>
|
||||
<icon name="kindred-create"/>
|
||||
<glob pattern="*.FCStd"/>
|
||||
<glob pattern="*.fcstd"/>
|
||||
<glob pattern="*.FCSTD"/>
|
||||
</mime-type>
|
||||
<mime-type type="application/x-kindred-create">
|
||||
<comment>Kindred Create Document</comment>
|
||||
<comment xml:lang="en">Kindred Create Document</comment>
|
||||
<icon name="kindred-create"/>
|
||||
<glob pattern="*.kc"/>
|
||||
<glob pattern="*.KC"/>
|
||||
</mime-type>
|
||||
</mime-info>
|
||||
|
||||
102
src/Mod/Create/kc_format.py
Normal file
102
src/Mod/Create/kc_format.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
kc_format.py — .kc file format support.
|
||||
|
||||
Handles two responsibilities:
|
||||
1. Round-trip preservation: caches silo/ ZIP entries before FreeCAD's C++
|
||||
save rewrites the ZIP from scratch, then re-injects them after save.
|
||||
2. Manifest auto-creation: ensures every .kc file has silo/manifest.json.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
import zipfile
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# Cache: filepath -> {entry_name: bytes}
|
||||
_silo_cache = {}
|
||||
|
||||
KC_VERSION = "1.0"
|
||||
|
||||
|
||||
def _default_manifest():
|
||||
"""Generate a default silo/manifest.json for new .kc files."""
|
||||
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
username = os.environ.get("USER", os.environ.get("USERNAME", "unknown"))
|
||||
return {
|
||||
"kc_version": KC_VERSION,
|
||||
"silo_instance": None,
|
||||
"part_uuid": str(uuid.uuid4()),
|
||||
"revision_hash": None,
|
||||
"created_at": now,
|
||||
"modified_at": now,
|
||||
"created_by": username,
|
||||
}
|
||||
|
||||
|
||||
class _KcFormatObserver:
|
||||
"""Document observer that preserves silo/ entries across saves."""
|
||||
|
||||
def slotStartSaveDocument(self, doc, filename):
|
||||
"""Before save: cache silo/ entries from the existing file."""
|
||||
if not filename.lower().endswith(".kc"):
|
||||
return
|
||||
if not os.path.isfile(filename):
|
||||
return
|
||||
try:
|
||||
with zipfile.ZipFile(filename, "r") as zf:
|
||||
entries = {}
|
||||
for name in zf.namelist():
|
||||
if name.startswith("silo/"):
|
||||
entries[name] = zf.read(name)
|
||||
if entries:
|
||||
_silo_cache[filename] = entries
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def slotFinishSaveDocument(self, doc, filename):
|
||||
"""After save: re-inject cached silo/ entries and ensure manifest."""
|
||||
if not filename.lower().endswith(".kc"):
|
||||
_silo_cache.pop(filename, None)
|
||||
return
|
||||
entries = _silo_cache.pop(filename, None)
|
||||
try:
|
||||
with zipfile.ZipFile(filename, "a") as zf:
|
||||
existing = set(zf.namelist())
|
||||
# Re-inject cached silo/ entries
|
||||
if entries:
|
||||
for name, data in entries.items():
|
||||
if name not in existing:
|
||||
zf.writestr(name, data)
|
||||
existing.add(name)
|
||||
# Ensure silo/manifest.json exists
|
||||
if "silo/manifest.json" not in existing:
|
||||
manifest = _default_manifest()
|
||||
zf.writestr(
|
||||
"silo/manifest.json",
|
||||
json.dumps(manifest, indent=2) + "\n",
|
||||
)
|
||||
else:
|
||||
# Update modified_at timestamp
|
||||
raw = zf.read("silo/manifest.json")
|
||||
manifest = json.loads(raw)
|
||||
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
if manifest.get("modified_at") != now:
|
||||
manifest["modified_at"] = now
|
||||
# ZipFile append mode can't overwrite; write new entry
|
||||
# (last duplicate wins in most ZIP readers)
|
||||
zf.writestr(
|
||||
"silo/manifest.json",
|
||||
json.dumps(manifest, indent=2) + "\n",
|
||||
)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
f"kc_format: failed to update .kc silo/ entries: {e}\n"
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
"""Connect to application-level save signals."""
|
||||
FreeCAD.addDocumentObserver(_KcFormatObserver())
|
||||
Reference in New Issue
Block a user