$ cat node-template.py

3

3D Print Check

// Analyzes a 3D model for printability without modifying it: watertightness, self-intersections, thin walls, overhangs, stability and size metrics. Outputs a scored report, a pass/fail verdict and a preview with problem areas highlighted.

Process
3D
#3d#mesh#3d-print#validation#analysis
template.py
1"""print-check-3d — analyze a 3D model for printability (read-only).23Calls Gais.model.print_check: watertightness, self-intersections, thin4walls, overhangs, stability, size metrics -> scored report + optional5color-coded preview. The input model is never modified. No os/pathlib/HTTP6here (lint-blocked).7"""89from __future__ import annotations1011import json12import sys13import traceback14from typing import Any1516from gais import Gais1718INPUT_DIR = "/data/input"19OUTPUT_DIR = "/data/output"2021PREVIEW_FILENAME = "check_preview.glb"2223SUPPORTED_EXTENSIONS = (".glb", ".gltf", ".stl", ".obj")242526def _f(value: Any, default: float) -> float:27    try:28        return float(value)29    except (TypeError, ValueError):30        return default313233def _i(value: Any, default: int) -> int:34    try:35        return int(float(value))36    except (TypeError, ValueError):37        return default383940def _b(value: Any, default: bool) -> bool:41    if isinstance(value, bool):42        return value43    if isinstance(value, str):44        return value.strip().lower() in ("1", "true", "yes", "on")45    if value is None:46        return default47    return bool(value)484950def _input_file_path(inputs: dict[str, Any], key: str) -> str | None:51    name = inputs.get(key)52    if not name or not isinstance(name, str):53        return None54    base = name.replace("\\", "/").rsplit("/", 1)[-1].strip()55    if not base or base in (".", ".."):56        return None57    return f"{INPUT_DIR}/{base}"585960def _copy_to_output(src_path: str, name: str) -> None:61    with open(src_path, "rb") as src, open(f"{OUTPUT_DIR}/{name}", "wb") as dst:62        while True:63            chunk = src.read(1024 * 1024)64            if not chunk:65                break66            dst.write(chunk)676869def process(inputs: dict[str, Any]) -> dict[str, Any]:70    mesh_path = _input_file_path(inputs, "mesh")71    if mesh_path is None:72        raise ValueError("Required input 'mesh' not provided")73    if not mesh_path.lower().endswith(SUPPORTED_EXTENSIONS):74        raise ValueError(75            f"Unsupported mesh format ({mesh_path.rsplit('/', 1)[-1]}). "76            f"Supported: {', '.join(SUPPORTED_EXTENSIONS)}."77        )7879    annotate = _b(inputs.get("annotate"), True)80    result = Gais.model.print_check(81        mesh_path,82        target_size_mm=_f(inputs.get("target_size_mm"), 100.0),83        min_wall_thickness_mm=_f(inputs.get("min_wall_thickness_mm"), 1.5),84        overhang_angle_deg=_f(inputs.get("overhang_angle_deg"), 45.0),85        annotate=annotate,86        wall_samples_per_face=_i(inputs.get("wall_samples_per_face"), 3),87    )8889    with open(str(result.file("report")), "r", encoding="utf-8") as fh:90        report = json.load(fh)91    summary = report.get("summary", {})9293    outputs: dict[str, Any] = {94        "report": json.dumps(report),95        "printable": bool(summary.get("printable", False)),96        "score": _i(summary.get("score"), 0),97    }9899    if annotate:100        _copy_to_output(str(result.file("preview")), PREVIEW_FILENAME)101        outputs["preview"] = PREVIEW_FILENAME102103    issues = report.get("issues", [])104    if issues:105        print(106            f"[print-check-3d] {len(issues)} issue(s), score={outputs['score']}, "107            f"printable={outputs['printable']}",108            file=sys.stderr,109        )110111    return outputs112113114def main() -> None:115    try:116        envelope = json.loads(sys.stdin.read() or "{}")117        if not isinstance(envelope, dict):118            envelope = {}119        inputs = envelope.get("inputs", {}) or {}120        json.dump(process(inputs), sys.stdout)121    except Exception as e:122        print(123            json.dumps({124                "error": str(e),125                "errorType": type(e).__name__,126                "traceback": traceback.format_exc(),127            }),128            file=sys.stderr,129        )130        sys.exit(1)131132133if __name__ == "__main__":134    main()

$ git log --oneline

v1.0.0
HEAD
2026-08-18