Files
OneCAD/skills/solidcraft-checking/scripts/run.py
T

99 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Run a SolidCraft command script and print a short report: a failure, feature warnings,
sketches that are not fully constrained, and body measurements. A document.inspect inside the
script reports that state too, its lines prefixed with its step; the end state comes last."""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
import tempfile
from pathlib import Path
INSPECT = {"command": "document.inspect", "params": {"measure": True}}
WARNING_LENGTH = 160
class SolidCraftError(RuntimeError):
pass
def run_commands(commands: list, design: Path | None = None, save: Path | None = None) -> list:
"""Results of every command, followed by a final document.inspect."""
with tempfile.TemporaryDirectory() as tmp:
script = Path(tmp) / "script.json"
script.write_text(json.dumps({"commands": [*commands, INSPECT]}))
args = ["solidcraft-cli", "run", str(script), "--results"]
if design:
args += ["--in", str(design)]
if save:
args += ["--save", str(save)]
done = subprocess.run(args, capture_output=True, text=True)
if done.returncode:
raise SolidCraftError(done.stderr.strip().removeprefix("error: "))
return json.loads(done.stdout)
def execute(commands: list, design: Path | None = None, save: Path | None = None) -> dict:
"""The document.inspect of the design after the commands."""
return run_commands(commands, design, save)[-1]
def number(value: float) -> str:
return f"{round(value, 3) + 0.0:.3f}".rstrip("0").rstrip(".")
def point(values: list) -> str:
return "(" + ", ".join(map(number, values)) + ")"
def report(inspection: dict) -> list[str]:
"""Warnings come from the timeline: a rebuild after a parameter change reports them only there."""
lines = [f'WARN "{feature["name"]}": {feature["warning"][:WARNING_LENGTH]}'
for feature in inspection["timeline"] if feature.get("warning")]
for sketch in inspection.get("sketches", []):
if sketch["dof"] or sketch["status"] != "solved":
lines.append(f'SKETCH "{sketch["name"]}": dof {sketch["dof"]}, {sketch["status"]}')
lines += map(body_line, inspection["bodies"])
if not inspection["bodies"]:
lines.append("no bodies")
return lines
def body_line(body: dict) -> str:
if "volume_mm3" not in body: # inspected without measure
return f'BODY {body["name"]}'
bbox = body["bbox"]
return (f'BODY {body["name"]}: volume {number(body["volume_mm3"])} mm3, area {number(body["area_mm2"])} mm2, '
f'bbox {point(bbox["min"])}..{point(bbox["max"])}, com {point(body["center_of_mass"])}')
def script_report(commands: list, results: list) -> list[str]:
lines = [f"step {step} {line}"
for step, (command, result) in enumerate(zip(commands, results), 1)
if command["command"] == "document.inspect"
for line in report(result)]
return lines + report(results[-1])
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("script", type=Path, help='command script: {"commands": [...]}')
parser.add_argument("--in", dest="design", type=Path, help="design to run the script on")
parser.add_argument("--save", type=Path, help="save the resulting design; a .json name keeps it JSON")
args = parser.parse_args()
commands = json.loads(args.script.read_text())["commands"]
try:
results = run_commands(commands, args.design, args.save)
except SolidCraftError as error:
print(f"FAIL {error}")
sys.exit(1)
print("\n".join(script_report(commands, results)))
if __name__ == "__main__":
main()