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