cosplay: Touhou/Houjuu Nue #4
|
@ -33,6 +33,7 @@ class Material(Enum):
|
||||||
PLASTIC_PLA = 0.5, _color('azure3', 0.6)
|
PLASTIC_PLA = 0.5, _color('azure3', 0.6)
|
||||||
RESIN_TRANSPARENT = 1.1, _color('cadetblue2', 0.6)
|
RESIN_TRANSPARENT = 1.1, _color('cadetblue2', 0.6)
|
||||||
ACRYLIC_BLACK = 0.5, _color('gray50', 0.6)
|
ACRYLIC_BLACK = 0.5, _color('gray50', 0.6)
|
||||||
|
ACRYLIC_TRANSPARENT = 0.5, _color('ghostwhite', 0.5)
|
||||||
|
|
||||||
def __init__(self, density: float, color: Cq.Color):
|
def __init__(self, density: float, color: Cq.Color):
|
||||||
self.density = density
|
self.density = density
|
||||||
|
|
|
@ -332,6 +332,7 @@ class TorsionJoint:
|
||||||
)
|
)
|
||||||
|
|
||||||
def track(self):
|
def track(self):
|
||||||
|
# TODO: Cover outer part of track only. Can we do this?
|
||||||
groove_profile = (
|
groove_profile = (
|
||||||
Cq.Sketch()
|
Cq.Sketch()
|
||||||
.circle(self.radius)
|
.circle(self.radius)
|
||||||
|
|
|
@ -104,12 +104,16 @@ class Parameters(Model):
|
||||||
shoulder_attach_diam: float = 8
|
shoulder_attach_diam: float = 8
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Heights for various wing joints, where the numbers start from the first joint.
|
Heights for various wing joints, where the numbers start from the first
|
||||||
|
joint.
|
||||||
"""
|
"""
|
||||||
wing_s0_thickness: float = 40
|
wing_s0_thickness: float = 40
|
||||||
wing_s0_height: float = 100
|
wing_s0_height: float = 100
|
||||||
wing_r1_height: float = 100
|
|
||||||
wing_r1_width: float = 400
|
# Length of the spacer
|
||||||
|
wing_s1_thickness: float = 20
|
||||||
|
wing_s1_spacer_thickness: float = 25.4 / 8
|
||||||
|
wing_s1_spacer_width: float = 20
|
||||||
|
|
||||||
trident_handle: Handle = field(default_factory=lambda: Handle(
|
trident_handle: Handle = field(default_factory=lambda: Handle(
|
||||||
diam=38,
|
diam=38,
|
||||||
|
@ -121,6 +125,9 @@ class Parameters(Model):
|
||||||
simplify_geometry=False,
|
simplify_geometry=False,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
material_panel: Material = Material.ACRYLIC_TRANSPARENT
|
||||||
|
material_bracket: Material = Material.ACRYLIC_TRANSPARENT
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__init__(name="houjuu-nue")
|
super().__init__(name="houjuu-nue")
|
||||||
assert self.wing_root_radius > self.hs_hirth_joint.radius,\
|
assert self.wing_root_radius > self.hs_hirth_joint.radius,\
|
||||||
|
@ -258,6 +265,10 @@ class Parameters(Model):
|
||||||
result.faces("<Z").tag("base")
|
result.faces("<Z").tag("base")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
#@target(name="wing/joining-plate", kind=TargetKind.DXF)
|
||||||
|
#def joining_plate(self) -> Cq.Workplane:
|
||||||
|
# return self.wing_joining_plate.plate()
|
||||||
|
|
||||||
@target(name="wing_root")
|
@target(name="wing_root")
|
||||||
def wing_root(self) -> Cq.Assembly:
|
def wing_root(self) -> Cq.Assembly:
|
||||||
"""
|
"""
|
||||||
|
@ -312,43 +323,42 @@ class Parameters(Model):
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def wing_r1_profile(self) -> Cq.Sketch:
|
@target(name="wing/s1-spacer", kind=TargetKind.DXF)
|
||||||
"""
|
def wing_s1_spacer(self) -> Cq.Workplane:
|
||||||
Generates the first wing segment profile, with the wing root pointing in
|
|
||||||
the positive x axis.
|
|
||||||
"""
|
|
||||||
# Depression of the wing middle
|
|
||||||
bend = 200
|
|
||||||
factor = 0.7
|
|
||||||
result = (
|
result = (
|
||||||
Cq.Sketch()
|
Cq.Workplane('XZ')
|
||||||
.segment((0, 0), (0, self.wing_r1_height))
|
.sketch()
|
||||||
.spline([
|
.rect(self.wing_s1_spacer_width, self.wing_s1_thickness)
|
||||||
(0, self.wing_r1_height),
|
.finalize()
|
||||||
(0.5 * self.wing_r1_width, self.wing_r1_height - factor * bend),
|
.extrude(self.wing_s1_spacer_thickness)
|
||||||
(self.wing_r1_width, self.wing_r1_height - bend),
|
|
||||||
])
|
|
||||||
.segment(
|
|
||||||
(self.wing_r1_width, self.wing_r1_height - bend),
|
|
||||||
(self.wing_r1_width, -bend),
|
|
||||||
)
|
|
||||||
.spline([
|
|
||||||
(self.wing_r1_width, - bend),
|
|
||||||
(0.5 * self.wing_r1_width, - factor * bend),
|
|
||||||
(0, 0),
|
|
||||||
])
|
|
||||||
.assemble()
|
|
||||||
)
|
)
|
||||||
|
result.faces("<Z").tag("mate1")
|
||||||
|
result.faces(">Z").tag("mate2")
|
||||||
|
result.faces(">Y").tag("dir")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def wing_r1(self) -> Cq.Solid:
|
@target(name="wing/r1s1", kind=TargetKind.DXF)
|
||||||
profile = self.wing_r1_profile()
|
def wing_r1s1_profile(self) -> Cq.Sketch:
|
||||||
|
return MW.wing_r1s1_profile()
|
||||||
|
|
||||||
|
def wing_r1s1_panel(self, front=True) -> Cq.Workplane:
|
||||||
|
profile = self.wing_r1s1_profile()
|
||||||
|
anchors = [
|
||||||
|
("shoulder_bot", 10, 10),
|
||||||
|
("middle", 50, -20),
|
||||||
|
("tip", 390, -150),
|
||||||
|
]
|
||||||
result = (
|
result = (
|
||||||
Cq.Workplane("XY")
|
Cq.Workplane("XY")
|
||||||
.placeSketch(profile)
|
.placeSketch(profile)
|
||||||
.extrude(self.panel_thickness)
|
.extrude(self.panel_thickness)
|
||||||
.val()
|
|
||||||
)
|
)
|
||||||
|
plane = result.faces(">Z" if front else "<Z").workplane()
|
||||||
|
sign = 1 if front else -1
|
||||||
|
tag_direction = "+Y" if front else "-Y"
|
||||||
|
for name, px, py in anchors:
|
||||||
|
plane.moveTo(px, sign * py).tagPlane(name)
|
||||||
|
#plane.moveTo(px, sign * py).tagPlane(f"{name}_dir", direction=tag_direction)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
######################
|
######################
|
||||||
|
@ -405,6 +415,29 @@ class Parameters(Model):
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def wing_r1s1_assembly(self) -> Cq.Assembly:
|
||||||
|
result = (
|
||||||
|
Cq.Assembly()
|
||||||
|
.add(self.wing_r1s1_panel(front=True), name="panel_front",
|
||||||
|
color=self.material_panel.color)
|
||||||
|
.add(self.wing_r1s1_panel(front=False), name="panel_back",
|
||||||
|
color=self.material_panel.color)
|
||||||
|
.constrain("panel_front@faces@>Z", "panel_back@faces@<Z", "Point",
|
||||||
|
param=self.wing_s1_thickness)
|
||||||
|
)
|
||||||
|
for tag in ["shoulder_bot", "middle", "tip"]:
|
||||||
|
(
|
||||||
|
result
|
||||||
|
.add(self.wing_s1_spacer(), name=f"{tag}_spacer",
|
||||||
|
color=self.material_bracket.color)
|
||||||
|
.constrain(f"panel_front?{tag}", f"{tag}_spacer?mate1", "Plane")
|
||||||
|
.constrain(f"panel_back?{tag}", f"{tag}_spacer?mate2", "Plane")
|
||||||
|
)
|
||||||
|
|
||||||
|
result.solve()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def wing_r1_assembly(self) -> Cq.Assembly:
|
def wing_r1_assembly(self) -> Cq.Assembly:
|
||||||
result = (
|
result = (
|
||||||
Cq.Assembly()
|
Cq.Assembly()
|
||||||
|
|
|
@ -226,3 +226,34 @@ def wing_root(joint: HirthJoint,
|
||||||
loc=Cq.Location((0, 0, -joint.total_height)))
|
loc=Cq.Location((0, 0, -joint.total_height)))
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def wing_r1s1_profile() -> Cq.Sketch:
|
||||||
|
"""
|
||||||
|
Generates the first wing segment profile, with the wing root pointing in
|
||||||
|
the positive x axis.
|
||||||
|
"""
|
||||||
|
# Depression of the wing middle
|
||||||
|
h = 100
|
||||||
|
w = 400
|
||||||
|
bend = 200
|
||||||
|
factor = 0.7
|
||||||
|
result = (
|
||||||
|
Cq.Sketch()
|
||||||
|
.segment((0, 0), (0, h))
|
||||||
|
.spline([
|
||||||
|
(0, h),
|
||||||
|
(0.5 * w, h - factor * bend),
|
||||||
|
(w, h - bend),
|
||||||
|
])
|
||||||
|
.segment(
|
||||||
|
(w, h - bend),
|
||||||
|
(w, -bend),
|
||||||
|
)
|
||||||
|
.spline([
|
||||||
|
(w, - bend),
|
||||||
|
(0.5 * w, - factor * bend),
|
||||||
|
(0, 0),
|
||||||
|
])
|
||||||
|
.assemble()
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
18
nhf/utils.py
18
nhf/utils.py
|
@ -6,6 +6,7 @@ Adds the functions to `Cq.Workplane`:
|
||||||
2. `tagPlane`
|
2. `tagPlane`
|
||||||
"""
|
"""
|
||||||
import cadquery as Cq
|
import cadquery as Cq
|
||||||
|
from typing import Union, Tuple
|
||||||
|
|
||||||
|
|
||||||
def tagPoint(self, tag: str):
|
def tagPoint(self, tag: str):
|
||||||
|
@ -18,12 +19,16 @@ def tagPoint(self, tag: str):
|
||||||
Cq.Workplane.tagPoint = tagPoint
|
Cq.Workplane.tagPoint = tagPoint
|
||||||
|
|
||||||
|
|
||||||
def tagPlane(self, tag: str, axis='Z'):
|
def tagPlane(self, tag: str,
|
||||||
|
direction: Union[str, Cq.Vector, Tuple[float, float, float]] = '+Z'):
|
||||||
"""
|
"""
|
||||||
Adds a phantom `Cq.Edge` in the given location which can be referenced in a
|
Adds a phantom `Cq.Edge` in the given location which can be referenced in a
|
||||||
`Axis`, `Point`, or `Plane` constraint.
|
`Axis`, `Point`, or `Plane` constraint.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(direction, str):
|
||||||
x, y, z = 0, 0, 0
|
x, y, z = 0, 0, 0
|
||||||
|
assert len(direction) == 2
|
||||||
|
sign, axis = direction
|
||||||
if axis in ('z', 'Z'):
|
if axis in ('z', 'Z'):
|
||||||
z = 1
|
z = 1
|
||||||
elif axis in ('y', 'Y'):
|
elif axis in ('y', 'Y'):
|
||||||
|
@ -32,7 +37,16 @@ def tagPlane(self, tag: str, axis='Z'):
|
||||||
x = 1
|
x = 1
|
||||||
else:
|
else:
|
||||||
assert False, "Axis must be one of x,y,z"
|
assert False, "Axis must be one of x,y,z"
|
||||||
edge = Cq.Edge.makeLine((-x, -y, -z), (x, y, z))
|
if sign == '+':
|
||||||
|
sign = 1
|
||||||
|
elif sign == '-':
|
||||||
|
sign = -1
|
||||||
|
else:
|
||||||
|
assert False, "Sign must be one of +/-"
|
||||||
|
v = Cq.Vector(x, y, z) * sign
|
||||||
|
else:
|
||||||
|
v = Cq.Vector(direction)
|
||||||
|
edge = Cq.Edge.makeLine(v * (-1), v)
|
||||||
self.eachpoint(edge.moved, useLocalCoordinates=True).tag(tag)
|
self.eachpoint(edge.moved, useLocalCoordinates=True).tag(tag)
|
||||||
|
|
||||||
Cq.Workplane.tagPlane = tagPlane
|
Cq.Workplane.tagPlane = tagPlane
|
||||||
|
|
Loading…
Reference in New Issue