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
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Graphics;Science;Engineering;
|
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;
|
Keywords=CAD;3D;modeling;engineering;design;parametric;
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
StartupWMClass=KindredCreate
|
StartupWMClass=KindredCreate
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||||
<mime-type type="application/x-extension-fcstd">
|
<mime-type type="application/x-extension-fcstd">
|
||||||
<comment>Kindred Create Document</comment>
|
<comment>FreeCAD Document</comment>
|
||||||
<comment xml:lang="en">Kindred Create Document</comment>
|
<comment xml:lang="en">FreeCAD Document</comment>
|
||||||
<icon name="kindred-create"/>
|
<icon name="kindred-create"/>
|
||||||
<glob pattern="*.FCStd"/>
|
<glob pattern="*.FCStd"/>
|
||||||
<glob pattern="*.fcstd"/>
|
<glob pattern="*.fcstd"/>
|
||||||
<glob pattern="*.FCSTD"/>
|
<glob pattern="*.FCSTD"/>
|
||||||
</mime-type>
|
</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>
|
</mime-info>
|
||||||
|
|||||||
@@ -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
|
Handles two responsibilities:
|
||||||
from scratch, then re-injects them after save completes.
|
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 os
|
||||||
|
import uuid
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
|
|
||||||
# Cache: filepath -> {entry_name: bytes}
|
# Cache: filepath -> {entry_name: bytes}
|
||||||
_silo_cache = {}
|
_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:
|
class _KcFormatObserver:
|
||||||
"""Document observer that preserves silo/ entries across saves."""
|
"""Document observer that preserves silo/ entries across saves."""
|
||||||
@@ -35,22 +57,43 @@ class _KcFormatObserver:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def slotFinishSaveDocument(self, doc, filename):
|
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"):
|
if not filename.lower().endswith(".kc"):
|
||||||
_silo_cache.pop(filename, None)
|
_silo_cache.pop(filename, None)
|
||||||
return
|
return
|
||||||
entries = _silo_cache.pop(filename, None)
|
entries = _silo_cache.pop(filename, None)
|
||||||
if not entries:
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
with zipfile.ZipFile(filename, "a") as zf:
|
with zipfile.ZipFile(filename, "a") as zf:
|
||||||
existing = set(zf.namelist())
|
existing = set(zf.namelist())
|
||||||
|
# Re-inject cached silo/ entries
|
||||||
|
if entries:
|
||||||
for name, data in entries.items():
|
for name, data in entries.items():
|
||||||
if name not in existing:
|
if name not in existing:
|
||||||
zf.writestr(name, data)
|
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:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(
|
FreeCAD.Console.PrintWarning(
|
||||||
f"kc_format: failed to preserve silo/ entries: {e}\n"
|
f"kc_format: failed to update .kc silo/ entries: {e}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user