from dataclasses import dataclass import unittest import cadquery as Cq import nhf.joints from nhf import Material @dataclass(frozen=True) class Parameters: """ Thickness of the exoskeleton panel in millimetres """ panel_thickness: float = 25.4 / 16 # Harness harness_thickness: float = 25.4 / 8 harness_width = 300 harness_height = 400 harness_fillet = 10 harness_wing_base_pos = [ ("r1", 70, 150), ("l1", -70, 150), ("r2", 100, 0), ("l2", -100, 0), ("r3", 70, -150), ("l3", -70, -150), ] # Holes drilled onto harness for attachment with HS joint harness_to_wing_base_hole_diam = 6 # Wing root properties """ The Houjuu-Scarlett joint mechanism at the base of the wing """ hs_joint_base_width = 85 hs_joint_base_thickness = 10 hs_joint_ring_thickness = 5 hs_joint_tooth_height = 10 hs_joint_radius = 30 hs_joint_radius_inner = 20 hs_joint_corner_fillet = 5 hs_joint_corner_cbore_diam = 12 hs_joint_corner_cbore_depth = 12 hs_joint_corner_inset = 15 """ Radius of the mounting mechanism of the wing root. This is constrained by the size of the harness. """ root_radius: float = 60 """ Heights for various wing joints, where the numbers start from the first joint. """ wing_r1_height = 100 wing_r1_width = 400 wing_r2_height = 100 wing_r3_height = 100 def print_geometries(self): return [ self.hs_joint_parent() ] def harness_profile(self) -> Cq.Sketch: """ Creates the harness shape """ w, h = self.harness_width / 2, self.harness_height / 2 sketch = ( Cq.Sketch() .polygon([ (0.7 * w, h), (w, 0), (0.7 * w, -h), (0.7 * -w, -h), (-w, 0), (0.7 * -w, h), ]) #.rect(self.harness_width, self.harness_height) .vertices() .fillet(self.harness_fillet) ) for tag, x, y in self.harness_wing_base_pos: conn = [(px + x, py + y) for px, py in self.hs_joint_harness_conn()] sketch = ( sketch .push(conn) .tag(tag) .circle(self.harness_to_wing_base_hole_diam / 2, mode='s') .reset() ) return sketch def harness(self) -> Cq.Shape: """ Creates the harness shape """ result = ( Cq.Workplane('XZ') .placeSketch(self.harness_profile()) .extrude(self.harness_thickness) ) result.faces(">Y").tag("mount") plane = result.faces(">Y").workplane() for tag, x, y in self.harness_wing_base_pos: conn = [(px + x, py + y) for px, py in self.hs_joint_harness_conn()] for i, (px, py) in enumerate(conn): ( plane .moveTo(px, py) .circle(1, forConstruction='True') .edges() .tag(f"{tag}_{i}") ) return result def hs_joint_harness_conn(self) -> list[tuple[int, int]]: """ Generates a set of points corresponding to the connectorss """ dx = self.hs_joint_base_width / 2 - self.hs_joint_corner_inset return [ (dx, dx), (dx, -dx), (-dx, -dx), (-dx, dx), ] def hs_joint_parent(self): """ Parent part of the Houjuu-Scarlett joint, which is composed of a Hirth coupling, a cylindrical base, and a mounting base. """ hirth = nhf.joints.hirth_joint( radius=self.hs_joint_radius, radius_inner=self.hs_joint_radius_inner, tooth_height=self.hs_joint_tooth_height, base_height=self.hs_joint_ring_thickness) hirth = ( hirth .faces("Z") .workplane() .pushPoints(conn) .cboreHole( diameter=self.harness_to_wing_base_hole_diam, cboreDiameter=self.hs_joint_corner_cbore_diam, cboreDepth=self.hs_joint_corner_cbore_depth) ) plane = result.faces(">Z").workplane(offset=-self.hs_joint_base_thickness) for i, (px, py) in enumerate(conn): ( plane .moveTo(px, py) .circle(1, forConstruction='True') .edges() .tag(f"h{i}") ) result = ( result .faces(">Z") .workplane() .union(hirth.move(Cq.Location((0, 0, self.hs_joint_base_thickness))), tol=0.1) .clean() ) result.faces(" Cq.Sketch: """ Generates the first wing segment profile, with the wing root pointing in the positive x axis. """ # Depression of the wing middle bend = 200 factor = 0.7 result = ( Cq.Sketch() .segment((0, 0), (0, self.wing_r1_height)) .spline([ (0, self.wing_r1_height), (0.5 * self.wing_r1_width, self.wing_r1_height - factor * bend), (self.wing_r1_width, self.wing_r1_height - bend), ]) .segment( (self.wing_r1_width, self.wing_r1_height - bend), (self.wing_r1_width, -bend), ) .spline([ (self.wing_r1_width, - bend), (0.5 * self.wing_r1_width, - factor * bend), (0, 0), ]) .assemble() ) return result def wing_r1(self) -> Cq.Solid: profile = self.wing_r1_profile() result = ( Cq.Workplane("XY") .placeSketch(profile) .extrude(self.panel_thickness) .val() ) return result def wing_root(self, side_width=30, side_height=100) -> Cq.Shape: """ Generate the wing root which contains a Hirth joint at its base and a rectangular opening on its side, with the necessary interfaces. """ result = ( Cq.Workplane("XY") .circle(self.root_radius) .transformed(offset=Cq.Vector(80, 0, 80), rotate=Cq.Vector(0, 45, 0)) .rect(side_width, side_height) .loft() .val() ) return result def harness_assembly(self): harness = self.harness() result = ( Cq.Assembly() .add(harness, name="harness", color=Material.WOOD_BIRCH.color) .constrain("harness", "Fixed") ) for name in ["l1", "l2", "l3", "r1", "r2", "r3"]: j = self.hs_joint_parent() ( result .add(j, name=f"hs_{name}p", color=Material.PLASTIC_PLA.color) #.constrain(f"harness?{name}", f"hs_{name}p?mate", "Point") .constrain("harness?mount", f"hs_{name}p?base", "Axis") ) for i in range(4): result.constrain(f"harness?{name}_{i}", f"hs_{name}p?h{i}", "Point") result.solve() return result