feat: .kc Layer 1 — manifest auto-creation and platform file associations #204

Merged
forbes merged 1 commits from feat/kc-layer1-finish into main 2026-02-14 18:47:58 +00:00
3 changed files with 63 additions and 13 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -1,18 +1,40 @@
"""
kc_format.py — .kc file format round-trip preservation.
kc_format.py — .kc file format support.
Caches silo/ ZIP entries before FreeCAD's C++ save rewrites the ZIP
from scratch, then re-injects them after save completes.
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."""
@@ -35,22 +57,43 @@ class _KcFormatObserver:
pass
def slotFinishSaveDocument(self, doc, filename):
"""After save: re-inject cached silo/ entries into the .kc ZIP."""
"""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)
if not entries:
return
try:
with zipfile.ZipFile(filename, "a") as zf:
existing = set(zf.namelist())
for name, data in entries.items():
if name not in existing:
zf.writestr(name, data)
# 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 preserve silo/ entries: {e}\n"
f"kc_format: failed to update .kc silo/ entries: {e}\n"
)