cosplay: Touhou/Houjuu Nue #4
62
nhf/build.py
62
nhf/build.py
|
@ -2,25 +2,69 @@
|
||||||
The NHF build system
|
The NHF build system
|
||||||
|
|
||||||
Usage: For any parametric assembly, inherit the `Model` class, and mark the
|
Usage: For any parametric assembly, inherit the `Model` class, and mark the
|
||||||
output objects with the `@target` decorator
|
output objects with the `@target` decorator. Each marked function should only
|
||||||
|
take `self` as an argument.
|
||||||
|
```python
|
||||||
|
class BuildScaffold(Model):
|
||||||
|
|
||||||
|
@target(name="obj1")
|
||||||
|
def o1(self):
|
||||||
|
return Cq.Solid.makeBox(10, 10, 10)
|
||||||
|
|
||||||
|
def o2(self):
|
||||||
|
return Cq.Solid.makeCylinder(10, 20)
|
||||||
|
```
|
||||||
"""
|
"""
|
||||||
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
|
|
||||||
|
class TargetKind(Enum):
|
||||||
|
|
||||||
|
STL = "stl",
|
||||||
|
DXF = "dxf",
|
||||||
|
|
||||||
|
def __init__(self, ext: str):
|
||||||
|
self.ext = ext
|
||||||
|
|
||||||
class Target:
|
class Target:
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
method,
|
method,
|
||||||
name: str):
|
name: str,
|
||||||
|
kind: TargetKind = TargetKind.STL,
|
||||||
|
**kwargs):
|
||||||
self._method = method
|
self._method = method
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.kind = kind
|
||||||
|
self.kwargs = kwargs
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"<target {self.name}>"
|
return f"<target {self.name}>"
|
||||||
def __call__(self, obj, *args, **kwargs):
|
def __call__(self, obj, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Raw call function which passes arguments directly to `_method`
|
||||||
|
"""
|
||||||
return self._method(obj, *args, **kwargs)
|
return self._method(obj, *args, **kwargs)
|
||||||
|
def file_name(self, file_name):
|
||||||
|
return f"{file_name}.{self.kind.ext}"
|
||||||
|
def write_to(self, obj, path: str):
|
||||||
|
x = self._method(obj)
|
||||||
|
if self.kind == TargetKind.STL:
|
||||||
|
assert isinstance(x, Union[
|
||||||
|
Cq.Workplane, Cq.Shape, Cq.Compound, Cq.Assembly])
|
||||||
|
if isinstance(x, Cq.Workplane):
|
||||||
|
x = x.val()
|
||||||
|
if isinstance(x, Cq.Assembly):
|
||||||
|
x = x.toCompound()
|
||||||
|
x.exportStl(path, **self.kwargs)
|
||||||
|
elif self.kind == TargetKind.DXF:
|
||||||
|
assert isinstance(x, Cq.Workplane)
|
||||||
|
Cq.exporters.exportDXF(x, path, **self.kwargs)
|
||||||
|
else:
|
||||||
|
assert False, f"Invalid kind: {self.kind}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def methods(cls, subject):
|
def methods(cls, subject):
|
||||||
|
@ -49,13 +93,6 @@ def target(name, **deco_kwargs):
|
||||||
return wrapper
|
return wrapper
|
||||||
return f
|
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:
|
class Model:
|
||||||
"""
|
"""
|
||||||
|
@ -82,16 +119,15 @@ class Model:
|
||||||
"""
|
"""
|
||||||
output_dir = Path(output_dir)
|
output_dir = Path(output_dir)
|
||||||
output_dir.mkdir(exist_ok=True, parents=True)
|
output_dir.mkdir(exist_ok=True, parents=True)
|
||||||
for k, f in Target.methods(self).items():
|
for k, target in Target.methods(self).items():
|
||||||
output_file = output_dir / f"{k}.stl"
|
output_file = output_dir / target.file_name(k)
|
||||||
if output_file.is_file():
|
if output_file.is_file():
|
||||||
if verbose >= 1:
|
if verbose >= 1:
|
||||||
print(f"{Fore.GREEN}Skipping{Style.RESET_ALL} {output_file}")
|
print(f"{Fore.GREEN}Skipping{Style.RESET_ALL} {output_file}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
model = f(self)
|
|
||||||
if verbose >= 1:
|
if verbose >= 1:
|
||||||
print(f"{Fore.BLUE}Building{Style.RESET_ALL} {output_file}")
|
print(f"{Fore.BLUE}Building{Style.RESET_ALL} {output_file}")
|
||||||
_to_shape(model).exportStl(str(output_file))
|
target.write_to(self, str(output_file))
|
||||||
if verbose >= 1:
|
if verbose >= 1:
|
||||||
print(f"{Fore.GREEN}Built{Style.RESET_ALL} {output_file}")
|
print(f"{Fore.GREEN}Built{Style.RESET_ALL} {output_file}")
|
||||||
|
|
|
@ -33,7 +33,7 @@ from dataclasses import dataclass, field
|
||||||
import unittest
|
import unittest
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf import Material
|
from nhf import Material
|
||||||
from nhf.build import Model, target
|
from nhf.build import Model, TargetKind, target
|
||||||
from nhf.parts.joints import HirthJoint
|
from nhf.parts.joints import HirthJoint
|
||||||
from nhf.parts.handle import Handle
|
from nhf.parts.handle import Handle
|
||||||
import nhf.touhou.houjuu_nue.wing as MW
|
import nhf.touhou.houjuu_nue.wing as MW
|
||||||
|
@ -113,11 +113,6 @@ class Parameters(Model):
|
||||||
"Wing root must be large enough to accomodate joint"
|
"Wing root must be large enough to accomodate joint"
|
||||||
|
|
||||||
|
|
||||||
def print_geometries(self):
|
|
||||||
return [
|
|
||||||
self.hs_joint_parent()
|
|
||||||
]
|
|
||||||
|
|
||||||
def harness_profile(self) -> Cq.Sketch:
|
def harness_profile(self) -> Cq.Sketch:
|
||||||
"""
|
"""
|
||||||
Creates the harness shape
|
Creates the harness shape
|
||||||
|
@ -148,6 +143,7 @@ class Parameters(Model):
|
||||||
)
|
)
|
||||||
return sketch
|
return sketch
|
||||||
|
|
||||||
|
@target(name="harness", kind=TargetKind.DXF)
|
||||||
def harness(self) -> Cq.Shape:
|
def harness(self) -> Cq.Shape:
|
||||||
"""
|
"""
|
||||||
Creates the harness shape
|
Creates the harness shape
|
||||||
|
@ -249,7 +245,8 @@ class Parameters(Model):
|
||||||
result.faces("<Z").tag("base")
|
result.faces("<Z").tag("base")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def wing_root(self) -> Cq.Shape:
|
@target(name="wing_root")
|
||||||
|
def wing_root(self) -> Cq.Assembly:
|
||||||
"""
|
"""
|
||||||
Generate the wing root which contains a Hirth joint at its base and a
|
Generate the wing root which contains a Hirth joint at its base and a
|
||||||
rectangular opening on its side, with the necessary interfaces.
|
rectangular opening on its side, with the necessary interfaces.
|
||||||
|
|
Loading…
Reference in New Issue