""" The NHF build system Usage: For any parametric assembly, inherit the `Model` class, and mark the output objects with the `@target` decorator """ from pathlib import Path from typing import Union import cadquery as Cq from colorama import Fore, Style class Target: def __init__(self, method, name: str): self._method = method self.name = name def __call__(self, obj, *args, **kwargs): return self._method(obj, *args, **kwargs) @classmethod def methods(cls, subject): """ List of all methods of a class or objects annotated with this decorator. """ def g(): for name in dir(subject): method = getattr(subject, name) if isinstance(method, Target): yield name, method return {name: method for name, method in g()} def target(name, **kwargs): """ Decorator for annotating a build output """ def f(method): return Target(method, name=name, **kwargs) return f def _to_shape(x: Union[Cq.Workplane, Cq.Shape, Cq.Compound, Cq.Assembly]) -> Cq.Shape: if isinstance(x, Cq.Workplane): x = x.val() if isinstance(x, Cq.Assembly): x = x.toCompound() return x class Model: """ Base class for a parametric assembly """ def target_names(self) -> list[str]: """ List of decorated target functions """ return list(Target.methods(self).keys()) def build_all(self, output_dir: Union[Path, str] ="build", verbose=1): """ Build all targets in this model """ output_dir = Path(output_dir) output_dir.mkdir(exist_ok=True, parents=True) for k, f in Target.methods(self).items(): output_file = output_dir / f"{k}.stl" if output_file.is_file(): if verbose >= 1: print(f"{Fore.GREEN}Skipping{Style.RESET_ALL} {output_file}") continue model = f(self) if verbose >= 1: print(f"{Fore.BLUE}Building{Style.RESET_ALL} {output_file}", end="") _to_shape(model).exportStl(str(output_file)) if verbose >= 1: print("\r", end="") print(f"{Fore.GREEN}Built{Style.RESET_ALL} {output_file}")