From 4613247e1b2453d3c46d18025598769c02d32ec8 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Wed, 19 Jun 2024 15:54:09 -0700 Subject: [PATCH] feat: Hirth Joint for wing root --- nhf/diag.py | 42 +++++++ nhf/joints.py | 106 ++++++++++++++++++ nhf/touhou/__init__.py | 0 nhf/touhou/houjuu_nue/README.org | 14 +++ nhf/touhou/houjuu_nue/__init__.py | 17 +++ .../houjuu_nue/controller/controller.ino | 68 +++++++++++ 6 files changed, 247 insertions(+) create mode 100644 nhf/diag.py create mode 100644 nhf/joints.py create mode 100644 nhf/touhou/__init__.py create mode 100644 nhf/touhou/houjuu_nue/README.org create mode 100644 nhf/touhou/houjuu_nue/__init__.py create mode 100644 nhf/touhou/houjuu_nue/controller/controller.ino diff --git a/nhf/diag.py b/nhf/diag.py new file mode 100644 index 0000000..1648383 --- /dev/null +++ b/nhf/diag.py @@ -0,0 +1,42 @@ +import cadquery as Cq + +def tidy_repr(obj): + """Shortens a default repr string""" + return repr(obj).split(".")[-1].rstrip(">") + + +def _ctx_str(self): + return ( + tidy_repr(self) + + ":\n" + + f" pendingWires: {self.pendingWires}\n" + + f" pendingEdges: {self.pendingEdges}\n" + + f" tags: {self.tags}" + ) + + +Cq.cq.CQContext.__str__ = _ctx_str + + +def _plane_str(self): + return ( + tidy_repr(self) + + ":\n" + + f" origin: {self.origin.toTuple()}\n" + + f" z direction: {self.zDir.toTuple()}" + ) + + +Cq.occ_impl.geom.Plane.__str__ = _plane_str + + +def _wp_str(self): + out = tidy_repr(self) + ":\n" + out += f" parent: {tidy_repr(self.parent)}\n" if self.parent else " no parent\n" + out += f" plane: {self.plane}\n" + out += f" objects: {self.objects}\n" + out += f" modelling context: {self.ctx}" + return out + + +Cq.Workplane.__str__ = _wp_str diff --git a/nhf/joints.py b/nhf/joints.py new file mode 100644 index 0000000..805d89e --- /dev/null +++ b/nhf/joints.py @@ -0,0 +1,106 @@ +import cadquery as Cq +import math +import unittest + +def hirth_joint(radius=60, + radius_inner=40, + radius_centre=30, + base_height=20, + n_tooth=16, + tooth_height=16, + tooth_height_inner=2): + """ + Creates a cylindrical Hirth Joint + """ + # ensures secant doesn't blow up + assert n_tooth >= 5 + + # angle of half of a single tooth + theta = math.pi / n_tooth + + # Generate a tooth by lofting between two curves + + inner_raise = (tooth_height - tooth_height_inner) / 2 + # Outer tooth triangle spans a curve of length `2 pi r / n_tooth`. This + # creates the side profile (looking radially inwards) of each of the + # triangles. + outer = [ + (radius * math.tan(theta), 0), + (0, tooth_height), + (-radius * math.tan(theta), 0), + ] + inner = [ + (radius_inner * math.sin(theta), 0), + (radius_inner * math.sin(theta), inner_raise - tooth_height_inner / 2), + (0, inner_raise + tooth_height_inner / 2), + (-radius_inner * math.sin(theta), inner_raise - tooth_height_inner / 2), + (-radius_inner * math.sin(theta), 0), + ] + tooth = ( + Cq.Workplane('YZ') + .polyline(inner) + .close() + .workplane(offset=radius - radius_inner) + .polyline(outer) + .close() + .loft(combine=True) + .val() + ) + tooth_centre_radius = radius_inner * math.cos(theta) + teeth = ( + Cq.Workplane('XY') + .polarArray(radius=radius_inner, startAngle=0, angle=360, count=n_tooth) + .eachpoint(lambda loc: tooth.located(loc)) + .intersect(Cq.Solid.makeCylinder( + height=base_height + tooth_height, + radius=radius, + )) + ) + base = ( + Cq.Workplane('XY') + .cylinder( + height=base_height, + radius=radius, + centered=(True, True, False)) + .faces(">Z").tag("bore") + .union(teeth.val().move(Cq.Location((0,0,base_height)))) + .clean() + ) + #base.workplane(offset=tooth_height/2).circle(radius=radius,forConstruction=True).tag("mate") + base.polyline([(0, 0, 0), (0, 0, 1)], forConstruction=True).tag("mate") + return base + +def hirth_assembly(): + """ + Example assembly of two Hirth joints + """ + rotate = 180 / 16 + obj1 = hirth_joint().faces(tag="bore").cboreHole( + diameter=10, + cboreDiameter=20, + cboreDepth=3) + obj2 = ( + hirth_joint() + .rotate( + axisStartPoint=(0,0,0), + axisEndPoint=(0,0,1), + angleDegrees=rotate + ) + ) + result = ( + Cq.Assembly() + .add(obj1, name="obj1", color=Cq.Color(0.8,0.8,0.5,0.3)) + .add(obj2, name="obj2", color=Cq.Color(0.5,0.5,0.5,0.3), loc=Cq.Location((0,0,80))) + .constrain("obj1?mate", "obj2?mate", "Axis") + .solve() + ) + return result + +class TestJoints(unittest.TestCase): + + def test_hirth_assembly(self): + print(Cq.__version__) + hirth_assembly() + +if __name__ == '__main__': + unittest.main() diff --git a/nhf/touhou/__init__.py b/nhf/touhou/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nhf/touhou/houjuu_nue/README.org b/nhf/touhou/houjuu_nue/README.org new file mode 100644 index 0000000..1649107 --- /dev/null +++ b/nhf/touhou/houjuu_nue/README.org @@ -0,0 +1,14 @@ +#+title: Cosplay: Houjuu Nue + +* Controller + +This part describes the electrical connections and the microcontroller code. + +* Structure + +This part describes the 3d printed and laser cut structures. ~structure.blend~ +is an overall sketch of the shapes and looks of the wing. + +* Pattern + +This part describes the sewing patterns. diff --git a/nhf/touhou/houjuu_nue/__init__.py b/nhf/touhou/houjuu_nue/__init__.py new file mode 100644 index 0000000..f91d538 --- /dev/null +++ b/nhf/touhou/houjuu_nue/__init__.py @@ -0,0 +1,17 @@ +import cadquery as Cq + +def mystery(): + return ( + Cq.Workplane() + .box(1, 1, 1) + .tag("base") + .wires(">Z") + .toPending() + .translate((0.1, 0.1, 1.0)) + .toPending() + .loft() + .faces(">>X", tag="base") + .workplane(centerOption="CenterOfMass") + .circle(0.2) + .extrude(3) + ) diff --git a/nhf/touhou/houjuu_nue/controller/controller.ino b/nhf/touhou/houjuu_nue/controller/controller.ino new file mode 100644 index 0000000..245d39d --- /dev/null +++ b/nhf/touhou/houjuu_nue/controller/controller.ino @@ -0,0 +1,68 @@ +#include + +// Main LED strip setup +#define LED_PIN 5 +#define NUM_LEDS 100 +#define LED_PART 50 +#define BRIGHTNESS 250 +#define LED_TYPE WS2811 +CRGB leds[NUM_LEDS]; + +CRGB color_red; +CRGB color_blue; +CRGB color_green; + +#define DIAG_PIN 6 + + +void setup() { + // Calculate colors + hsv2rgb_spectrum(CHSV(4, 255, 100), color_red); + hsv2rgb_spectrum(CHSV(170, 255, 100), color_blue); + hsv2rgb_spectrum(CHSV(90, 255, 100), color_green); + pinMode(LED_BUILTIN, OUTPUT); + pinMode(LED_PIN, OUTPUT); + pinMode(DIAG_PIN, OUTPUT); + + // Main LED strip + FastLED.addLeds(leds, NUM_LEDS); +} + +void loop() { + fill_segmented(CRGB::Green, CRGB::Orange); + delay(500); + + flash(leds, NUM_LEDS, color_red, 10, 20); + delay(500); + flash(leds, NUM_LEDS, color_blue, 10, 20); + delay(500); +} + +void fill_segmented(CRGB c1, CRGB c2) +{ + //fill_solid(leds, LED_PART, c1); + fill_gradient_RGB(leds, LED_PART, CRGB::Black ,c1); + fill_gradient_RGB(leds + LED_PART, NUM_LEDS - LED_PART, CRGB::Black, c2); + FastLED.show(); +} +void flash(CRGB *ptr, uint16_t num, CRGB const& lead, int steps, int step_time) +{ + digitalWrite(LED_BUILTIN, LOW); + + //fill_solid(leds, NUM_LEDS, CRGB::Black); + for (int i = 0; i < steps; ++i) + { + uint8_t factor = 255 * i / steps; + analogWrite(DIAG_PIN, factor); + CRGB tail = blend(lead, CRGB::Black, factor); + uint16_t front = factor * (int) num / 255; + fill_solid(ptr, front, tail); + //fill_gradient_RGB(ptr, front, tail, lead); + //fill_solid(leds + front, NUM_LEDS - front, CRGB::Black); + FastLED.show(); + delay(step_time); + } + fill_gradient_RGB(ptr, num, CRGB::Black, lead); + FastLED.show(); + analogWrite(DIAG_PIN, LOW); +} \ No newline at end of file