Files
Web_FreeCAD_Bitbybit/scripts/freecad-partdesign-structure-oracle.py
wangdequan f97eae4153
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: add candidate FreeCAD naming SDK and attachment oracles
Build and verify the candidate-only FreeCAD naming bridge and OCCT worker path, including three-stage StringHasher restoration. Add Datum, ShapeBinder, attachment-mode, and PartDesign structure oracles plus offline SDK build plans and CI boundary checks.
2026-08-13 17:16:07 -04:00

241 lines
12 KiB
Python

import json
import os
import tempfile
import FreeCAD as App
def version_text():
return ".".join(str(value) for value in App.Version()[:3])
def names(objects):
return [obj.Name for obj in objects]
def link_sub_values(value):
output = []
for obj, sub_elements in value:
if isinstance(sub_elements, str):
subs = [sub_elements] if sub_elements else []
else:
subs = [str(sub) for sub in sub_elements if str(sub)]
output.append({"object": obj.Name, "subElements": subs})
return output
def placement_json(placement):
axis = placement.Rotation.Axis
base = placement.Base
return {
"position": [float(base.x), float(base.y), float(base.z)],
"axis": [float(axis.x), float(axis.y), float(axis.z)],
"angleDegrees": float(placement.Rotation.Angle * 180.0 / 3.141592653589793),
}
document = App.newDocument("PartDesignStructureOracle")
source_body = document.addObject("PartDesign::Body", "SourceBody")
source_box = source_body.newObject("PartDesign::AdditiveBox", "SourceBox")
source_box.Length = 10
source_box.Width = 8
source_box.Height = 6
datum_body = document.addObject("PartDesign::Body", "DatumBody")
datum_plane = datum_body.newObject("PartDesign::Plane", "DatumPlane")
datum_plane.AttachmentSupport = [(document.XY_Plane, "")]
datum_plane.MapMode = "FlatFace"
datum_plane.AttachmentOffset = App.Placement(App.Vector(0, 0, 5), App.Rotation(App.Vector(0, 0, 1), 30))
datum_line = datum_body.newObject("PartDesign::Line", "DatumLine")
datum_line.AttachmentSupport = [(document.XY_Plane, "")]
datum_line.MapMode = "ObjectX"
datum_point = datum_body.newObject("PartDesign::Point", "DatumPoint")
datum_point.AttachmentSupport = [(document.XY_Plane, "")]
datum_point.MapMode = "ObjectOrigin"
datum_point.AttachmentOffset = App.Placement(App.Vector(1, 2, 3), App.Rotation())
binder_body = document.addObject("PartDesign::Body", "BinderBody")
shape_binder = binder_body.newObject("PartDesign::ShapeBinder", "ShapeBinder")
shape_binder.Support = [(source_box, "")]
shape_binder.TraceSupport = True
sub_binder_body = document.addObject("PartDesign::Body", "SubBinderBody")
sub_binder = sub_binder_body.newObject("PartDesign::SubShapeBinder", "SubShapeBinder")
sub_binder.Support = [(source_box, ("Edge1", "Edge2", "Edge3", "Edge4"))]
document.recompute()
initial = {
"sourceVolume": float(source_box.Shape.Volume),
"shapeBinderVolume": float(shape_binder.Shape.Volume),
"subShapeBinderLength": float(sub_binder.Shape.Length),
"bodyTips": {body.Name: body.Tip.Name if body.Tip else None for body in [source_body, datum_body, binder_body, sub_binder_body]},
"bodyGroups": {body.Name: names(body.Group) for body in [source_body, datum_body, binder_body, sub_binder_body]},
"datumPlane": {"mapMode": datum_plane.MapMode, "support": link_sub_values(datum_plane.AttachmentSupport), "attachmentOffset": placement_json(datum_plane.AttachmentOffset), "placement": placement_json(datum_plane.Placement), "state": [str(value) for value in datum_plane.State]},
"datumLine": {"mapMode": datum_line.MapMode, "support": link_sub_values(datum_line.AttachmentSupport), "state": [str(value) for value in datum_line.State]},
"datumPoint": {"mapMode": datum_point.MapMode, "support": link_sub_values(datum_point.AttachmentSupport), "attachmentOffset": placement_json(datum_point.AttachmentOffset), "placement": placement_json(datum_point.Placement), "state": [str(value) for value in datum_point.State]},
"shapeBinder": {"support": link_sub_values(shape_binder.Support), "traceSupport": bool(shape_binder.TraceSupport), "state": [str(value) for value in shape_binder.State]},
"subShapeBinder": {"support": link_sub_values(sub_binder.Support), "state": [str(value) for value in sub_binder.State]},
}
source_box.Length = 14
datum_plane.AttachmentOffset = App.Placement(App.Vector(0, 0, 7), App.Rotation(App.Vector(0, 0, 1), 45))
document.recompute()
edited = {
"sourceVolume": float(source_box.Shape.Volume),
"shapeBinderVolume": float(shape_binder.Shape.Volume),
"subShapeBinderLength": float(sub_binder.Shape.Length),
"datumPlaneAttachmentOffset": placement_json(datum_plane.AttachmentOffset),
"datumPlanePlacement": placement_json(datum_plane.Placement),
"bodyTips": {body.Name: body.Tip.Name if body.Tip else None for body in [source_body, datum_body, binder_body, sub_binder_body]},
}
invalid_plane = datum_body.newObject("PartDesign::Plane", "InvalidDatumPlane")
invalid_plane.AttachmentSupport = [(source_box, "Face999")]
invalid_plane.MapMode = "FlatFace"
document.recompute()
invalid_support = {
"typeId": invalid_plane.TypeId,
"support": link_sub_values(invalid_plane.AttachmentSupport),
"state": [str(value) for value in invalid_plane.State],
"status": str(invalid_plane.getStatusString()),
}
with tempfile.TemporaryDirectory(prefix="freecad-partdesign-structure-") as temp_dir:
path = os.path.join(temp_dir, "PartDesignStructureOracle.FCStd")
document.recompute()
document.saveAs(path)
App.closeDocument(document.Name)
reopened_document = App.openDocument(path)
reopened_document.recompute()
reopened_source = reopened_document.getObject("SourceBox")
reopened_plane = reopened_document.getObject("DatumPlane")
reopened_shape_binder = reopened_document.getObject("ShapeBinder")
reopened_sub_binder = reopened_document.getObject("SubShapeBinder")
reopened_invalid = reopened_document.getObject("InvalidDatumPlane")
reopened_bodies = [reopened_document.getObject(name) for name in ["SourceBody", "DatumBody", "BinderBody", "SubBinderBody"]]
roundtrip = {
"sourceVolume": float(reopened_source.Shape.Volume),
"shapeBinderVolume": float(reopened_shape_binder.Shape.Volume),
"subShapeBinderLength": float(reopened_sub_binder.Shape.Length),
"shapeBinderSupport": link_sub_values(reopened_shape_binder.Support),
"subShapeBinderSupport": link_sub_values(reopened_sub_binder.Support),
"datumPlaneSupport": link_sub_values(reopened_plane.AttachmentSupport),
"datumPlaneMapMode": reopened_plane.MapMode,
"datumPlaneAttachmentOffset": placement_json(reopened_plane.AttachmentOffset),
"bodyTips": {body.Name: body.Tip.Name if body.Tip else None for body in reopened_bodies},
"bodyGroups": {body.Name: names(body.Group) for body in reopened_bodies},
"invalidDatumState": [str(value) for value in reopened_invalid.State],
"invalidDatumStatus": str(reopened_invalid.getStatusString()),
}
App.closeDocument(reopened_document.Name)
with tempfile.TemporaryDirectory(prefix="freecad-partdesign-cross-document-") as temp_dir:
source_path = os.path.join(temp_dir, "PartDesignExternalSource.FCStd")
consumer_path = os.path.join(temp_dir, "PartDesignExternalConsumer.FCStd")
external_source = App.newDocument("PartDesignExternalSource")
external_source_body = external_source.addObject("PartDesign::Body", "ExternalSourceBody")
external_box = external_source_body.newObject("PartDesign::AdditiveBox", "ExternalBox")
external_box.Length = 4
external_box.Width = 5
external_box.Height = 6
external_source.recompute()
external_source.saveAs(source_path)
external_consumer = App.newDocument("PartDesignExternalConsumer")
external_consumer.saveAs(consumer_path)
external_shape_body = external_consumer.addObject("PartDesign::Body", "ExternalShapeBody")
external_shape_binder = external_shape_body.newObject("PartDesign::ShapeBinder", "ExternalShapeBinder")
try:
external_shape_binder.Support = [(external_box, "")]
shape_binder_external_link = {"accepted": True, "errorType": None, "error": None}
except Exception as error:
shape_binder_external_link = {"accepted": False, "errorType": type(error).__name__, "error": str(error)}
external_sub_body = external_consumer.addObject("PartDesign::Body", "ExternalSubBody")
external_sub_binder = external_sub_body.newObject("PartDesign::SubShapeBinder", "ExternalSubShapeBinder")
external_sub_binder.Support = [(external_box, ("Face1",))]
external_consumer.recompute()
cross_document_initial = {
"sourceVolume": float(external_box.Shape.Volume),
"shapeBinderExternalLink": shape_binder_external_link,
"shapeBinderSupportType": external_shape_binder.getTypeIdOfProperty("Support"),
"subShapeBinderSupportType": external_sub_binder.getTypeIdOfProperty("Support"),
"shapeBinderShapeNull": bool(external_shape_binder.Shape.isNull()),
"subShapeBinderArea": float(external_sub_binder.Shape.Area),
"sourceFaceArea": float(external_box.Shape.Face1.Area),
"shapeBinderSupport": link_sub_values(external_shape_binder.Support),
"subShapeBinderSupport": link_sub_values(external_sub_binder.Support),
"subShapeBinderSourceDocument": external_sub_binder.Support[0][0].Document.Name,
"shapeBodyTip": external_shape_body.Tip.Name if external_shape_body.Tip else None,
"subBodyTip": external_sub_body.Tip.Name if external_sub_body.Tip else None,
}
external_source.save()
external_consumer.save()
App.closeDocument(external_consumer.Name)
App.closeDocument(external_source.Name)
reopened_source = App.openDocument(source_path)
reopened_consumer = App.openDocument(consumer_path)
reopened_source.recompute()
reopened_consumer.recompute()
reopened_box = reopened_source.getObject("ExternalBox")
reopened_shape_body = reopened_consumer.getObject("ExternalShapeBody")
reopened_sub_body = reopened_consumer.getObject("ExternalSubBody")
reopened_shape_binder = reopened_consumer.getObject("ExternalShapeBinder")
reopened_sub_binder = reopened_consumer.getObject("ExternalSubShapeBinder")
cross_document_roundtrip = {
"sourceVolume": float(reopened_box.Shape.Volume),
"shapeBinderShapeNull": bool(reopened_shape_binder.Shape.isNull()),
"subShapeBinderArea": float(reopened_sub_binder.Shape.Area),
"sourceFaceArea": float(reopened_box.Shape.Face1.Area),
"shapeBinderSupport": link_sub_values(reopened_shape_binder.Support),
"subShapeBinderSupport": link_sub_values(reopened_sub_binder.Support),
"subShapeBinderSourceDocument": reopened_sub_binder.Support[0][0].Document.Name,
"shapeBodyTip": reopened_shape_body.Tip.Name if reopened_shape_body.Tip else None,
"subBodyTip": reopened_sub_body.Tip.Name if reopened_sub_body.Tip else None,
}
reopened_box.Width = 8
reopened_source.recompute()
reopened_consumer.recompute()
cross_document_edited = {
"sourceVolume": float(reopened_box.Shape.Volume),
"shapeBinderShapeNull": bool(reopened_shape_binder.Shape.isNull()),
"subShapeBinderArea": float(reopened_sub_binder.Shape.Area),
"sourceFaceArea": float(reopened_box.Shape.Face1.Area),
"shapeBinderState": [str(value) for value in reopened_shape_binder.State],
"subShapeBinderState": [str(value) for value in reopened_sub_binder.State],
}
reopened_source.removeObject("ExternalBox")
reopened_source.recompute()
reopened_consumer.recompute()
cross_document_deleted = {
"shapeBinderSupport": link_sub_values(reopened_shape_binder.Support),
"subShapeBinderSupport": link_sub_values(reopened_sub_binder.Support),
"shapeBinderShapeNull": bool(reopened_shape_binder.Shape.isNull()),
"subShapeBinderShapeNull": bool(reopened_sub_binder.Shape.isNull()),
"subShapeBinderCachedArea": float(reopened_sub_binder.Shape.Area),
"shapeBinderState": [str(value) for value in reopened_shape_binder.State],
"subShapeBinderState": [str(value) for value in reopened_sub_binder.State],
"shapeBinderStatus": str(reopened_shape_binder.getStatusString()),
"subShapeBinderStatus": str(reopened_sub_binder.getStatusString()),
}
App.closeDocument(reopened_consumer.Name)
App.closeDocument(reopened_source.Name)
report = {
"schemaVersion": 1,
"baselineId": "freecad-1.1.1-partdesign-structure-oracle",
"freecadVersion": version_text(),
"gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d",
"status": "pass",
"tolerance": 1e-7,
"initial": initial,
"edited": edited,
"invalidSupport": invalid_support,
"roundtrip": roundtrip,
"crossDocument": {
"initial": cross_document_initial,
"roundtrip": cross_document_roundtrip,
"edited": cross_document_edited,
"deleted": cross_document_deleted,
},
}
print("FREECAD_PARTDESIGN_STRUCTURE_RESULT=" + json.dumps(report, sort_keys=True, separators=(",", ":")))