"""Headless runner entry points for silorunner compute jobs. These functions are invoked via ``create --console -e`` by the silorunner binary. They must work without a display server. Entry Points ------------ dag_extract(input_path, output_path) Extract feature DAG and write JSON. validate(input_path, output_path) Rebuild all features and report pass/fail per node. export(input_path, output_path, format='step') Export geometry to STEP, IGES, STL, or OBJ. """ import json import FreeCAD def dag_extract(input_path, output_path): """Extract the feature DAG from a Create file. Parameters ---------- input_path : str Path to the ``.kc`` or ``.FCStd`` file. output_path : str Path to write the JSON output. Output JSON:: {"nodes": [...], "edges": [...]} """ from dag import extract_dag doc = FreeCAD.openDocument(input_path) try: nodes, edges = extract_dag(doc) with open(output_path, "w") as f: json.dump({"nodes": nodes, "edges": edges}, f) FreeCAD.Console.PrintMessage( f"DAG extracted: {len(nodes)} nodes, {len(edges)} edges -> {output_path}\n" ) finally: FreeCAD.closeDocument(doc.Name) def validate(input_path, output_path): """Validate a Create file by rebuilding all features. Parameters ---------- input_path : str Path to the ``.kc`` or ``.FCStd`` file. output_path : str Path to write the JSON output. Output JSON:: { "valid": true/false, "nodes": [ {"node_key": "Pad001", "state": "clean", "message": null, "properties_hash": "..."}, ... ] } """ from dag import classify_type, compute_properties_hash doc = FreeCAD.openDocument(input_path) try: doc.recompute() results = [] all_valid = True for obj in doc.Objects: if not hasattr(obj, "TypeId"): continue node_type = classify_type(obj.TypeId) if node_type is None: continue state = "clean" message = None if hasattr(obj, "isValid") and not obj.isValid(): state = "failed" message = f"Feature {obj.Label} failed to recompute" all_valid = False results.append( { "node_key": obj.Name, "state": state, "message": message, "properties_hash": compute_properties_hash(obj), } ) with open(output_path, "w") as f: json.dump({"valid": all_valid, "nodes": results}, f) status = "PASS" if all_valid else "FAIL" FreeCAD.Console.PrintMessage( f"Validation {status}: {len(results)} nodes -> {output_path}\n" ) finally: FreeCAD.closeDocument(doc.Name) def export(input_path, output_path, format="step"): """Export a Create file to an external geometry format. Parameters ---------- input_path : str Path to the ``.kc`` or ``.FCStd`` file. output_path : str Path to write the exported file. format : str One of ``step``, ``iges``, ``stl``, ``obj``. """ import Part doc = FreeCAD.openDocument(input_path) try: shapes = [ obj.Shape for obj in doc.Objects if hasattr(obj, "Shape") and obj.Shape ] if not shapes: raise ValueError("No geometry found in document") compound = Part.makeCompound(shapes) format_lower = format.lower() if format_lower == "step": compound.exportStep(output_path) elif format_lower == "iges": compound.exportIges(output_path) elif format_lower == "stl": import Mesh Mesh.export([compound], output_path) elif format_lower == "obj": import Mesh Mesh.export([compound], output_path) else: raise ValueError(f"Unsupported format: {format}") FreeCAD.Console.PrintMessage( f"Exported {format_lower.upper()} -> {output_path}\n" ) finally: FreeCAD.closeDocument(doc.Name)