2562 lines
89 KiB
Python
2562 lines
89 KiB
Python
from nhf.build import Model, TargetKind, target, assembly, submodel
|
|
from nhf.materials import Role, Material
|
|
import nhf.utils
|
|
from nhf.parts.fasteners import FlatHeadBolt, HexNut, Washer
|
|
from nhf.parts.electronics import ArduinoUnoR3, BatteryBox18650
|
|
|
|
from typing import Optional, Union
|
|
import math
|
|
from dataclasses import dataclass, field
|
|
import cadquery as Cq
|
|
|
|
def has_part(li: Optional[list[str]], name: Union[str, list[str]]) -> bool:
|
|
"""
|
|
Check if a part exists in a name list
|
|
"""
|
|
if li:
|
|
if isinstance(name, list):
|
|
return all(n in li for n in name)
|
|
else:
|
|
return name in li
|
|
else:
|
|
return True
|
|
|
|
NUT_COMMON = HexNut(
|
|
# FIXME: weigh
|
|
mass=0.0,
|
|
diam_thread=6.0,
|
|
pitch=1.0,
|
|
thickness=5.0,
|
|
width=9.89,
|
|
)
|
|
WASHER_COMMON = Washer(
|
|
# FIXME: weigh
|
|
mass=0.0,
|
|
diam_thread=6.0,
|
|
diam_outer=11.68,
|
|
thickness=1.5,
|
|
)
|
|
BOLT_COMMON = FlatHeadBolt(
|
|
# FIXME: weigh
|
|
mass=0.0,
|
|
diam_head=12.8,
|
|
height_head=2.8,
|
|
diam_thread=6.0,
|
|
height_thread=30.0,
|
|
pitch=1.0,
|
|
)
|
|
BOLT_LONG = FlatHeadBolt(
|
|
# FIXME: weigh
|
|
mass=0.0,
|
|
diam_head=12.8,
|
|
height_head=2.8,
|
|
diam_thread=6.0,
|
|
height_thread=50.0,
|
|
pitch=1.0,
|
|
)
|
|
BOLT_BEARING = FlatHeadBolt(
|
|
# FIXME: weigh
|
|
mass=0.0,
|
|
diam_head=12.8,
|
|
height_head=2.8,
|
|
diam_thread=4.0,
|
|
height_thread=30.0,
|
|
pitch=1.0,
|
|
)
|
|
|
|
@dataclass(frozen=True)
|
|
class Display(Model):
|
|
thickness: float = 2.5
|
|
length: float = 38.0
|
|
width: float = 12.0
|
|
|
|
@dataclass(frozen=True)
|
|
class FlangeCoupler(Model):
|
|
|
|
diam_thread: float = 8.0
|
|
diam_inner: float = 16.0
|
|
diam_outer: float = 32.0
|
|
height: float = 12.0
|
|
height_flange: float = 2.0
|
|
height_hole: float = 7.0
|
|
|
|
diam_thread_flange: float = 4.0
|
|
n_hole_flange: int = 4
|
|
r_hole_flange: float = 12.0 # FIXME: Measure!
|
|
|
|
def generate(self) -> Cq.Workplane:
|
|
result = (
|
|
Cq.Workplane()
|
|
.cylinder(
|
|
radius=self.diam_outer/2,
|
|
height=self.height_flange,
|
|
centered=(True, True, False),
|
|
)
|
|
.faces(">Z")
|
|
.cylinder(
|
|
radius=self.diam_inner/2,
|
|
height=self.height - self.height_flange,
|
|
centered=(True, True, False),
|
|
)
|
|
.faces(">Z")
|
|
.hole(self.diam_thread)
|
|
)
|
|
holes = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(
|
|
self.r_hole_flange,
|
|
self.n_hole_flange,
|
|
mode="c",
|
|
tag="holes",
|
|
)
|
|
.vertices(tag="holes")
|
|
.circle(self.diam_thread_flange/2)
|
|
.finalize()
|
|
.extrude(self.height_flange)
|
|
)
|
|
hole_subtractor = Cq.Solid.makeCylinder(
|
|
radius=self.diam_thread_flange/2,
|
|
height=self.diam_inner,
|
|
pnt=(-self.diam_inner/2, 0, self.height_hole),
|
|
dir=(1, 0, 0)
|
|
)
|
|
result -= holes
|
|
result -= hole_subtractor
|
|
result.tagAbsolute("top", (0, 0, self.height), direction="+Z")
|
|
result.tagAbsolute("bot", (0, 0, 9), direction="-Z")
|
|
for i in range(self.n_hole_flange):
|
|
loc = Cq.Location.rot2d(i * 360 / self.n_hole_flange) * Cq.Location(self.r_hole_flange, 0)
|
|
result.tagAbsolute(f"holeT{i}", loc * Cq.Location(0, 0, self.height_flange), direction="+Z")
|
|
result.tagAbsolute(f"holeB{i}", loc, direction="-Z")
|
|
result.tagAbsolute("dir", (0, 0, self.height_hole), direction="+X")
|
|
return result
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Motor(Model):
|
|
"""
|
|
Drive motor for the main barrel
|
|
"""
|
|
|
|
mass: float = 589.7
|
|
voltage: float = 12.0 # V
|
|
power: float = 30.0 # watts
|
|
|
|
diam_thread: float = 4.0
|
|
diam_shaft: float = 8.0
|
|
diam_body: float = 51.0
|
|
height_body: float = 83.5
|
|
diam_ring: float = 25.93
|
|
height_ring: float = 6.55
|
|
height_hole: float = 10.0
|
|
height_shaft: float = 13.0
|
|
height_base_shaft: float = 24.77
|
|
# Distance between anchor and the body
|
|
dx_anchor: float = 20.0
|
|
height_anchor: float = 10.4
|
|
|
|
def __post_init__(self):
|
|
assert self.diam_ring < self.diam_body
|
|
assert self.height_ring < self.height_body
|
|
assert self.dx_anchor < self.diam_body / 2
|
|
pass
|
|
|
|
@property
|
|
def dist_mount_hole(self):
|
|
"""
|
|
Distance between mount point and shaft
|
|
"""
|
|
return self.height_hole + self.height_ring
|
|
@property
|
|
def dist_mount_rotor(self):
|
|
"""
|
|
Distance between mount point and shaft
|
|
"""
|
|
return self.height_base_shaft + self.height_ring
|
|
|
|
def generate(self) -> Cq.Workplane:
|
|
result = (
|
|
Cq.Workplane()
|
|
.cylinder(
|
|
radius=self.diam_body/2,
|
|
height=self.height_body - self.height_ring,
|
|
centered=(True, True, False)
|
|
)
|
|
.faces(">Z")
|
|
.cylinder(
|
|
radius=self.diam_ring/2,
|
|
height=self.height_ring,
|
|
centered=(True, True, False)
|
|
)
|
|
)
|
|
hole_subtractor = Cq.Solid.makeCylinder(
|
|
radius=self.diam_thread/2,
|
|
height=self.diam_shaft,
|
|
pnt=(-self.diam_shaft/2, 0, self.height_body + self.height_hole),
|
|
dir=(1, 0, 0)
|
|
)
|
|
base_shaft = Cq.Solid.makeCylinder(
|
|
radius=self.diam_shaft/2,
|
|
height=self.height_base_shaft,
|
|
pnt=(0, 0, self.height_body),
|
|
)
|
|
shaft = Cq.Solid.makeCylinder(
|
|
radius=self.diam_shaft/2 * 0.9,
|
|
height=self.height_shaft,
|
|
pnt=(0, 0, self.height_body + self.height_base_shaft)
|
|
)
|
|
z_anchor = self.height_body - self.height_ring
|
|
anchor = Cq.Solid.makeCylinder(
|
|
radius=self.diam_thread/2,
|
|
height=self.height_anchor,
|
|
pnt=(0, 0, z_anchor)
|
|
)
|
|
result = result + base_shaft + shaft + anchor.moved(self.dx_anchor, 0, 0) + anchor.moved(-self.dx_anchor, 0, 0) - hole_subtractor
|
|
result.tagAbsolute("anchor1", (self.dx_anchor, 0, z_anchor), direction="+Z")
|
|
result.tagAbsolute("anchor2", (-self.dx_anchor, 0, z_anchor), direction="+Z")
|
|
result.tagAbsolute("shaft", (0, 0, self.height_body + self.height_base_shaft), direction="+Z")
|
|
result.tagAbsolute("dir", (0, 0, self.height_body + self.height_hole), direction="+X")
|
|
return result
|
|
|
|
|
|
@dataclass
|
|
class Onbashira(Model):
|
|
|
|
n_side: int = 6
|
|
# Dimensions of each side panel
|
|
side_width: float = 150.0
|
|
|
|
# Side panels have different lengths
|
|
side_length1: float = 200.0
|
|
side_length2: float = 350.0
|
|
side_length3: float = 400.0
|
|
|
|
side_thickness: float = 25.4 / 8
|
|
|
|
section1_gohei_loc: float = 30.0
|
|
gohei_bolt_diam: float = 6.0
|
|
# Extension from each gohei bolt's centre
|
|
front_bracket_ext: float = 6.0
|
|
front_bracket_depth: float = 15.0
|
|
front_bracket_thickness: float = 6.0
|
|
|
|
magnet_size: float = 6.0
|
|
|
|
# The angle joint bridges between two sets of side panels.
|
|
|
|
# Extra thickness beyond the onbashira's body
|
|
angle_joint_thickness: float = 10.0
|
|
# Z-axis size of each angle joint
|
|
angle_joint_depth: float = 50.0
|
|
# Gap of each angle joint to connect the outside to the inside
|
|
angle_joint_gap: float = 8.0
|
|
angle_joint_bolt_length: float = 50.0
|
|
angle_joint_bolt_diam: float = BOLT_COMMON.diam_thread
|
|
angle_joint_bolt_head_diam: float = 13.0
|
|
angle_joint_bolt_head_depth: float = 3.0
|
|
# Position of the holes, with (0, 0) being the centre of each side
|
|
angle_joint_bolt_position: list[float] = field(default_factory=lambda: [
|
|
(40, 10),
|
|
])
|
|
angle_joint_flange_thickness: float = 7.8
|
|
angle_joint_flange_radius: float = 23.0
|
|
angle_joint_flange_extension: float = 23.0
|
|
|
|
# Mating structure on the angle joint
|
|
angle_joint_conn_thickness: float = 4.0
|
|
angle_joint_conn_depth: float = 15.0
|
|
angle_joint_conn_width: float = 15.0
|
|
angle_joint_bind_radius: float = 135.0
|
|
|
|
chamber_side_length: float = 400.0
|
|
chamber_side_width_ex: float = 20.0
|
|
# Circular hole to hold a switch
|
|
chamber_front_switch_diam: float = 20.0
|
|
|
|
# Dimensions of gun barrels
|
|
barrel_diam: float = 25.4 * 1.5
|
|
barrel_wall_thickness: float = 25.4 / 8
|
|
barrel_length: float = 25.4 * 12
|
|
# Longitudinal shift
|
|
barrel_shift: float = 30.0
|
|
|
|
# Gap between the stator edge and the inner face of the barrel
|
|
stator_gap: float = 3.0
|
|
# Radius from barrel centre to axis
|
|
rotation_radius: float = 64.0
|
|
n_bearing_balls: int = 12
|
|
# Thickness of bearing disks
|
|
bearing_thickness: float = 20.0
|
|
bearing_track_radius: float = 97.0
|
|
# Gap between the inner and outer bearing disks
|
|
bearing_gap: float = 10.0
|
|
bearing_disk_gap: float = 10.0
|
|
bearing_spindle_max_diam: float = 16.0
|
|
bearing_spindle_ext: float = 5.0
|
|
# Gap on the interior and exterior
|
|
bearing_spindle_gap: float = 1.0
|
|
bearing_spindle_tail: float = 4.0
|
|
bearing_spindle_tail_diam: float = 6.0
|
|
bearing_gasket_extend: float = 12.0
|
|
bearing_disk_thickness: float = 25.4 / 16
|
|
|
|
# Coupling mechanism onto the chassis
|
|
stator_coupler_width: float = 14.0
|
|
stator_coupler_thickness: float = 30.0
|
|
stator_coupler_thickness_inner: float = 10.0
|
|
|
|
stator_bind_radius: float = 117.0
|
|
# Extra bind sites for stator to prevent warping
|
|
stator_bind_extra: int = 2
|
|
rotor_inner_radius: float = 36.0
|
|
rotor_bind_bolt_diam: float = BOLT_BEARING.diam_thread
|
|
rotor_bind_radius: float = 82.0
|
|
rotor_bind_extra: int = 1
|
|
stator_spacer_outer_diam: float = 15.0
|
|
rotor_spacer_outer_diam: float = 12.0
|
|
|
|
handle_base_height: float = 10.0
|
|
handle_thickness_x: float = 20.0
|
|
handle_thickness_y: float = 10.0
|
|
handle_radius: float = 20.0
|
|
handle_height: float = 50.0
|
|
|
|
motor: Motor = Motor()
|
|
flange_coupler: FlangeCoupler = FlangeCoupler()
|
|
auxiliary_thickness: float = 25.4 / 8
|
|
|
|
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, 0, 90),
|
|
Cq.Location.from2d(140, 0, 90),
|
|
Cq.Location.from2d(-70, 0, 90),
|
|
Cq.Location.from2d(-140, 0, 90),
|
|
])
|
|
|
|
# Distance between bind point and motor's mount points
|
|
motor_driver_radius: float = 110.0
|
|
motor_seat_depth: float = 95.0
|
|
motor_seat_radius: float = 50.0
|
|
motor_coupler_flange_thickness: float = 10.0
|
|
motor_coupler_flange_radius: float = 8.0
|
|
motor_coupler_height: float = 120.0
|
|
motor_coupler_conn_dx: float = 30.0
|
|
motor_coupler_wall_thickness: float = 5.0
|
|
motor_coupler_inner_gap: float = 1.0
|
|
turning_bar_width: float = 15.0
|
|
electronic_mount_dx: float = 50.0
|
|
|
|
material_side: Material = Material.WOOD_BIRCH
|
|
material_bearing: Material = Material.PLASTIC_PLA
|
|
material_spacer: Material = Material.PLASTIC_PLA
|
|
material_bearing_ball: Material = Material.PLASTIC_PLA
|
|
material_barrel: Material = Material.ACRYLIC_BLACK
|
|
material_brace: Material = Material.PLASTIC_PLA
|
|
material_fastener: Material = Material.STEEL_STAINLESS
|
|
material_auxiliary: Material = Material.WOOD_BIRCH
|
|
|
|
def __post_init__(self):
|
|
assert self.n_side >= 3
|
|
# Bulk must be large enough for the barrel + bearing to rotate
|
|
assert self.bulk_radius - self.side_thickness - self.bearing_thickness > self.rotation_radius + self.barrel_diam / 2
|
|
assert BOLT_COMMON.diam_thread < self.rotor_bind_radius < self.bearing_track_radius
|
|
assert self.rotor_inner_radius < self.bearing_track_radius < self.angle_joint_bind_radius
|
|
assert self.angle_joint_thickness > self.side_thickness
|
|
|
|
for (x, y) in self.angle_joint_bolt_position:
|
|
assert y < self.angle_joint_depth / 2
|
|
assert self.angle_joint_depth / 2 > self.angle_joint_conn_depth
|
|
assert self.angle_joint_thickness > self.angle_joint_conn_thickness
|
|
|
|
# Ensure the stator could be printed on a 12x12in board
|
|
assert self.side_width * 2 < 12 * 25.4
|
|
|
|
assert self.barrel_wall_thickness * 2 < self.barrel_diam
|
|
|
|
@property
|
|
def angle_side(self) -> float:
|
|
return 360 / self.n_side
|
|
@property
|
|
def ratio_side_width(self) -> float:
|
|
"""
|
|
Difference between interior and exterior side width due to side thickness
|
|
"""
|
|
theta = math.pi / self.n_side
|
|
return math.tan(theta)
|
|
@property
|
|
def delta_side_width(self) -> float:
|
|
"""
|
|
Difference between interior and exterior side width due to side thickness
|
|
"""
|
|
dt = self.side_thickness * self.ratio_side_width
|
|
return dt * 2
|
|
@property
|
|
def side_width_inner(self) -> float:
|
|
"""
|
|
Interior side width
|
|
|
|
If outer width is `wi`, inner width is `wo`, each side's cross section
|
|
is a trapezoid with sides `wi`, `wo`, and height `h` (side thickness)
|
|
"""
|
|
return self.side_width - self.delta_side_width
|
|
@property
|
|
def angle_joint_extra_width(self) -> float:
|
|
theta = math.pi / self.n_side
|
|
dt = self.angle_joint_thickness * math.tan(theta)
|
|
return dt * 2
|
|
@property
|
|
def angle_joint_bind_pos(self) -> Cq.Location:
|
|
"""
|
|
Planar position of the joint bind position
|
|
"""
|
|
th = math.pi / self.n_side
|
|
x = self.angle_joint_bind_radius * math.cos(th)
|
|
y = self.angle_joint_bind_radius * math.sin(th)
|
|
return Cq.Location.from2d(x, y)
|
|
|
|
@property
|
|
def angle_dihedral(self) -> float:
|
|
return 180 - self.angle_side
|
|
@property
|
|
def bulk_radius(self) -> float:
|
|
"""
|
|
Radius of the bulk (surface of each side) to the centre
|
|
"""
|
|
return self.side_width / 2 / math.tan(math.radians(self.angle_side / 2))
|
|
@property
|
|
def chamber_side_width(self) -> float:
|
|
return self.side_width + self.chamber_side_width_ex
|
|
@property
|
|
def chamber_bulk_radius(self) -> float:
|
|
"""
|
|
Radius of the bulk (surface of each side) to the centre
|
|
"""
|
|
return self.chamber_side_width / 2 / math.tan(math.radians(self.angle_side / 2))
|
|
|
|
@target(name="sanding-block")
|
|
def sanding_block(self) -> Cq.Workplane:
|
|
# Dihedral angle / 2
|
|
angle = math.radians(180 / self.n_side)
|
|
r = math.sin(angle)
|
|
x = 50.0
|
|
return (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.polygon([(0,0), (0, x), (x, (1-r) * x), (x, 0)])
|
|
.finalize()
|
|
.extrude(self.side_width * 1.5)
|
|
)
|
|
|
|
@target(name="magnet-holder")
|
|
def magnet_holder(self) -> Cq.Workplane:
|
|
magnet_size = self.magnet_size
|
|
gap = 1.0
|
|
length1 = 10.0
|
|
width = magnet_size * 3
|
|
height = gap + magnet_size + 1
|
|
result = (
|
|
Cq.Workplane()
|
|
.box(
|
|
length=length1 + magnet_size,
|
|
width=width,
|
|
height=height,
|
|
centered=(False, True, False)
|
|
)
|
|
)
|
|
corner_cut = Cq.Solid.makeBox(
|
|
length=height,
|
|
width=width,
|
|
height=height,
|
|
).moved(0, -width/2, 0) - Cq.Solid.makeCylinder(
|
|
radius=height,
|
|
height=width,
|
|
pnt=(height, -width/2, 0),
|
|
dir=(0, 1, 0)
|
|
)
|
|
box_cut = Cq.Solid.makeBox(
|
|
length=magnet_size,
|
|
width=magnet_size,
|
|
height=magnet_size + gap,
|
|
).moved(length1, -magnet_size/2, 0)
|
|
return result - box_cut - corner_cut
|
|
|
|
### Motor ###
|
|
|
|
@target(name="motor-coupler")
|
|
def motor_coupler(self) -> Cq.Workplane:
|
|
"""
|
|
Coupler which connects to each barrel to move them.
|
|
"""
|
|
x = self.motor_coupler_conn_dx
|
|
y0 = self.barrel_diam/2 + self.motor_coupler_wall_thickness
|
|
y = self.motor_coupler_flange_radius
|
|
t = self.motor_coupler_flange_thickness
|
|
flange = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.polygon([
|
|
(x, y),
|
|
(0, y0),
|
|
(-x, y),
|
|
(-x, -y),
|
|
(0, -y0),
|
|
(x, -y),
|
|
])
|
|
.reset()
|
|
.push([
|
|
(x, 0), (-x, 0)
|
|
])
|
|
.circle(y, mode="a")
|
|
.circle(BOLT_BEARING.diam_thread/2, mode="s")
|
|
.reset()
|
|
.circle(self.barrel_diam/2, mode="s")
|
|
.finalize()
|
|
.extrude(t)
|
|
)
|
|
body = (
|
|
Cq.Workplane()
|
|
.cylinder(
|
|
radius=self.barrel_diam/2 + self.motor_coupler_wall_thickness,
|
|
height=self.motor_coupler_height,
|
|
centered=(True, True, False)
|
|
)
|
|
.faces(">Z")
|
|
.hole(self.barrel_diam + self.motor_coupler_inner_gap*2)
|
|
)
|
|
result = body + flange
|
|
result.tagAbsolute("holeT1", (x, 0, t), direction="+Z")
|
|
result.tagAbsolute("holeT2", (-x, 0, t), direction="+Z")
|
|
result.tagAbsolute("holeB1", (x, 0, 0), direction="-Z")
|
|
result.tagAbsolute("holeB2", (-x, 0, 0), direction="-Z")
|
|
return result
|
|
|
|
@target(name="motor-driver-disk", kind=TargetKind.DXF)
|
|
def profile_motor_driver_disk(self) -> Cq.Sketch:
|
|
"""
|
|
A drive disk mounts onto the motor, and extends into gun barrels to turn them.
|
|
"""
|
|
hole_diam = self.barrel_diam - self.barrel_wall_thickness * 2
|
|
|
|
coupler_holes = [
|
|
Cq.Location.rot2d(i * 360 / self.n_side) *
|
|
Cq.Location.from2d(self.rotation_radius + sx * self.motor_coupler_conn_dx, 0)
|
|
for i in range(self.n_side)
|
|
for sx in (-1, 1)
|
|
]
|
|
return (
|
|
Cq.Sketch()
|
|
.circle(self.motor_driver_radius)
|
|
# Drill out the centre which will accomodate the motor shaft
|
|
.circle(self.motor.diam_shaft/2, mode="s")
|
|
# Drill out couplers
|
|
.reset()
|
|
.regularPolygon(
|
|
self.flange_coupler.r_hole_flange,
|
|
self.flange_coupler.n_hole_flange,
|
|
mode="c",
|
|
tag="hole",
|
|
)
|
|
.vertices(tag="hole")
|
|
.circle(self.flange_coupler.diam_thread_flange/2, mode="s")
|
|
.reset()
|
|
.regularPolygon(
|
|
self.rotation_radius,
|
|
self.n_side,
|
|
angle=180 / self.n_side,
|
|
mode="c",
|
|
tag="const",
|
|
)
|
|
.vertices(tag="const")
|
|
.circle(hole_diam/2, mode="s")
|
|
.reset()
|
|
# Create coupler holes
|
|
.push([
|
|
loc.to2d_pos()
|
|
for loc in coupler_holes
|
|
])
|
|
.circle(BOLT_BEARING.diam_thread /2, mode="s")
|
|
)
|
|
def motor_driver_disk(self) -> Cq.Workplane:
|
|
result = (
|
|
Cq.Workplane()
|
|
.placeSketch(self.profile_motor_driver_disk())
|
|
.extrude(self.auxiliary_thickness)
|
|
)
|
|
n = self.flange_coupler.n_hole_flange
|
|
for i in range(n):
|
|
loc = Cq.Location.rot2d(i * 360 / n) * Cq.Location(self.flange_coupler.r_hole_flange, 0)
|
|
result.tagAbsolute(f"holeT{i}", loc * Cq.Location(0, 0, self.auxiliary_thickness), direction="+Z")
|
|
result.tagAbsolute(f"holeB{i}", loc, direction="-Z")
|
|
loc_z = Cq.Location(0, 0, self.auxiliary_thickness)
|
|
loc_outer = Cq.Location.from2d(self.rotation_radius + self.motor_coupler_conn_dx, 0)
|
|
loc_inner = Cq.Location.from2d(self.rotation_radius - self.motor_coupler_conn_dx, 0)
|
|
for i in range(self.n_side):
|
|
loc_rot = Cq.Location.rot2d(i * 360 / self.n_side)
|
|
p_outer, _ = (loc_z * loc_rot * loc_outer).toTuple()
|
|
p_inner, _ = (loc_z * loc_rot * loc_inner).toTuple()
|
|
result.tagAbsolute(f"holeCOF{i}", p_outer, direction="+Z")
|
|
result.tagAbsolute(f"holeCIF{i}", p_inner, direction="+Z")
|
|
return result
|
|
|
|
@target(name="motor-mount-plate", kind=TargetKind.DXF)
|
|
def profile_motor_mount_plate(self) -> Cq.Sketch:
|
|
r = self.motor_seat_radius
|
|
gap = 10.0
|
|
hole_dx = self.motor.dx_anchor
|
|
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")
|
|
.reset()
|
|
.push([
|
|
(hole_dx, 0),
|
|
(-hole_dx, 0),
|
|
])
|
|
.circle(self.motor.diam_thread/2, mode="s")
|
|
)
|
|
def motor_mount_plate(self) -> Cq.Workplane:
|
|
result = (
|
|
Cq.Workplane()
|
|
.placeSketch(self.profile_motor_mount_plate())
|
|
.extrude(self.auxiliary_thickness)
|
|
)
|
|
result.tagAbsolute("anchor1", (self.motor.dx_anchor, 0, 0), direction="-Z")
|
|
result.tagAbsolute("anchor2", (-self.motor.dx_anchor, 0, 0), direction="-Z")
|
|
r = self.motor_seat_radius
|
|
for i in range(self.n_side):
|
|
angle = i * 360 / self.n_side
|
|
x, y = (Cq.Location.rot2d(angle) * Cq.Location.from2d(0, r)).to2d_pos()
|
|
result.tagAbsolute(f"holeF{i}", (x, y, self.auxiliary_thickness), direction="+Z")
|
|
result.tagAbsolute(f"holeB{i}", (x, -y, 0), direction="-Z")
|
|
return result
|
|
|
|
@target(name="stator-coupler")
|
|
def stator_coupler(self) -> Cq.Workplane:
|
|
"""
|
|
Couples the stator to the chassis
|
|
"""
|
|
r1 = self.angle_joint_bind_radius
|
|
r2 = self.stator_bind_radius
|
|
assert r1 > r2
|
|
|
|
l = r1 - r2
|
|
w = self.stator_coupler_width
|
|
h = self.stator_coupler_thickness
|
|
h_step = h - self.stator_coupler_thickness_inner
|
|
|
|
intersector = Cq.Solid.makeBox(
|
|
length=l + w,
|
|
width=w,
|
|
height=h_step,
|
|
).moved(0, -w/2, 0)
|
|
|
|
profile = (
|
|
Cq.Sketch()
|
|
.rect(l, w)
|
|
.push([
|
|
(-l/2, 0),
|
|
(l/2, 0),
|
|
])
|
|
.circle(w/2, mode="a")
|
|
.push([
|
|
(-l/2, 0),
|
|
])
|
|
.circle(BOLT_COMMON.diam_thread/2, mode="s")
|
|
.reset()
|
|
.push([
|
|
(l/2, 0),
|
|
])
|
|
.circle(BOLT_BEARING.diam_thread/2, mode="s")
|
|
)
|
|
result = (
|
|
Cq.Workplane()
|
|
.placeSketch(profile)
|
|
.extrude(h)
|
|
)
|
|
dx = l / 2
|
|
result = result - intersector
|
|
result.tagAbsolute(f"holeOB", (-dx, 0, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeIB", (+dx, 0, h_step), direction="-Z")
|
|
result.tagAbsolute(f"holeOF", (-dx, 0, h), direction="+Z")
|
|
result.tagAbsolute(f"holeIF", (+dx, 0, h), direction="+Z")
|
|
return result
|
|
|
|
@target(name="bearing-stator", kind=TargetKind.DXF)
|
|
def profile_bearing_stator(self) -> Cq.Sketch:
|
|
assert self.stator_bind_radius < self.angle_joint_bind_radius
|
|
return (
|
|
Cq.Sketch()
|
|
.circle(self.bulk_radius - self.side_thickness - self.stator_gap)
|
|
#.regularPolygon(self.side_width - self.side_thickness - self.stator_gap, self.n_side*2)
|
|
.circle(self.bearing_track_radius + self.bearing_gap/2, mode="s")
|
|
.reset()
|
|
.regularPolygon(
|
|
self.stator_bind_radius, self.n_side * (1 + self.stator_bind_extra),
|
|
mode="c", tag="bolt")
|
|
.vertices(tag="bolt")
|
|
.circle(self.rotor_bind_bolt_diam/2, mode="s")
|
|
)
|
|
def bearing_stator(self) -> Cq.Workplane:
|
|
result = (
|
|
Cq.Workplane()
|
|
.placeSketch(self.profile_bearing_stator())
|
|
.extrude(self.bearing_disk_thickness)
|
|
)
|
|
br = self.stator_bind_radius
|
|
th1 = math.radians(360 / self.n_side)
|
|
th2 = math.radians(360 / (self.n_side * (1 + self.stator_bind_extra)))
|
|
for i in range(self.n_side):
|
|
angle = (i+0.5) * th1
|
|
result.faces(">Z").moveTo(
|
|
br * math.cos(-angle),
|
|
br * math.sin(-angle),
|
|
).tagPlane(f"holeF{i}")
|
|
result.faces("<Z").moveTo(
|
|
br * math.cos(-angle),
|
|
br * math.sin(-angle),
|
|
).tagPlane(f"holeB{i}", direction="-Z")
|
|
for j in range(1, 1 + self.stator_bind_extra):
|
|
angle2 = angle + (j + 1) * th2
|
|
result.faces("<Z").moveTo(
|
|
br * math.cos(-angle2),
|
|
br * math.sin(-angle2),
|
|
).tagPlane(f"holeE{i}", direction="-Z")
|
|
return result
|
|
@property
|
|
def rotor_radius(self) -> float:
|
|
return self.bearing_track_radius - self.bearing_gap/2
|
|
@target(name="bearing-rotor", kind=TargetKind.DXF)
|
|
def profile_bearing_rotor(self) -> Cq.Sketch:
|
|
bolt_angle = (180 / self.n_side) * 1.5
|
|
n_binds = 1 + self.rotor_bind_extra
|
|
return (
|
|
Cq.Sketch()
|
|
.circle(self.rotor_radius)
|
|
.circle(self.rotor_inner_radius, mode="s")
|
|
.reset()
|
|
.regularPolygon(
|
|
self.rotation_radius, self.n_side,
|
|
mode="c", tag="corners")
|
|
.vertices(tag="corners")
|
|
.circle(self.barrel_diam/2, mode="s")
|
|
.reset()
|
|
.regularPolygon(
|
|
r=self.rotor_bind_radius,
|
|
n=self.n_side * n_binds,
|
|
mode="c", tag="bolt",
|
|
angle=bolt_angle)
|
|
.vertices(tag="bolt")
|
|
.circle(self.rotor_bind_bolt_diam/2, mode="s")
|
|
)
|
|
def bearing_rotor(self) -> Cq.Workplane:
|
|
return (
|
|
Cq.Workplane()
|
|
.placeSketch(self.profile_bearing_rotor())
|
|
.extrude(self.bearing_disk_thickness)
|
|
)
|
|
@target(name="bearing-gasket", kind=TargetKind.DXF)
|
|
def profile_bearing_gasket(self) -> Cq.Sketch:
|
|
dr = self.bearing_gasket_extend
|
|
eps = 0.05
|
|
return (
|
|
Cq.Sketch()
|
|
.circle(self.bearing_track_radius + dr)
|
|
.circle(self.bearing_track_radius - dr, mode="s")
|
|
.reset()
|
|
.regularPolygon(
|
|
self.bearing_track_radius, self.n_bearing_balls,
|
|
mode="c", tag="corners")
|
|
.vertices(tag="corners")
|
|
.circle(BOLT_BEARING.diam_thread/2, mode="s")
|
|
)
|
|
def bearing_gasket(self) -> Cq.Workplane:
|
|
return (
|
|
Cq.Workplane()
|
|
.placeSketch(self.profile_bearing_gasket())
|
|
.extrude(self.bearing_disk_thickness)
|
|
)
|
|
|
|
|
|
@target(name="stator-spacer")
|
|
def stator_spacer(self) -> Cq.Solid:
|
|
outer = Cq.Solid.makeCylinder(
|
|
radius=self.stator_spacer_outer_diam/2,
|
|
height=self.bearing_disk_gap,
|
|
)
|
|
inner = Cq.Solid.makeCylinder(
|
|
radius=self.rotor_bind_bolt_diam/2,
|
|
height=self.bearing_disk_gap
|
|
)
|
|
return outer - inner
|
|
@target(name="rotor-spacer")
|
|
def rotor_spacer(self) -> Cq.Solid:
|
|
outer = Cq.Solid.makeCylinder(
|
|
radius=self.rotor_spacer_outer_diam/2,
|
|
height=self.bearing_disk_gap,
|
|
)
|
|
inner = Cq.Solid.makeCylinder(
|
|
radius=BOLT_BEARING.diam_thread/2,
|
|
height=self.bearing_disk_gap
|
|
)
|
|
return outer - inner
|
|
|
|
@property
|
|
def bearing_spindle_height(self) -> float:
|
|
h = self.bearing_disk_gap + 2 * self.bearing_disk_thickness
|
|
return h + self.bearing_spindle_ext * 2
|
|
|
|
@target(name="bearing-spindle")
|
|
def bearing_spindle(self) -> Cq.Solid:
|
|
r1 = (self.bearing_gap - self.bearing_spindle_gap) / 2
|
|
r2 = self.bearing_spindle_max_diam / 2
|
|
h = self.bearing_disk_gap
|
|
h2 = self.bearing_spindle_ext + self.bearing_disk_thickness - self.bearing_spindle_tail
|
|
cone = Cq.Solid.makeCone(
|
|
radius1=r2,
|
|
radius2=r1,
|
|
height=h/2,
|
|
)
|
|
cyl = Cq.Solid.makeCylinder(
|
|
radius=r1,
|
|
height=h2,
|
|
pnt=(0, 0, h/2)
|
|
)
|
|
tail = Cq.Solid.makeCone(
|
|
radius1=r1,
|
|
radius2=self.bearing_spindle_tail_diam/2,
|
|
height=self.bearing_spindle_tail,
|
|
pnt=(0, 0, h/2 + h2),
|
|
)
|
|
assert self.bearing_spindle_tail < self.bearing_spindle_ext
|
|
assert self.bearing_spindle_tail_diam > BOLT_BEARING.diam_thread
|
|
hole = Cq.Solid.makeCylinder(
|
|
radius=(BOLT_BEARING.diam_thread + self.bearing_spindle_gap)/2,
|
|
height=self.bearing_spindle_height
|
|
).moved(0, 0, -self.bearing_spindle_height/2)
|
|
top = cone + cyl + tail - hole
|
|
return top + top.mirror("XY")
|
|
|
|
def barrel(self) -> Cq.Compound:
|
|
"""
|
|
One gun barrel
|
|
"""
|
|
outer = Cq.Solid.makeCylinder(
|
|
radius=self.barrel_diam/2,
|
|
height=self.barrel_length,
|
|
)
|
|
inner = Cq.Solid.makeCylinder(
|
|
radius=self.barrel_diam/2-self.barrel_wall_thickness,
|
|
height=self.barrel_length
|
|
)
|
|
return outer - inner
|
|
|
|
@assembly()
|
|
def assembly_machine(self) -> Cq.Assembly:
|
|
"""
|
|
The assembly with gun barrels
|
|
"""
|
|
z_lower = -self.bearing_disk_gap/2 - self.bearing_disk_thickness
|
|
gasket_h = self.bearing_spindle_height / 2
|
|
a = (
|
|
Cq.Assembly()
|
|
.addS(
|
|
self.bearing_stator(),
|
|
name="stator1",
|
|
material=self.material_bearing,
|
|
role=Role.STATOR,
|
|
loc=Cq.Location(0, 0, self.bearing_disk_gap/2)
|
|
)
|
|
.addS(
|
|
self.bearing_rotor(),
|
|
name="rotor1",
|
|
material=self.material_bearing,
|
|
role=Role.ROTOR,
|
|
loc=Cq.Location(0, 0, self.bearing_disk_gap/2)
|
|
)
|
|
.addS(
|
|
self.bearing_stator(),
|
|
name="stator2",
|
|
material=self.material_bearing,
|
|
role=Role.STATOR,
|
|
loc=Cq.Location(0, 0, z_lower)
|
|
)
|
|
.addS(
|
|
self.bearing_rotor(),
|
|
name="rotor2",
|
|
material=self.material_bearing,
|
|
role=Role.ROTOR,
|
|
loc=Cq.Location(0, 0, z_lower)
|
|
)
|
|
.addS(
|
|
self.bearing_gasket(),
|
|
name="gasket_bot",
|
|
material=self.material_bearing,
|
|
role=Role.ROTOR,
|
|
loc=Cq.Location(0, 0, -gasket_h-self.bearing_disk_thickness)
|
|
)
|
|
.addS(
|
|
self.bearing_gasket(),
|
|
name="gasket_top",
|
|
material=self.material_bearing,
|
|
role=Role.ROTOR,
|
|
loc=Cq.Location(0, 0, gasket_h)
|
|
)
|
|
)
|
|
z = -self.bearing_disk_gap/2
|
|
da_bind_stator = 360 / self.n_side
|
|
da_bind_rotor = 360 / self.n_side
|
|
da_bind_stator_minor = 360 / self.n_side / (1 + self.stator_bind_extra)
|
|
da_bind_rotor_minor = 360 / self.n_side / (1 + self.rotor_bind_extra)
|
|
for i in range(self.n_side):
|
|
loc_barrel = Cq.Location.rot2d((i+1/2) * 360/self.n_side) * \
|
|
Cq.Location(self.rotation_radius, 0, self.barrel_shift-self.barrel_length/2)
|
|
a = a.addS(
|
|
self.barrel(),
|
|
name=f"barrel{i}",
|
|
material=self.material_barrel,
|
|
role=Role.DECORATION,
|
|
loc=loc_barrel,
|
|
)
|
|
for j in range(1 + self.rotor_bind_extra):
|
|
angle = i * da_bind_rotor + (j+0.5) * da_bind_rotor_minor
|
|
loc = Cq.Location.rot2d(angle) * Cq.Location(self.rotor_bind_radius, 0, z)
|
|
a = a.addS(
|
|
self.rotor_spacer(),
|
|
name=f"spacer_rotor{i}_{j}",
|
|
material=self.material_spacer,
|
|
role=Role.STRUCTURE,
|
|
loc=loc
|
|
)
|
|
for j in range(1 + self.stator_bind_extra):
|
|
angle = i * da_bind_stator + (j+0.5) * da_bind_stator_minor
|
|
loc = Cq.Location.rot2d(angle) * Cq.Location(self.stator_bind_radius, 0, z)
|
|
a = a.addS(
|
|
self.stator_spacer(),
|
|
name=f"spacer_stator{i}_{j}",
|
|
material=self.material_spacer,
|
|
role=Role.STRUCTURE,
|
|
loc=loc
|
|
)
|
|
for i in range(self.n_bearing_balls):
|
|
ball = self.bearing_spindle()
|
|
loc = Cq.Location.rot2d(i * 360/self.n_bearing_balls) * Cq.Location(self.bearing_track_radius, 0, 0)
|
|
a = a.addS(
|
|
ball,
|
|
name=f"bearing_spindle{i}",
|
|
material=self.material_bearing_ball,
|
|
role=Role.BEARING,
|
|
loc=loc,
|
|
)
|
|
return a
|
|
|
|
### Motor ###
|
|
|
|
@target(name="motor-seat")
|
|
def motor_seat(self) -> Cq.Workplane:
|
|
"""
|
|
Create new longitudinal mount points closer to the centre axis, and a
|
|
ring for mounting lights
|
|
"""
|
|
bx, by = self.angle_joint_bind_pos.to2d_pos()
|
|
gap = 7
|
|
t1 = 10
|
|
base_w = 17.0
|
|
theta = math.pi / self.n_side
|
|
theta2 = theta * 0.7
|
|
theta1 = theta * 1.3
|
|
cover_thickness = 4.0
|
|
track_width = 7.0
|
|
r0 = self.bulk_radius
|
|
r1 = self.rotation_radius + gap
|
|
r2 = self.rotation_radius - gap
|
|
profile_arc = (
|
|
Cq.Sketch()
|
|
.circle(r1)
|
|
.circle(r2, mode="s")
|
|
.polygon([
|
|
(0, 0),
|
|
(r0 * math.cos(theta1), r0 * math.sin(theta1)),
|
|
(r0 * math.cos(theta1), -r0 * math.sin(theta1)),
|
|
], mode="i")
|
|
)
|
|
profile_base = (
|
|
profile_arc
|
|
.reset()
|
|
.polygon([
|
|
(r1 * math.cos(theta1), r1 * math.sin(theta1)),
|
|
(r1 * math.cos(theta2), r1 * math.sin(theta2)),
|
|
(bx, by - base_w/2),
|
|
(bx, by + base_w/2),
|
|
])
|
|
.polygon([
|
|
(r1 * math.cos(theta1), -r1 * math.sin(theta1)),
|
|
(r1 * math.cos(theta2), -r1 * math.sin(theta2)),
|
|
(bx, -by + base_w/2),
|
|
(bx, -by - base_w/2),
|
|
])
|
|
.reset()
|
|
.push([
|
|
(bx, by), (bx, -by),
|
|
])
|
|
.circle(base_w/2, mode="a")
|
|
.reset()
|
|
.push([
|
|
(bx, by), (bx, -by),
|
|
])
|
|
.circle(BOLT_COMMON.diam_thread/2, mode="s")
|
|
)
|
|
base = (
|
|
Cq.Workplane()
|
|
.placeSketch(profile_base)
|
|
.extrude(t1)
|
|
)
|
|
r3 = self.motor_seat_radius
|
|
r2_5 = r3 + BOLT_COMMON.diam_thread/2
|
|
mount_x = r3 * math.cos(theta)
|
|
mount_y = r3 * math.sin(theta)
|
|
front = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.circle(r1)
|
|
.circle(r2_5, mode="s")
|
|
.polygon([
|
|
(0, 0),
|
|
(r0 * math.cos(theta1), r0 * math.sin(theta1)),
|
|
(r0 * math.cos(theta1), -r0 * math.sin(theta1)),
|
|
], mode="i")
|
|
.push([
|
|
(mount_x, mount_y),
|
|
(mount_x, -mount_y),
|
|
])
|
|
.circle(base_w/2)
|
|
.circle(BOLT_COMMON.diam_thread/2, mode="s")
|
|
.finalize()
|
|
.extrude(t1)
|
|
)
|
|
channel = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.circle(self.rotation_radius+track_width/2)
|
|
.circle(self.rotation_radius-track_width/2, mode="s")
|
|
.finalize()
|
|
.extrude(t1)
|
|
.translate((0, 0, self.motor_seat_depth - t1))
|
|
)
|
|
channel_cover = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.circle(self.rotation_radius+track_width/2)
|
|
.circle(self.rotation_radius-track_width/2, mode="s")
|
|
.polygon([
|
|
(0, 0),
|
|
(r0 * math.cos(theta1), r0 * math.sin(theta1)),
|
|
(r0 * math.cos(theta2), r0 * math.sin(theta2)),
|
|
], mode="i")
|
|
.finalize()
|
|
.extrude(cover_thickness)
|
|
.translate((0, 0, self.motor_seat_depth - cover_thickness))
|
|
)
|
|
|
|
# Construct the connection between the front and back
|
|
|
|
x11 = r1 * math.cos(theta1)
|
|
y11 = r1 * math.sin(theta1)
|
|
x21 = r1 * math.cos(theta2)
|
|
y21 = r1 * math.sin(theta2)
|
|
x12 = bx + base_w/2 * math.sin(-math.pi * 0.3)
|
|
y12 = by + base_w/2 * math.cos(-math.pi * 0.3)
|
|
x22 = bx
|
|
y22 = by - base_w/2
|
|
a1 = .8
|
|
a2 = .95
|
|
profile_bridge_outer_base = (
|
|
Cq.Sketch()
|
|
.polygon([
|
|
((1 - a1) * x11 + a1 * x12, (1 - a1) * y11 + a1 * y12),
|
|
((1 - a1) * x21 + a1 * x22, (1 - a1) * y21 + a1 * y22),
|
|
((1 - a2) * x21 + a2 * x22, (1 - a2) * y21 + a2 * y22),
|
|
((1 - a2) * x11 + a2 * x12, (1 - a2) * y11 + a2 * y12),
|
|
])
|
|
.wires()
|
|
.val()
|
|
.moved(0, 0, t1)
|
|
)
|
|
profile_bridge_outer_top = (
|
|
Cq.Sketch()
|
|
.circle(r1)
|
|
.circle(self.rotation_radius, mode="s")
|
|
.polygon([
|
|
(0, 0),
|
|
(r0 * math.cos(theta1), r0 * math.sin(theta1)),
|
|
(r0 * math.cos(theta2), r0 * math.sin(theta2)),
|
|
], mode="i")
|
|
.wires()
|
|
.val()
|
|
.moved(0, 0, self.motor_seat_depth)
|
|
)
|
|
profile_bridge_inner_base = (
|
|
Cq.Sketch()
|
|
.circle(r1)
|
|
.circle(r2, mode="s")
|
|
.polygon([
|
|
(0, 0),
|
|
(r0 * math.cos(theta1), r0 * math.sin(theta1)),
|
|
(r0 * math.cos(theta2), r0 * math.sin(theta2)),
|
|
], mode="i")
|
|
.wires()
|
|
.val()
|
|
)
|
|
profile_bridge_inner_top = (
|
|
Cq.Sketch()
|
|
.circle(r1)
|
|
.circle(r2, mode="s")
|
|
.polygon([
|
|
(0, 0),
|
|
(r0 * math.cos(theta1), r0 * math.sin(theta1)),
|
|
(r0 * math.cos(theta2), r0 * math.sin(theta2)),
|
|
], mode="i")
|
|
.wires()
|
|
.val()
|
|
.moved(0, 0, self.motor_seat_depth - t1)
|
|
)
|
|
bridge_outer = Cq.Solid.makeLoft([profile_bridge_outer_base, profile_bridge_outer_top])
|
|
bridge_inner = Cq.Solid.makeLoft([profile_bridge_inner_base, profile_bridge_inner_top])
|
|
hole_subtractor = Cq.Solid.makeCylinder(
|
|
radius=BOLT_COMMON.diam_thread/2,
|
|
height=t1,
|
|
pnt=((r1+r2)/2, 0, 0)
|
|
)
|
|
result = (
|
|
base
|
|
+ front.translate((0, 0, self.motor_seat_depth - t1))
|
|
+ bridge_outer
|
|
+ bridge_outer.mirror("XZ")
|
|
+ bridge_inner
|
|
+ bridge_inner.mirror("XZ")
|
|
- hole_subtractor
|
|
- channel
|
|
+ channel_cover
|
|
+ channel_cover.mirror("XZ")
|
|
)
|
|
|
|
# Mark the mount points
|
|
result.tagAbsolute("holeBB1", (bx, +by, 0), direction="-Z")
|
|
result.tagAbsolute("holeBB2", (bx, -by, 0), direction="-Z")
|
|
result.tagAbsolute("holeMF1", (mount_x, +mount_y, self.motor_seat_depth), direction="+Z")
|
|
result.tagAbsolute("holeMF2", (mount_x, -mount_y, self.motor_seat_depth), 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()
|
|
|
|
### 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
|
|
|
|
@target(name="front-bracket")
|
|
def front_bracket(self) -> Cq.Workplane:
|
|
assert self.front_bracket_ext > self.gohei_bolt_diam / 2
|
|
assert self.front_bracket_depth > self.gohei_bolt_diam
|
|
x0 = self.bulk_radius
|
|
y0 = x0 * math.tan(2 * math.pi / self.n_side)
|
|
s1 = self.side_width_inner
|
|
s2 = s1 - self.front_bracket_thickness * self.ratio_side_width
|
|
result = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(s1, self.n_side)
|
|
.regularPolygon(s2, self.n_side, mode="s")
|
|
.polygon([
|
|
(0, 0),
|
|
(x0, 0),
|
|
(x0, y0),
|
|
], mode="i")
|
|
.finalize()
|
|
.extrude(self.front_bracket_depth)
|
|
.translate((0, 0, -self.front_bracket_depth/2))
|
|
)
|
|
hole_subtractor = Cq.Solid.makeCylinder(
|
|
radius=BOLT_COMMON.diam_thread/2,
|
|
height=self.bulk_radius,
|
|
dir=(1, 0, 0)
|
|
)
|
|
result -= hole_subtractor
|
|
angle = 360 / self.n_side
|
|
result -= hole_subtractor.rotate((0,0,0), (0, 0, 1), angle)
|
|
loc_rot = Cq.Location.rot2d(angle)
|
|
r1 = self.bulk_radius - self.side_thickness
|
|
result.tagAbsolute("holeT1", (r1, 0, 0), direction="+X")
|
|
loc_ht1 = (loc_rot * Cq.Location(r1, 0, 0)).toTuple()[0]
|
|
result.tagAbsolute("holeT2", loc_ht1, direction=loc_ht1)
|
|
return result
|
|
|
|
@target(name="front-bracket-large")
|
|
def front_bracket_large(self) -> Cq.Workplane:
|
|
"""
|
|
Optional alternative that is a bit bigger
|
|
"""
|
|
result = self.front_bracket()
|
|
return result + result.mirror("XZ")
|
|
|
|
def profile_side_panel(
|
|
self,
|
|
length: float,
|
|
hasFrontHole: bool = False,
|
|
hasBackHole: bool = True) -> Cq.Sketch:
|
|
assert hasFrontHole or hasBackHole
|
|
signs = ([1] if hasFrontHole else []) + ([-1] if hasBackHole else [])
|
|
return (
|
|
Cq.Sketch()
|
|
.rect(self.side_width, length)
|
|
.push([
|
|
(sx * x, sy * (length/2 - y))
|
|
for (x, y) in self.angle_joint_bolt_position
|
|
for sx in [1, -1]
|
|
for sy in signs
|
|
])
|
|
.circle(self.angle_joint_bolt_diam/2, mode="s")
|
|
)
|
|
|
|
def side_panel(
|
|
self,
|
|
length: float,
|
|
hasFrontHole: bool = True,
|
|
hasBackHole: bool = True,
|
|
) -> Cq.Workplane:
|
|
w = self.side_width
|
|
sketch = self.profile_side_panel(
|
|
length=length,
|
|
hasFrontHole=hasFrontHole,
|
|
hasBackHole=hasBackHole,
|
|
)
|
|
result = (
|
|
Cq.Workplane()
|
|
.placeSketch(sketch)
|
|
.extrude(self.side_thickness)
|
|
)
|
|
# Bevel the edges
|
|
intersector = (
|
|
Cq.Workplane('XZ')
|
|
.polyline([
|
|
(-w/2, 0),
|
|
(w/2, 0),
|
|
(0, self.bulk_radius),
|
|
])
|
|
.close()
|
|
.extrude(length)
|
|
.translate(Cq.Vector(0, length/2, 0))
|
|
)
|
|
# Intersect the side panel
|
|
result = result * intersector
|
|
|
|
# Mark all attachment points
|
|
t = self.side_thickness
|
|
for i, (x, y) in enumerate(self.angle_joint_bolt_position):
|
|
px = x
|
|
py = length / 2 - y
|
|
result.tagAbsolute(f"holeFPI{i}", (+px, py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeFSI{i}", (-px, py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeFPO{i}", (+px, py, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeFSO{i}", (-px, py, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeBPI{i}", (+px, -py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeBSI{i}", (-px, -py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeBPO{i}", (+px, -py, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeBSO{i}", (-px, -py, 0), direction="-Z")
|
|
|
|
return result
|
|
|
|
@target(name="side-panel1", kind=TargetKind.DXF)
|
|
def profile_side_panel1(self) -> Cq.Sketch:
|
|
return (
|
|
self.profile_side_panel(
|
|
length=self.side_length1,
|
|
hasFrontHole=False,
|
|
hasBackHole=True,
|
|
)
|
|
.push([
|
|
(0, self.side_length1/2 - self.section1_gohei_loc)
|
|
])
|
|
.circle(self.gohei_bolt_diam/2, mode="s")
|
|
)
|
|
def side_panel1(self) -> Cq.Workplane:
|
|
l = self.side_length1
|
|
w = self.side_width
|
|
sketch = self.profile_side_panel1()
|
|
result = (
|
|
Cq.Workplane()
|
|
.placeSketch(sketch)
|
|
.extrude(self.side_thickness)
|
|
)
|
|
# Bevel the edges
|
|
intersector = (
|
|
Cq.Workplane('XZ')
|
|
.polyline([
|
|
(-w/2, 0),
|
|
(w/2, 0),
|
|
(0, self.bulk_radius),
|
|
])
|
|
.close()
|
|
.extrude(l)
|
|
.translate(Cq.Vector(0, l/2, 0))
|
|
)
|
|
# Intersect the side panel
|
|
result = result * intersector
|
|
|
|
# Mark all attachment points
|
|
t = self.side_thickness
|
|
for i, (x, y) in enumerate(self.angle_joint_bolt_position):
|
|
px = x
|
|
py = l / 2 - y
|
|
result.tagAbsolute(f"holeFPI{i}", (+px, py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeFSI{i}", (-px, py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeFPO{i}", (+px, py, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeFSO{i}", (-px, py, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeBPI{i}", (+px, -py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeBSI{i}", (-px, -py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeBPO{i}", (+px, -py, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeBSO{i}", (-px, -py, 0), direction="-Z")
|
|
# Mark the gohei attachment points
|
|
y_gohei = self.side_length1/2 - self.section1_gohei_loc
|
|
result.tagAbsolute(f"holeGoheiB", (0, y_gohei, t), direction="+Z")
|
|
result.tagAbsolute(f"holeGoheiF", (0, y_gohei, 0), direction="-Z")
|
|
return result
|
|
|
|
@target(name="side-panel2", kind=TargetKind.DXF)
|
|
def profile_side_panel2(self) -> Cq.Sketch:
|
|
return (
|
|
self.profile_side_panel(
|
|
length=self.side_length2,
|
|
hasFrontHole=True,
|
|
hasBackHole=True,
|
|
)
|
|
)
|
|
@target(name="side-panel3", kind=TargetKind.DXF)
|
|
def profile_side_panel3(self) -> Cq.Sketch:
|
|
return (
|
|
self.profile_side_panel(
|
|
length=self.side_length3,
|
|
hasFrontHole=True,
|
|
hasBackHole=True,
|
|
)
|
|
)
|
|
|
|
def assembly_section1(self) -> Cq.Assembly:
|
|
a = Cq.Assembly()
|
|
side = self.side_panel1()
|
|
r = self.bulk_radius
|
|
for i in range(self.n_side):
|
|
a = a.addS(
|
|
side,
|
|
name=f"side{i}",
|
|
material=self.material_side,
|
|
role=Role.STRUCTURE | Role.DECORATION,
|
|
loc=Cq.Location.rot2d(i*360/self.n_side) * Cq.Location(-r,0,0,90,0,90),
|
|
)
|
|
a = a.constrain(f"side{i}", "Fixed")
|
|
for i in range(self.n_side):
|
|
i1 = (i + 1) % self.n_side
|
|
name_bracket = f"front_bracket{i}"
|
|
a = a.addS(
|
|
self.front_bracket(),
|
|
name=name_bracket,
|
|
role=Role.STRUCTURE,
|
|
)
|
|
a = a.constrain(
|
|
f"side{i1}?holeGoheiB",
|
|
f"{name_bracket}?holeT1",
|
|
"Plane",
|
|
)
|
|
a = a.constrain(
|
|
f"side{i}?holeGoheiB",
|
|
f"{name_bracket}?holeT2",
|
|
"Plane",
|
|
)
|
|
return a.solve()
|
|
def assembly_section(self, **kwargs) -> Cq.Assembly:
|
|
a = Cq.Assembly()
|
|
side = self.side_panel(**kwargs)
|
|
r = self.bulk_radius
|
|
for i in range(self.n_side):
|
|
a = a.addS(
|
|
side,
|
|
name=f"side{i}",
|
|
material=self.material_side,
|
|
role=Role.STRUCTURE | Role.DECORATION,
|
|
loc=Cq.Location.rot2d(i*360/self.n_side) * Cq.Location(-r,0,0,90,0,90),
|
|
)
|
|
return a
|
|
|
|
@target(name="chamber-side-panel", kind=TargetKind.DXF)
|
|
def profile_chamber_side_panel(self) -> Cq.Sketch:
|
|
l = self.chamber_side_length
|
|
w = self.chamber_side_width
|
|
return (
|
|
Cq.Sketch()
|
|
.rect(w, l)
|
|
.push([
|
|
(sx * x, sy * (l/2 - y))
|
|
for (x, y) in self.angle_joint_bolt_position
|
|
for sx in [1, -1]
|
|
for sy in [1, -1]
|
|
])
|
|
.circle(self.angle_joint_bolt_diam/2, mode="s")
|
|
)
|
|
|
|
def chamber_side_panel(self) -> Cq.Workplane:
|
|
w = self.chamber_side_width
|
|
l = self.chamber_side_length
|
|
sketch = self.profile_chamber_side_panel()
|
|
result = (
|
|
Cq.Workplane()
|
|
.placeSketch(sketch)
|
|
.extrude(self.side_thickness)
|
|
)
|
|
# Bevel the edges
|
|
intersector = (
|
|
Cq.Workplane('XZ')
|
|
.polyline([
|
|
(-w/2, 0),
|
|
(w/2, 0),
|
|
(0, self.chamber_bulk_radius),
|
|
])
|
|
.close()
|
|
.extrude(l)
|
|
.translate(Cq.Vector(0, l/2, 0))
|
|
)
|
|
# Intersect the side panel
|
|
result = result * intersector
|
|
|
|
# Mark all attachment points
|
|
t = self.side_thickness
|
|
for i, (x, y) in enumerate(self.angle_joint_bolt_position):
|
|
px = x
|
|
py = l / 2 - y
|
|
result.tagAbsolute(f"holeFPI{i}", (+px, py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeFSI{i}", (-px, py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeFPO{i}", (+px, py, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeFSO{i}", (-px, py, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeBPI{i}", (+px, -py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeBSI{i}", (-px, -py, t), direction="+Z")
|
|
result.tagAbsolute(f"holeBPO{i}", (+px, -py, 0), direction="-Z")
|
|
result.tagAbsolute(f"holeBSO{i}", (-px, -py, 0), direction="-Z")
|
|
|
|
return result
|
|
|
|
@target(name="chamber-back", kind=TargetKind.DXF)
|
|
def profile_chamber_back(self) -> Cq.Sketch:
|
|
shift = 180 / self.n_side
|
|
return (
|
|
Cq.Sketch()
|
|
.regularPolygon(
|
|
self.side_width - self.side_thickness,
|
|
self.n_side,
|
|
angle=shift)
|
|
.reset()
|
|
.regularPolygon(
|
|
self.angle_joint_bind_radius, self.n_side,
|
|
angle=shift,
|
|
mode="c", tag="bolt")
|
|
.vertices(tag="bolt")
|
|
.circle(self.rotor_bind_bolt_diam/2, mode="s")
|
|
)
|
|
def chamber_back(self) -> Cq.Workplane:
|
|
sketch = self.profile_chamber_back()
|
|
result = (
|
|
Cq.Workplane()
|
|
.placeSketch(sketch)
|
|
.extrude(self.side_thickness)
|
|
)
|
|
# Mark all attachment points
|
|
for i in range(self.n_side):
|
|
angle = i * math.radians(360 / self.n_side)
|
|
x = self.angle_joint_bind_radius * math.cos(angle)
|
|
y = self.angle_joint_bind_radius * math.sin(angle)
|
|
result.tagAbsolute(f"holeF{i}", (x, y, self.side_thickness), direction="+Z")
|
|
result.tagAbsolute(f"holeB{i}", (x, -y, 0), direction="-Z")
|
|
return result
|
|
|
|
def assembly_chamber(self) -> Cq.Assembly:
|
|
a = Cq.Assembly()
|
|
side = self.chamber_side_panel()
|
|
r = self.chamber_bulk_radius
|
|
for i in range(self.n_side):
|
|
a = a.addS(
|
|
side,
|
|
name=f"side{i}",
|
|
material=self.material_side,
|
|
role=Role.STRUCTURE | Role.DECORATION,
|
|
loc=Cq.Location.rot2d(i*360/self.n_side) * Cq.Location(-r,0,0,90,0,90),
|
|
)
|
|
return a
|
|
|
|
@target(name="chamber-front", kind=TargetKind.DXF)
|
|
def profile_chamber_front(self) -> Cq.Sketch:
|
|
"""
|
|
Front chamber must allow access to the electronics section
|
|
"""
|
|
l = self.side_width
|
|
h = self.side_width
|
|
gap = 20
|
|
return (
|
|
self.profile_chamber_back()
|
|
.reset()
|
|
.rect(l, h, mode="s")
|
|
.push([
|
|
(l/2 + gap + self.chamber_front_switch_diam/2, 0)
|
|
])
|
|
.circle(self.chamber_front_switch_diam/2, mode="s")
|
|
.reset()
|
|
.push([
|
|
(0, h/2 + gap),
|
|
(0, -h/2 - gap),
|
|
])
|
|
.rect(l/4, gap, mode="s")
|
|
)
|
|
def chamber_front(self) -> Cq.Sketch:
|
|
sketch = self.profile_chamber_front()
|
|
result = (
|
|
Cq.Workplane()
|
|
.placeSketch(sketch)
|
|
.extrude(self.side_thickness)
|
|
)
|
|
# Mark all attachment points
|
|
for i in range(self.n_side):
|
|
angle = i * math.radians(360 / self.n_side)
|
|
x = self.angle_joint_bind_radius * math.cos(angle)
|
|
y = self.angle_joint_bind_radius * math.sin(angle)
|
|
result.tagAbsolute(f"holeF{i}", (x, y, self.side_thickness), direction="+Z")
|
|
result.tagAbsolute(f"holeB{i}", (x, -y, 0), direction="-Z")
|
|
return result
|
|
|
|
def angle_joint_flange(self) -> Cq.Workplane:
|
|
th = math.pi / self.n_side
|
|
r = self.bulk_radius
|
|
flange = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.push([
|
|
(r, r * math.tan(th))
|
|
])
|
|
.circle(self.angle_joint_flange_radius)
|
|
.reset()
|
|
.regularPolygon(self.side_width_inner, self.n_side, mode="i")
|
|
.finalize()
|
|
.extrude(self.angle_joint_flange_thickness)
|
|
.translate((0, 0, -self.angle_joint_flange_thickness/2))
|
|
)
|
|
ri = self.angle_joint_bind_radius
|
|
h = self.angle_joint_flange_thickness
|
|
# drill hole
|
|
cyl = Cq.Solid.makeCylinder(
|
|
radius=self.rotor_bind_bolt_diam/2,
|
|
height=h,
|
|
pnt=(ri * math.cos(th), ri * math.sin(th), -h/2),
|
|
)
|
|
return flange - cyl
|
|
|
|
@target(name="angle-joint-chamber-back")
|
|
def angle_joint_chamber_back(self) -> Cq.Workplane:
|
|
slot = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(
|
|
self.side_width,
|
|
self.n_side
|
|
)
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth)
|
|
)
|
|
thickness = self.chamber_bulk_radius - self.bulk_radius
|
|
|
|
h = (self.bulk_radius + self.angle_joint_extra_width) * 2
|
|
# Intersector for 1/n of the ring
|
|
intersector = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.polygon([
|
|
(0, 0),
|
|
(h, 0),
|
|
(h, h * math.tan(2 * math.pi / self.n_side))
|
|
])
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth*4)
|
|
.translate((0, 0, -self.angle_joint_depth*2))
|
|
)
|
|
# The mating structure
|
|
z1 = self.bulk_radius + (thickness - self.angle_joint_conn_thickness) / 2
|
|
z2 = z1 + self.angle_joint_conn_thickness
|
|
mating1n = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.polygon([
|
|
(z1, 0),
|
|
(z1, self.angle_joint_conn_width),
|
|
(z2, self.angle_joint_conn_width),
|
|
(z2, 0),
|
|
])
|
|
.finalize()
|
|
.extrude(self.angle_joint_conn_depth)
|
|
)
|
|
mating1p = mating1n.rotate((0,0,0), (1,0,0), 180)
|
|
angle = 360 / self.n_side
|
|
|
|
chamber_intersector = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(self.chamber_side_width, self.n_side)
|
|
.regularPolygon(self.chamber_side_width - self.delta_side_width, self.n_side, mode="s")
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth)
|
|
.translate((0,0,self.angle_joint_gap/2))
|
|
)
|
|
result = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(
|
|
self.chamber_side_width,
|
|
self.n_side
|
|
)
|
|
.regularPolygon(
|
|
self.side_width_inner,
|
|
self.n_side, mode="s"
|
|
)
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth)
|
|
.translate((0, 0, -self.angle_joint_depth/2))
|
|
.cut(slot.translate((0, 0, self.angle_joint_gap/2)))
|
|
.intersect(intersector)
|
|
.cut(chamber_intersector)
|
|
.cut(mating1n)
|
|
.union(mating1p)
|
|
.union(mating1n.rotate((0,0,0),(0,0,1),angle))
|
|
.cut(mating1p.rotate((0,0,0),(0,0,1),angle))
|
|
)
|
|
h = self.chamber_bulk_radius
|
|
hole_negative = Cq.Solid.makeCylinder(
|
|
radius=self.angle_joint_bolt_diam/2,
|
|
height=h,
|
|
pnt=(0,0,0),
|
|
dir=(1,0,0),
|
|
) + Cq.Solid.makeCylinder(
|
|
radius=self.angle_joint_bolt_head_diam/2,
|
|
height=self.angle_joint_bolt_head_depth,
|
|
pnt=(h,0,0),
|
|
dir=(-1,0,0),
|
|
)
|
|
dy = self.angle_joint_gap / 2
|
|
locrot = Cq.Location(0, 0, 0, 0, 0, 360/self.n_side)
|
|
for (x, y) in self.angle_joint_bolt_position:
|
|
p1 = Cq.Location((0, x, dy+y))
|
|
p1r = locrot * Cq.Location((0, -x, dy+y))
|
|
result = result \
|
|
- hole_negative.moved(p1) \
|
|
- hole_negative.moved(p1r)
|
|
# Mark the absolute locations of the mount points
|
|
dr = self.chamber_bulk_radius - self.side_thickness
|
|
locrot = Cq.Location(0, 0, 0, 0, 0, 360/self.n_side)
|
|
dr = self.chamber_bulk_radius - self.side_thickness
|
|
dy = self.angle_joint_gap / 2
|
|
for i, (x, y) in enumerate(self.angle_joint_bolt_position):
|
|
py = dy + y
|
|
#result.tagAbsolute(f"holeLPO{i}", (dr, x, py), direction="+X")
|
|
result.tagAbsolute(f"holeLPO{i}", (dr, x, py), direction="+X")
|
|
#result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X")
|
|
result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X")
|
|
|
|
th = math.pi / self.n_side
|
|
r = self.bulk_radius
|
|
flange_z = self.angle_joint_depth / 2 - self.side_thickness
|
|
flange = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.push([
|
|
(r, r * math.tan(th))
|
|
])
|
|
.circle(self.angle_joint_flange_extension)
|
|
.reset()
|
|
.regularPolygon(self.side_width_inner, self.n_side, mode="i")
|
|
.finalize()
|
|
.extrude(self.angle_joint_gap)
|
|
.translate((0, 0, -flange_z))
|
|
)
|
|
ri = self.angle_joint_bind_radius
|
|
h = self.angle_joint_gap
|
|
# Drill holes for connectors
|
|
cyl = Cq.Solid.makeCylinder(
|
|
radius=self.rotor_bind_bolt_diam/2,
|
|
height=h,
|
|
pnt=(0, 0, -flange_z),
|
|
)
|
|
result = (
|
|
result
|
|
+ flange
|
|
- cyl.moved(ri * math.cos(th), ri * math.sin(th), 0)
|
|
)
|
|
result.tagAbsolute("holeStatorO", (ri * math.cos(th), ri * math.sin(th), -flange_z), direction="-Z")
|
|
result.tagAbsolute("holeStatorI", (ri * math.cos(th), ri * math.sin(th), -flange_z+h), direction="+Z")
|
|
return result
|
|
|
|
|
|
@target(name="angle-joint-chamber-front")
|
|
def angle_joint_chamber_front(self) -> Cq.Workplane:
|
|
"""
|
|
Angle joint for connecting the chamber to the chassis of the barrel
|
|
"""
|
|
# This slot cuts the interior of the joint
|
|
slot = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(
|
|
self.side_width,
|
|
self.n_side
|
|
)
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth)
|
|
)
|
|
thickness = self.chamber_bulk_radius - self.bulk_radius
|
|
|
|
h = (self.bulk_radius + self.angle_joint_extra_width) * 2
|
|
# Intersector for 1/n of the ring
|
|
intersector = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.polygon([
|
|
(0, 0),
|
|
(h, 0),
|
|
(h, h * math.tan(2 * math.pi / self.n_side))
|
|
])
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth*4)
|
|
.translate((0, 0, -self.angle_joint_depth*2))
|
|
)
|
|
# The mating structure
|
|
z1 = self.bulk_radius + (thickness - self.angle_joint_conn_thickness) / 2
|
|
z2 = z1 + self.angle_joint_conn_thickness
|
|
mating1n = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.polygon([
|
|
(z1, 0),
|
|
(z1, self.angle_joint_conn_width),
|
|
(z2, self.angle_joint_conn_width),
|
|
(z2, 0),
|
|
])
|
|
.finalize()
|
|
.extrude(self.angle_joint_conn_depth)
|
|
)
|
|
mating1p = mating1n.rotate((0,0,0), (1,0,0), 180)
|
|
angle = 360 / self.n_side
|
|
|
|
chamber_intersector = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(self.chamber_side_width, self.n_side)
|
|
.regularPolygon(self.chamber_side_width - self.delta_side_width, self.n_side, mode="s")
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth)
|
|
.translate((0,0,-self.angle_joint_depth-self.angle_joint_gap/2))
|
|
)
|
|
result = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(
|
|
self.chamber_side_width,
|
|
self.n_side
|
|
)
|
|
.regularPolygon(
|
|
self.side_width_inner,
|
|
self.n_side, mode="s"
|
|
)
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth)
|
|
.translate((0, 0, -self.angle_joint_depth/2))
|
|
.cut(slot.translate((0, 0, self.angle_joint_gap/2)))
|
|
.cut(slot.translate((0, 0, -self.angle_joint_depth-self.angle_joint_gap/2)))
|
|
.intersect(intersector)
|
|
.cut(chamber_intersector)
|
|
.cut(mating1n)
|
|
.union(mating1p)
|
|
.union(mating1n.rotate((0,0,0),(0,0,1),angle))
|
|
.cut(mating1p.rotate((0,0,0),(0,0,1),angle))
|
|
)
|
|
h = self.chamber_bulk_radius
|
|
hole_negative = Cq.Solid.makeCylinder(
|
|
radius=self.angle_joint_bolt_diam/2,
|
|
height=h,
|
|
pnt=(0,0,0),
|
|
dir=(1,0,0),
|
|
) + Cq.Solid.makeCylinder(
|
|
radius=self.angle_joint_bolt_head_diam/2,
|
|
height=self.angle_joint_bolt_head_depth,
|
|
pnt=(h,0,0),
|
|
dir=(-1,0,0),
|
|
)
|
|
dy = self.angle_joint_gap / 2
|
|
locrot = Cq.Location(0, 0, 0, 0, 0, 360/self.n_side)
|
|
for (x, y) in self.angle_joint_bolt_position:
|
|
p1 = Cq.Location((0, x, dy+y))
|
|
p2 = Cq.Location((0, x, -dy-y))
|
|
p1r = locrot * Cq.Location((0, -x, dy+y))
|
|
p2r = locrot * Cq.Location((0, -x, -dy-y))
|
|
result = result \
|
|
- hole_negative.moved(p1) \
|
|
- hole_negative.moved(p2) \
|
|
- hole_negative.moved(p1r) \
|
|
- hole_negative.moved(p2r)
|
|
# Mark the absolute locations of the mount points
|
|
dr = self.bulk_radius + self.angle_joint_thickness
|
|
dr0 = self.bulk_radius
|
|
for i, (x, y) in enumerate(self.angle_joint_bolt_position):
|
|
py = dy + y
|
|
result.tagAbsolute(f"holeLPO{i}", (dr, x, py), direction="+X")
|
|
result.tagAbsolute(f"holeLPM{i}", (dr0, x, py), direction="-X")
|
|
result.tagAbsolute(f"holeRPM{i}", (dr0, x, -py), direction="-X")
|
|
result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X")
|
|
result.tagAbsolute(f"holeLSM{i}", locrot * Cq.Location(dr0, -x, py), direction="-X")
|
|
result.tagAbsolute(f"holeRSM{i}", locrot * Cq.Location(dr0, -x, -py), direction="-X")
|
|
locrot = Cq.Location(0, 0, 0, 0, 0, 360/self.n_side)
|
|
dr = self.chamber_bulk_radius - self.side_thickness
|
|
dy = self.angle_joint_gap / 2
|
|
for i, (x, y) in enumerate(self.angle_joint_bolt_position):
|
|
py = dy + y
|
|
#result.tagAbsolute(f"holeLPO{i}", (dr, x, py), direction="+X")
|
|
result.tagAbsolute(f"holeRPO{i}", (dr, x, -py), direction="+X")
|
|
#result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X")
|
|
result.tagAbsolute(f"holeRSO{i}", locrot * Cq.Location(dr, -x, -py), direction="+X")
|
|
|
|
# Generate the flange geometry
|
|
flange = self.angle_joint_flange()
|
|
result = result + self.angle_joint_flange()
|
|
th = math.pi / self.n_side
|
|
ri = self.angle_joint_bind_radius
|
|
h = self.angle_joint_gap
|
|
result.tagAbsolute("holeStatorL", (ri * math.cos(th), ri * math.sin(th), h/2), direction="+Z")
|
|
result.tagAbsolute("holeStatorR", (ri * math.cos(th), ri * math.sin(th), -h/2), direction="-Z")
|
|
return result
|
|
|
|
@target(name="angle-joint")
|
|
def angle_joint(self) -> Cq.Workplane:
|
|
"""
|
|
Angular joint between two side panels (excluding chamber). This sits at the intersection of
|
|
4 side panels to provide compressive, shear, and tensile strength.
|
|
|
|
To provide tensile strength along the Z-axis, the panels must be bolted
|
|
onto the angle joint.
|
|
|
|
The holes are marked hole(L/R)(P/S)(O/I)(i), where L/R corresponds to the two
|
|
sections being joined, and P/S corresponds to the two facets
|
|
(primary/secondary) being joined. O/I corresponds to the outside/inside
|
|
"""
|
|
|
|
# This slot cuts the interior of the joint
|
|
slot = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(
|
|
self.side_width,
|
|
self.n_side
|
|
)
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth)
|
|
)
|
|
|
|
h = (self.bulk_radius + self.angle_joint_extra_width) * 2
|
|
# Intersector for 1/n of the ring
|
|
intersector = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.polygon([
|
|
(0, 0),
|
|
(h, 0),
|
|
(h, h * math.tan(2 * math.pi / self.n_side))
|
|
])
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth*4)
|
|
.translate((0, 0, -self.angle_joint_depth*2))
|
|
)
|
|
# The mating structure
|
|
z1 = self.bulk_radius + (self.angle_joint_thickness - self.angle_joint_conn_thickness) / 2
|
|
z2 = z1 + self.angle_joint_conn_thickness
|
|
mating1n = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.polygon([
|
|
(z1, 0),
|
|
(z1, self.angle_joint_conn_width),
|
|
(z2, self.angle_joint_conn_width),
|
|
(z2, 0),
|
|
])
|
|
.finalize()
|
|
.extrude(self.angle_joint_conn_depth)
|
|
)
|
|
mating1p = mating1n.rotate((0,0,0), (1,0,0), 180)
|
|
angle = 360 / self.n_side
|
|
result = (
|
|
Cq.Workplane()
|
|
.sketch()
|
|
.regularPolygon(
|
|
self.side_width + self.angle_joint_extra_width,
|
|
self.n_side
|
|
)
|
|
.regularPolygon(
|
|
self.side_width_inner,
|
|
self.n_side, mode="s"
|
|
)
|
|
.finalize()
|
|
.extrude(self.angle_joint_depth)
|
|
.translate((0, 0, -self.angle_joint_depth/2))
|
|
.cut(slot.translate((0, 0, self.angle_joint_gap/2)))
|
|
.cut(slot.translate((0, 0, -self.angle_joint_depth-self.angle_joint_gap/2)))
|
|
.intersect(intersector)
|
|
.cut(mating1n)
|
|
.union(mating1p)
|
|
.union(mating1n.rotate((0,0,0),(0,0,1),angle))
|
|
.cut(mating1p.rotate((0,0,0),(0,0,1),angle))
|
|
)
|
|
h = self.bulk_radius + self.angle_joint_thickness
|
|
hole_negative = Cq.Solid.makeCylinder(
|
|
radius=self.angle_joint_bolt_diam/2,
|
|
height=h,
|
|
pnt=(0,0,0),
|
|
dir=(1,0,0),
|
|
) + Cq.Solid.makeCylinder(
|
|
radius=self.angle_joint_bolt_head_diam/2,
|
|
height=self.angle_joint_bolt_head_depth,
|
|
pnt=(h,0,0),
|
|
dir=(-1,0,0),
|
|
)
|
|
dy = self.angle_joint_gap / 2
|
|
locrot = Cq.Location(0, 0, 0, 0, 0, 360/self.n_side)
|
|
for (x, y) in self.angle_joint_bolt_position:
|
|
p1 = Cq.Location((0, x, dy+y))
|
|
p2 = Cq.Location((0, x, -dy-y))
|
|
p1r = locrot * Cq.Location((0, -x, dy+y))
|
|
p2r = locrot * Cq.Location((0, -x, -dy-y))
|
|
result = result \
|
|
- hole_negative.moved(p1) \
|
|
- hole_negative.moved(p2) \
|
|
- hole_negative.moved(p1r) \
|
|
- hole_negative.moved(p2r)
|
|
# Mark the absolute locations of the mount points
|
|
dr = self.bulk_radius + self.angle_joint_thickness
|
|
dr0 = self.bulk_radius
|
|
for i, (x, y) in enumerate(self.angle_joint_bolt_position):
|
|
py = dy + y
|
|
result.tagAbsolute(f"holeLPO{i}", (dr, x, py), direction="+X")
|
|
result.tagAbsolute(f"holeRPO{i}", (dr, x, -py), direction="+X")
|
|
result.tagAbsolute(f"holeLPM{i}", (dr0, x, py), direction="-X")
|
|
result.tagAbsolute(f"holeRPM{i}", (dr0, x, -py), direction="-X")
|
|
result.tagAbsolute(f"holeLSO{i}", locrot * Cq.Location(dr, -x, py), direction="+X")
|
|
result.tagAbsolute(f"holeRSO{i}", locrot * Cq.Location(dr, -x, -py), direction="+X")
|
|
result.tagAbsolute(f"holeLSM{i}", locrot * Cq.Location(dr0, -x, py), direction="-X")
|
|
result.tagAbsolute(f"holeRSM{i}", locrot * Cq.Location(dr0, -x, -py), direction="-X")
|
|
|
|
# Generate the flange geometry
|
|
flange = self.angle_joint_flange()
|
|
result = result + self.angle_joint_flange()
|
|
th = math.pi / self.n_side
|
|
ri = self.angle_joint_bind_radius
|
|
h = self.angle_joint_gap
|
|
result.tagAbsolute("holeStatorL", (ri * math.cos(th), ri * math.sin(th), h/2), direction="+Z")
|
|
result.tagAbsolute("holeStatorR", (ri * math.cos(th), ri * math.sin(th), -h/2), direction="-Z")
|
|
return result
|
|
|
|
@assembly()
|
|
def assembly_ring(self, base) -> Cq.Assembly:
|
|
a = Cq.Assembly()
|
|
r = self.bulk_radius
|
|
for i in range(self.n_side):
|
|
a = a.addS(
|
|
base,
|
|
name=f"side{i}",
|
|
material=self.material_brace,
|
|
role=Role.CASING | Role.DECORATION,
|
|
loc=Cq.Location.rot2d(i*360/self.n_side),
|
|
)
|
|
return a
|
|
|
|
@target(name="handle")
|
|
def handle(self) -> Cq.Workplane:
|
|
w = self.side_width * 0.7
|
|
base = (
|
|
Cq.Workplane(
|
|
origin=(0, 0, -self.handle_base_height)
|
|
)
|
|
.box(
|
|
length=w,
|
|
width=self.angle_joint_depth,
|
|
height=self.handle_base_height,
|
|
centered=(True, True, False)
|
|
)
|
|
.faces(">Z")
|
|
.workplane()
|
|
.pushPoints([
|
|
(x * sx, (y + self.angle_joint_gap/2) * sy)
|
|
for (x, y) in self.angle_joint_bolt_position
|
|
for sx in (-1, 1)
|
|
for sy in (-1, 1)
|
|
])
|
|
.cboreHole(
|
|
self.angle_joint_bolt_diam,
|
|
self.angle_joint_bolt_head_diam,
|
|
self.angle_joint_bolt_head_depth,
|
|
depth=None,
|
|
)
|
|
)
|
|
# Construct the handle
|
|
bar = (
|
|
Cq.Workplane(origin=(0, self.handle_radius, 0))
|
|
.rect(self.handle_thickness_x, self.handle_thickness_y)
|
|
.revolve(180, (0, -self.handle_radius, 0), (1, -self.handle_radius, 0))
|
|
)
|
|
result = (
|
|
base +
|
|
bar
|
|
)
|
|
t = self.handle_base_height
|
|
for i, (x, yi) in enumerate(self.angle_joint_bolt_position):
|
|
y = yi + self.angle_joint_gap/2
|
|
result.tagAbsolute(f"holeLPO{i}", (+x, y, 0), direction="+Z")
|
|
result.tagAbsolute(f"holeLSO{i}", (-x, y, 0), direction="+Z")
|
|
result.tagAbsolute(f"holeLPI{i}", (+x, y, -t), direction="-Z")
|
|
result.tagAbsolute(f"holeLSI{i}", (-x, y, -t), direction="-Z")
|
|
result.tagAbsolute(f"holeRPO{i}", (+x, -y, 0), direction="+Z")
|
|
result.tagAbsolute(f"holeRSO{i}", (-x, -y, 0), direction="+Z")
|
|
result.tagAbsolute(f"holeRPI{i}", (+x, -y, -t), direction="-Z")
|
|
result.tagAbsolute(f"holeRSI{i}", (-x, -y, -t), direction="-Z")
|
|
|
|
return result
|
|
|
|
@assembly()
|
|
def assembly(self, parts: Optional[list[str]] = None) -> Cq.Assembly:
|
|
a = Cq.Assembly()
|
|
if has_part(parts, "section1"):
|
|
a = a.add(
|
|
self.assembly_section1(),
|
|
name="section1",
|
|
)
|
|
if has_part(parts, "ring1"):
|
|
a = a.add(
|
|
self.assembly_ring(self.angle_joint()),
|
|
name="ring1",
|
|
)
|
|
if has_part(parts, "section2"):
|
|
a = a.add(
|
|
self.assembly_section(length=self.side_length2, hasFrontHole=True, hasBackHole=True),
|
|
name="section2",
|
|
)
|
|
if has_part(parts, "ring2"):
|
|
a = a.add(
|
|
self.assembly_ring(self.angle_joint()),
|
|
name="ring2",
|
|
)
|
|
|
|
name_handle1 = "handle2_1"
|
|
a = a.addS(
|
|
self.handle(),
|
|
name=name_handle1,
|
|
material=self.material_brace,
|
|
role=Role.HANDLE,
|
|
)
|
|
name_handle2 = "handle2_2"
|
|
a = a.addS(
|
|
self.handle(),
|
|
name=name_handle2,
|
|
material=self.material_brace,
|
|
role=Role.HANDLE,
|
|
)
|
|
# Handle constrain
|
|
for ih, (x, y) in enumerate(self.angle_joint_bolt_position):
|
|
a = a.constrain(
|
|
f"{name_handle1}?holeLPI{ih}",
|
|
f"ring2/side2?holeLPO{ih}",
|
|
"Plane",
|
|
)
|
|
a = a.constrain(
|
|
f"{name_handle1}?holeRPI{ih}",
|
|
f"ring2/side2?holeRPO{ih}",
|
|
"Plane",
|
|
)
|
|
a = a.constrain(
|
|
f"{name_handle2}?holeLPI{ih}",
|
|
f"ring2/side4?holeLPO{ih}",
|
|
"Plane",
|
|
)
|
|
a = a.constrain(
|
|
f"{name_handle2}?holeRPI{ih}",
|
|
f"ring2/side4?holeRPO{ih}",
|
|
"Plane",
|
|
)
|
|
if has_part(parts, "section3"):
|
|
a = a.add(
|
|
self.assembly_section(length=self.side_length3, hasFrontHole=True, hasBackHole=True),
|
|
name="section3",
|
|
)
|
|
if has_part(parts, "ring3"):
|
|
a = a.add(
|
|
self.assembly_ring(self.angle_joint_chamber_front()),
|
|
name="ring3",
|
|
)
|
|
if has_part(parts, "chamber"):
|
|
a = a.add(
|
|
self.assembly_chamber(),
|
|
name="chamber",
|
|
)
|
|
if has_part(parts, "ring4"):
|
|
a = a.add(
|
|
self.assembly_ring(self.angle_joint_chamber_back()),
|
|
name="ring4",
|
|
)
|
|
if has_part(parts, "chamber_back"):
|
|
a = a.addS(
|
|
self.chamber_back(),
|
|
name="chamber_back",
|
|
material=self.material_side,
|
|
role=Role.STRUCTURE | Role.DECORATION,
|
|
)
|
|
if has_part(parts, "chamber_front"):
|
|
a = a.addS(
|
|
self.chamber_front(),
|
|
name="chamber_front",
|
|
material=self.material_side,
|
|
role=Role.STRUCTURE | Role.DECORATION,
|
|
)
|
|
if has_part(parts, "motor"):
|
|
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, "electronics1"):
|
|
a = a.add(self.assembly_electronics1(), name="electronics1")
|
|
a = a.constrain("electronics1/controller", "Fixed")
|
|
if has_part(parts, ["electronics1", "ring3"]):
|
|
a = a.constrain(
|
|
f"electronics1/barL?holeBO2",
|
|
f"ring3/side1?holeStatorL",
|
|
"Plane",
|
|
)
|
|
a = a.constrain(
|
|
f"electronics1/barL?holeBO1",
|
|
f"ring3/side2?holeStatorL",
|
|
"Plane",
|
|
)
|
|
|
|
# FIXME: Filter
|
|
if has_part(parts, ["motor", "ring2"]):
|
|
for i in range(self.n_side // 2):
|
|
j = self.n_side // 2 - 1 - i
|
|
a = a.constrain(
|
|
f"motor/seat{j}?holeBB1",
|
|
f"ring2/side{i*2}?holeStatorL",
|
|
"Plane",
|
|
)
|
|
#a = a.constrain(
|
|
# f"motor/seat{j}?holeBB2",
|
|
# f"ring2/side{i*2+1}?holeStatorL",
|
|
# "Plane",
|
|
#)
|
|
for i in range(self.n_side):
|
|
j = (i + 1) % self.n_side
|
|
ir = (self.n_side - i) % self.n_side
|
|
|
|
coupler_name = f"stator_coupler{i}"
|
|
a = a.addS(
|
|
self.stator_coupler(),
|
|
name=coupler_name,
|
|
material=self.material_brace,
|
|
role=Role.STRUCTURE,
|
|
)
|
|
a = a.constrain(
|
|
f"{coupler_name}?holeOB",
|
|
f"ring1/side{i}?holeStatorR",
|
|
"Plane",
|
|
)
|
|
a = a.constrain(
|
|
f"{coupler_name}?holeIF",
|
|
f"machine/stator1?holeF{ir}",
|
|
"Plane",
|
|
)
|
|
|
|
#name_bolt =f"stator_outer_bolt{i}"
|
|
#a = a.addS(
|
|
# BOLT_LONG.generate(),
|
|
# name=name_bolt,
|
|
# material=self.material_fastener,
|
|
# role=Role.CONNECTION,
|
|
#)
|
|
#a = a.constrain(
|
|
# f"{coupler_name}?holeOF",
|
|
# f"{name_bolt}?root",
|
|
# "Plane",
|
|
#)
|
|
|
|
name_bolt =f"chamber_back{i}boltFPI{i}"
|
|
a = a.addS(
|
|
BOLT_COMMON.generate(),
|
|
name=name_bolt,
|
|
material=self.material_fastener,
|
|
role=Role.CONNECTION,
|
|
)
|
|
a = a.constrain(
|
|
f"chamber_back?holeF{i}",
|
|
f"{name_bolt}?root",
|
|
"Plane",
|
|
)
|
|
|
|
name_bolt =f"chamber_front{i}boltFPI{i}"
|
|
a = a.addS(
|
|
BOLT_COMMON.generate(),
|
|
name=name_bolt,
|
|
material=self.material_fastener,
|
|
role=Role.CONNECTION,
|
|
)
|
|
a = a.constrain(
|
|
f"chamber_front?holeF{i}",
|
|
f"{name_bolt}?root",
|
|
"Plane",
|
|
)
|
|
for ih in range(len(self.angle_joint_bolt_position)):
|
|
a = a.constrain(
|
|
f"chamber/side{i}?holeFPI{ih}",
|
|
f"ring3/side{i}?holeRSO{ih}",
|
|
"Plane",
|
|
)
|
|
a = a.constrain(
|
|
f"chamber/side{i}?holeBPI{ih}",
|
|
f"ring4/side{i}?holeLSO{ih}",
|
|
"Plane",
|
|
)
|
|
a = a.constrain(
|
|
f"ring4/side{i}?holeStatorO",
|
|
f"chamber_back?holeB{i}",
|
|
"Plane",
|
|
)
|
|
a = a.constrain(
|
|
f"ring3/side{i}?holeStatorR",
|
|
f"chamber_front?holeB{i}",
|
|
"Plane",
|
|
)
|
|
|
|
#a = a.constrain(
|
|
# f"barrel/stator2?holeB{i}",
|
|
# f"ring1/side{i}?holeStatorR",
|
|
# "Plane",
|
|
#)
|
|
|
|
# Generate bolts for the chamber back
|
|
for (nl, nc, nr) in [
|
|
("section1", "ring1", "section2"),
|
|
("section2", "ring2", "section3"),
|
|
("section3", "ring3", None),
|
|
]:
|
|
a = a.constrain(
|
|
f"{nl}/side{i}?holeBSO{ih}",
|
|
f"{nc}/side{i}?holeLPM{ih}",
|
|
"Plane",
|
|
)
|
|
if nr:
|
|
a = a.constrain(
|
|
f"{nr}/side{i}?holeFPO{ih}",
|
|
f"{nc}/side{i}?holeRSM{ih}",
|
|
"Plane",
|
|
)
|
|
|
|
return a.solve()
|