Files
Web_FreeCAD_Bitbybit/scripts/freecad-xlink-relink-oracle.py
wangdequan f64b78865c
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled
feat: complete production naming and reference lifecycle gates
2026-08-14 10:10:30 -04:00

232 lines
8.4 KiB
Python

import json
import os
import tempfile
import FreeCAD as App
import Part # noqa: F401 - registers Part and PartDesign shapes
FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"
def version_text():
return ".".join(str(value) for value in App.Version()[:3])
def document_open(name):
return name in App.listDocuments()
def object_ref(obj):
if obj is None:
return None
return {"document": obj.Document.Name, "object": obj.Name}
def xlink_ref(value):
return object_ref(value)
def xlink_sub_ref(value):
if not value or value[0] is None:
return None
obj, sub_elements = value
subs = [sub_elements] if isinstance(sub_elements, str) else list(sub_elements)
return {**object_ref(obj), "subElements": [str(value) for value in subs if str(value)]}
def xlink_list_ref(value):
return [object_ref(obj) for obj in value if obj is not None]
def xlink_sub_list_ref(value):
output = []
for obj, sub_elements in value:
if obj is None:
continue
subs = [sub_elements] if isinstance(sub_elements, str) else list(sub_elements)
output.append({**object_ref(obj), "subElements": [str(value) for value in subs if str(value)]})
return output
def property_status(holder, name):
return [str(value) for value in holder.getPropertyStatus(name)]
def holder_state(holder):
return {
"xLink": xlink_ref(holder.XLink),
"xLinkSub": xlink_sub_ref(holder.XLinkSub),
"xLinkList": xlink_list_ref(holder.XLinkList),
"xLinkSubList": xlink_sub_list_ref(holder.XLinkSubList),
"propertyStatus": {
name: property_status(holder, name)
for name in ["XLink", "XLinkSub", "XLinkList", "XLinkSubList"]
},
"state": [str(value) for value in holder.State],
"status": str(holder.getStatusString()),
}
def binder_state(binder):
support = []
for obj, sub_elements in binder.Support:
subs = [sub_elements] if isinstance(sub_elements, str) else list(sub_elements)
support.append({**object_ref(obj), "subElements": [str(value) for value in subs if str(value)]})
return {
"support": support,
"area": float(binder.Shape.Area),
"shapeNull": bool(binder.Shape.isNull()),
"state": [str(value) for value in binder.State],
"status": str(binder.getStatusString()),
}
def linked_dimensions(holder):
return {
"xLinkLength": float(holder.XLink.Length) if holder.XLink else None,
"xLinkSubWidth": float(holder.XLinkSub[0].Width) if holder.XLinkSub and holder.XLinkSub[0] else None,
"xLinkListHeights": [float(obj.Height) for obj in holder.XLinkList if obj is not None],
"xLinkSubListAreas": [float(obj.getSubObject(sub).Area) for obj, subs in holder.XLinkSubList for sub in subs],
}
with tempfile.TemporaryDirectory(prefix="freecad-xlink-relink-") as temp_dir:
source_path = os.path.join(temp_dir, "XLinkSource.FCStd")
consumer_path = os.path.join(temp_dir, "XLinkConsumer.FCStd")
missing_path = os.path.join(temp_dir, "XLinkSource.missing.FCStd")
source = App.newDocument("XLinkSource")
source_body = source.addObject("PartDesign::Body", "SourceBody")
source_box = source_body.newObject("PartDesign::AdditiveBox", "SourceBox")
source_box.Length = 4
source_box.Width = 5
source_box.Height = 6
second_body = source.addObject("PartDesign::Body", "SecondBody")
second_box = second_body.newObject("PartDesign::AdditiveBox", "SecondBox")
second_box.Length = 3
second_box.Width = 7
second_box.Height = 2
source.recompute()
source.saveAs(source_path)
consumer = App.newDocument("XLinkConsumer")
consumer.saveAs(consumer_path)
holder = consumer.addObject("App::FeaturePython", "XLinkHolder")
holder.addProperty("App::PropertyXLink", "XLink", "External")
holder.addProperty("App::PropertyXLinkSub", "XLinkSub", "External")
holder.addProperty("App::PropertyXLinkList", "XLinkList", "External")
holder.addProperty("App::PropertyXLinkSubList", "XLinkSubList", "External")
holder.XLink = source_box
holder.XLinkSub = (source_box, ["Face1"])
holder.XLinkList = [source_box, second_box]
holder.XLinkSubList = [(source_box, ["Face1"]), (second_box, ["Face1", "Face2"])]
binder_body = consumer.addObject("PartDesign::Body", "BinderBody")
binder = binder_body.newObject("PartDesign::SubShapeBinder", "ExternalBinder")
binder.Support = [(source_box, ("Face1",))]
consumer.recompute()
consumer.saveAs(consumer_path)
source.save()
consumer.save()
initial = {
"holder": holder_state(holder),
"dimensions": linked_dimensions(holder),
"binder": binder_state(binder),
"sourceDocuments": sorted(document.Name for document in App.listDocuments().values()),
}
App.closeDocument(source.Name)
consumer.recompute()
source_closed = {
"holder": holder_state(holder),
"binder": binder_state(binder),
"sourceOpen": document_open("XLinkSource"),
}
reopened_source = App.openDocument(source_path)
reopened_source.recompute()
consumer.recompute()
relink_after_close = {
"holder": holder_state(holder),
"dimensions": linked_dimensions(holder),
"binder": binder_state(binder),
"sourceOpen": document_open("XLinkSource") and App.listDocuments()["XLinkSource"] is reopened_source,
}
App.closeDocument(consumer.Name)
App.closeDocument(reopened_source.Name)
os.rename(source_path, missing_path)
missing_consumer = App.openDocument(consumer_path)
missing_consumer.recompute()
missing_holder = missing_consumer.getObject("XLinkHolder")
missing_binder = missing_consumer.getObject("ExternalBinder")
missing_source = {
"holder": holder_state(missing_holder),
"binder": binder_state(missing_binder),
"sourceOpen": document_open("XLinkSource"),
}
os.rename(missing_path, source_path)
restored_source = App.openDocument(source_path)
restored_source.recompute()
missing_consumer.recompute()
restored_holder = missing_consumer.getObject("XLinkHolder")
restored_binder = missing_consumer.getObject("ExternalBinder")
relink_after_missing = {
"holder": holder_state(restored_holder),
"dimensions": linked_dimensions(restored_holder),
"binder": binder_state(restored_binder),
"sourceOpen": document_open("XLinkSource") and App.listDocuments()["XLinkSource"] is restored_source,
}
restored_box = restored_source.getObject("SourceBox")
restored_second = restored_source.getObject("SecondBox")
restored_box.Length = 8
restored_box.Width = 9
restored_second.Height = 4
restored_source.recompute()
missing_consumer.recompute()
edited = {
"holder": holder_state(restored_holder),
"dimensions": linked_dimensions(restored_holder),
"binder": binder_state(restored_binder),
"sourceFaceArea": float(restored_box.Shape.Face1.Area),
}
restored_source.save()
missing_consumer.save()
App.closeDocument(missing_consumer.Name)
App.closeDocument(restored_source.Name)
final_source = App.openDocument(source_path)
final_consumer = App.openDocument(consumer_path)
final_source.recompute()
final_consumer.recompute()
final_holder = final_consumer.getObject("XLinkHolder")
final_binder = final_consumer.getObject("ExternalBinder")
final_roundtrip = {
"holder": holder_state(final_holder),
"dimensions": linked_dimensions(final_holder),
"binder": binder_state(final_binder),
"sourceFaceArea": float(final_source.getObject("SourceBox").Shape.Face1.Area),
}
App.closeDocument(final_consumer.Name)
App.closeDocument(final_source.Name)
report = {
"schemaVersion": 1,
"baselineId": "freecad-1.1.1-xlink-relink-oracle",
"freecadVersion": version_text(),
"gitCommit": FREECAD_COMMIT,
"status": "pass",
"tolerance": 1e-7,
"propertyTypes": ["App::PropertyXLink", "App::PropertyXLinkSub", "App::PropertyXLinkList", "App::PropertyXLinkSubList"],
"initial": initial,
"sourceClosed": source_closed,
"relinkAfterClose": relink_after_close,
"missingSource": missing_source,
"relinkAfterMissing": relink_after_missing,
"edited": edited,
"finalRoundtrip": final_roundtrip,
}
print("FREECAD_XLINK_RELINK_RESULT=" + json.dumps(report, sort_keys=True, separators=(",", ":")))