cosplay: Touhou/Houjuu Nue #4
|
@ -605,12 +605,12 @@ class ElbowJoint(Model):
|
||||||
|
|
||||||
material: Material = Material.RESIN_TRANSPERENT
|
material: Material = Material.RESIN_TRANSPERENT
|
||||||
|
|
||||||
angle_neutral: float = 20.0
|
angle_neutral: float = 0.0
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
assert self.child_arm_radius > self.disk_joint.radius_housing
|
assert self.child_arm_radius > self.disk_joint.radius_housing
|
||||||
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.disk_joint.tongue_length = self.child_arm_radius - self.disk_joint.radius_disk - self.lip_thickness / 2
|
||||||
assert self.disk_joint.movement_angle < self.parent_arm_angle < 360 - self.parent_arm_span
|
assert self.disk_joint.movement_angle < self.parent_arm_angle < 360 - self.parent_arm_span
|
||||||
|
|
||||||
def lip(self) -> Cq.Workplane:
|
def lip(self) -> Cq.Workplane:
|
||||||
|
@ -640,7 +640,7 @@ class ElbowJoint(Model):
|
||||||
# We need to ensure the disk is on the "other" side so
|
# We need to ensure the disk is on the "other" side so
|
||||||
flip_x = Cq.Location((0, 0, 0), (1, 0, 0), 180)
|
flip_x = Cq.Location((0, 0, 0), (1, 0, 0), 180)
|
||||||
flip_z = Cq.Location((0, 0, 0), (0, 0, 1), 180)
|
flip_z = Cq.Location((0, 0, 0), (0, 0, 1), 180)
|
||||||
lip_dz = self.lip_thickness / 2
|
lip_dz = self.lip_thickness
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.add(self.lip(), name="lip", loc=
|
.add(self.lip(), name="lip", loc=
|
||||||
|
@ -664,7 +664,7 @@ class ElbowJoint(Model):
|
||||||
connector = (
|
connector = (
|
||||||
Cq.Solid.makeCylinder(
|
Cq.Solid.makeCylinder(
|
||||||
height=conn_h,
|
height=conn_h,
|
||||||
radius=self.parent_arm_radius,
|
radius=self.parent_arm_radius - self.lip_thickness / 2,
|
||||||
angleDegrees=self.parent_arm_span)
|
angleDegrees=self.parent_arm_span)
|
||||||
.cut(Cq.Solid.makeCylinder(
|
.cut(Cq.Solid.makeCylinder(
|
||||||
height=conn_h,
|
height=conn_h,
|
||||||
|
@ -679,7 +679,7 @@ class ElbowJoint(Model):
|
||||||
(0, 0, 1),
|
(0, 0, 1),
|
||||||
-self.disk_joint.tongue_span / 2 + self.angle_neutral
|
-self.disk_joint.tongue_span / 2 + self.angle_neutral
|
||||||
)
|
)
|
||||||
lip_dz = self.lip_thickness / 2
|
lip_dz = self.lip_thickness
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
.add(self.lip(), name="lip", loc=
|
.add(self.lip(), name="lip", loc=
|
||||||
|
|
|
@ -10,6 +10,23 @@ class TestJoints(unittest.TestCase):
|
||||||
j = MJ.ShoulderJoint()
|
j = MJ.ShoulderJoint()
|
||||||
assembly = j.assembly()
|
assembly = j.assembly()
|
||||||
self.assertEqual(pairwise_intersection(assembly), [])
|
self.assertEqual(pairwise_intersection(assembly), [])
|
||||||
|
|
||||||
|
def test_shoulder_joint_dist(self):
|
||||||
|
"""
|
||||||
|
Tests the arm radius
|
||||||
|
"""
|
||||||
|
j = MJ.ShoulderJoint()
|
||||||
|
for deflection in [0, 40, 95, 120]:
|
||||||
|
with self.subTest(deflection=deflection):
|
||||||
|
a = j.assembly(deflection=deflection)
|
||||||
|
# Axle
|
||||||
|
o = a.get_abs_location("parent_top/track?spring")
|
||||||
|
l_c1 = a.get_abs_location("parent_top/lip?conn0")
|
||||||
|
l_c2= a.get_abs_location("parent_top/lip?conn1")
|
||||||
|
v_c = 0.5 * ((l_c1 - o) + (l_c2 - o))
|
||||||
|
v_c.z = 0
|
||||||
|
self.assertAlmostEqual(v_c.Length, j.parent_lip_ext)
|
||||||
|
|
||||||
def test_disk_collision_0(self):
|
def test_disk_collision_0(self):
|
||||||
j = MJ.DiskJoint()
|
j = MJ.DiskJoint()
|
||||||
assembly = j.assembly(angle=0)
|
assembly = j.assembly(angle=0)
|
||||||
|
@ -23,6 +40,28 @@ class TestJoints(unittest.TestCase):
|
||||||
assembly = j.assembly(angle=j.movement_angle)
|
assembly = j.assembly(angle=j.movement_angle)
|
||||||
self.assertEqual(pairwise_intersection(assembly), [])
|
self.assertEqual(pairwise_intersection(assembly), [])
|
||||||
|
|
||||||
|
def test_elbow_joint_dist(self):
|
||||||
|
"""
|
||||||
|
Tests the arm radius
|
||||||
|
"""
|
||||||
|
j = MJ.ElbowJoint()
|
||||||
|
for angle in [0, 10, 20, j.disk_joint.movement_angle]:
|
||||||
|
with self.subTest(angle=angle):
|
||||||
|
a = j.assembly(angle=angle)
|
||||||
|
o = a.get_abs_location("child/disk?mate_bot")
|
||||||
|
l_c1 = a.get_abs_location("child/lip?conn_top0")
|
||||||
|
l_c2 = a.get_abs_location("child/lip?conn_bot0")
|
||||||
|
v_c = 0.5 * ((l_c1 - o) + (l_c2 - o))
|
||||||
|
v_c.z = 0
|
||||||
|
self.assertAlmostEqual(v_c.Length, j.child_arm_radius)
|
||||||
|
|
||||||
|
l_p1 = a.get_abs_location("parent_upper/lip?conn_top0")
|
||||||
|
l_p2 = a.get_abs_location("parent_upper/lip?conn_bot0")
|
||||||
|
v_p = 0.5 * ((l_p1 - o) + (l_p2 - o))
|
||||||
|
v_p.z = 0
|
||||||
|
self.assertAlmostEqual(v_p.Length, j.parent_arm_radius)
|
||||||
|
|
||||||
|
|
||||||
class Test(unittest.TestCase):
|
class Test(unittest.TestCase):
|
||||||
|
|
||||||
def test_hs_joint_parent(self):
|
def test_hs_joint_parent(self):
|
||||||
|
|
|
@ -445,23 +445,20 @@ class WingProfile(Model):
|
||||||
.polygon(self._mask_elbow(), mode='i')
|
.polygon(self._mask_elbow(), mode='i')
|
||||||
)
|
)
|
||||||
return profile
|
return profile
|
||||||
def surface_s1(self,
|
def surface_s1(self, front: bool = True) -> Cq.Workplane:
|
||||||
shoulder_mount_inset: float = 0,
|
|
||||||
elbow_mount_inset: float = 0,
|
|
||||||
front: bool = True) -> Cq.Workplane:
|
|
||||||
shoulder_h = self.shoulder_joint.child_height
|
shoulder_h = self.shoulder_joint.child_height
|
||||||
h = (self.shoulder_joint.height - shoulder_h) / 2
|
h = (self.shoulder_joint.height - shoulder_h) / 2
|
||||||
tags_shoulder = [
|
tags_shoulder = [
|
||||||
("shoulder_bot", (shoulder_mount_inset, h), 90),
|
("shoulder_bot", (0, h), 90),
|
||||||
("shoulder_top", (shoulder_mount_inset, h + shoulder_h), 270),
|
("shoulder_top", (0, h + shoulder_h), 270),
|
||||||
]
|
]
|
||||||
h = self.elbow_height / 2
|
h = self.elbow_height / 2
|
||||||
tags_elbow = [
|
tags_elbow = [
|
||||||
("elbow_bot",
|
("elbow_bot",
|
||||||
self.elbow_to_abs(-elbow_mount_inset, h - self.elbow_h2),
|
self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h - self.elbow_h2),
|
||||||
self.elbow_angle + 0),
|
self.elbow_angle + 0),
|
||||||
("elbow_top",
|
("elbow_top",
|
||||||
self.elbow_to_abs(-elbow_mount_inset, h + self.elbow_h2),
|
self.elbow_to_abs(-self.elbow_joint.parent_arm_radius, h + self.elbow_h2),
|
||||||
self.elbow_angle + 0),
|
self.elbow_angle + 0),
|
||||||
]
|
]
|
||||||
profile = self.profile_s1()
|
profile = self.profile_s1()
|
||||||
|
@ -522,26 +519,23 @@ class WingProfile(Model):
|
||||||
.polygon(self._mask_wrist(), mode='i')
|
.polygon(self._mask_wrist(), mode='i')
|
||||||
)
|
)
|
||||||
return profile
|
return profile
|
||||||
def surface_s2(self,
|
def surface_s2(self, front: bool = True) -> Cq.Workplane:
|
||||||
elbow_mount_inset: float = 0,
|
|
||||||
wrist_mount_inset: float = 0,
|
|
||||||
front: bool = True) -> Cq.Workplane:
|
|
||||||
h = self.elbow_height / 2
|
h = self.elbow_height / 2
|
||||||
tags_elbow = [
|
tags_elbow = [
|
||||||
("elbow_bot",
|
("elbow_bot",
|
||||||
self.elbow_to_abs(elbow_mount_inset, h - self.elbow_h2),
|
self.elbow_to_abs(self.elbow_joint.child_arm_radius, h - self.elbow_h2),
|
||||||
self.elbow_angle + 180),
|
self.elbow_angle + 180),
|
||||||
("elbow_top",
|
("elbow_top",
|
||||||
self.elbow_to_abs(elbow_mount_inset, h + self.elbow_h2),
|
self.elbow_to_abs(self.elbow_joint.child_arm_radius, h + self.elbow_h2),
|
||||||
self.elbow_angle + 180),
|
self.elbow_angle + 180),
|
||||||
]
|
]
|
||||||
h = self.wrist_height / 2
|
h = self.wrist_height / 2
|
||||||
tags_wrist = [
|
tags_wrist = [
|
||||||
("wrist_bot",
|
("wrist_bot",
|
||||||
self.wrist_to_abs(-wrist_mount_inset, h - self.wrist_h2),
|
self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h - self.wrist_h2),
|
||||||
self.wrist_angle),
|
self.wrist_angle),
|
||||||
("wrist_top",
|
("wrist_top",
|
||||||
self.wrist_to_abs(-wrist_mount_inset, h + self.wrist_h2),
|
self.wrist_to_abs(-self.wrist_joint.parent_arm_radius, h + self.wrist_h2),
|
||||||
self.wrist_angle),
|
self.wrist_angle),
|
||||||
]
|
]
|
||||||
profile = self.profile_s2()
|
profile = self.profile_s2()
|
||||||
|
@ -596,14 +590,13 @@ class WingProfile(Model):
|
||||||
return profile
|
return profile
|
||||||
def surface_s3(self,
|
def surface_s3(self,
|
||||||
front: bool = True) -> Cq.Workplane:
|
front: bool = True) -> Cq.Workplane:
|
||||||
wrist_mount_inset = 0
|
|
||||||
h = self.wrist_height / 2
|
h = self.wrist_height / 2
|
||||||
tags = [
|
tags = [
|
||||||
("wrist_bot",
|
("wrist_bot",
|
||||||
self.wrist_to_abs(wrist_mount_inset, h - self.wrist_h2),
|
self.wrist_to_abs(self.wrist_joint.child_arm_radius, h - self.wrist_h2),
|
||||||
self.wrist_angle + 180),
|
self.wrist_angle + 180),
|
||||||
("wrist_top",
|
("wrist_top",
|
||||||
self.wrist_to_abs(wrist_mount_inset, h + self.wrist_h2),
|
self.wrist_to_abs(self.wrist_joint.child_arm_radius, h + self.wrist_h2),
|
||||||
self.wrist_angle + 180),
|
self.wrist_angle + 180),
|
||||||
]
|
]
|
||||||
profile = self.profile_s3()
|
profile = self.profile_s3()
|
||||||
|
@ -629,14 +622,12 @@ class WingProfile(Model):
|
||||||
)
|
)
|
||||||
for t in ["wrist_bot", "wrist_top"]:
|
for t in ["wrist_bot", "wrist_top"]:
|
||||||
is_top = t.endswith("_top")
|
is_top = t.endswith("_top")
|
||||||
is_parent = True
|
|
||||||
o = self.spacer_s3_wrist()
|
o = self.spacer_s3_wrist()
|
||||||
self._assembly_insert_spacer(
|
self._assembly_insert_spacer(
|
||||||
result,
|
result,
|
||||||
o.generate(),
|
o.generate(),
|
||||||
point_tag=t,
|
point_tag=t,
|
||||||
flipped=is_top,# != is_parent,
|
flipped=is_top,
|
||||||
#rotate=True,
|
|
||||||
)
|
)
|
||||||
return result.solve()
|
return result.solve()
|
||||||
|
|
||||||
|
|
23
nhf/utils.py
23
nhf/utils.py
|
@ -11,8 +11,6 @@ import cadquery as Cq
|
||||||
from nhf import Role
|
from nhf import Role
|
||||||
from typing import Union, Tuple, cast
|
from typing import Union, Tuple, cast
|
||||||
|
|
||||||
COLOR_MARKER = Cq.Color(0, 1, 1, 1)
|
|
||||||
|
|
||||||
# Bug fixes
|
# Bug fixes
|
||||||
def _subloc(self, name: str) -> Tuple[Cq.Location, str]:
|
def _subloc(self, name: str) -> Tuple[Cq.Location, str]:
|
||||||
"""
|
"""
|
||||||
|
@ -37,6 +35,15 @@ def _subloc(self, name: str) -> Tuple[Cq.Location, str]:
|
||||||
return (rv, name_out)
|
return (rv, name_out)
|
||||||
Cq.Assembly._subloc = _subloc
|
Cq.Assembly._subloc = _subloc
|
||||||
|
|
||||||
|
### Vector arithmetic
|
||||||
|
|
||||||
|
def location_sub(self: Cq.Location, rhs: Cq.Location) -> Cq.Vector:
|
||||||
|
(x1, y1, z1), _ = self.toTuple()
|
||||||
|
(x2, y2, z2), _ = rhs.toTuple()
|
||||||
|
return Cq.Vector(x1 - x2, y1 - y2, z1 - z2)
|
||||||
|
Cq.Location.__sub__ = location_sub
|
||||||
|
|
||||||
|
### Tags
|
||||||
|
|
||||||
def tagPoint(self, tag: str):
|
def tagPoint(self, tag: str):
|
||||||
"""
|
"""
|
||||||
|
@ -100,6 +107,8 @@ def make_arrow(size: float = 2) -> Cq.Workplane:
|
||||||
def to_marker_name(tag: str) -> str:
|
def to_marker_name(tag: str) -> str:
|
||||||
return tag.replace("?", "__T").replace("/", "__Z") + "_marker"
|
return tag.replace("?", "__T").replace("/", "__Z") + "_marker"
|
||||||
|
|
||||||
|
COLOR_MARKER = Cq.Color(0, 1, 1, 1)
|
||||||
|
|
||||||
def mark_point(self: Cq.Assembly,
|
def mark_point(self: Cq.Assembly,
|
||||||
tag: str,
|
tag: str,
|
||||||
size: float = 2,
|
size: float = 2,
|
||||||
|
@ -132,6 +141,16 @@ def mark_plane(self: Cq.Assembly,
|
||||||
|
|
||||||
Cq.Assembly.markPlane = mark_plane
|
Cq.Assembly.markPlane = mark_plane
|
||||||
|
|
||||||
|
def get_abs_location(self: Cq.Assembly,
|
||||||
|
tag: str) -> Cq.Location:
|
||||||
|
name, shape = self._query(tag)
|
||||||
|
loc_self = shape.location()
|
||||||
|
loc_parent, _ = self._subloc(name)
|
||||||
|
loc = loc_parent * loc_self
|
||||||
|
return loc
|
||||||
|
|
||||||
|
Cq.Assembly.get_abs_location = get_abs_location
|
||||||
|
|
||||||
|
|
||||||
def extrude_with_markers(sketch: Cq.Sketch,
|
def extrude_with_markers(sketch: Cq.Sketch,
|
||||||
thickness: float,
|
thickness: float,
|
||||||
|
|
Loading…
Reference in New Issue