232 lines
8.4 KiB
Python
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=(",", ":")))
|