From e4cfc71f1a4b93fed1986e297626d44eae2ef07c Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Fri, 30 May 2025 14:20:25 -0700 Subject: [PATCH] Electrnics assembly --- nhf/parts/electronics.py | 15 +- nhf/touhou/yasaka_kanako/onbashira.py | 512 +++++++++++++++++--------- 2 files changed, 341 insertions(+), 186 deletions(-) diff --git a/nhf/parts/electronics.py b/nhf/parts/electronics.py index 8ac43bd..e901276 100644 --- a/nhf/parts/electronics.py +++ b/nhf/parts/electronics.py @@ -100,6 +100,13 @@ class BatteryBox18650(Item): def name(self) -> str: return f"BatteryBox 18650*{self.n_batteries}" + @property + def holes(self) -> list[Cq.Location]: + return [ + Cq.Location.from2d(0, self.hole_dy), + Cq.Location.from2d(0, -self.hole_dy), + ] + @property def role(self) -> Role: return Role.ELECTRONIC @@ -144,8 +151,8 @@ class BatteryBox18650(Item): ) result -= hole.moved(0, self.hole_dy) result -= hole.moved(0, -self.hole_dy) - result.tagAbsolute("holeT1", (0, self.hole_dy, self.thickness), direction="+Z") - result.tagAbsolute("holeT2", (0, -self.hole_dy, self.thickness), direction="+Z") - result.tagAbsolute("holeB1", (0, self.hole_dy, 0), direction="-Z") - result.tagAbsolute("holeB2", (0, -self.hole_dy, 0), direction="-Z") + result.tagAbsolute("holeT0", (0, self.hole_dy, self.thickness), direction="+Z") + result.tagAbsolute("holeT1", (0, -self.hole_dy, self.thickness), direction="+Z") + result.tagAbsolute("holeB0", (0, self.hole_dy, 0), direction="-Z") + result.tagAbsolute("holeB1", (0, -self.hole_dy, 0), direction="-Z") return result diff --git a/nhf/touhou/yasaka_kanako/onbashira.py b/nhf/touhou/yasaka_kanako/onbashira.py index c527d5b..d479479 100644 --- a/nhf/touhou/yasaka_kanako/onbashira.py +++ b/nhf/touhou/yasaka_kanako/onbashira.py @@ -284,7 +284,9 @@ class Onbashira(Model): # Gap between the inner and outer bearing disks bearing_gap: float = 10.0 bearing_disk_gap: float = 10.0 - bearing_spindle_max_diam: float = 13.0 + bearing_spindle_max_diam: float = 16.0 + bearing_spindle_ext: float = 5.0 + bearing_spindle_gap: float = 0.2 bearing_gasket_extend: float = 12.0 bearing_disk_thickness: float = 25.4 / 16 @@ -314,6 +316,13 @@ class Onbashira(Model): battery_box: BatteryBox18650 = BatteryBox18650() controller: ArduinoUnoR3 = ArduinoUnoR3() + controller_loc: Cq.Location = Cq.Location.from2d(-30, -35, 90) + battery_box_locs: list[Cq.Location] = field(default_factory=lambda: [ + Cq.Location.from2d(70, -35, 90), + Cq.Location.from2d(140, -35, 90), + Cq.Location.from2d(-70, -35, 90), + Cq.Location.from2d(-140, -35, 90), + ]) # Distance between bind point and motor's mount points motor_driver_radius: float = 110.0 @@ -547,6 +556,7 @@ class Onbashira(Model): return ( Cq.Sketch() .circle(r + gap) + .circle(self.motor.diam_ring/2, mode="s") .regularPolygon(r, self.n_side, mode="c", tag="corner") .vertices(tag="corner") .circle(BOLT_COMMON.diam_thread/2, mode="s") @@ -573,107 +583,6 @@ class Onbashira(Model): result.tagAbsolute(f"holeB{i}", (x, -y, 0), direction="-Z") return result - @assembly() - def assembly_motor(self) -> Cq.Assembly: - a = ( - Cq.Assembly() - .addS( - self.motor.generate(), - name="motor", - role=Role.MOTOR, - ) - .addS( - self.flange_coupler.generate(), - name="flange_coupler", - role=Role.CONNECTION | Role.STRUCTURE, - material=self.material_fastener, - ) - .addS( - self.motor_driver_disk(), - name="driver_disk", - role=Role.CONNECTION | Role.STRUCTURE, - material=self.material_auxiliary, - ) - .addS( - self.motor_mount_plate(), - name="mount_plate", - role=Role.CONNECTION | Role.STRUCTURE, - material=self.material_auxiliary, - ) - .constrain( - "mount_plate?anchor1", - "motor?anchor1", - "Plane", - ) - .constrain( - "mount_plate?anchor2", - "motor?anchor2", - "Plane", - ) - .constrain( - "flange_coupler?top", - "motor?shaft", - "Axis" - ) - .constrain( - "flange_coupler?dir", - "motor?dir", - "Plane", - param=0, - ) - ) - for i in range(self.flange_coupler.n_hole_flange): - j = self.flange_coupler.n_hole_flange - i - 1 - a = a.constrain( - f"flange_coupler?holeB{i}", - f"driver_disk?holeB{j}", - "Plane", - ) - - # Add the motor seats - assert self.n_side % 2 == 0 - for i in range(self.n_side // 2): - name_seat = f"seat{i}" - a = ( - a.addS( - self.motor_seat(), - name=name_seat, - role=Role.STRUCTURE, - material=self.material_brace - ) - .constrain( - f"{name_seat}?holeMF1", - f"mount_plate?holeB{i*2}", - "Plane" - ) - .constrain( - f"{name_seat}?holeMF2", - f"mount_plate?holeB{i*2+1}", - "Plane" - ) - ) - for i in range(self.n_side): - name_coupler = f"coupler{i}" - a = ( - a.addS( - self.motor_coupler(), - name=name_coupler, - role=Role.CONNECTION, - material=self.material_brace, - ) - .constrain( - f"{name_coupler}?holeB1", - f"driver_disk?holeCOF{i}", - "Plane", - ) - .constrain( - f"{name_coupler}?holeB2", - f"driver_disk?holeCIF{i}", - "Plane", - ) - ) - return a.solve() - @target(name="stator-coupler") def stator_coupler(self) -> Cq.Workplane: """ @@ -811,7 +720,7 @@ class Onbashira(Model): self.bearing_track_radius, self.n_bearing_balls, mode="c", tag="corners") .vertices(tag="corners") - .circle(BOLT_BEARING.diam_thread, mode="s") + .circle(BOLT_BEARING.diam_thread/2, mode="s") ) def bearing_gasket(self) -> Cq.Workplane: return ( @@ -821,13 +730,6 @@ class Onbashira(Model): ) - @target(name="pipe", kind=TargetKind.DXF) - def pipe(self) -> Cq.Sketch: - """ - The rotating pipes. Purely for decoration - """ - pass - @target(name="stator-spacer") def stator_spacer(self) -> Cq.Solid: outer = Cq.Solid.makeCylinder( @@ -854,28 +756,29 @@ class Onbashira(Model): @property def bearing_spindle_height(self) -> float: h = self.bearing_disk_gap + 2 * self.bearing_disk_thickness - return h * 2 + return h + self.bearing_spindle_ext * 2 @target(name="bearing-spindle") def bearing_spindle(self) -> Cq.Solid: r1 = self.bearing_gap / 2 - r2 = self.bearing_spindle_max_diam - h = self.bearing_disk_gap + 2 * self.bearing_disk_thickness - cone1 = Cq.Solid.makeCylinder( - radius=r1, + r2 = self.bearing_spindle_max_diam / 2 + h = self.bearing_disk_gap + cone = Cq.Solid.makeCone( + radius1=r2, + radius2=r1, height=h/2, ) - cone2 = Cq.Solid.makeCone( - radius1=r1, - radius2=r2, - height=h/2, + cyl = Cq.Solid.makeCylinder( + radius=r1, + height=self.bearing_spindle_ext + self.bearing_disk_thickness, + pnt=(0, 0, h/2) ) hole = Cq.Solid.makeCylinder( - radius=(BOLT_BEARING.diam_thread + 1)/2, + radius=(BOLT_BEARING.diam_thread + self.bearing_spindle_gap)/2, height=h*2 ).moved(0, 0, -h) - top = (cone1 + cone2.moved(0, 0, h/2)) - hole - return top + top.rotate((0,0,0),(1,0,0),180) + top = cone + cyl - hole + return top + top.mirror("XY") def barrel(self) -> Cq.Compound: """ @@ -990,59 +893,7 @@ class Onbashira(Model): ) return a - @target(name="turning-bar") - def turning_bar(self) -> Cq.Workplane: - """ - Converts the longitudinal/axial mount points on angle joints to - transverse mount points to make them more suitable for electronics. - """ - _, dx = self.angle_joint_bind_pos.to2d_pos() - t = 8 - w = self.turning_bar_width - result = ( - Cq.Workplane() - .box( - length=dx*2 + w, - width=w, - height=t, - centered=(True, True, False) - ) - ) - flange = Cq.Solid.makeBox( - length=w, - width=t, - height=w/2, - ).moved(-w/2, -t, -w/2) + Cq.Solid.makeCylinder( - radius=w/2, - height=t, - pnt=(0, -t, -w/2), - dir=(0, 1, 0), - ) - remover = Cq.Solid.makeCylinder( - radius=BOLT_COMMON.diam_thread/2, - height=w, - ) - removerf = Cq.Solid.makeCylinder( - radius=BOLT_COMMON.diam_thread/2, - height=w*2, - pnt=(0, -w, -w/2), - dir=(0, 1, 0), - ) - dxe = self.electronic_mount_dx - result = ( - result - + flange.moved(dx, w/2, 0) - + flange.moved(-dx, w/2, 0) - - remover.moved(dxe, 0, 0) - - remover.moved(-dxe, 0, 0) - - removerf.moved(dx, 0, 0) - - removerf.moved(-dx, 0, 0) - ) - result.tagAbsolute("holeBO1", (dx, w/2, -w/2), direction="+Y") - result.tagAbsolute("holeBO2", (-dx, w/2, -w/2), direction="+Y") - result.tagAbsolute("holeMO1", (dxe, 0, t)) - result.tagAbsolute("holeMO2", (-dxe, 0, t)) - return result + ### Motor ### @target(name="motor-seat") def motor_seat(self) -> Cq.Workplane: @@ -1240,6 +1091,303 @@ class Onbashira(Model): return result + @assembly() + def assembly_motor(self) -> Cq.Assembly: + a = ( + Cq.Assembly() + .addS( + self.motor.generate(), + name="motor", + role=Role.MOTOR, + ) + .addS( + self.flange_coupler.generate(), + name="flange_coupler", + role=Role.CONNECTION | Role.STRUCTURE, + material=self.material_fastener, + ) + .addS( + self.motor_driver_disk(), + name="driver_disk", + role=Role.CONNECTION | Role.STRUCTURE, + material=self.material_auxiliary, + ) + .addS( + self.motor_mount_plate(), + name="mount_plate", + role=Role.CONNECTION | Role.STRUCTURE, + material=self.material_auxiliary, + ) + .constrain( + "mount_plate?anchor1", + "motor?anchor1", + "Plane", + ) + .constrain( + "mount_plate?anchor2", + "motor?anchor2", + "Plane", + ) + .constrain( + "flange_coupler?top", + "motor?shaft", + "Axis" + ) + .constrain( + "flange_coupler?dir", + "motor?dir", + "Plane", + param=0, + ) + ) + for i in range(self.flange_coupler.n_hole_flange): + j = self.flange_coupler.n_hole_flange - i - 1 + a = a.constrain( + f"flange_coupler?holeB{i}", + f"driver_disk?holeB{j}", + "Plane", + ) + + # Add the motor seats + assert self.n_side % 2 == 0 + for i in range(self.n_side // 2): + name_seat = f"seat{i}" + a = ( + a.addS( + self.motor_seat(), + name=name_seat, + role=Role.STRUCTURE, + material=self.material_brace + ) + .constrain( + f"{name_seat}?holeMF1", + f"mount_plate?holeB{i*2}", + "Plane" + ) + .constrain( + f"{name_seat}?holeMF2", + f"mount_plate?holeB{i*2+1}", + "Plane" + ) + ) + for i in range(self.n_side): + name_coupler = f"coupler{i}" + a = ( + a.addS( + self.motor_coupler(), + name=name_coupler, + role=Role.CONNECTION, + material=self.material_brace, + ) + .constrain( + f"{name_coupler}?holeB1", + f"driver_disk?holeCOF{i}", + "Plane", + ) + .constrain( + f"{name_coupler}?holeB2", + f"driver_disk?holeCIF{i}", + "Plane", + ) + ) + return a.solve() + + ### Electronics ### + + @property + def turning_bar_hole_dy(self) -> float: + """ + Distance between centre of mounting holes in the turning bar and top of + the side panels. + """ + panel_to_mount = self.angle_joint_flange_thickness / 2 - self.angle_joint_gap / 2 + return panel_to_mount + self.turning_bar_width / 2 + + @target(name="turning-bar") + def turning_bar(self) -> Cq.Workplane: + """ + Converts the longitudinal/axial mount points on angle joints to + transverse mount points to make them more suitable for electronics. + """ + _, dx = self.angle_joint_bind_pos.to2d_pos() + t = 8 + w = self.turning_bar_width + result = ( + Cq.Workplane() + .box( + length=dx*2 + w, + width=w, + height=t, + centered=(True, True, False) + ) + ) + flange = Cq.Solid.makeBox( + length=w, + width=t, + height=w/2, + ).moved(-w/2, -t, -w/2) + Cq.Solid.makeCylinder( + radius=w/2, + height=t, + pnt=(0, -t, -w/2), + dir=(0, 1, 0), + ) + remover = Cq.Solid.makeCylinder( + radius=BOLT_COMMON.diam_thread/2, + height=w, + ) + removerf = Cq.Solid.makeCylinder( + radius=BOLT_COMMON.diam_thread/2, + height=w*2, + pnt=(0, -w, -w/2), + dir=(0, 1, 0), + ) + dxe = self.electronic_mount_dx + result = ( + result + + flange.moved(dx, w/2, 0) + + flange.moved(-dx, w/2, 0) + - remover.moved(dxe, 0, 0) + - remover.moved(-dxe, 0, 0) + - removerf.moved(dx, 0, 0) + - removerf.moved(-dx, 0, 0) + ) + result.tagAbsolute("holeBO1", (dx, w/2, -w/2), direction="+Y") + result.tagAbsolute("holeBO2", (-dx, w/2, -w/2), direction="+Y") + result.tagAbsolute("holeMO1", (dxe, 0, t)) + result.tagAbsolute("holeMO2", (-dxe, 0, t)) + return result + + @target(name="electronics-panel1", kind=TargetKind.DXF) + def profile_electronics_panel1(self) -> Cq.Sketch: + hole_dy = self.turning_bar_hole_dy + hole_dx = self.electronic_mount_dx + l = self.side_length3 - hole_dy * 2 + 12 + y = self.side_length3 - hole_dy * 2 + w = self.side_width + controller_holes = [ + self.controller_loc * Cq.Location.from2d(*h).flip_y() + for h in self.controller.holes + ] + battery_box_holes = [ + loc * h + for h in self.battery_box.holes + for loc in self.battery_box_locs + ] + profile = ( + Cq.Sketch() + .rect(l, w) + .rect(y, hole_dx * 2, mode="c", tag="corner") + .vertices(tag="corner") + .circle(BOLT_COMMON.diam_thread/2, mode="s") + .reset() + .push([ + h.to2d_pos() for h in controller_holes + ] + [ + h.to2d_pos() for h in battery_box_holes + ]) + .circle(self.controller.hole_diam/2, mode="s") + ) + return profile + + def electronics_panel1(self) -> Cq.Workplane: + hole_dy = self.turning_bar_hole_dy + hole_dx = self.electronic_mount_dx + l = self.side_length3 + t = self.side_thickness + result = ( + Cq.Workplane() + .placeSketch(self.profile_electronics_panel1()) + .extrude(t) + ) + x = l/2 - hole_dy + for side, z, d in [("T", t, "+Z"), ("B", 0, "-Z")]: + result.tagAbsolute(f"holeLP{side}", (-x, hole_dx, z), direction=d) + result.tagAbsolute(f"holeLS{side}", (-x, -hole_dx, z), direction=d) + result.tagAbsolute(f"holeRP{side}", (x, -hole_dx, z), direction=d) + result.tagAbsolute(f"holeRS{side}", (x, hole_dx, z), direction=d) + for (i, h) in enumerate(self.controller.holes): + loc = self.controller_loc * Cq.Location.from2d(*h).flip_y() + hx, hy = loc.to2d_pos() + result.tagAbsolute(f"holeController{i}", (hx, hy, t), direction="+Z") + for (j, loc) in enumerate(self.battery_box_locs): + for (i, h) in enumerate(self.battery_box.holes): + loch = loc * h + hx, hy = loch.to2d_pos() + result.tagAbsolute(f"holeBB{j}_{i}", (hx, hy, t), direction="+Z") + return result + + @assembly() + def assembly_electronics1(self) -> Cq.Assembly: + name_barL = "barL" + name_barR = "barR" + name_panel = "panel" + name_controller = "controller" + a = ( + Cq.Assembly() + .addS( + self.turning_bar(), + name=name_barL, + material=self.material_brace, + role=Role.STRUCTURE, + ) + .addS( + self.turning_bar(), + name=name_barR, + material=self.material_brace, + role=Role.STRUCTURE, + ) + .addS( + self.electronics_panel1(), + name=name_panel, + material=self.material_auxiliary, + role=Role.STRUCTURE, + ) + .add( + self.controller.assembly(), + name=name_controller, + ) + .constrain( + f"{name_panel}?holeLPB", + f"{name_barL}?holeMO1", + "Plane" + ) + .constrain( + f"{name_panel}?holeLSB", + f"{name_barL}?holeMO2", + "Plane" + ) + .constrain( + f"{name_panel}?holeRPB", + f"{name_barR}?holeMO1", + "Plane" + ) + .constrain( + f"{name_panel}?holeRSB", + f"{name_barR}?holeMO2", + "Plane" + ) + ) + for i in range(len(self.controller.holes)): + a = a.constrain( + f"{name_panel}?holeController{i}", + f"{name_controller}?conn{i}", + "Plane", + ) + for j in range(len(self.battery_box_locs)): + name_box = f"battery_box{j}" + a = a.add( + self.battery_box.assembly(), + name=name_box + ) + for i in range(len(self.battery_box.holes)): + a = a.constrain( + f"{name_panel}?holeBB{j}_{i}", + f"{name_box}?holeB{i}", + "Plane", + ) + return a.solve() + + ### Side Panels def profile_side_panel( self, @@ -2086,17 +2234,17 @@ class Onbashira(Model): a = a.add(self.assembly_motor(), name="motor") if has_part(parts, "machine"): a = a.add(self.assembly_machine(), name="machine") - if has_part(parts, "turning_bar"): - a = a.add(self.turning_bar(), name="turning_bar1") - if has_part(parts, ["turning_bar", "ring3"]): + if has_part(parts, "electronics1"): + a = a.add(self.assembly_electronics1(), name="electronics1") + if has_part(parts, ["electronics1", "ring3"]): a = a.constrain( - f"turning_bar1?holeBO2", - f"ring3/side0?holeStatorL", + f"electronics1/barL?holeBO2", + f"ring3/side1?holeStatorL", "Plane", ) a = a.constrain( - f"turning_bar1?holeBO1", - f"ring3/side1?holeStatorL", + f"electronics1/barL?holeBO1", + f"ring3/side2?holeStatorL", "Plane", )