Files
Web_FreeCAD_Bitbybit/scripts/freecad-partdesign-transform-oracle.py

306 lines
12 KiB
Python

import json
import FreeCAD as App
import Part
import Sketcher
def version_text():
return ".".join(str(value) for value in App.Version()[:3])
def shape_result(case_id, document, feature):
document.recompute()
shape = feature.Shape
return {
"id": case_id,
"typeId": feature.TypeId,
"passed": not shape.isNull() and shape.isValid() and len(shape.Solids) == 1,
"status": feature.getStatusString(),
"solids": len(shape.Solids) if not shape.isNull() else 0,
"faces": len(shape.Faces) if not shape.isNull() else 0,
"edges": len(shape.Edges) if not shape.isNull() else 0,
"volume": float(shape.Volume) if not shape.isNull() else 0.0,
}
def add_rectangle(sketch, x_min, y_min, x_max, y_max):
points = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)]
for index, start in enumerate(points):
end = points[(index + 1) % len(points)]
sketch.addGeometry(Part.LineSegment(App.Vector(start[0], start[1], 0), App.Vector(end[0], end[1], 0)), False)
def feature_linear_pattern():
document = App.newDocument("FeatureLinearPatternOracle")
body = document.addObject("PartDesign::Body", "Body")
base = body.newObject("PartDesign::AdditiveBox", "Base")
base.Length = 20
base.Width = 10
base.Height = 2
boss = body.newObject("PartDesign::AdditiveBox", "Boss")
boss.Length = 2
boss.Width = 2
boss.Height = 2
boss.Placement.Base = App.Vector(2, 4, 2)
document.recompute()
pattern = body.newObject("PartDesign::LinearPattern", "LinearPattern")
pattern.TransformMode = "Features"
pattern.Originals = [boss]
pattern.Direction = (document.X_Axis, [""])
pattern.Occurrences = 3
pattern.Length = 12
result = shape_result("linear-feature-list", document, pattern)
result.update({
"transformMode": pattern.TransformMode,
"originals": [item.Name for item in pattern.Originals],
"direction": pattern.Direction[0].Name,
"occurrences": int(pattern.Occurrences),
"length": float(pattern.Length),
})
App.closeDocument(document.Name)
return result
def feature_mirrored():
document = App.newDocument("FeatureMirroredOracle")
body = document.addObject("PartDesign::Body", "Body")
base = body.newObject("PartDesign::AdditiveBox", "Base")
base.Length = 20
base.Width = 10
base.Height = 2
boss = body.newObject("PartDesign::AdditiveBox", "Boss")
boss.Length = 2
boss.Width = 2
boss.Height = 2
boss.Placement.Base = App.Vector(0, 4, 2)
document.recompute()
mirrored = body.newObject("PartDesign::Mirrored", "Mirrored")
mirrored.TransformMode = "Features"
mirrored.Originals = [boss]
mirrored.MirrorPlane = (document.YZ_Plane, [""])
result = shape_result("mirrored-feature-list", document, mirrored)
result.update({
"transformMode": mirrored.TransformMode,
"originals": [item.Name for item in mirrored.Originals],
"plane": mirrored.MirrorPlane[0].Name,
})
App.closeDocument(document.Name)
return result
def feature_polar_pattern():
document = App.newDocument("FeaturePolarPatternOracle")
body = document.addObject("PartDesign::Body", "Body")
box = document.addObject("PartDesign::AdditiveBox", "Box")
body.addObject(box)
box.Length = 10
box.Width = 10
box.Height = 10
document.recompute()
pattern = document.addObject("PartDesign::PolarPattern", "PolarPattern")
pattern.Originals = [box]
pattern.Axis = (document.X_Axis, [""])
pattern.Angle = 360
pattern.Occurrences = 4
pattern.Refine = True
body.addObject(pattern)
result = shape_result("polar-feature-list", document, pattern)
result.update({
"transformMode": pattern.TransformMode,
"originals": [item.Name for item in pattern.Originals],
"axis": pattern.Axis[0].Name,
"angle": float(pattern.Angle),
"occurrences": int(pattern.Occurrences),
})
App.closeDocument(document.Name)
return result
def feature_multi_transform():
document = App.newDocument("FeatureMultiTransformOracle")
body = document.addObject("PartDesign::Body", "Body")
sketch = document.addObject("Sketcher::SketchObject", "SketchPad")
body.addObject(sketch)
add_rectangle(sketch, 0, 0, 10, 10)
document.recompute()
pad = document.addObject("PartDesign::Pad", "Pad")
body.addObject(pad)
pad.Profile = sketch
pad.Length = 10
document.recompute()
multi = document.addObject("PartDesign::MultiTransform", "MultiTransform")
document.recompute()
multi.Originals = [pad]
multi.Shape = pad.Shape
body.addObject(multi)
document.recompute()
mirrored = document.addObject("PartDesign::Mirrored", "Mirrored")
mirrored.MirrorPlane = (sketch, ["H_Axis"])
body.addObject(mirrored)
linear = document.addObject("PartDesign::LinearPattern", "LinearPattern")
linear.Direction = (sketch, ["H_Axis"])
linear.Length = 20
linear.Occurrences = 3
body.addObject(linear)
polar = document.addObject("PartDesign::PolarPattern", "PolarPattern")
polar.Axis = (sketch, ["N_Axis"])
polar.Angle = 360
polar.Occurrences = 4
body.addObject(polar)
multi.Transformations = [mirrored, linear, polar]
result = shape_result("multi-transform-feature-list", document, multi)
result.update({
"transformMode": multi.TransformMode,
"originals": [item.Name for item in multi.Originals],
"transformations": [item.TypeId for item in multi.Transformations],
})
App.closeDocument(document.Name)
return result
def transform_failure(type_id):
suffix = type_id.split("::", 1)[1]
document = App.newDocument("Feature{}FailureOracle".format(suffix))
try:
body = document.addObject("PartDesign::Body", "Body")
base = body.newObject("PartDesign::AdditiveBox", "Base")
base.Length = 20
base.Width = 10
base.Height = 2
boss = body.newObject("PartDesign::AdditiveBox", "Boss")
boss.Length = 2
boss.Width = 2
boss.Height = 2
boss.Placement.Base = App.Vector(2, 4, 2)
document.recompute()
feature = document.addObject(type_id, suffix)
feature.Originals = [boss]
if type_id == "PartDesign::MultiTransform":
feature.Shape = boss.Shape
body.addObject(feature)
if type_id == "PartDesign::LinearPattern":
feature.Direction = (base, ["Edge999"])
if type_id == "PartDesign::MultiTransform":
child = document.addObject("PartDesign::LinearPattern", "InvalidLinearChild")
child.Length = 12
child.Occurrences = 3
child.Direction = (base, ["Edge999"])
body.addObject(child)
feature.Transformations = [child]
document.recompute()
shape = feature.Shape
shape_null = bool(shape.isNull())
state = [str(value) for value in feature.State]
status_string = str(feature.getStatusString())
invalid = "Invalid" in state or status_string != "Valid"
observed = "rejected" if invalid else "accepted-empty" if shape_null else "accepted"
return {
"id": "{}-missing-reference".format(suffix.lower()),
"typeId": type_id,
"inputClass": "valid-original-missing-transform-reference",
"expected": "rejected",
"observed": observed,
"passed": observed == "rejected",
"shapeNull": shape_null,
"staleShapePreserved": bool(invalid and not shape_null),
"state": state,
"status": status_string,
"originals": [item.Name for item in feature.Originals],
"transformations": [item.TypeId for item in feature.Transformations] if type_id == "PartDesign::MultiTransform" else [],
}
except Exception as error:
return {
"id": "{}-missing-reference".format(suffix.lower()),
"typeId": type_id,
"inputClass": "valid-original-missing-transform-reference",
"expected": "rejected",
"observed": "rejected",
"passed": True,
"errorType": type(error).__name__,
"error": str(error),
}
finally:
App.closeDocument(document.Name)
def hole_document(name):
document = App.newDocument(name)
body = document.addObject("PartDesign::Body", "Body")
box = body.newObject("PartDesign::AdditiveBox", "Box")
box.Length = 20
box.Width = 20
box.Height = 10
document.recompute()
top_face = max(range(len(box.Shape.Faces)), key=lambda index: box.Shape.Faces[index].CenterOfMass.z)
sketch = body.newObject("Sketcher::SketchObject", "HoleSketch")
sketch.AttachmentSupport = (box, ["Face{}".format(top_face + 1)])
sketch.MapMode = "FlatFace"
sketch.addGeometry(Part.Circle(App.Vector(10, 10, 0), App.Vector(0, 0, 1), 3), False)
hole = body.newObject("PartDesign::Hole", "Hole")
hole.Profile = sketch
hole.DepthType = "Dimension"
hole.Depth = 8
hole.DrillPoint = "Flat"
return document, body, hole
def iso_hole(modeled):
case_id = "iso-m6-modeled-thread" if modeled else "iso-m6-tap-drill"
document, body, hole = hole_document("ModeledThreadOracle" if modeled else "TapDrillOracle")
hole.ThreadType = "ISOMetricProfile"
hole.ThreadSize = "M6x1.0"
hole.Threaded = True
hole.ModelThread = modeled
hole.ThreadDirection = "Left" if modeled else "Right"
hole.ThreadDepthType = "Dimension"
hole.ThreadDepth = 6
result = shape_result(case_id, document, hole)
result.update({
"threaded": bool(hole.Threaded),
"modeled": bool(hole.ModelThread),
"threadType": hole.ThreadType,
"threadSize": hole.ThreadSize,
"threadClass": hole.ThreadClass,
"threadFit": hole.ThreadFit,
"threadDirection": hole.ThreadDirection,
"threadDepthType": hole.ThreadDepthType,
"threadDepth": float(hole.ThreadDepth),
"diameter": float(hole.Diameter),
"threadDiameter": float(hole.ThreadDiameter),
"threadPitch": float(hole.ThreadPitch) if "ThreadPitch" in hole.PropertiesList else 1.0,
"properties": list(hole.PropertiesList),
})
App.closeDocument(document.Name)
return result
success_cases = [feature_linear_pattern(), feature_mirrored(), feature_polar_pattern(), feature_multi_transform(), iso_hole(False), iso_hole(True)]
failure_cases = [transform_failure(type_id) for type_id in [
"PartDesign::Mirrored",
"PartDesign::MultiTransform",
"PartDesign::LinearPattern",
"PartDesign::PolarPattern",
]]
cases = success_cases + failure_cases
report = {
"schemaVersion": 1,
"baselineId": "freecad-1.1.1-partdesign-transform-oracle",
"freecadVersion": version_text(),
"gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d",
"status": "pass" if all(case["passed"] for case in cases) else "failed",
"tolerance": 1e-7,
"cases": cases,
"summary": {
"cases": len(cases),
"passed": sum(1 for case in cases if case["passed"]),
"successCases": len(success_cases),
"successPassed": sum(1 for case in success_cases if case["passed"]),
"failureCases": len(failure_cases),
"failurePassed": sum(1 for case in failure_cases if case["passed"]),
"rejected": sum(1 for case in failure_cases if case.get("observed") == "rejected"),
"acceptedEmpty": sum(1 for case in failure_cases if case.get("observed") == "accepted-empty"),
},
}
print("FREECAD_PARTDESIGN_TRANSFORM_RESULT=" + json.dumps(report, sort_keys=True, separators=(",", ":")))