feat: Submodel in build system

This commit is contained in:
Leni Aniva 2024-07-16 15:42:39 -07:00
parent 0cc6100d0e
commit 66b26fa056
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
6 changed files with 239 additions and 163 deletions

View File

@ -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))

View File

@ -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]:

View File

@ -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__':

View File

@ -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 = (

View File

@ -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()

View File

@ -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,