cosplay: Touhou/Houjuu Nue #4
|
@ -4,6 +4,42 @@ import cadquery as Cq
|
||||||
from nhf import Item, Role
|
from nhf import Item, Role
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class FlatHeadBolt(Item):
|
||||||
|
diam_head: float
|
||||||
|
height_head: float
|
||||||
|
diam_thread: float
|
||||||
|
height_thread: float
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return f"Bolt M{int(self.diam_thread)} h{int(self.height_thread)}mm"
|
||||||
|
|
||||||
|
|
||||||
|
def generate(self) -> Cq.Assembly:
|
||||||
|
print(self.name)
|
||||||
|
head = Cq.Solid.makeCylinder(
|
||||||
|
radius=self.diam_head / 2,
|
||||||
|
height=self.height_head,
|
||||||
|
)
|
||||||
|
rod = (
|
||||||
|
Cq.Workplane('XY')
|
||||||
|
.cylinder(
|
||||||
|
radius=self.diam_thread/ 2,
|
||||||
|
height=self.height_thread,
|
||||||
|
centered=(True, True, False))
|
||||||
|
)
|
||||||
|
rod.faces("<Z").tag("tip")
|
||||||
|
rod.faces(">Z").tag("root")
|
||||||
|
|
||||||
|
return (
|
||||||
|
Cq.Assembly()
|
||||||
|
.addS(rod, name="thread", role=Role.CONNECTION)
|
||||||
|
.addS(head, name="head", role=Role.CONNECTION,
|
||||||
|
loc=Cq.Location((0, 0, self.height_thread)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ThreaddedKnob(Item):
|
class ThreaddedKnob(Item):
|
||||||
"""
|
"""
|
||||||
|
@ -12,8 +48,8 @@ class ThreaddedKnob(Item):
|
||||||
> Othmro Black 12mm(M12) x 50mm Thread Replacement Star Hand Knob Tightening
|
> Othmro Black 12mm(M12) x 50mm Thread Replacement Star Hand Knob Tightening
|
||||||
> Screws
|
> Screws
|
||||||
"""
|
"""
|
||||||
diam_rod: float
|
diam_thread: float
|
||||||
height_rod: float
|
height_thread: float
|
||||||
diam_knob: float
|
diam_knob: float
|
||||||
|
|
||||||
diam_neck: float
|
diam_neck: float
|
||||||
|
@ -22,7 +58,7 @@ class ThreaddedKnob(Item):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return f"Knob-M{int(self.diam_rod)}-{int(self.height_rod)}mm"
|
return f"Knob M{int(self.diam_thread)} h{int(self.height_thread)}mm"
|
||||||
|
|
||||||
def generate(self) -> Cq.Assembly:
|
def generate(self) -> Cq.Assembly:
|
||||||
print(self.name)
|
print(self.name)
|
||||||
|
@ -34,41 +70,40 @@ class ThreaddedKnob(Item):
|
||||||
radius=self.diam_neck / 2,
|
radius=self.diam_neck / 2,
|
||||||
height=self.height_neck,
|
height=self.height_neck,
|
||||||
)
|
)
|
||||||
rod = (
|
thread = (
|
||||||
Cq.Workplane('XY')
|
Cq.Workplane('XY')
|
||||||
.cylinder(
|
.cylinder(
|
||||||
radius=self.diam_rod / 2,
|
radius=self.diam_thread / 2,
|
||||||
height=self.height_rod,
|
height=self.height_thread,
|
||||||
centered=(True, True, False))
|
centered=(True, True, False))
|
||||||
)
|
)
|
||||||
rod.faces("<Z").tag("tip")
|
thread.faces("<Z").tag("tip")
|
||||||
rod.faces(">Z").tag("root")
|
thread.faces(">Z").tag("root")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.addS(rod, name="rod", role=Role.CONNECTION)
|
.addS(thread, name="thread", role=Role.CONNECTION)
|
||||||
.addS(neck, name="neck", role=Role.HANDLE,
|
.addS(neck, name="neck", role=Role.HANDLE,
|
||||||
loc=Cq.Location((0, 0, self.height_rod)))
|
loc=Cq.Location((0, 0, self.height_thread)))
|
||||||
.addS(knob, name="knob", role=Role.HANDLE,
|
.addS(knob, name="knob", role=Role.HANDLE,
|
||||||
loc=Cq.Location((0, 0, self.height_rod + self.height_neck)))
|
loc=Cq.Location((0, 0, self.height_thread + self.height_neck)))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class HexNut(Item):
|
class HexNut(Item):
|
||||||
diam: float
|
diam_thread: float
|
||||||
pitch: float
|
pitch: float
|
||||||
|
|
||||||
# FIXME: Measure these
|
thickness: float
|
||||||
m: float
|
width: float
|
||||||
s: float
|
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
assert self.s > self.diam
|
assert self.width > self.diam_thread
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return f"HexNut-M{int(self.diam)}-{self.pitch}"
|
return f"HexNut M{int(self.diam_thread)}-{self.pitch}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def role(self):
|
def role(self):
|
||||||
|
@ -76,14 +111,14 @@ class HexNut(Item):
|
||||||
|
|
||||||
def generate(self) -> Cq.Workplane:
|
def generate(self) -> Cq.Workplane:
|
||||||
print(self.name)
|
print(self.name)
|
||||||
r = self.s / math.sqrt(3)
|
r = self.width / math.sqrt(3)
|
||||||
result = (
|
result = (
|
||||||
Cq.Workplane("XY")
|
Cq.Workplane("XY")
|
||||||
.sketch()
|
.sketch()
|
||||||
.regularPolygon(r=r, n=6)
|
.regularPolygon(r=r, n=6)
|
||||||
.circle(r=self.diam/2, mode='s')
|
.circle(r=self.diam_thread/2, mode='s')
|
||||||
.finalize()
|
.finalize()
|
||||||
.extrude(self.m)
|
.extrude(self.thickness)
|
||||||
)
|
)
|
||||||
result.faces("<Z").tag("bot")
|
result.faces("<Z").tag("bot")
|
||||||
result.faces(">Z").tag("top")
|
result.faces(">Z").tag("top")
|
||||||
|
|
|
@ -31,17 +31,17 @@ class Item:
|
||||||
def role(self) -> Optional[Role]:
|
def role(self) -> Optional[Role]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def generate(self) -> Union[Cq.Assembly, Cq.Workplane]:
|
def generate(self, **kwargs) -> Union[Cq.Assembly, Cq.Workplane]:
|
||||||
"""
|
"""
|
||||||
Creates an assembly for this item. Subclass should implement this
|
Creates an assembly for this item. Subclass should implement this
|
||||||
"""
|
"""
|
||||||
return Cq.Assembly()
|
return Cq.Assembly()
|
||||||
|
|
||||||
def assembly(self) -> Cq.Assembly:
|
def assembly(self, **kwargs) -> Cq.Assembly:
|
||||||
"""
|
"""
|
||||||
Interface for creating assembly with the necessary metadata
|
Interface for creating assembly with the necessary metadata
|
||||||
"""
|
"""
|
||||||
a = self.generate()
|
a = self.generate(**kwargs)
|
||||||
if isinstance(a, Cq.Workplane):
|
if isinstance(a, Cq.Workplane):
|
||||||
a = Cq.Assembly(a)
|
a = Cq.Assembly(a)
|
||||||
if role := self.role:
|
if role := self.role:
|
||||||
|
|
|
@ -180,6 +180,7 @@ class TorsionJoint:
|
||||||
3. An outer and an inner annuli which forms a track the rider can move on
|
3. An outer and an inner annuli which forms a track the rider can move on
|
||||||
"""
|
"""
|
||||||
spring: TorsionSpring = field(default_factory=lambda: TorsionSpring(
|
spring: TorsionSpring = field(default_factory=lambda: TorsionSpring(
|
||||||
|
mass=float('nan'),
|
||||||
radius=10.0,
|
radius=10.0,
|
||||||
thickness=2.0,
|
thickness=2.0,
|
||||||
height=15.0,
|
height=15.0,
|
||||||
|
@ -306,6 +307,7 @@ class TorsionJoint:
|
||||||
.hole(self.radius_axle * 2)
|
.hole(self.radius_axle * 2)
|
||||||
.cut(slot.moved(Cq.Location((0, 0, self.track_disk_height))))
|
.cut(slot.moved(Cq.Location((0, 0, self.track_disk_height))))
|
||||||
)
|
)
|
||||||
|
result.faces("<Z").tag("bot")
|
||||||
# Insert directrix
|
# Insert directrix
|
||||||
result.polyline(self._directrix(self.track_disk_height),
|
result.polyline(self._directrix(self.track_disk_height),
|
||||||
forConstruction=True).tag("dir")
|
forConstruction=True).tag("dir")
|
||||||
|
|
|
@ -2,9 +2,10 @@ import math
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
|
from nhf import Item, Role
|
||||||
|
|
||||||
@dataclass
|
@dataclass(frozen=True)
|
||||||
class TorsionSpring:
|
class TorsionSpring(Item):
|
||||||
"""
|
"""
|
||||||
A torsion spring with abridged geometry (since sweep is slow)
|
A torsion spring with abridged geometry (since sweep is slow)
|
||||||
"""
|
"""
|
||||||
|
@ -21,6 +22,10 @@ class TorsionSpring:
|
||||||
|
|
||||||
torsion_rate: Optional[float] = None
|
torsion_rate: Optional[float] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return f"TorsionSpring-{int(self.radius)}-{int(self.height)}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def radius_inner(self) -> float:
|
def radius_inner(self) -> float:
|
||||||
return self.radius - self.thickness
|
return self.radius - self.thickness
|
||||||
|
@ -28,7 +33,7 @@ class TorsionSpring:
|
||||||
def torque_at(self, theta: float) -> float:
|
def torque_at(self, theta: float) -> float:
|
||||||
return self.torsion_rate * theta
|
return self.torsion_rate * theta
|
||||||
|
|
||||||
def generate(self, deflection: float = 0):
|
def generate(self, deflection: float = 0) -> Cq.Workplane:
|
||||||
omega = self.angle_neutral + deflection
|
omega = self.angle_neutral + deflection
|
||||||
omega = -omega if self.right_handed else omega
|
omega = -omega if self.right_handed else omega
|
||||||
base = (
|
base = (
|
||||||
|
@ -39,7 +44,6 @@ class TorsionSpring:
|
||||||
base.faces(">Z").tag("top")
|
base.faces(">Z").tag("top")
|
||||||
base.faces("<Z").tag("bot")
|
base.faces("<Z").tag("bot")
|
||||||
|
|
||||||
box_shift = -self.radius if self.right_handed else self.radius-self.thickness
|
|
||||||
tail = Cq.Solid.makeCylinder(
|
tail = Cq.Solid.makeCylinder(
|
||||||
height=self.tail_length,
|
height=self.tail_length,
|
||||||
radius=self.thickness / 2)
|
radius=self.thickness / 2)
|
||||||
|
|
|
@ -2,6 +2,25 @@ import unittest
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
from nhf.checks import binary_intersection, pairwise_intersection
|
from nhf.checks import binary_intersection, pairwise_intersection
|
||||||
from nhf.parts import joints, handle, metric_threads, springs
|
from nhf.parts import joints, handle, metric_threads, springs
|
||||||
|
import nhf.parts.fasteners as fasteners
|
||||||
|
|
||||||
|
class TestFasteners(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_hex_nut(self):
|
||||||
|
width = 18.9
|
||||||
|
height = 9.8
|
||||||
|
item = fasteners.HexNut(
|
||||||
|
mass=float('nan'),
|
||||||
|
diam_thread=12.0,
|
||||||
|
pitch=1.75,
|
||||||
|
thickness=9.8,
|
||||||
|
width=width,
|
||||||
|
)
|
||||||
|
obj = item.generate()
|
||||||
|
self.assertEqual(len(obj.vals()), 1)
|
||||||
|
bbox = obj.val().BoundingBox()
|
||||||
|
self.assertAlmostEqual(bbox.xlen, width)
|
||||||
|
self.assertAlmostEqual(bbox.zlen, height)
|
||||||
|
|
||||||
class TestJoints(unittest.TestCase):
|
class TestJoints(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import cadquery as Cq
|
||||||
from nhf import Material, Role
|
from nhf import Material, Role
|
||||||
from nhf.build import Model, target, assembly
|
from nhf.build import Model, target, assembly
|
||||||
from nhf.parts.springs import TorsionSpring
|
from nhf.parts.springs import TorsionSpring
|
||||||
|
from nhf.parts.fasteners import FlatHeadBolt
|
||||||
from nhf.parts.joints import TorsionJoint
|
from nhf.parts.joints import TorsionJoint
|
||||||
from nhf.parts.box import Hole, MountingBox, box_with_centre_holes
|
from nhf.parts.box import Hole, MountingBox, box_with_centre_holes
|
||||||
import nhf.utils
|
import nhf.utils
|
||||||
|
@ -14,6 +15,15 @@ TOL = 1e-6
|
||||||
@dataclass
|
@dataclass
|
||||||
class ShoulderJoint(Model):
|
class ShoulderJoint(Model):
|
||||||
|
|
||||||
|
bolt: FlatHeadBolt = FlatHeadBolt(
|
||||||
|
# FIXME: measure
|
||||||
|
diam_head=10.0,
|
||||||
|
height_head=3.0,
|
||||||
|
diam_thread=6.0,
|
||||||
|
height_thread=20.0,
|
||||||
|
mass=float('nan'),
|
||||||
|
)
|
||||||
|
|
||||||
height: float = 60.0
|
height: float = 60.0
|
||||||
torsion_joint: TorsionJoint = field(default_factory=lambda: TorsionJoint(
|
torsion_joint: TorsionJoint = field(default_factory=lambda: TorsionJoint(
|
||||||
radius_track=18,
|
radius_track=18,
|
||||||
|
@ -23,9 +33,9 @@ class ShoulderJoint(Model):
|
||||||
groove_radius_inner=13,
|
groove_radius_inner=13,
|
||||||
track_disk_height=5.0,
|
track_disk_height=5.0,
|
||||||
rider_disk_height=5.0,
|
rider_disk_height=5.0,
|
||||||
# M8 Axle
|
|
||||||
radius_axle=3.0,
|
radius_axle=3.0,
|
||||||
spring=TorsionSpring(
|
spring=TorsionSpring(
|
||||||
|
mass=float('nan'),
|
||||||
# inner diameter = 9
|
# inner diameter = 9
|
||||||
radius=9/2 + 1.2,
|
radius=9/2 + 1.2,
|
||||||
thickness=1.3,
|
thickness=1.3,
|
||||||
|
@ -69,6 +79,16 @@ class ShoulderJoint(Model):
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
assert self.parent_lip_length * 2 < self.height
|
assert self.parent_lip_length * 2 < self.height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def radius(self):
|
||||||
|
return self.torsion_joint.radius
|
||||||
|
|
||||||
|
def parent_arm_loc(self) -> Cq.Location:
|
||||||
|
"""
|
||||||
|
2d location of the arm surface on the parent side, relative to axle
|
||||||
|
"""
|
||||||
|
return Cq.Location.rot2d(self.angle_neutral) * Cq.Location.from2d(self.parent_lip_ext, 0, 0)
|
||||||
|
|
||||||
def parent(self, top: bool = False) -> Cq.Assembly:
|
def parent(self, top: bool = False) -> Cq.Assembly:
|
||||||
joint = self.torsion_joint
|
joint = self.torsion_joint
|
||||||
# Thickness of the lip connecting this joint to the wing root
|
# Thickness of the lip connecting this joint to the wing root
|
||||||
|
@ -219,14 +239,18 @@ class ShoulderJoint(Model):
|
||||||
.constrain("child/core", "Fixed")
|
.constrain("child/core", "Fixed")
|
||||||
.addS(self.torsion_joint.spring.generate(deflection=-deflection), name="spring_top",
|
.addS(self.torsion_joint.spring.generate(deflection=-deflection), name="spring_top",
|
||||||
role=Role.DAMPING, material=mat_spring)
|
role=Role.DAMPING, material=mat_spring)
|
||||||
|
.addS(self.bolt.assembly(), name="bolt_top")
|
||||||
.addS(self.parent_top(),
|
.addS(self.parent_top(),
|
||||||
name="parent_top",
|
name="parent_top",
|
||||||
role=Role.PARENT, material=mat)
|
role=Role.PARENT, material=mat)
|
||||||
.addS(self.torsion_joint.spring.generate(deflection=deflection), name="spring_bot",
|
.addS(self.torsion_joint.spring.generate(deflection=deflection), name="spring_bot",
|
||||||
role=Role.DAMPING, material=mat_spring)
|
role=Role.DAMPING, material=mat_spring)
|
||||||
|
.addS(self.bolt.assembly(), name="bolt_bot")
|
||||||
.addS(self.parent_bot(),
|
.addS(self.parent_bot(),
|
||||||
name="parent_bot",
|
name="parent_bot",
|
||||||
role=Role.PARENT, material=mat)
|
role=Role.PARENT, material=mat)
|
||||||
|
.constrain("bolt_top/thread?root", "parent_top/track?bot", "Plane", param=0)
|
||||||
|
.constrain("bolt_bot/thread?root", "parent_bot/track?bot", "Plane", param=0)
|
||||||
)
|
)
|
||||||
TorsionJoint.add_constraints(
|
TorsionJoint.add_constraints(
|
||||||
result,
|
result,
|
||||||
|
@ -318,6 +342,7 @@ class DiskJoint(Model):
|
||||||
the housing. This provides torsion resistance.
|
the housing. This provides torsion resistance.
|
||||||
"""
|
"""
|
||||||
spring: TorsionSpring = field(default_factory=lambda: TorsionSpring(
|
spring: TorsionSpring = field(default_factory=lambda: TorsionSpring(
|
||||||
|
mass=float('nan'),
|
||||||
radius=9 / 2,
|
radius=9 / 2,
|
||||||
thickness=1.3,
|
thickness=1.3,
|
||||||
height=6.5,
|
height=6.5,
|
||||||
|
@ -617,7 +642,7 @@ class ElbowJoint(Model):
|
||||||
parent_arm_angle: float = 180.0
|
parent_arm_angle: float = 180.0
|
||||||
|
|
||||||
# Size of the mounting holes
|
# Size of the mounting holes
|
||||||
hole_diam: float = 6.0
|
hole_diam: float = 4.0
|
||||||
|
|
||||||
material: Material = Material.RESIN_TRANSPERENT
|
material: Material = Material.RESIN_TRANSPERENT
|
||||||
|
|
||||||
|
@ -628,6 +653,10 @@ class ElbowJoint(Model):
|
||||||
assert self.parent_arm_radius > self.disk_joint.radius_housing
|
assert self.parent_arm_radius > self.disk_joint.radius_housing
|
||||||
self.disk_joint.tongue_length = self.child_arm_radius - self.disk_joint.radius_disk - self.lip_thickness / 2
|
self.disk_joint.tongue_length = self.child_arm_radius - self.disk_joint.radius_disk - self.lip_thickness / 2
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_thickness(self):
|
||||||
|
return self.disk_joint.total_thickness
|
||||||
|
|
||||||
def parent_arm_loc(self) -> Cq.Location:
|
def parent_arm_loc(self) -> Cq.Location:
|
||||||
"""
|
"""
|
||||||
2d Location of the centre of the arm surface on the parent side, assuming
|
2d Location of the centre of the arm surface on the parent side, assuming
|
||||||
|
|
|
@ -41,8 +41,8 @@ class WingProfile(Model):
|
||||||
))
|
))
|
||||||
shoulder_angle_bias: float = 0.0
|
shoulder_angle_bias: float = 0.0
|
||||||
shoulder_width: float = 36.0
|
shoulder_width: float = 36.0
|
||||||
shoulder_tip_x: float = -200.0
|
shoulder_tip_x: float = -260.0
|
||||||
shoulder_tip_y: float = 160.0
|
shoulder_tip_y: float = 165.0
|
||||||
shoulder_mid_x: float = -105.0
|
shoulder_mid_x: float = -105.0
|
||||||
shoulder_mid_y: float = 75.0
|
shoulder_mid_y: float = 75.0
|
||||||
|
|
||||||
|
@ -102,7 +102,11 @@ class WingProfile(Model):
|
||||||
self.elbow_axle_loc = self.elbow_bot_loc * Cq.Location.from2d(0, self.elbow_height / 2)
|
self.elbow_axle_loc = self.elbow_bot_loc * Cq.Location.from2d(0, self.elbow_height / 2)
|
||||||
self.wrist_axle_loc = self.wrist_bot_loc * Cq.Location.from2d(0, self.wrist_height / 2)
|
self.wrist_axle_loc = self.wrist_bot_loc * Cq.Location.from2d(0, self.wrist_height / 2)
|
||||||
|
|
||||||
|
assert self.elbow_joint.total_thickness < min(self.s1_thickness, self.s2_thickness)
|
||||||
|
assert self.wrist_joint.total_thickness < min(self.s2_thickness, self.s3_thickness)
|
||||||
|
|
||||||
self.shoulder_joint.angle_neutral = -self.shoulder_angle_neutral - self.shoulder_angle_bias
|
self.shoulder_joint.angle_neutral = -self.shoulder_angle_neutral - self.shoulder_angle_bias
|
||||||
|
self.shoulder_axle_loc = Cq.Location.from2d(self.shoulder_tip_x, self.shoulder_tip_y - self.shoulder_width / 2, -self.shoulder_angle_bias)
|
||||||
|
|
||||||
@submodel(name="shoulder-joint")
|
@submodel(name="shoulder-joint")
|
||||||
def submodel_shoulder_joint(self) -> Model:
|
def submodel_shoulder_joint(self) -> Model:
|
||||||
|
@ -225,6 +229,9 @@ class WingProfile(Model):
|
||||||
(-self.base_width, 0),
|
(-self.base_width, 0),
|
||||||
)
|
)
|
||||||
.assemble()
|
.assemble()
|
||||||
|
.push([self.shoulder_axle_loc.to2d_pos()])
|
||||||
|
.circle(self.shoulder_joint.radius, mode='a')
|
||||||
|
.circle(self.shoulder_joint.bolt.diam_head / 2, mode='s')
|
||||||
)
|
)
|
||||||
return sketch
|
return sketch
|
||||||
|
|
||||||
|
@ -292,17 +299,12 @@ class WingProfile(Model):
|
||||||
def surface_s0(self, top: bool = False) -> Cq.Workplane:
|
def surface_s0(self, top: bool = False) -> Cq.Workplane:
|
||||||
base_dx = -(self.base_width - self.base_plate_width) / 2
|
base_dx = -(self.base_width - self.base_plate_width) / 2
|
||||||
base_dy = self.base_joint.joint_height
|
base_dy = self.base_joint.joint_height
|
||||||
sw = self.shoulder_width
|
loc_tip = Cq.Location(0, -self.shoulder_joint.parent_lip_width / 2)
|
||||||
|
|
||||||
axle_dist = self.shoulder_joint.parent_lip_ext
|
|
||||||
theta = math.radians(self.shoulder_angle_neutral)
|
|
||||||
c, s = math.cos(theta), math.sin(theta)
|
|
||||||
tags = [
|
tags = [
|
||||||
# transforms [axle_dist, -sw/2] about the centre (tip_x, tip_y - sw/2)
|
("shoulder",
|
||||||
("shoulder", Cq.Location.from2d(
|
self.shoulder_axle_loc *
|
||||||
self.shoulder_tip_x + axle_dist * c + (-sw/2) * s,
|
self.shoulder_joint.parent_arm_loc() *
|
||||||
self.shoulder_tip_y - sw / 2 - axle_dist * s + (-sw/2) * c,
|
loc_tip),
|
||||||
-self.shoulder_angle_neutral)),
|
|
||||||
("base", Cq.Location.from2d(base_dx, base_dy, 90)),
|
("base", Cq.Location.from2d(base_dx, base_dy, 90)),
|
||||||
]
|
]
|
||||||
result = extrude_with_markers(
|
result = extrude_with_markers(
|
||||||
|
|
Loading…
Reference in New Issue