feat: advance FreeCAD exact parity evidence
This commit is contained in:
@@ -2,6 +2,7 @@ import json
|
||||
import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import FreeCAD as App
|
||||
|
||||
@@ -9,6 +10,43 @@ import FreeCAD as App
|
||||
FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"
|
||||
|
||||
|
||||
class PropertyObserver:
|
||||
def __init__(self, document_name):
|
||||
self.document_name = document_name
|
||||
self.events = []
|
||||
|
||||
def clear(self):
|
||||
self.events = []
|
||||
|
||||
def slotOpenTransaction(self, document, name):
|
||||
if document.Name == self.document_name:
|
||||
self.events.append({"type": "transaction.opened", "name": str(name)})
|
||||
|
||||
def slotCommitTransaction(self, document):
|
||||
if document.Name == self.document_name:
|
||||
self.events.append({"type": "transaction.committed"})
|
||||
|
||||
def slotAbortTransaction(self, document):
|
||||
if document.Name == self.document_name:
|
||||
self.events.append({"type": "transaction.aborted"})
|
||||
|
||||
def slotBeforeChangeObject(self, obj, prop):
|
||||
if obj.Document and obj.Document.Name == self.document_name:
|
||||
self.events.append({"type": "property.before-change", "object": obj.Name, "property": str(prop)})
|
||||
|
||||
def slotChangedObject(self, obj, prop):
|
||||
if obj.Document and obj.Document.Name == self.document_name:
|
||||
self.events.append({"type": "property.changed", "object": obj.Name, "property": str(prop)})
|
||||
|
||||
def slotAppendDynamicProperty(self, obj, prop):
|
||||
if getattr(obj, "Document", None) and obj.Document.Name == self.document_name:
|
||||
self.events.append({"type": "property.added", "object": obj.Name, "property": str(prop)})
|
||||
|
||||
def slotRemoveDynamicProperty(self, obj, prop):
|
||||
if getattr(obj, "Document", None) and obj.Document.Name == self.document_name:
|
||||
self.events.append({"type": "property.removed", "object": obj.Name, "property": str(prop)})
|
||||
|
||||
|
||||
def object_state(obj):
|
||||
return {
|
||||
"state": [str(value) for value in obj.State],
|
||||
@@ -48,10 +86,17 @@ def change_case(case_id, attr=0, runtime_status=None):
|
||||
|
||||
def locked_dynamic_case():
|
||||
document = App.newDocument("PropertyStatus_LockDynamic")
|
||||
observer = None
|
||||
try:
|
||||
document.UndoMode = 1
|
||||
obj = document.addObject("App::FeaturePython", "Feature")
|
||||
obj.addProperty("App::PropertyInteger", "LockedValue", "Oracle")
|
||||
obj.setPropertyStatus("LockedValue", "LockDynamic")
|
||||
observer = PropertyObserver(document.Name)
|
||||
App.addDocumentObserver(observer)
|
||||
document.openTransaction("dynamic-property-mutation")
|
||||
obj.addProperty("App::PropertyInteger", "MutableValue", "Oracle")
|
||||
obj.MutableValue = 3
|
||||
failures = {}
|
||||
for operation, callback in {
|
||||
"remove": lambda: obj.removeProperty("LockedValue"),
|
||||
@@ -62,13 +107,48 @@ def locked_dynamic_case():
|
||||
failures[operation] = None
|
||||
except Exception as error:
|
||||
failures[operation] = {"type": type(error).__name__, "message": str(error)}
|
||||
obj.renameProperty("MutableValue", "RenamedValue")
|
||||
removed_mutable = bool(obj.removeProperty("RenamedValue"))
|
||||
document.commitTransaction()
|
||||
return {
|
||||
"reportedStatus": [str(value) for value in obj.getPropertyStatus("LockedValue")],
|
||||
"propertyStillPresent": "LockedValue" in obj.PropertiesList,
|
||||
"renamedPropertyPresent": "RenamedValue" in obj.PropertiesList,
|
||||
"failures": failures,
|
||||
"mutableRenameAndRemove": removed_mutable and "MutableValue" not in obj.PropertiesList and "RenamedValue" not in obj.PropertiesList,
|
||||
"observerEvents": observer.events,
|
||||
"undoAvailable": document.UndoCount == 1,
|
||||
}
|
||||
finally:
|
||||
if observer:
|
||||
App.removeDocumentObserver(observer)
|
||||
App.closeDocument(document.Name)
|
||||
|
||||
|
||||
def partial_trigger_case():
|
||||
document = App.newDocument("PropertyStatus_PartialTrigger")
|
||||
observer = PropertyObserver(document.Name)
|
||||
App.addDocumentObserver(observer)
|
||||
try:
|
||||
document.UndoMode = 1
|
||||
obj = document.addObject("PartDesign::SubShapeBinder", "Binder")
|
||||
document.recompute()
|
||||
document.purgeTouched()
|
||||
observer.clear()
|
||||
document.openTransaction("partial-trigger-mutation")
|
||||
obj.PartialLoad = True
|
||||
recomputed = int(document.recompute())
|
||||
document.commitTransaction()
|
||||
return {
|
||||
"reportedStatus": [str(value) for value in obj.getPropertyStatus("PartialLoad")],
|
||||
"value": bool(obj.PartialLoad),
|
||||
"recomputeCount": recomputed,
|
||||
"state": object_state(obj),
|
||||
"observerEvents": observer.events,
|
||||
"undoAvailable": document.UndoCount == 1,
|
||||
}
|
||||
finally:
|
||||
App.removeDocumentObserver(observer)
|
||||
App.closeDocument(document.Name)
|
||||
|
||||
|
||||
@@ -78,6 +158,7 @@ def archive_case():
|
||||
document = App.newDocument("PropertyStatusArchive")
|
||||
try:
|
||||
obj = document.addObject("App::FeaturePython", "Feature")
|
||||
target = document.addObject("App::FeaturePython", "Target")
|
||||
obj.addProperty("App::PropertyInteger", "Persisted", "Oracle")
|
||||
obj.Persisted = 11
|
||||
obj.setPropertyStatus("Persisted", "Output")
|
||||
@@ -88,6 +169,14 @@ def archive_case():
|
||||
obj.TypeTransient = 13
|
||||
obj.addProperty("App::PropertyInteger", "NoPersist", "Oracle", "", int(App.PropertyType.Prop_NoPersist))
|
||||
obj.NoPersist = 14
|
||||
obj.addProperty("App::PropertyFloatConstraint", "FloatConstraint", "Oracle")
|
||||
obj.FloatConstraint = 0.25
|
||||
obj.addProperty("App::PropertyQuantityConstraint", "QuantityConstraint", "Oracle")
|
||||
obj.QuantityConstraint = 12.5
|
||||
obj.addProperty("App::PropertyPrecision", "Precision", "Oracle")
|
||||
obj.Precision = 0.001
|
||||
obj.addProperty("App::PropertyLinkHidden", "HiddenLink", "Oracle")
|
||||
obj.HiddenLink = target
|
||||
document.recompute()
|
||||
document.saveAs(path)
|
||||
finally:
|
||||
@@ -95,6 +184,16 @@ def archive_case():
|
||||
|
||||
with zipfile.ZipFile(path, "r") as archive:
|
||||
document_xml = archive.read("Document.xml").decode("utf-8")
|
||||
xml_root = ET.fromstring(document_xml)
|
||||
xml_properties = {
|
||||
element.attrib.get("name"): element
|
||||
for element in xml_root.findall("./ObjectData/Object/Properties/Property")
|
||||
}
|
||||
|
||||
def native_float_property(name, expected):
|
||||
element = xml_properties.get(name)
|
||||
child = element.find("Float") if element is not None else None
|
||||
return bool(element is not None and element.attrib.get("type") == "App::Property" + name and child is not None and abs(float(child.attrib["value"]) - expected) < 1e-12)
|
||||
|
||||
reopened = App.openDocument(path)
|
||||
try:
|
||||
@@ -107,6 +206,10 @@ def archive_case():
|
||||
"noPersistProperty": '<Property name="NoPersist"' in document_xml,
|
||||
"runtimeTransientHasValue": '<Integer value="12"' in document_xml,
|
||||
"typeTransientHasValue": '<Integer value="13"' in document_xml,
|
||||
"floatConstraintNative": native_float_property("FloatConstraint", 0.25),
|
||||
"quantityConstraintNative": native_float_property("QuantityConstraint", 12.5),
|
||||
"precisionNative": native_float_property("Precision", 0.001),
|
||||
"hiddenLinkNative": xml_properties.get("HiddenLink") is not None and xml_properties["HiddenLink"].attrib.get("type") == "App::PropertyLinkHidden" and xml_properties["HiddenLink"].find("Link") is not None and xml_properties["HiddenLink"].find("Link").attrib.get("value") == "Target",
|
||||
},
|
||||
"reopened": {
|
||||
"properties": sorted(str(value) for value in obj.PropertiesList if value in {"Persisted", "RuntimeTransient", "TypeTransient", "NoPersist"}),
|
||||
@@ -119,6 +222,12 @@ def archive_case():
|
||||
name: [str(value) for value in obj.getPropertyStatus(name)]
|
||||
for name in ["Persisted", "RuntimeTransient", "TypeTransient"]
|
||||
},
|
||||
"codecValues": {
|
||||
"FloatConstraint": float(obj.FloatConstraint),
|
||||
"QuantityConstraint": float(obj.QuantityConstraint.Value),
|
||||
"Precision": float(obj.Precision),
|
||||
"HiddenLink": obj.HiddenLink.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
finally:
|
||||
@@ -133,13 +242,14 @@ cases = [
|
||||
change_case("type-no-recompute", attr=int(App.PropertyType.Prop_NoRecompute)),
|
||||
]
|
||||
report = {
|
||||
"schemaVersion": 1,
|
||||
"schemaVersion": 2,
|
||||
"baselineId": "freecad-1.1.1-property-status-oracle",
|
||||
"freecadVersion": ".".join(str(value) for value in App.Version()[:3]),
|
||||
"gitCommit": FREECAD_COMMIT,
|
||||
"status": "pass",
|
||||
"cases": cases,
|
||||
"lockDynamic": locked_dynamic_case(),
|
||||
"partialTrigger": partial_trigger_case(),
|
||||
"fcstd": archive_case(),
|
||||
}
|
||||
print("FREECAD_PROPERTY_STATUS_ORACLE_RESULT=" + json.dumps(report, sort_keys=True, separators=(",", ":")))
|
||||
|
||||
Reference in New Issue
Block a user