feat: Submodel in build system
This commit is contained in:
parent
0cc6100d0e
commit
66b26fa056
106
nhf/build.py
106
nhf/build.py
|
@ -17,11 +17,13 @@ class BuildScaffold(Model):
|
||||||
"""
|
"""
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
from typing import Union, Optional
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
import traceback
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
import nhf.checks as NC
|
import nhf.checks as NC
|
||||||
|
import nhf.utils
|
||||||
|
|
||||||
TOL=1e-6
|
TOL=1e-6
|
||||||
|
|
||||||
|
@ -40,7 +42,7 @@ class Target:
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
method,
|
method,
|
||||||
name: str,
|
name: Optional[str] = None,
|
||||||
prototype: bool = False,
|
prototype: bool = False,
|
||||||
kind: TargetKind = TargetKind.STL,
|
kind: TargetKind = TargetKind.STL,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
|
@ -58,11 +60,14 @@ class Target:
|
||||||
return self._method(obj, *args, **kwargs)
|
return self._method(obj, *args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def file_name(self):
|
def file_name(self) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Output file name
|
Output file name
|
||||||
"""
|
"""
|
||||||
return f"{self.name}.{self.kind.ext}"
|
if self.name:
|
||||||
|
return f"{self.name}.{self.kind.ext}"
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def write_to(self, obj, path: str):
|
def write_to(self, obj, path: str):
|
||||||
x = self._method(obj)
|
x = self._method(obj)
|
||||||
|
@ -75,6 +80,14 @@ class Target:
|
||||||
x = x.toCompound().fuse(tol=TOL)
|
x = x.toCompound().fuse(tol=TOL)
|
||||||
x.exportStl(path, **self.kwargs)
|
x.exportStl(path, **self.kwargs)
|
||||||
elif self.kind == TargetKind.DXF:
|
elif self.kind == TargetKind.DXF:
|
||||||
|
if isinstance(x, Cq.Sketch):
|
||||||
|
# https://github.com/CadQuery/cadquery/issues/1575
|
||||||
|
x = (
|
||||||
|
Cq.Workplane()
|
||||||
|
.add(x._faces)
|
||||||
|
.add(x._wires)
|
||||||
|
.add(x._edges)
|
||||||
|
)
|
||||||
assert isinstance(x, Cq.Workplane)
|
assert isinstance(x, Cq.Workplane)
|
||||||
Cq.exporters.exportDXF(x, path, **self.kwargs)
|
Cq.exporters.exportDXF(x, path, **self.kwargs)
|
||||||
else:
|
else:
|
||||||
|
@ -95,7 +108,7 @@ class Target:
|
||||||
return {method.name: method for method in g()}
|
return {method.name: method for method in g()}
|
||||||
|
|
||||||
|
|
||||||
def target(name, **deco_kwargs):
|
def target(**deco_kwargs):
|
||||||
"""
|
"""
|
||||||
Decorator for annotating a build output
|
Decorator for annotating a build output
|
||||||
"""
|
"""
|
||||||
|
@ -103,7 +116,7 @@ def target(name, **deco_kwargs):
|
||||||
@wraps(method)
|
@wraps(method)
|
||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self, *args, **kwargs):
|
||||||
return method(self, *args, **kwargs)
|
return method(self, *args, **kwargs)
|
||||||
wrapper._target = Target(method, name, **deco_kwargs)
|
wrapper._target = Target(method, **deco_kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
@ -161,11 +174,74 @@ def assembly(**deco_kwargs):
|
||||||
return wrapper
|
return wrapper
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Submodel:
|
||||||
|
"""
|
||||||
|
Marks a function's output as a submodel
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
method,
|
||||||
|
name: str,
|
||||||
|
prototype: bool = False,
|
||||||
|
**kwargs):
|
||||||
|
self._method = method
|
||||||
|
self.name = name
|
||||||
|
self.prototype = prototype
|
||||||
|
self.kwargs = kwargs
|
||||||
|
def __str__(self):
|
||||||
|
return f"<submodel {self.name} {self._method}>"
|
||||||
|
def __call__(self, obj, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Raw call function which passes arguments directly to `_method`
|
||||||
|
"""
|
||||||
|
return self._method(obj, *args, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def file_name(self):
|
||||||
|
"""
|
||||||
|
Output file name
|
||||||
|
"""
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def write_to(self, obj, path: str):
|
||||||
|
x = self._method(obj)
|
||||||
|
assert isinstance(x, Model), f"Unexpected type: {type(x)}"
|
||||||
|
x.build_all(path)
|
||||||
|
|
||||||
|
@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):
|
||||||
|
if name == 'target_names':
|
||||||
|
continue
|
||||||
|
method = getattr(subject, name)
|
||||||
|
if hasattr(method, '_submodel'):
|
||||||
|
yield method._submodel
|
||||||
|
return {method.name: method for method in g()}
|
||||||
|
|
||||||
|
|
||||||
|
def submodel(name, **deco_kwargs):
|
||||||
|
"""
|
||||||
|
Decorator for annotating a build output
|
||||||
|
"""
|
||||||
|
def f(method):
|
||||||
|
@wraps(method)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
return method(self, *args, **kwargs)
|
||||||
|
wrapper._submodel = Submodel(method, name, **deco_kwargs)
|
||||||
|
return wrapper
|
||||||
|
return f
|
||||||
|
|
||||||
class Model:
|
class Model:
|
||||||
"""
|
"""
|
||||||
Base class for a parametric assembly
|
Base class for a parametric assembly
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str):
|
def __init__(self, name: Optional[str] = None):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -193,8 +269,15 @@ class Model:
|
||||||
Build all targets in this model and write the results to file
|
Build all targets in this model and write the results to file
|
||||||
"""
|
"""
|
||||||
output_dir = Path(output_dir)
|
output_dir = Path(output_dir)
|
||||||
for t in Target.methods(self).values():
|
targets = Target.methods(self)
|
||||||
output_file = output_dir / self.name / t.file_name
|
for t in targets.values():
|
||||||
|
file_name = t.file_name
|
||||||
|
if file_name is None:
|
||||||
|
assert len(targets) == 1, "Only one anonymous target is permitted"
|
||||||
|
output_file = output_dir.with_suffix('.' + t.kind.ext)
|
||||||
|
else:
|
||||||
|
output_file = output_dir / file_name
|
||||||
|
|
||||||
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}")
|
||||||
|
@ -210,3 +293,8 @@ class Model:
|
||||||
print(f"{Fore.GREEN}Built{Style.RESET_ALL} {output_file}")
|
print(f"{Fore.GREEN}Built{Style.RESET_ALL} {output_file}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{Fore.RED}Failed to build{Style.RESET_ALL} {output_file}: {e}")
|
print(f"{Fore.RED}Failed to build{Style.RESET_ALL} {output_file}: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
for t in Submodel.methods(self).values():
|
||||||
|
d = output_dir / t.name
|
||||||
|
t.write_to(self, str(d))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Tuple, Optional, Union
|
from typing import Tuple, Optional, Union
|
||||||
|
from nhf.build import Model, TargetKind, target
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
def box_with_centre_holes(
|
def box_with_centre_holes(
|
||||||
|
@ -34,7 +35,7 @@ class Hole:
|
||||||
tag: Optional[str] = None
|
tag: Optional[str] = None
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MountingBox:
|
class MountingBox(Model):
|
||||||
"""
|
"""
|
||||||
Create a box with marked holes
|
Create a box with marked holes
|
||||||
"""
|
"""
|
||||||
|
@ -53,6 +54,7 @@ class MountingBox:
|
||||||
|
|
||||||
generate_side_tags: bool = True
|
generate_side_tags: bool = True
|
||||||
|
|
||||||
|
@target(kind=TargetKind.DXF)
|
||||||
def profile(self) -> Cq.Sketch:
|
def profile(self) -> Cq.Sketch:
|
||||||
bx, by = 0, 0
|
bx, by = 0, 0
|
||||||
if not self.centred[0]:
|
if not self.centred[0]:
|
||||||
|
|
|
@ -32,9 +32,8 @@ shoulder, elbow, wrist in analogy with human anatomy.
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf.build import Model, TargetKind, target, assembly
|
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||||
from nhf.parts.joints import HirthJoint, TorsionJoint
|
from nhf.parts.joints import HirthJoint, TorsionJoint
|
||||||
from nhf.parts.handle import Handle, BayonetMount
|
|
||||||
import nhf.touhou.houjuu_nue.wing as MW
|
import nhf.touhou.houjuu_nue.wing as MW
|
||||||
import nhf.touhou.houjuu_nue.trident as MT
|
import nhf.touhou.houjuu_nue.trident as MT
|
||||||
import nhf.touhou.houjuu_nue.joints as MJ
|
import nhf.touhou.houjuu_nue.joints as MJ
|
||||||
|
@ -47,9 +46,6 @@ class Parameters(Model):
|
||||||
Defines dimensions for the Houjuu Nue cosplay
|
Defines dimensions for the Houjuu Nue cosplay
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Thickness of the exoskeleton panel in millimetres
|
|
||||||
panel_thickness: float = 25.4 / 16
|
|
||||||
|
|
||||||
# Harness
|
# Harness
|
||||||
harness: MH.Harness = field(default_factory=lambda: MH.Harness())
|
harness: MH.Harness = field(default_factory=lambda: MH.Harness())
|
||||||
|
|
||||||
|
@ -61,120 +57,53 @@ class Parameters(Model):
|
||||||
n_tooth=24
|
n_tooth=24
|
||||||
))
|
))
|
||||||
|
|
||||||
wing_profile: MW.WingProfile = field(default_factory=lambda: MW.WingProfile(
|
wing_r1: MW.WingProfile = field(default_factory=lambda: MW.WingProfile(name="r1"))
|
||||||
shoulder_joint=MJ.ShoulderJoint(
|
wing_r2: MW.WingProfile = field(default_factory=lambda: MW.WingProfile(name="r2"))
|
||||||
height=100.0,
|
wing_r3: MW.WingProfile = field(default_factory=lambda: MW.WingProfile(name="r3"))
|
||||||
),
|
|
||||||
elbow_height=110.0,
|
|
||||||
))
|
|
||||||
|
|
||||||
# Exterior radius of the wing root assembly
|
trident: MT.Trident = field(default_factory=lambda: MT.Trident())
|
||||||
wing_root_radius: float = 40
|
|
||||||
wing_root_wall_thickness: float = 8
|
|
||||||
|
|
||||||
"""
|
|
||||||
Heights for various wing joints, where the numbers start from the first
|
|
||||||
joint.
|
|
||||||
"""
|
|
||||||
wing_s0_thickness: float = 40
|
|
||||||
|
|
||||||
# Length of the spacer
|
|
||||||
wing_s1_thickness: float = 20
|
|
||||||
wing_s1_spacer_thickness: float = 25.4 / 8
|
|
||||||
wing_s1_spacer_width: float = 20
|
|
||||||
wing_s1_spacer_hole_diam: float = 8
|
|
||||||
wing_s1_shoulder_spacer_hole_dist: float = 20
|
|
||||||
wing_s1_shoulder_spacer_width: float = 60
|
|
||||||
|
|
||||||
trident_handle: Handle = field(default_factory=lambda: Handle(
|
|
||||||
diam=38,
|
|
||||||
diam_inner=38-2 * 25.4/8,
|
|
||||||
diam_connector_internal=18,
|
|
||||||
simplify_geometry=False,
|
|
||||||
mount=BayonetMount(n_pin=3),
|
|
||||||
))
|
|
||||||
trident_terminal_height: float = 80
|
|
||||||
trident_terminal_hole_diam: float = 24
|
|
||||||
trident_terminal_bottom_thickness: float = 10
|
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__init__(name="houjuu-nue")
|
super().__init__(name="houjuu-nue")
|
||||||
self.harness.hs_hirth_joint = self.hs_hirth_joint
|
self.harness.hs_hirth_joint = self.hs_hirth_joint
|
||||||
self.wing_profile.base_joint = self.hs_hirth_joint
|
self.wing_r1.base_joint = self.hs_hirth_joint
|
||||||
assert self.wing_root_radius > self.hs_hirth_joint.radius, \
|
self.wing_r2.base_joint = self.hs_hirth_joint
|
||||||
"Wing root must be large enough to accomodate joint"
|
self.wing_r3.base_joint = self.hs_hirth_joint
|
||||||
assert self.wing_s1_shoulder_spacer_hole_dist > self.wing_s1_spacer_hole_diam, \
|
|
||||||
"Spacer holes are too close to each other"
|
|
||||||
|
|
||||||
@target(name="trident/handle-connector")
|
@submodel(name="harness")
|
||||||
def handle_connector(self):
|
def submodel_harness(self) -> Model:
|
||||||
return self.trident_handle.connector()
|
return self.harness
|
||||||
@target(name="trident/handle-insertion")
|
|
||||||
def handle_insertion(self):
|
|
||||||
return self.trident_handle.insertion()
|
|
||||||
@target(name="trident/proto-handle-connector", prototype=True)
|
|
||||||
def proto_handle_connector(self):
|
|
||||||
return self.trident_handle.one_side_connector(height=15)
|
|
||||||
@target(name="trident/handle-terminal-connector")
|
|
||||||
def handle_terminal_connector(self):
|
|
||||||
result = self.trident_handle.one_side_connector(height=self.trident_terminal_height)
|
|
||||||
#result.faces("<Z").circle(radius=25/2).cutThruAll()
|
|
||||||
h = self.trident_terminal_height + self.trident_handle.insertion_length - self.trident_terminal_bottom_thickness
|
|
||||||
result = result.faces(">Z").hole(self.trident_terminal_hole_diam, depth=h)
|
|
||||||
return result
|
|
||||||
|
|
||||||
@target(name="harness", kind=TargetKind.DXF)
|
@submodel(name="wing-r1")
|
||||||
def harness_profile(self) -> Cq.Sketch:
|
def submodel_wing_r1(self) -> Model:
|
||||||
return self.harness.profile()
|
return self.wing_r1
|
||||||
|
@submodel(name="wing-r2")
|
||||||
def harness_surface(self) -> Cq.Workplane:
|
def submodel_wing_r2(self) -> Model:
|
||||||
return self.harness.surface()
|
return self.wing_r2
|
||||||
|
@submodel(name="wing-r3")
|
||||||
def hs_joint_parent(self) -> Cq.Workplane:
|
def submodel_wing_r3(self) -> Model:
|
||||||
return self.harness.hs_joint_parent()
|
return self.wing_r3
|
||||||
|
|
||||||
@assembly()
|
|
||||||
def harness_assembly(self) -> Cq.Assembly:
|
|
||||||
return self.harness.assembly()
|
|
||||||
|
|
||||||
@target(name="wing/proto-shoulder-joint-parent", prototype=True)
|
|
||||||
def proto_shoulder_joint_parent(self):
|
|
||||||
return self.wing_profile.shoulder_joint.torsion_joint.track()
|
|
||||||
@target(name="wing/proto-shoulder-joint-child", prototype=True)
|
|
||||||
def proto_shoulder_joint_child(self):
|
|
||||||
return self.wing_profile.shoulder_joint.torsion_joint.rider()
|
|
||||||
|
|
||||||
|
|
||||||
@assembly()
|
|
||||||
def wing_r1_assembly(self, parts: Optional[list[str]] = None) -> Cq.Assembly:
|
|
||||||
return self.wing_profile.assembly(parts)
|
|
||||||
|
|
||||||
@assembly()
|
@assembly()
|
||||||
def wings_harness_assembly(self, parts: Optional[list[str]] = None) -> Cq.Assembly:
|
def wings_harness_assembly(self, parts: Optional[list[str]] = None) -> Cq.Assembly:
|
||||||
"""
|
"""
|
||||||
Assembly of harness with all the wings
|
Assembly of harness with all the wings
|
||||||
"""
|
"""
|
||||||
a_tooth = self.hs_hirth_joint.tooth_angle
|
|
||||||
|
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.add(self.harness_assembly(), name="harness", loc=Cq.Location((0, 0, 0)))
|
.add(self.harness.assembly(), name="harness", loc=Cq.Location((0, 0, 0)))
|
||||||
.add(self.wing_r1_assembly(parts), name="wing_r1")
|
.add(self.wing_r1.assembly(parts), name="wing_r1")
|
||||||
.add(self.wing_r1_assembly(parts), name="wing_r2")
|
.add(self.wing_r2.assembly(parts), name="wing_r2")
|
||||||
.add(self.wing_r1_assembly(parts), name="wing_r3")
|
.add(self.wing_r3.assembly(parts), name="wing_r3")
|
||||||
)
|
)
|
||||||
self.hs_hirth_joint.add_constraints(result, "harness/r1", "wing_r1/s0/hs", offset=9)
|
self.hs_hirth_joint.add_constraints(result, "harness/r1", "wing_r1/s0/hs", offset=9)
|
||||||
self.hs_hirth_joint.add_constraints(result, "harness/r2", "wing_r2/s0/hs", offset=8)
|
self.hs_hirth_joint.add_constraints(result, "harness/r2", "wing_r2/s0/hs", offset=8)
|
||||||
self.hs_hirth_joint.add_constraints(result, "harness/r3", "wing_r3/s0/hs", offset=7)
|
self.hs_hirth_joint.add_constraints(result, "harness/r3", "wing_r3/s0/hs", offset=7)
|
||||||
return result.solve()
|
return result.solve()
|
||||||
|
|
||||||
@assembly(collision_check=False)
|
@submodel(name="trident")
|
||||||
def trident_assembly(self) -> Cq.Assembly:
|
def submodel_trident(self) -> Model:
|
||||||
"""
|
return self.trident
|
||||||
Disable collision check since the threads may not align.
|
|
||||||
"""
|
|
||||||
return MT.trident_assembly(self.trident_handle)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -2,10 +2,11 @@ from dataclasses import dataclass, field
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf.parts.joints import HirthJoint
|
from nhf.parts.joints import HirthJoint
|
||||||
from nhf import Material, Role
|
from nhf import Material, Role
|
||||||
|
from nhf.build import Model, TargetKind, target, assembly
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Harness:
|
class Harness(Model):
|
||||||
thickness: float = 25.4 / 8
|
thickness: float = 25.4 / 8
|
||||||
width: float = 300.0
|
width: float = 300.0
|
||||||
height: float = 400.0
|
height: float = 400.0
|
||||||
|
@ -40,6 +41,10 @@ class Harness:
|
||||||
hs_joint_axis_cbore_diam: float = 20
|
hs_joint_axis_cbore_diam: float = 20
|
||||||
hs_joint_axis_cbore_depth: float = 3
|
hs_joint_axis_cbore_depth: float = 3
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
super().__init__(name="harness")
|
||||||
|
|
||||||
|
@target(name="profile", kind=TargetKind.DXF)
|
||||||
def profile(self) -> Cq.Sketch:
|
def profile(self) -> Cq.Sketch:
|
||||||
"""
|
"""
|
||||||
Creates the harness shape
|
Creates the harness shape
|
||||||
|
@ -100,6 +105,7 @@ class Harness:
|
||||||
(-dx, dx),
|
(-dx, dx),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@target(name="hs-joint-parent")
|
||||||
def hs_joint_parent(self):
|
def hs_joint_parent(self):
|
||||||
"""
|
"""
|
||||||
Parent part of the Houjuu-Scarlett joint, which is composed of a Hirth
|
Parent part of the Houjuu-Scarlett joint, which is composed of a Hirth
|
||||||
|
@ -150,6 +156,7 @@ class Harness:
|
||||||
result.faces("<Z").tag("base")
|
result.faces("<Z").tag("base")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@assembly()
|
||||||
def assembly(self) -> Cq.Assembly:
|
def assembly(self) -> Cq.Assembly:
|
||||||
harness = self.surface()
|
harness = self.surface()
|
||||||
result = (
|
result = (
|
||||||
|
|
|
@ -1,55 +1,88 @@
|
||||||
import math
|
import math
|
||||||
|
from dataclasses import dataclass, field
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf import Material, Role
|
from nhf import Material, Role
|
||||||
from nhf.parts.handle import Handle
|
from nhf.parts.handle import Handle, BayonetMount
|
||||||
|
from nhf.build import Model, target, assembly
|
||||||
|
import nhf.utils
|
||||||
|
|
||||||
def trident_assembly(
|
@dataclass
|
||||||
handle: Handle,
|
class Trident(Model):
|
||||||
handle_segment_length: float = 24*25.4,
|
handle: Handle = field(default_factory=lambda: Handle(
|
||||||
terminal_height=100):
|
diam=38,
|
||||||
def segment():
|
diam_inner=38-2 * 25.4/8,
|
||||||
return handle.segment(handle_segment_length)
|
diam_connector_internal=18,
|
||||||
|
simplify_geometry=False,
|
||||||
|
mount=BayonetMount(n_pin=3),
|
||||||
|
))
|
||||||
|
terminal_height: float = 80
|
||||||
|
terminal_hole_diam: float = 24
|
||||||
|
terminal_bottom_thickness: float = 10
|
||||||
|
segment_length: float = 24 * 25.4
|
||||||
|
|
||||||
terminal = (
|
@target(name="handle-connector")
|
||||||
handle
|
def handle_connector(self):
|
||||||
.one_side_connector(height=terminal_height)
|
return self.handle.connector()
|
||||||
.faces(">Z")
|
@target(name="handle-insertion")
|
||||||
.hole(15, terminal_height + handle.insertion_length - 10)
|
def handle_insertion(self):
|
||||||
)
|
return self.handle.insertion()
|
||||||
mat_c = Material.PLASTIC_PLA
|
@target(name="proto-handle-terminal-connector", prototype=True)
|
||||||
mat_i = Material.RESIN_TOUGH_1500
|
def proto_handle_connector(self):
|
||||||
mat_s = Material.ACRYLIC_BLACK
|
return self.handle.one_side_connector(height=15)
|
||||||
role_i = Role.CONNECTION
|
|
||||||
role_c = Role.CONNECTION
|
@target(name="handle-terminal-connector")
|
||||||
role_s = Role.STRUCTURE
|
def handle_terminal_connector(self):
|
||||||
assembly = (
|
result = self.handle.one_side_connector(height=self.terminal_height)
|
||||||
Cq.Assembly()
|
#result.faces("<Z").circle(radius=25/2).cutThruAll()
|
||||||
.addS(handle.insertion(), name="i0",
|
h = self.terminal_height + self.handle.insertion_length - self.terminal_bottom_thickness
|
||||||
material=mat_i, role=role_i)
|
result = result.faces(">Z").hole(self.terminal_hole_diam, depth=h)
|
||||||
.constrain("i0", "Fixed")
|
return result
|
||||||
.addS(segment(), name="s1",
|
|
||||||
material=mat_s, role=role_s)
|
@assembly()
|
||||||
.constrain("i0?rim", "s1?mate1", "Plane", param=0)
|
def assembly(self):
|
||||||
.addS(handle.insertion(), name="i1",
|
def segment():
|
||||||
material=mat_i, role=role_i)
|
return self.handle.segment(self.segment_length)
|
||||||
.addS(handle.connector(), name="c1",
|
|
||||||
material=mat_c, role=role_c)
|
terminal = (
|
||||||
.addS(handle.insertion(), name="i2",
|
self.handle
|
||||||
material=mat_i, role=role_i)
|
.one_side_connector(height=self.terminal_height)
|
||||||
.constrain("s1?mate2", "i1?rim", "Plane", param=0)
|
.faces(">Z")
|
||||||
.constrain("i1?mate", "c1?mate1", "Plane")
|
.hole(15, self.terminal_height + self.handle.insertion_length - 10)
|
||||||
.constrain("i2?mate", "c1?mate2", "Plane")
|
)
|
||||||
.addS(segment(), name="s2",
|
mat_c = Material.PLASTIC_PLA
|
||||||
material=mat_s, role=role_s)
|
mat_i = Material.RESIN_TOUGH_1500
|
||||||
.constrain("i2?rim", "s2?mate1", "Plane", param=0)
|
mat_s = Material.ACRYLIC_BLACK
|
||||||
.addS(handle.insertion(), name="i3",
|
role_i = Role.CONNECTION
|
||||||
material=mat_i, role=role_i)
|
role_c = Role.CONNECTION
|
||||||
.constrain("s2?mate2", "i3?rim", "Plane", param=0)
|
role_s = Role.STRUCTURE
|
||||||
.addS(handle.one_side_connector(), name="head",
|
a = (
|
||||||
material=mat_c, role=role_c)
|
Cq.Assembly()
|
||||||
.constrain("i3?mate", "head?mate", "Plane")
|
.addS(self.handle.insertion(), name="i0",
|
||||||
.addS(terminal, name="terminal",
|
material=mat_i, role=role_i)
|
||||||
material=mat_c, role=role_c)
|
.constrain("i0", "Fixed")
|
||||||
.constrain("i0?mate", "terminal?mate", "Plane")
|
.addS(segment(), name="s1",
|
||||||
)
|
material=mat_s, role=role_s)
|
||||||
return assembly.solve()
|
.constrain("i0?rim", "s1?mate1", "Plane", param=0)
|
||||||
|
.addS(self.handle.insertion(), name="i1",
|
||||||
|
material=mat_i, role=role_i)
|
||||||
|
.addS(self.handle.connector(), name="c1",
|
||||||
|
material=mat_c, role=role_c)
|
||||||
|
.addS(self.handle.insertion(), name="i2",
|
||||||
|
material=mat_i, role=role_i)
|
||||||
|
.constrain("s1?mate2", "i1?rim", "Plane", param=0)
|
||||||
|
.constrain("i1?mate", "c1?mate1", "Plane")
|
||||||
|
.constrain("i2?mate", "c1?mate2", "Plane")
|
||||||
|
.addS(segment(), name="s2",
|
||||||
|
material=mat_s, role=role_s)
|
||||||
|
.constrain("i2?rim", "s2?mate1", "Plane", param=0)
|
||||||
|
.addS(self.handle.insertion(), name="i3",
|
||||||
|
material=mat_i, role=role_i)
|
||||||
|
.constrain("s2?mate2", "i3?rim", "Plane", param=0)
|
||||||
|
.addS(self.handle.one_side_connector(), name="head",
|
||||||
|
material=mat_c, role=role_c)
|
||||||
|
.constrain("i3?mate", "head?mate", "Plane")
|
||||||
|
.addS(terminal, name="terminal",
|
||||||
|
material=mat_c, role=role_c)
|
||||||
|
.constrain("i0?mate", "terminal?mate", "Plane")
|
||||||
|
)
|
||||||
|
return a.solve()
|
||||||
|
|
|
@ -8,7 +8,7 @@ from dataclasses import dataclass, field
|
||||||
from typing import Mapping, Tuple, Optional
|
from typing import Mapping, Tuple, Optional
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf import Material, Role
|
from nhf import Material, Role
|
||||||
from nhf.build import Model, target, assembly
|
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||||
from nhf.parts.box import box_with_centre_holes, MountingBox, Hole
|
from nhf.parts.box import box_with_centre_holes, MountingBox, Hole
|
||||||
from nhf.parts.joints import HirthJoint
|
from nhf.parts.joints import HirthJoint
|
||||||
from nhf.touhou.houjuu_nue.joints import ShoulderJoint, ElbowJoint, DiskJoint
|
from nhf.touhou.houjuu_nue.joints import ShoulderJoint, ElbowJoint, DiskJoint
|
||||||
|
@ -27,7 +27,9 @@ class WingProfile(Model):
|
||||||
panel_thickness: float = 25.4 / 16
|
panel_thickness: float = 25.4 / 16
|
||||||
spacer_thickness: float = 25.4 / 8
|
spacer_thickness: float = 25.4 / 8
|
||||||
|
|
||||||
shoulder_joint: ShoulderJoint = field(default_factory=lambda: ShoulderJoint())
|
shoulder_joint: ShoulderJoint = field(default_factory=lambda: ShoulderJoint(
|
||||||
|
height=100.0,
|
||||||
|
))
|
||||||
shoulder_width: float = 30.0
|
shoulder_width: float = 30.0
|
||||||
shoulder_tip_x: float = -200.0
|
shoulder_tip_x: float = -200.0
|
||||||
shoulder_tip_y: float = 160.0
|
shoulder_tip_y: float = 160.0
|
||||||
|
@ -40,7 +42,7 @@ class WingProfile(Model):
|
||||||
),
|
),
|
||||||
flip=False,
|
flip=False,
|
||||||
))
|
))
|
||||||
elbow_height: float = 100
|
elbow_height: float = 110
|
||||||
elbow_x: float = 240
|
elbow_x: float = 240
|
||||||
elbow_y: float = 30
|
elbow_y: float = 30
|
||||||
# Tilt of elbow w.r.t. shoulder
|
# Tilt of elbow w.r.t. shoulder
|
||||||
|
@ -104,6 +106,7 @@ class WingProfile(Model):
|
||||||
def root_height(self) -> float:
|
def root_height(self) -> float:
|
||||||
return self.shoulder_joint.height
|
return self.shoulder_joint.height
|
||||||
|
|
||||||
|
@target(name="profile-s0", kind=TargetKind.DXF)
|
||||||
def profile_s0(self) -> Cq.Sketch:
|
def profile_s0(self) -> Cq.Sketch:
|
||||||
tip_x = self.shoulder_tip_x
|
tip_x = self.shoulder_tip_x
|
||||||
tip_y = self.shoulder_tip_y
|
tip_y = self.shoulder_tip_y
|
||||||
|
@ -127,6 +130,7 @@ class WingProfile(Model):
|
||||||
)
|
)
|
||||||
return sketch
|
return sketch
|
||||||
|
|
||||||
|
@submodel(name="spacer-s0-shoulder")
|
||||||
def spacer_s0_shoulder(self) -> MountingBox:
|
def spacer_s0_shoulder(self) -> MountingBox:
|
||||||
"""
|
"""
|
||||||
Should be cut
|
Should be cut
|
||||||
|
@ -147,6 +151,7 @@ class WingProfile(Model):
|
||||||
hole_diam=self.shoulder_joint.parent_conn_hole_diam,
|
hole_diam=self.shoulder_joint.parent_conn_hole_diam,
|
||||||
centred=(True, True),
|
centred=(True, True),
|
||||||
)
|
)
|
||||||
|
@submodel(name="spacer-s0-shoulder")
|
||||||
def spacer_s0_base(self) -> MountingBox:
|
def spacer_s0_base(self) -> MountingBox:
|
||||||
"""
|
"""
|
||||||
Should be cut
|
Should be cut
|
||||||
|
@ -179,6 +184,7 @@ class WingProfile(Model):
|
||||||
reverse=not top,
|
reverse=not top,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@assembly()
|
||||||
def assembly_s0(self) -> Cq.Assembly:
|
def assembly_s0(self) -> Cq.Assembly:
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
|
@ -335,6 +341,7 @@ class WingProfile(Model):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@target(name="profile-s1", kind=TargetKind.DXF)
|
||||||
def profile_s1(self) -> Cq.Sketch:
|
def profile_s1(self) -> Cq.Sketch:
|
||||||
profile = (
|
profile = (
|
||||||
self.profile()
|
self.profile()
|
||||||
|
@ -365,6 +372,7 @@ class WingProfile(Model):
|
||||||
profile = self.profile_s1()
|
profile = self.profile_s1()
|
||||||
tags = tags_shoulder + tags_elbow
|
tags = tags_shoulder + tags_elbow
|
||||||
return nhf.utils.extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
return nhf.utils.extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||||
|
@submodel(name="spacer-s1-shoulder")
|
||||||
def spacer_s1_shoulder(self) -> MountingBox:
|
def spacer_s1_shoulder(self) -> MountingBox:
|
||||||
holes = [
|
holes = [
|
||||||
Hole(x)
|
Hole(x)
|
||||||
|
@ -377,6 +385,7 @@ class WingProfile(Model):
|
||||||
holes=holes,
|
holes=holes,
|
||||||
hole_diam=self.shoulder_joint.child_conn_hole_diam,
|
hole_diam=self.shoulder_joint.child_conn_hole_diam,
|
||||||
)
|
)
|
||||||
|
@submodel(name="spacer-s1-elbow")
|
||||||
def spacer_s1_elbow(self) -> MountingBox:
|
def spacer_s1_elbow(self) -> MountingBox:
|
||||||
holes = [
|
holes = [
|
||||||
Hole(x)
|
Hole(x)
|
||||||
|
@ -389,6 +398,7 @@ class WingProfile(Model):
|
||||||
holes=holes,
|
holes=holes,
|
||||||
hole_diam=self.elbow_joint.hole_diam,
|
hole_diam=self.elbow_joint.hole_diam,
|
||||||
)
|
)
|
||||||
|
@assembly()
|
||||||
def assembly_s1(self) -> Cq.Assembly:
|
def assembly_s1(self) -> Cq.Assembly:
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
|
@ -412,7 +422,7 @@ class WingProfile(Model):
|
||||||
)
|
)
|
||||||
return result.solve()
|
return result.solve()
|
||||||
|
|
||||||
|
@target(name="profile-s2", kind=TargetKind.DXF)
|
||||||
def profile_s2(self) -> Cq.Sketch:
|
def profile_s2(self) -> Cq.Sketch:
|
||||||
profile = (
|
profile = (
|
||||||
self.profile()
|
self.profile()
|
||||||
|
@ -450,6 +460,7 @@ class WingProfile(Model):
|
||||||
profile = self.profile_s2()
|
profile = self.profile_s2()
|
||||||
tags = tags_elbow + tags_wrist
|
tags = tags_elbow + tags_wrist
|
||||||
return nhf.utils.extrude_with_markers(profile, thickness, tags, reverse=front)
|
return nhf.utils.extrude_with_markers(profile, thickness, tags, reverse=front)
|
||||||
|
@submodel(name="spacer-s2-elbow")
|
||||||
def spacer_s2_elbow(self) -> MountingBox:
|
def spacer_s2_elbow(self) -> MountingBox:
|
||||||
holes = [
|
holes = [
|
||||||
Hole(x)
|
Hole(x)
|
||||||
|
@ -462,6 +473,7 @@ class WingProfile(Model):
|
||||||
holes=holes,
|
holes=holes,
|
||||||
hole_diam=self.elbow_joint.hole_diam,
|
hole_diam=self.elbow_joint.hole_diam,
|
||||||
)
|
)
|
||||||
|
@submodel(name="spacer-s2-wrist")
|
||||||
def spacer_s2_wrist(self) -> MountingBox:
|
def spacer_s2_wrist(self) -> MountingBox:
|
||||||
holes = [
|
holes = [
|
||||||
Hole(x)
|
Hole(x)
|
||||||
|
@ -474,6 +486,7 @@ class WingProfile(Model):
|
||||||
holes=holes,
|
holes=holes,
|
||||||
hole_diam=self.wrist_joint.hole_diam,
|
hole_diam=self.wrist_joint.hole_diam,
|
||||||
)
|
)
|
||||||
|
@assembly()
|
||||||
def assembly_s2(self) -> Cq.Assembly:
|
def assembly_s2(self) -> Cq.Assembly:
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
|
@ -497,6 +510,7 @@ class WingProfile(Model):
|
||||||
)
|
)
|
||||||
return result.solve()
|
return result.solve()
|
||||||
|
|
||||||
|
@target(name="profile-s3", kind=TargetKind.DXF)
|
||||||
def profile_s3(self) -> Cq.Sketch:
|
def profile_s3(self) -> Cq.Sketch:
|
||||||
profile = (
|
profile = (
|
||||||
self.profile()
|
self.profile()
|
||||||
|
@ -519,6 +533,7 @@ class WingProfile(Model):
|
||||||
]
|
]
|
||||||
profile = self.profile_s3()
|
profile = self.profile_s3()
|
||||||
return nhf.utils.extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
return nhf.utils.extrude_with_markers(profile, self.panel_thickness, tags, reverse=front)
|
||||||
|
@submodel(name="spacer-s3-wrist")
|
||||||
def spacer_s3_wrist(self) -> MountingBox:
|
def spacer_s3_wrist(self) -> MountingBox:
|
||||||
holes = [
|
holes = [
|
||||||
Hole(x)
|
Hole(x)
|
||||||
|
@ -531,6 +546,7 @@ class WingProfile(Model):
|
||||||
holes=holes,
|
holes=holes,
|
||||||
hole_diam=self.wrist_joint.hole_diam
|
hole_diam=self.wrist_joint.hole_diam
|
||||||
)
|
)
|
||||||
|
@assembly()
|
||||||
def assembly_s3(self) -> Cq.Assembly:
|
def assembly_s3(self) -> Cq.Assembly:
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
|
@ -554,6 +570,7 @@ class WingProfile(Model):
|
||||||
)
|
)
|
||||||
return result.solve()
|
return result.solve()
|
||||||
|
|
||||||
|
@assembly()
|
||||||
def assembly(self,
|
def assembly(self,
|
||||||
parts: Optional[list[str]] = None,
|
parts: Optional[list[str]] = None,
|
||||||
angle_elbow_wrist: float = 0.0,
|
angle_elbow_wrist: float = 0.0,
|
||||||
|
|
Loading…
Reference in New Issue